
import { useEffect, useRef, useState } from 'react'
import { useFrame, useThree, ThreeEvent } from '@react-three/fiber';
import * as THREE from 'three';
import { Line } from '@react-three/drei';
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useActiveTool, useSelectedPath } from '../../../../../store/builder/store';


interface VehicleAnimatorProps {
    path: [number, number, number][];
    handleCallBack: () => void;
    reset: () => void;
    startUnloadingProcess: () => void;
    currentPhase: string;
    agvUuid: string;
    agvDetail: VehicleStatus;
}

function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset, startUnloadingProcess }: Readonly<VehicleAnimatorProps>) {
    const { vehicleStore } = useSceneContext();
    const { getVehicleById } = vehicleStore();
    const { isPaused } = usePauseButtonStore();
    const { isPlaying } = usePlayButtonStore();
    const { speed } = useAnimationPlaySpeed();
    const { isReset, setReset } = useResetButtonStore();
    const progressRef = useRef<number>(0);
    const movingForward = useRef<boolean>(true);
    const completedRef = useRef<boolean>(false);
    const [objectRotation, setObjectRotation] = useState<{ x: number; y: number; z: number } | undefined>(agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 })
    const [restRotation, setRestingRotation] = useState<boolean>(true);
    const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
    const { scene, controls } = useThree();
    const { selectedPath } = useSelectedPath();
    const [isAnyDragging, setIsAnyDragging] = useState<string>("");


    useEffect(() => {
        if (currentPhase === 'stationed-pickup' && path.length > 0 && selectedPath === "auto") {
            setCurrentPath(path);
            setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation)
        } else if (currentPhase === 'pickup-drop' && path.length > 0) {
            setObjectRotation(agvDetail.point.action?.unLoadPoint?.rotation)
            setCurrentPath(path);
        } else if (currentPhase === 'drop-pickup' && path.length > 0) {
            setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation)
            setCurrentPath(path);
        }
    }, [currentPhase, path, objectRotation, selectedPath]);

    useEffect(() => {
        completedRef.current = false;
    }, [currentPath]);

    useEffect(() => {
        if (isReset || !isPlaying) {
            reset();
            setCurrentPath([]);
            completedRef.current = false;
            movingForward.current = true;
            progressRef.current = 0;
            setReset(false);
            setRestingRotation(true);
            const object = scene.getObjectByProperty('uuid', agvUuid);
            const vehicle = getVehicleById(agvDetail.modelUuid);
            if (object && vehicle) {
                object.position.set(vehicle.position[0], vehicle.position[1], vehicle.position[2]);
                object.rotation.set(vehicle.rotation[0], vehicle.rotation[1], vehicle.rotation[2]);
            }
        }
    }, [isReset, isPlaying])

    const lastTimeRef = useRef(performance.now());

    useFrame(() => {
        if (!isPlaying) return
        const now = performance.now();
        const delta = (now - lastTimeRef.current) / 1000;
        lastTimeRef.current = now;

        const object = scene.getObjectByProperty('uuid', agvUuid);
        if (!object || currentPath.length < 2) return;
        if (isPaused) return;

        let totalDistance = 0;
        const distances = [];
        let accumulatedDistance = 0;
        let index = 0;
        const rotationSpeed = 0.75;

        for (let i = 0; i < currentPath.length - 1; i++) {
            const start = new THREE.Vector3(...currentPath[i]);
            const end = new THREE.Vector3(...currentPath[i + 1]);
            const segmentDistance = start.distanceTo(end);
            distances.push(segmentDistance);
            totalDistance += segmentDistance;
        }

        while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) {
            accumulatedDistance += distances[index];
            index++;
        }

        if (index < distances.length) {
            const start = new THREE.Vector3(...currentPath[index]);
            const end = new THREE.Vector3(...currentPath[index + 1]);
            const segmentDistance = distances[index];

            const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)));
            const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
            targetQuaternion.multiply(y180);

            const angle = object.quaternion.angleTo(targetQuaternion);
            if (angle < 0.01) {
                object.quaternion.copy(targetQuaternion);
            } else {
                const step = rotationSpeed * delta * speed * agvDetail.speed;
                const angle = object.quaternion.angleTo(targetQuaternion);

                if (angle < step) {
                    object.quaternion.copy(targetQuaternion);
                } else {
                    object.quaternion.rotateTowards(targetQuaternion, step);
                }
            }

            const isAligned = angle < 0.01;

            if (isAligned) {
                progressRef.current += delta * (speed * agvDetail.speed);
                const t = (progressRef.current - accumulatedDistance) / segmentDistance;
                const position = start.clone().lerp(end, t);
                object.position.copy(position);
            }
        }

        if (progressRef.current >= totalDistance) {
            if (restRotation && objectRotation) {
                const targetEuler = new THREE.Euler(0, objectRotation.y - agvDetail.point.action.steeringAngle, 0);

                const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
                const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
                const targetQuaternion = baseQuaternion.multiply(y180);

                const angle = object.quaternion.angleTo(targetQuaternion);
                if (angle < 0.01) {
                    object.quaternion.copy(targetQuaternion);
                    setRestingRotation(false);
                    setTimeout(() => {
                        setRestingRotation(true);
                        progressRef.current = 0;
                        movingForward.current = !movingForward.current;
                        setCurrentPath([]);
                        handleCallBack();
                        if (currentPhase === 'pickup-drop') {
                            requestAnimationFrame(startUnloadingProcess);
                        }
                    }, 0)
                } else {
                    const step = rotationSpeed * delta * speed * agvDetail.speed;
                    const angle = object.quaternion.angleTo(targetQuaternion);

                    if (angle < step) {
                        object.quaternion.copy(targetQuaternion);
                    } else {
                        object.quaternion.rotateTowards(targetQuaternion, step);
                    }
                }
                return;
            }
        }

        if (progressRef.current >= totalDistance) {
            setRestingRotation(true);
            progressRef.current = 0;
            movingForward.current = !movingForward.current;
            setCurrentPath([]);
            handleCallBack();
            if (currentPhase === 'pickup-drop') {
                requestAnimationFrame(startUnloadingProcess);
            }
        }
    });

    const updatePoint = (index: number, pos: THREE.Vector3) => {
        const updated = [...currentPath];
        updated[index] = pos.toArray() as [number, number, number];
        setCurrentPath(updated);
    };

    return (
        <>
            {selectedPath === "auto" &&
                <group visible={false}>
                    {currentPath.map((pos, i) => {
                        if (i < currentPath.length - 1) {
                            return (
                                <DraggableLineSegment
                                    key={i}
                                    index={i}
                                    start={new THREE.Vector3(...currentPath[i])}
                                    end={new THREE.Vector3(...currentPath[i + 1])}
                                    updatePoints={(i0, p0, i1, p1) => {
                                        const updated = [...currentPath];
                                        updated[i0] = p0.toArray() as [number, number, number];
                                        updated[i1] = p1.toArray() as [number, number, number];
                                        setCurrentPath(updated);
                                    }}
                                    isAnyDragging={isAnyDragging}
                                    setIsAnyDragging={setIsAnyDragging}
                                />
                            );
                        }
                        return null;
                    })}
                    {currentPath.length > 0 && (
                        <group onPointerMissed={() => { if (controls) (controls as any).enabled = true; }}>
                            {currentPath.map((pos, i) =>
                            (
                                <DraggableSphere
                                    key={i}
                                    index={i}
                                    position={new THREE.Vector3(...pos)}
                                    onMove={updatePoint}
                                    isAnyDragging={isAnyDragging}
                                    setIsAnyDragging={setIsAnyDragging}
                                />)
                            )}
                        </group >
                    )}
                </group >
            }
        </>
    );
}

export default VehicleAnimator;

function DraggableSphere({
    index,
    position,
    onMove,
    isAnyDragging,
    setIsAnyDragging,
}: {
    index: number;
    position: THREE.Vector3;
    onMove: (index: number, pos: THREE.Vector3) => void;
    isAnyDragging: string;
    setIsAnyDragging: (val: string) => void;
}) {
    const meshRef = useRef<THREE.Mesh>(null);
    const { gl, controls, raycaster } = useThree();
    const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
    const { activeTool } = useActiveTool();

    const onPointerDown = (e: ThreeEvent<PointerEvent>) => {
        e.stopPropagation()
        if (activeTool !== 'pen') return;
        setIsAnyDragging("point");
        gl.domElement.style.cursor = 'grabbing';
        if (controls) (controls as any).enabled = false;
    };

    const onPointerMove = (e: ThreeEvent<PointerEvent>) => {
        if (isAnyDragging !== "point" || activeTool !== 'pen') return;

        const intersect = new THREE.Vector3();
        if (raycaster.ray.intersectPlane(plane, intersect)) {
            meshRef.current!.position.copy(intersect);
            onMove(index, intersect);
        }
    };

    const onPointerUp = () => {
        if (activeTool !== 'pen') return;
        setIsAnyDragging("");
        gl.domElement.style.cursor = 'default';
        if (controls) (controls as any).enabled = true;
    };
    useEffect(() => {
        gl.domElement.addEventListener("pointerup", onPointerUp);
        return (() => {
            gl.domElement.removeEventListener("pointerup", onPointerUp);
        })
    }, [activeTool])

    return (
        <mesh
            ref={meshRef}
            position={position}
            onPointerDown={onPointerDown}
            onPointerMove={onPointerMove}
            onPointerUp={onPointerUp}
            onPointerMissed={onPointerUp}
        >
            <sphereGeometry args={[0.2, 16, 16]} />
            <meshStandardMaterial color="red" />
        </mesh>
    );
}

function DraggableLineSegment({
    index,
    start,
    end,
    updatePoints,
    isAnyDragging,
    setIsAnyDragging,
}: {
    index: number;
    start: THREE.Vector3;
    end: THREE.Vector3;
    updatePoints: (i0: number, p0: THREE.Vector3, i1: number, p1: THREE.Vector3) => void;
    isAnyDragging: string;
    setIsAnyDragging: (val: string) => void;
}) {
    const { gl, raycaster, controls } = useThree();
    const { activeTool } = useActiveTool();
    const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
    const dragStart = useRef<THREE.Vector3 | null>(null);

    const onPointerDown = () => {
        if (activeTool !== 'pen' || isAnyDragging) return;
        setIsAnyDragging("line");
        gl.domElement.style.cursor = 'grabbing';
        if (controls) (controls as any).enabled = false;
    };

    const onPointerMove = (e: ThreeEvent<PointerEvent>) => {
        if (isAnyDragging !== "line" || activeTool !== 'pen') return;

        const intersect = new THREE.Vector3();
        if (raycaster.ray.intersectPlane(plane, intersect)) {
            if (!dragStart.current) dragStart.current = intersect.clone();
            const offset = new THREE.Vector3().subVectors(intersect, dragStart.current);
            const newStart = start.clone().add(offset);
            const newEnd = end.clone().add(offset);
            updatePoints(index, newStart, index + 1, newEnd);
        }
    };

    const onPointerUp = () => {
        if (activeTool !== 'pen') return;
        setIsAnyDragging("");
        dragStart.current = null;
        gl.domElement.style.cursor = 'default';
        if (controls) (controls as any).enabled = true;
    };
    useEffect(() => {
        gl.domElement.addEventListener("pointerup", onPointerUp);
        return (() => {
            gl.domElement.removeEventListener("pointerup", onPointerUp);
        })
    }, [activeTool])
    return (
        <Line
            points={[start, end]}
            color="blue"
            lineWidth={5}
            onPointerDown={onPointerDown}
            onPointerMove={onPointerMove}
            onPointerUp={onPointerUp}
            onPointerMissed={onPointerUp}
        />
    );
}
