
import { useRef, useEffect, forwardRef, useImperativeHandle } from 'react'
import { useFrame, useThree } from '@react-three/fiber'
import { Vector3, Mesh, Euler } from 'three'
import * as THREE from 'three'
import { useGLTF } from '@react-three/drei'

interface CarProps {
    isDriving: boolean
    onExit: () => void
    position?: [number, number, number]
    showHitboxes?: boolean
}

export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [5, 0.5, 5], showHitboxes = false }, ref) => {
    const internalRef = useRef<Mesh>(null)
    useImperativeHandle(ref, () => internalRef.current!)

    const gltf = useGLTF('/car.glb')
    const { camera } = useThree()
    const keys = useRef({ w: false, a: false, s: false, d: false, f: false })
    const speed = useRef(0)

    // Camera state for car
    const cameraState = useRef({
        yaw: 0,
        pitch: -0.2, // Adjusted to -0.2
        distance: 15
    })

    // Initial position setup
    useEffect(() => {
        if (internalRef.current) {
            internalRef.current.position.set(...position)
        }
    }, []) // Run only once on mount

    useEffect(() => {
        // Reset camera yaw and pitch when entering vehicle
        if (isDriving) {
            cameraState.current.yaw = 0
            cameraState.current.pitch = -0.2 // Adjusted to -0.2
        }
    }, [isDriving])

    useEffect(() => {
        const handleKeyDown = (e: KeyboardEvent) => {
            const key = e.key.toLowerCase()
            if (Object.prototype.hasOwnProperty.call(keys.current, key)) {
                (keys.current as Record<string, boolean>)[key] = true
            }
            if (e.key.toLowerCase() === 'f') {
                if (isDriving) {
                    onExit()
                }
            }
        }
        const handleKeyUp = (e: KeyboardEvent) => {
            const key = e.key.toLowerCase()
            if (Object.prototype.hasOwnProperty.call(keys.current, key)) {
                (keys.current as Record<string, boolean>)[key] = false
            }
        }

        const handleMouseMove = (e: MouseEvent) => {
            if (isDriving && document.pointerLockElement === document.body) {
                cameraState.current.yaw -= e.movementX * 0.002
                cameraState.current.pitch -= e.movementY * 0.002

                // Relaxed pitch limits
                cameraState.current.pitch = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, cameraState.current.pitch))
            }
        }

        window.addEventListener('keydown', handleKeyDown)
        window.addEventListener('keyup', handleKeyUp)
        document.addEventListener('mousemove', handleMouseMove)

        return () => {
            window.removeEventListener('keydown', handleKeyDown)
            window.removeEventListener('keyup', handleKeyUp)
            document.removeEventListener('mousemove', handleMouseMove)
        }
    }, [isDriving, onExit])

    useFrame((_state, delta) => {
        if (!internalRef.current) return

        // Car Physics / Movement
        const maxSpeed = 60
        const acceleration = 30
        const friction = 5
        const turnSpeed = 2

        // Input handling (only when driving)
        if (isDriving) {
            if (keys.current.w) speed.current += acceleration * delta
            if (keys.current.s) speed.current -= acceleration * delta
        }

        // Friction (always apply if not accelerating)
        if (!isDriving || (!keys.current.w && !keys.current.s)) {
            if (speed.current > 0) speed.current -= friction * delta
            if (speed.current < 0) speed.current += friction * delta
            if (Math.abs(speed.current) < 0.1) speed.current = 0
        }

        // Clamp speed
        speed.current = Math.max(-maxSpeed / 2, Math.min(maxSpeed, speed.current))

        // Steering (only when driving and moving)
        if (isDriving && Math.abs(speed.current) > 0.1) {
            if (keys.current.a) internalRef.current.rotation.y += turnSpeed * delta * (speed.current > 0 ? 1 : -1)
            if (keys.current.d) internalRef.current.rotation.y -= turnSpeed * delta * (speed.current > 0 ? 1 : -1)
        }

        // Move (always apply speed)
        internalRef.current.translateZ(speed.current * delta)

        // Clamp position to map boundaries with rotation-aware hitbox
        const mapSize = 2500
        const halfMapSize = mapSize / 2
        const carWidth = 3
        const carLength = 7
        const halfWidth = carWidth / 2
        const halfLength = carLength / 2

        const yaw = internalRef.current.rotation.y
        const absSin = Math.abs(Math.sin(yaw))
        const absCos = Math.abs(Math.cos(yaw))

        const extentX = absSin * halfLength + absCos * halfWidth
        const extentZ = absCos * halfLength + absSin * halfWidth

        const boundaryX = halfMapSize - extentX
        const boundaryZ = halfMapSize - extentZ

        internalRef.current.position.x = Math.max(-boundaryX, Math.min(boundaryX, internalRef.current.position.x))
        internalRef.current.position.z = Math.max(-boundaryZ, Math.min(boundaryZ, internalRef.current.position.z))

        // Ground Collision & Pitch
        // Cast rays from front and back to determine pitch and height
        const carPos = internalRef.current.position
        const carRot = internalRef.current.rotation

        const forwardDir = new Vector3(0, 0, 1).applyEuler(carRot)
        const frontOffset = forwardDir.clone().multiplyScalar(2.5) // Half length approx
        const backOffset = forwardDir.clone().multiplyScalar(-2.5)

        const rayOriginFront = carPos.clone().add(frontOffset).add(new Vector3(0, 5, 0))
        const rayOriginBack = carPos.clone().add(backOffset).add(new Vector3(0, 5, 0))
        const rayDir = new Vector3(0, -1, 0)

        const raycasterFront = new THREE.Raycaster(rayOriginFront, rayDir, 0, 10)
        const raycasterBack = new THREE.Raycaster(rayOriginBack, rayDir, 0, 10)

        const intersectsFront = raycasterFront.intersectObjects(_state.scene.children, true)
        const intersectsBack = raycasterBack.intersectObjects(_state.scene.children, true)

        const groundHitFront = intersectsFront.find(hit => hit.object.userData.isGround)
        const groundHitBack = intersectsBack.find(hit => hit.object.userData.isGround)

        let frontY = 0.5
        let backY = 0.5

        if (groundHitFront) frontY = groundHitFront.point.y + 0.5 // Car half height
        if (groundHitBack) backY = groundHitBack.point.y + 0.5

        // Average height
        const targetY = (frontY + backY) / 2

        // Apply gravity if in air (simple approach: just lerp to targetY)
        // For ramps, we want to snap to it
        internalRef.current.position.y = THREE.MathUtils.lerp(internalRef.current.position.y, targetY, 0.2)

        // Calculate pitch
        // atan2(deltaY, length)
        const deltaY = frontY - backY
        const dist = 5 // Distance between ray points
        const targetPitch = Math.atan2(deltaY, dist)

        // Smoothly interpolate pitch
        // Note: We need to preserve the yaw, so we can't just set rotation
        // But rotation.x is pitch? No, it depends on order. Default is XYZ.
        // If we rotate Y (yaw), then X is pitch.
        internalRef.current.rotation.x = THREE.MathUtils.lerp(internalRef.current.rotation.x, targetPitch, 0.2)


        if (isDriving) {
            // Camera Follow
            const { yaw: camYaw, pitch, distance } = cameraState.current

            // Target position (above car)
            const targetPos = internalRef.current.position.clone().add(new Vector3(0, 2, 0))

            // Calculate max camera position (relative to target)
            const maxCamPos = new Vector3(0, 0, distance)
            // Apply rotation: pitch, then yaw + car rotation + 180deg (to look from behind)
            maxCamPos.applyEuler(new Euler(pitch, camYaw + internalRef.current.rotation.y + Math.PI, 0, 'YXZ'))

            // Check for ground collision
            const minCameraHeight = 0.5
            let actualDistance = distance

            // Calculate world position of camera if at full distance
            const potentialWorldPos = targetPos.clone().add(maxCamPos)

            if (potentialWorldPos.y < minCameraHeight) {
                // We need to shorten the distance so that worldPos.y === minCameraHeight
                if (maxCamPos.y < 0) {
                    const ratio = (minCameraHeight - targetPos.y) / maxCamPos.y
                    actualDistance = distance * ratio
                }
            }

            const actualCamPos = new Vector3(0, 0, actualDistance)
            actualCamPos.applyEuler(new Euler(pitch, camYaw + internalRef.current.rotation.y + Math.PI, 0, 'YXZ'))

            // Lerp to the collided position
            // Increased lerp speed from 0.1 to 0.5 to reduce lag
            camera.position.lerp(targetPos.clone().add(actualCamPos), 0.5)
            camera.lookAt(targetPos)
        }
    })

    return (
        <group ref={internalRef}>
            <primitive object={gltf.scene} scale={1.5} rotation={[0, 0, 0]} />

            {/* Debug Hitbox */}
            {showHitboxes && (
                <mesh position={[0, 1, 0]}>
                    <boxGeometry args={[3, 2, 7]} />
                    <meshBasicMaterial wireframe color="yellow" />
                </mesh>
            )}
        </group>
    )
})
