import { useEffect, useRef, useState } from "react";
import { FEAR_SETTINGS, fearState } from "../state";
import { useTexture, PositionalAudio } from "@react-three/drei";

import * as THREE from "three";
import { useFrame } from "@react-three/fiber";

function ShaderPatch(shader: { vertexShader: string, fragmentShader: string, uniforms: Object }) {
    shader.vertexShader = `
        varying float vDepth;
        #ifdef USE_MAP
            varying vec2 vAffineUv;
        #endif
        ${shader.vertexShader}
    `;

    shader.vertexShader = shader.vertexShader.replace(
        `#include <project_vertex>`,
        `
        vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
        gl_Position = projectionMatrix * mvPosition;
        
        float precisionModifier = 200.0;
        gl_Position.xy /= gl_Position.w;
        gl_Position.xy = floor(gl_Position.xy * precisionModifier) / precisionModifier;
        gl_Position.xy *= gl_Position.w;

        vDepth = gl_Position.w;
        
        #ifdef USE_MAP
            vAffineUv = vMapUv * gl_Position.w; 
        #endif
        `
    );

    shader.fragmentShader = `
        varying float vDepth;
        #ifdef USE_MAP
            varying vec2 vAffineUv;
        #endif
        ${shader.fragmentShader}
    `;

    shader.fragmentShader = shader.fragmentShader.replace(
        `#include <map_fragment>`,
        `
        #ifdef USE_MAP
            vec2 flatAffineUV = vAffineUv / max(vDepth, 0.001);
            
            vec2 warpDiff = flatAffineUV - vMapUv;
            float warpDist = length(warpDiff);
            
            float maxDistortion = 0.25; 
            
            float falloff = maxDistortion / (maxDistortion + warpDist);
            
            vec2 finalUV = vMapUv + (warpDiff * falloff);
            
            vec4 texelColor = texture2D( map, finalUV );
            diffuseColor *= texelColor;
        #endif
        `
    );
}

interface DoorProps {
    position: [number, number, number];
    rotation: [number, number, number];
}
function Door({ position, rotation }: DoorProps) {
    const [soundUrl, setSoundUrl] = useState<string | null>(null);
    const currentSound = useRef<string | null>(null);
    const steelTex = useTexture('fear/img/steel.png');

    useEffect(() => {
        const interval = setInterval(() => {
            if (Math.random() < 0.02) {
                const chosenSound = Math.random() < 0.5 ? "fear/snd/knock1.mp3" : "fear/snd/knock2.mp3";

                setSoundUrl(chosenSound);
                currentSound.current = chosenSound;
            }
        }, 5000);

        return () => clearInterval(interval);
    }, []);

    const handleAudioEnded = () => {
        setSoundUrl(null);
        currentSound.current = null;
    };

    return (
        <group position={position} rotation={rotation}>
            {/* frame */}
            <mesh position={[0, 2, -0.1]}>
                <boxGeometry args={[2.4, 4.0, 0.2, 4, 4, 1]} />
                <meshStandardMaterial map={steelTex} color="#8d8d8d" onBeforeCompile={ShaderPatch} />
            </mesh>

            {/* panel */}
            <mesh position={[0, 1.95, -0.0]}>
                <boxGeometry args={[2.1, 3.8, 0.1, 4, 4, 1]} />
                <meshStandardMaterial map={steelTex} color="#4e4a4a" onBeforeCompile={ShaderPatch} />
            </mesh>

            {/* handle */}
            <mesh position={[0.75, 1.8, .085]}>
                <boxGeometry args={[0.3, 0.08, 0.1]} />
                <meshStandardMaterial map={steelTex} color="#ffffff" onBeforeCompile={ShaderPatch} />
            </mesh>

            {soundUrl && (
                <PositionalAudio
                    url={soundUrl}
                    distance={25}
                    loop={false}
                    autoplay={true}
                    onEnded={handleAudioEnded}
                />
            )}
        </group>
    );
}

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

    useEffect(() => {
        [floorTex, wallTex, rustWallTex, rustFloorTex].forEach((tex) => {
            tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
            tex.minFilter = tex.magFilter = THREE.NearestFilter;
            tex.colorSpace = THREE.SRGBColorSpace;
        });
    }, [floorTex, wallTex, rustWallTex, rustFloorTex]);


    const segmentPool = [0, 1, 2, 3, 4];
    const segmentCount = segmentPool.length;

    const lightRefs = useRef<(THREE.PointLight | null)[]>([]);
    const matRefs = 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;

        /*
           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;
            }
        }

        /*
            objects
        */
        const length = FEAR_SETTINGS.HALLWAY_LENGTH;
        const playerSegmentZ = Math.floor(state.camera.position.z / length);

        const horizontalTexRepeat = width / FEAR_SETTINGS.HALLWAY_WIDTH;
        floorTex.repeat.set(horizontalTexRepeat, 10);
        wallTex.repeat.set(10, 1);
        rustWallTex.repeat.set(10, 1);
        rustFloorTex.repeat.set(horizontalTexRepeat, 10);

        floorTex.needsUpdate = true;
        wallTex.needsUpdate = true;
        rustWallTex.needsUpdate = true;
        rustFloorTex.needsUpdate = true;

        let closestPoolIndex = 0;
        let minDistance = Infinity;

        segmentsRef.current.forEach((segGroup, poolIndex) => {
            if (!segGroup) return;

            let segmentZIndex = poolIndex - Math.floor(segmentCount / 2) + playerSegmentZ;
            segGroup.position.z = segmentZIndex * length;

            const distance = Math.abs(segGroup.position.z - state.camera.position.z);
            if (distance < minDistance) {
                minDistance = distance;
                closestPoolIndex = poolIndex;
            }

            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;
                });
            }
        });

        /*
            dyn light
        */
        segmentPool.forEach((poolIndex) => {
            const light = lightRefs.current[poolIndex];
            const mat = matRefs.current[poolIndex];

            if (poolIndex === closestPoolIndex) {
                if (light) light.intensity = intensity1 * 1.2;
                if (mat) {
                    mat.emissiveIntensity = intensity1 * 2.5;
                    if (lightState.current !== 'normal') mat.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7));
                    else mat.emissive.setHex(0xa8a1a1);
                }
            } else {
                if (light) light.intensity = 0.9;
                if (mat) {
                    mat.emissiveIntensity = 0.8;
                    mat.emissive.setHex(0xa8a1a1);
                }
            }
        });

        /*
            materials
        */
        const updateMaterials = (materials: THREE.MeshStandardMaterial[], defaultTex: THREE.Texture, targetRustTex: THREE.Texture, activeColor: string, defaultColor: string, activeRough: number, defaultRough: number, activeMetal: number, defaultMetal: number) => {
            materials.forEach(mat => {
                if (!mat) return;
                const targetTex = isRustActive ? targetRustTex : defaultTex;
                if (mat.map !== targetTex) {
                    mat.map = targetTex;
                    mat.needsUpdate = true;
                }
                mat.color.set(isRustActive ? activeColor : defaultColor);
                mat.roughness = isRustActive ? activeRough : defaultRough;
                mat.metalness = isRustActive ? activeMetal : defaultMetal;
            });
        };

        updateMaterials(wallMaterialsRef.current, wallTex, rustWallTex, "#c5c0be", "#ffffff", 0.95, 0.7, 0.05, 0.1);
        updateMaterials(floorMaterialsRef.current, floorTex, rustFloorTex, "#cabdb9", "#ffffff", 0.95, 0.8, 0.05, 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 (
        <>
            {segmentPool.map((poolIndex) => (
                <group
                    key={poolIndex}
                    ref={(el) => { if (el) segmentsRef.current[poolIndex] = el; }}
                    position={[0, 0, 0]}
                >
                    {/* lights */}
                    <group position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1, -FEAR_SETTINGS.HALLWAY_LENGTH / 4]}>
                        <pointLight
                            ref={(el) => { lightRefs.current[poolIndex] = el; }}
                            intensity={0.9}
                            distance={15}
                            color="#a8a1a1"
                        />
                        <mesh position={[0, 0.09, 0]}>
                            <boxGeometry args={[0.3, 0.01, 0.3]} />
                            <meshStandardMaterial
                                ref={(el) => { matRefs.current[poolIndex] = el; }}
                                color="#111111"
                                emissive="#a8a1a1"
                                emissiveIntensity={0.8}
                                roughness={0.9}
                                onBeforeCompile={ShaderPatch}
                            />
                        </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, 4, 10]} />
                        <meshStandardMaterial
                            ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
                            map={floorTex}
                            onBeforeCompile={ShaderPatch}
                        />
                    </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, 4, 10]} />
                        <meshStandardMaterial
                            ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
                            map={floorTex}
                            onBeforeCompile={ShaderPatch}
                        />
                    </mesh>

                    {/* left 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, 10, 4]} />
                            <meshStandardMaterial
                                ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
                                map={wallTex}
                                onBeforeCompile={ShaderPatch}
                            />
                        </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>

                    {/* right 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, 10, 4]} />
                            <meshStandardMaterial
                                ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
                                map={wallTex}
                                onBeforeCompile={ShaderPatch}
                            />
                        </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}
                                onBeforeCompile={ShaderPatch}
                            />
                        </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}
                                        onBeforeCompile={ShaderPatch}
                                    />
                                </mesh>
                            );
                        })}
                    </group>
                </group>
            ))}
        </>
    );
}
