'use client';

import './page.css';

import { PointerLockControls, useTexture } from "@react-three/drei";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import { BrightnessContrast, EffectComposer, Noise, Pixelation, Vignette } from "@react-three/postprocessing";
import { Suspense, useEffect, useRef, useState } from "react";

import * as THREE from "three";
import { AmbientSound } from './scene-components/ambient-sound';

import { fearState, FEAR_SETTINGS } from './state';

function Player() {
    const { camera } = useThree();
    const controls = usePlayerControls();

    const flashlightRef = useRef<THREE.SpotLight>(null);
    const movementCounter = useRef<number>(0);

    const forward = new THREE.Vector3();
    const side = new THREE.Vector3();
    const direction = new THREE.Vector3();
    const viewDirection = new THREE.Vector3();

    useFrame((state, delta) => {
        camera.getWorldDirection(forward);
        forward.y = 0;
        forward.normalize();

        side.crossVectors(forward, new THREE.Vector3(0, 1, 0)).normalize();

        const moveForward = Number(controls.Forward) - Number(controls.Backward);
        const moveSide = Number(controls.Right) - Number(controls.Left);

        direction.set(0, 0, 0);
        if (moveForward !== 0) direction.addScaledVector(forward, moveForward);
        if (moveSide !== 0) direction.addScaledVector(side, moveSide);

        if (direction.lengthSq() > 0)
            direction.normalize().multiplyScalar(4 * delta);

        camera.position.x += direction.x;
        camera.position.z += direction.z;

        const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right;
        if (isMoving) {
            movementCounter.current += delta * 10;
            camera.position.y = FEAR_SETTINGS.PLAYER_HEIGHT + Math.sin(movementCounter.current) * 0.08;
            camera.position.x += Math.cos(movementCounter.current / 2) * 0.006;
        } else {
            const breatheTime = state.clock.elapsedTime * 1.5;
            const breatheY = FEAR_SETTINGS.PLAYER_HEIGHT + Math.sin(breatheTime) * 0.1;
            camera.position.y = THREE.MathUtils.lerp(camera.position.y, breatheY, 4 * delta);
        }

        if (flashlightRef.current) {
            flashlightRef.current.position.copy(camera.position);
            camera.getWorldDirection(viewDirection);

            const targetDest = new THREE.Vector3()
                .copy(camera.position)
                .addScaledVector(viewDirection, 10);

            flashlightRef.current.target.position.lerp(targetDest, 10 * delta);
            flashlightRef.current.target.updateMatrixWorld();

            flashlightRef.current.intensity = 5 + Math.sin(state.clock.elapsedTime * 30) * 0.3;
        }

        const minX = -fearState.currentWidth / 2 + FEAR_SETTINGS.WALL_BUFFER;
        const maxX = fearState.currentWidth / 2 - FEAR_SETTINGS.WALL_BUFFER;

        if (camera.position.x < minX) camera.position.x = minX;
        if (camera.position.x > maxX) camera.position.x = maxX;

        if (camera.position.z < -FEAR_SETTINGS.HALLWAY_LENGTH) {
            camera.position.z += FEAR_SETTINGS.HALLWAY_LENGTH;
            fearState.registerLoop('forward');
        }
        if (camera.position.z > 0) {
            camera.position.z -= FEAR_SETTINGS.HALLWAY_LENGTH;
            fearState.registerLoop('backward');
        }
    });

    return (
        <>
            <PointerLockControls />
            <spotLight
                ref={flashlightRef}
                distance={22}
                angle={0.35}
                penumbra={0.7}
                intensity={5}
                color="#fffaed"
            />
        </>
    );
}

function usePlayerControls() {
    const keys = useRef({ Forward: false, Backward: false, Left: false, Right: false });

    useEffect(() => {
        const handleKeyDown = (e: KeyboardEvent) => {
            if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = true;
            if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = true;
            if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = true;
            if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = true;
        };

        const handleKeyUp = (e: KeyboardEvent) => {
            if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = false;
            if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = false;
            if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = false;
            if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = false;
        };

        window.addEventListener('keydown', handleKeyDown);
        window.addEventListener('keyup', handleKeyUp);

        return () => {
            window.removeEventListener('keydown', handleKeyDown);
            window.removeEventListener('keyup', handleKeyUp);
        };
    }, []);

    return keys.current;
}

interface DoorProps {
    position: [number, number, number];
    rotation: [number, number, number];
}
function Door({ position, rotation }: DoorProps) {
    return (
        <group position={position} rotation={rotation}>
            <mesh position={[0, 2, -0.14]}>
                <boxGeometry args={[2.4, 4.0, 0.2]} />
                <meshStandardMaterial color="#8a8585" roughness={0.8} metalness={0.2} />
            </mesh>

            <mesh position={[0, 1.95, -0.08]}>
                <boxGeometry args={[2.1, 3.8, 0.1]} />
                <meshStandardMaterial color="#4e4b4b" roughness={0.7} metalness={0.2} />
            </mesh>

            <mesh position={[0.9, 1.8, 0.08]}>
                <boxGeometry args={[0.08, 0.08, 0.15]} />
                <meshStandardMaterial color="#4e4b4b" roughness={0.4} metalness={0.2} />
            </mesh>
        </group>
    );
}

function Hallway() {
    const [width, setWidth] = useState(fearState.currentWidth);
    const [floorTex, wallTex, rustTex] = useTexture([
        'fear/img/concrete-floor.png',
        'fear/img/concrete-wall.png',
        'fear/img/rust.png'
    ]);

    [floorTex, wallTex, rustTex].forEach((tex, index) => {
        tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
        tex.minFilter = tex.magFilter = THREE.NearestFilter;
        tex.colorSpace = THREE.SRGBColorSpace;
        tex.repeat.set(index === 0 ? 1 : 10, index === 0 ? 10 : 1);
    });

    const segments = [0, -1, 1, -2, 2];

    const lightRef1 = useRef<THREE.PointLight>(null);
    const lightRef2 = useRef<THREE.PointLight>(null);
    const matRef1 = useRef<THREE.MeshStandardMaterial>(null);
    const matRef2 = useRef<THREE.MeshStandardMaterial>(null);
    const lightState = useRef<'normal' | 'flickering' | 'dead'>('normal');
    const stateEndTime = useRef<number>(0);
    const nextEventTime = useRef<number>(5);

    const segmentsRef = useRef<THREE.Group[]>([]);

    const wallMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
    const floorMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
    const pipeMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);
    const bracketMaterialsRef = useRef<THREE.MeshStandardMaterial[]>([]);

    wallMaterialsRef.current = [];
    floorMaterialsRef.current = [];
    pipeMaterialsRef.current = [];
    bracketMaterialsRef.current = [];

    const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);

    useEffect(() => {
        const unsubscribe = fearState.subscribe(() => {
            setWidth(fearState.currentWidth);
            setIsRustActive(fearState.isRustActive);
        });
        return () => unsubscribe();
    }, []);

    useFrame((state, delta) => {
        const time = state.clock.elapsedTime;

        fearState.update(delta);
        const currentRust = fearState.isRustActive;
        if (currentRust !== isRustActive)
            setIsRustActive(currentRust);

        /*
            lights
        */
        let intensity1 = 0.85 + Math.sin(time * 2) * 0.03;
        if (time > nextEventTime.current && lightState.current === 'normal') {
            lightState.current = 'flickering';
            stateEndTime.current = time + 1.5 + Math.random() * 2;
        }
        if (lightState.current === 'flickering') {
            if (time > stateEndTime.current) {
                if (Math.random() > 0.4) {
                    lightState.current = 'dead';
                    stateEndTime.current = time + 1.0 + Math.random() * 2.5;
                } else {
                    lightState.current = 'normal';
                    nextEventTime.current = time + 10 + Math.random() * 20;
                }
            } else {
                const baseWave = Math.sin(time * 45) * 0.4 + Math.sin(time * 90) * 0.3;
                intensity1 = 0.5 + baseWave;
                if (Math.sin(time * 150) + Math.cos(time * 220) > 1.2) intensity1 *= Math.random() > 0.5 ? 0.0 : 0.15;
            }
        }
        if (lightState.current === 'dead') {
            if (time > stateEndTime.current) {
                lightState.current = 'normal';
                nextEventTime.current = time + 12 + Math.random() * 15;
            } else
                intensity1 = Math.random() > 0.98 ? 0.08 : 0.0;
        }
        let intensity2 = lightState.current === 'dead' ? 0 : intensity1 * (0.7 + Math.sin(time * 3) * 0.1);

        if (lightRef1.current) lightRef1.current.intensity = intensity1 * 1.2;
        if (matRef1.current) {
            matRef1.current.emissiveIntensity = intensity1 * 2.5;
            if (lightState.current !== 'normal') matRef1.current.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7));
            else matRef1.current.emissive.setHex(0xa8a1a1);
        }
        if (lightRef2.current) lightRef2.current.intensity = intensity2 * 0.6;
        if (matRef2.current) matRef2.current.emissiveIntensity = intensity2 * 1.5;

        /*
            walls
        */
        segmentsRef.current.forEach((segGroup) => {
            if (!segGroup) return;

            const leftWallGroup = segGroup.getObjectByName("left-wall-group");
            if (leftWallGroup) leftWallGroup.position.x = -width / 2;

            const rightWallGroup = segGroup.getObjectByName("right-wall-group");
            if (rightWallGroup) rightWallGroup.position.x = width / 2;

            const floorMesh = segGroup.getObjectByName("floor-mesh");
            if (floorMesh) floorMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;

            const ceilingMesh = segGroup.getObjectByName("ceiling-mesh");
            if (ceilingMesh) ceilingMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;

            for (let i = 0; i < 3; i++) {
                const pipe = segGroup.getObjectByName(`pipe-${i}`);
                if (pipe) pipe.position.x = -width / 2 + 0.4 + (i * 0.20);
            }
            const bracketGroup = segGroup.getObjectByName("brackets-group");
            if (bracketGroup) {
                bracketGroup.children.forEach(b => {
                    b.position.x = -width / 2 + 0.6;
                });
            }
        });

        /*
            rust
        */
        wallMaterialsRef.current.forEach(mat => {
            if (!mat) return;
            const targetTex = isRustActive ? rustTex : wallTex;
            if (mat.map !== targetTex) {
                mat.map = targetTex;
                mat.needsUpdate = true;
            }
            if (isRustActive) {
                mat.color.set("#918a87");
                mat.roughness = 0.95;
                mat.metalness = 0.05;
            } else {
                mat.color.set("#ffffff");
                mat.roughness = 0.7;
                mat.metalness = 0.1;
            }
        });

        floorMaterialsRef.current.forEach(mat => {
            if (!mat) return;
            const targetTex = isRustActive ? rustTex : floorTex;
            if (mat.map !== targetTex) {
                mat.map = targetTex;
                mat.needsUpdate = true;
            }
            if (isRustActive) {
                mat.color.set("#8b827f");
                mat.roughness = 0.95;
                mat.metalness = 0.05;
            } else {
                mat.color.set("#ffffff");
                mat.roughness = 0.8;
                mat.metalness = 0.2;
            }
        });

        pipeMaterialsRef.current.forEach(mat => {
            if (!mat) return;
            mat.color.set(isRustActive ? "#3d1b0f" : "#a5aca8");
            mat.roughness = isRustActive ? 0.95 : 0.0;
            mat.metalness = isRustActive ? 0.05 : 0.4;
        });

        bracketMaterialsRef.current.forEach(mat => {
            if (!mat) return;
            mat.color.set(isRustActive ? "#1b0b05" : "#a5aca8");
            mat.roughness = isRustActive ? 0.95 : 0.0;
            mat.metalness = isRustActive ? 0.05 : 0.4;
        });
    });

    return (
        <>
            <ambientLight intensity={0.0225} />

            {segments.map((segmentValue, index) => (
                /*
                    lights
                */
                <group
                    key={segmentValue}
                    ref={(el) => { if (el) segmentsRef.current[index] = el; }}
                    position={[0, 0, segmentValue * FEAR_SETTINGS.HALLWAY_LENGTH]}
                >
                    <group position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1, -FEAR_SETTINGS.HALLWAY_LENGTH / 4]}>
                        <pointLight
                            ref={segmentValue === 0 ? lightRef1 : null}
                            intensity={0.9}
                            distance={15}
                            color="#a8a1a1"
                        />
                        <mesh position={[0, 0.09, 0]}>
                            <boxGeometry args={[0.3, 0.01, 0.3]} />
                            <meshStandardMaterial
                                ref={segmentValue === 0 ? matRef1 : null}
                                color="#111111"
                                emissive="#a8a1a1"
                                emissiveIntensity={segmentValue === 0 ? 0 : 0.8}
                                roughness={0.9}
                            />
                        </mesh>
                    </group>

                    {/* floor */}
                    <mesh
                        name="floor-mesh"
                        rotation={[-Math.PI / 2, 0, 0]}
                        position={[0, 0, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
                    >
                        <planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH]} />
                        <meshStandardMaterial
                            ref={(el) => el && floorMaterialsRef.current.push(el)}
                            map={floorTex}
                            roughness={0.8}
                            metalness={0.2}
                        />
                    </mesh>

                    {/* ceiling */}
                    <mesh
                        name="ceiling-mesh"
                        rotation={[Math.PI / 2, 0, 0]}
                        position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
                    >
                        <planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH]} />
                        <meshStandardMaterial
                            ref={(el) => el && floorMaterialsRef.current.push(el)}
                            map={floorTex}
                            roughness={0.8}
                            metalness={0.2}
                        />
                    </mesh>

                    {/* l wall */}
                    <group name="left-wall-group">
                        <mesh rotation={[0, Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
                            <planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT]} />
                            <meshStandardMaterial
                                ref={(el) => el && wallMaterialsRef.current.push(el)}
                                map={wallTex}
                                roughness={0.7}
                                metalness={0.1}
                            />
                        </mesh>
                        {!isRustActive && (
                            <>
                                <Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.25]} rotation={[0, Math.PI / 2, 0]} />
                                <Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.85]} rotation={[0, Math.PI / 2, 0]} />
                            </>
                        )}
                    </group>

                    {/* r wall */}
                    <group name="right-wall-group">
                        <mesh rotation={[0, -Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
                            <planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT]} />
                            <meshStandardMaterial
                                ref={(el) => el && wallMaterialsRef.current.push(el)}
                                map={wallTex}
                                roughness={0.7}
                                metalness={0.1}
                            />
                        </mesh>
                        {!isRustActive && (
                            <Door position={[-0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.65]} rotation={[0, -Math.PI / 2, 0]} />
                        )}
                    </group>

                    {/* pipes */}
                    {Array.from({ length: 3 }).map((_, idx) => (
                        <mesh
                            key={idx}
                            name={`pipe-${idx}`}
                            rotation={[Math.PI / 2, 0, 0]}
                            position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.4 + (idx * 0.20), FEAR_SETTINGS.HALLWAY_HEIGHT - 0.2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
                        >
                            <cylinderGeometry args={[0.06, 0.06, FEAR_SETTINGS.HALLWAY_LENGTH, 4]} />
                            <meshStandardMaterial
                                ref={(el) => el && pipeMaterialsRef.current.push(el)}
                                color="#a5aca8"
                                roughness={0.0}
                                metalness={0.4}
                            />
                        </mesh>
                    ))}

                    {/* brackets */}
                    <group name="brackets-group">
                        {Array.from({ length: 5 }).map((_, idx) => {
                            const zOffset = -(idx * 8 + 4);
                            return (
                                <mesh
                                    key={`bracket-${idx}`}
                                    position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.6, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.15, zOffset]}
                                >
                                    <boxGeometry args={[0.7, 0.3, 0.15]} />
                                    <meshStandardMaterial
                                        ref={(el) => el && bracketMaterialsRef.current.push(el)}
                                        color="#a5aca8"
                                        roughness={0.0}
                                        metalness={0.4}
                                    />
                                </mesh>
                            );
                        })}
                    </group>
                </group>
            ))}
        </>
    );
}

function PostProcessing() {
    return (<EffectComposer>
        <Pixelation granularity={12} />
        <Vignette />
        <Noise opacity={0.005} />
        <BrightnessContrast
            brightness={-0.01}
            contrast={0.05}
        />
    </EffectComposer>)
}

export default function Fear() {
    const [isRustActive, setIsRustActive] = useState(fearState.isRustActive);

    useEffect(() => {
        const unsubscribe = fearState.subscribe(() => {
            setIsRustActive(fearState.isRustActive);
        });
        return () => unsubscribe();
    }, []);

    return (<>
        <Canvas
            shadows
            gl={{ antialias: true }}
            className='canvas'
            camera={{ position: [0, 3, -5], fov: 65, far: 100 }}
        >
            <color attach="background" args={['#050505']} />

            <fogExp2 attach='fog' args={[0x050505, 0.035]} />
            <PostProcessing />
            <Suspense fallback={null}>
                <Hallway />
            </Suspense>

            <AmbientSound
                url='fear/snd/ambience.mp3'
                volume={isRustActive ? 0 : 1}
            />

            <Player />
        </Canvas>
    </>)
}