import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from "../../../../../store/ui/usePlayButtonStore";
import { useSceneContext } from "../../../../scene/sceneContext";

function PillarJibAnimator({
    crane,
    points,
    setPoints,
    animationPhase,
    setAnimationPhase,
    onAnimationComplete,
}: Readonly<{
    crane: CraneStatus;
    points: [THREE.Vector3, THREE.Vector3] | null;
    setPoints: (points: [THREE.Vector3, THREE.Vector3] | null) => void;
    animationPhase: string;
    setAnimationPhase: (phase: string) => void;
    onAnimationComplete: (action: string) => void;
}>) {
    const { scene } = useThree();
    const { assetStore, productStore, materialStore } = useSceneContext();
    const { getActionByUuid, getPointByUuid, selectedProduct } = productStore();
    const { resetAsset } = assetStore();
    const { setIsVisible } = materialStore();
    const { isPaused } = usePauseButtonStore();
    const { isPlaying } = usePlayButtonStore();
    const { isReset } = useResetButtonStore();
    const { speed } = useAnimationPlaySpeed();
    const clockRef = useRef(new THREE.Clock());

    const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();

    useEffect(() => {
        if (!isPlaying || isReset) {
            resetAsset(crane.modelUuid);
            setAnimationPhase("idle");
            setPoints(null);
        }
    }, [isPlaying, scene, crane.modelUuid, isReset]);

    useEffect(() => {
        const model = scene.getObjectByProperty("uuid", crane.modelUuid);

        if (!model) return;
        const hook = model.getObjectByName("hook");

        if (!hook) return;
        const hookWorld = new THREE.Vector3();
        hook.getWorldPosition(hookWorld);

        if (crane.currentPhase === "init-pickup") {
            if (crane.currentMaterials.length > 0) {
                const materials = scene.getObjectsByProperty("uuid", crane.currentMaterials[0].materialId);
                const material = materials.find((material) => material.visible === true);
                if (material) {
                    const materialWorld = new THREE.Vector3();
                    material.getWorldPosition(materialWorld);
                    setAnimationPhase("init-hook-adjust");
                    setPoints([new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z), new THREE.Vector3(materialWorld.x, materialWorld.y + 0.5, materialWorld.z)]);
                }
            }
        } else if (crane.currentPhase === "pickup-drop") {
            if (crane.currentMaterials.length > 0) {
                const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || "");
                const humanAction = getActionByUuid(selectedProduct.productUuid, action?.triggers[0].triggeredAsset?.triggeredAction?.actionUuid || "");

                if (humanAction) {
                    const point = getPointByUuid(
                        selectedProduct.productUuid,
                        humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid || "",
                        humanAction.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid || ""
                    );
                    const eventModel = scene.getObjectByProperty("uuid", humanAction.triggers[0].triggeredAsset?.triggeredModel?.modelUuid);
                    if (point && eventModel) {
                        const pointLocal = new THREE.Vector3(...point.position);
                        const pointWorld = pointLocal.clone().applyMatrix4(eventModel.matrixWorld);

                        const startPoint = new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z);
                        const endPoint = new THREE.Vector3(pointWorld.x, pointWorld.y + 0.5, pointWorld.z);

                        setIsVisible(crane.currentMaterials[0].materialId, false);

                        setAnimationPhase("init-hook-adjust");
                        setPoints([startPoint, endPoint]);
                    }
                }
            }
        }
    }, [crane.currentPhase]);

    useEffect(() => {
        const model = scene.getObjectByProperty("uuid", crane.modelUuid);
        if (!model?.userData.fieldData) return;

        const base = model.getObjectByName("base");
        const trolley = model.getObjectByName("trolley");
        const hook = model.getObjectByName("hook");

        if (!base || !trolley || !hook || !points) return;

        const baseWorld = new THREE.Vector3();
        base.getWorldPosition(baseWorld);

        const trolleyWorld = new THREE.Vector3();
        trolley.getWorldPosition(trolleyWorld);

        const hookWorld = new THREE.Vector3();
        hook.getWorldPosition(hookWorld);

        const trolleyMinOffset = model.userData.trolleyMinOffset || -1;
        const trolleyMaxOffset = model.userData.trolleyMaxOffset || 1.75;
        const hookMinOffset = model.userData.hookMinOffset || 0.25;
        const hookMaxOffset = model.userData.hookMaxOffset || -1.5;

        const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
        const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05);
        const outerRadius = Math.max(new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length() + trolleyMaxOffset, innerRadius);

        const yMin = hookWorld.y + hookMaxOffset;
        const yMax = hookWorld.y + hookMinOffset;

        function clampToCylinder(pos: THREE.Vector3) {
            const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
            const distance = xzDist.length();

            let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
            if (distance > 0) {
                clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
            }

            const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
            const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);

            return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
        }

        const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()];
        const newIsInside: [boolean, boolean] = [false, false];

        points.forEach((point, i) => {
            const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length();
            const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius;
            const insideY = point.y >= yMin && point.y <= yMax;
            newIsInside[i] = insideXZ && insideY;

            newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point);
        });

        setClampedPoints(newClampedPoints);
    }, [crane.modelUuid, points]);

    useFrame(() => {
        if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === "idle") return;

        const delta = clockRef.current.getDelta();
        stepCrane(delta * speed);
    });

    function stepCrane(dt: number) {
        const model = scene.getObjectByProperty("uuid", crane.modelUuid);
        if (!model || !clampedPoints) return;

        const base = model.getObjectByName("base");
        const trolley = model.getObjectByName("trolley");
        const hook = model.getObjectByName("hook");

        if (!base || !trolley || !hook || !trolley.parent || !hook.parent) return;

        const baseWorld = new THREE.Vector3();
        base.getWorldPosition(baseWorld);

        const hookWorld = new THREE.Vector3();
        hook.getWorldPosition(hookWorld);

        const hookSpeed = (model.userData.fieldData.hookSpeed || 0.1) * speed;
        const rotationSpeed = (model.userData.fieldData.rotationSpeed || 0.25) * speed;
        const trolleySpeed = (model.userData.fieldData.trolleySpeed || 0.1) * speed;

        const hookStep = hookSpeed * dt;
        const rotationStep = rotationSpeed * dt;
        const trolleyStep = trolleySpeed * dt;

        const threshold = Math.max(0.01, 0.05 / speed);

        switch (animationPhase) {
            case "init-hook-adjust": {
                const hookWorld = new THREE.Vector3();
                hook.getWorldPosition(hookWorld);

                const targetY = clampedPoints[0].y;
                const diff = targetY - hookWorld.y;

                if (Math.abs(diff) > threshold) {
                    const step = Math.sign(diff) * hookStep;
                    if (Math.abs(step) > Math.abs(diff)) {
                        const localTarget = hook.parent.worldToLocal(clampedPoints[0].clone());
                        hook.position.y = localTarget.y;
                        setAnimationPhase("init-rotate-base");
                    } else {
                        hook.position.y += step;
                    }
                } else {
                    const localTarget = hook.parent.worldToLocal(clampedPoints[0].clone());
                    hook.position.y = localTarget.y;
                    setAnimationPhase("init-rotate-base");
                }
                break;
            }

            case "init-rotate-base": {
                const baseWorld = new THREE.Vector3();
                base.getWorldPosition(baseWorld);

                const baseForward = new THREE.Vector3(1, 0, 0);
                base.localToWorld(baseForward);
                baseForward.sub(baseWorld);

                const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();

                const targetWorld = clampedPoints[0];
                const targetDir = new THREE.Vector2(targetWorld.x - baseWorld.x, targetWorld.z - baseWorld.z).normalize();

                const currentAngle = Math.atan2(currentDir.y, currentDir.x);
                const targetAngle = Math.atan2(targetDir.y, targetDir.x);
                let angleDiff = currentAngle - targetAngle;

                angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));

                if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) {
                    const step = Math.sign(angleDiff) * rotationStep;

                    if (Math.abs(step) > Math.abs(angleDiff)) {
                        base.rotation.y += angleDiff;
                        setAnimationPhase("init-move-trolley");
                    } else {
                        base.rotation.y += step;
                    }
                } else {
                    base.rotation.y += angleDiff;
                    setAnimationPhase("init-move-trolley");
                }
                break;
            }

            case "init-move-trolley": {
                const baseWorld = new THREE.Vector3();
                base.getWorldPosition(baseWorld);

                const targetWorld = clampedPoints[0].clone();
                const localTarget = trolley.parent.worldToLocal(targetWorld.clone());

                const diff = localTarget.x - trolley.position.x;

                if (Math.abs(diff) > threshold) {
                    const step = Math.sign(diff) * trolleyStep;
                    if (Math.abs(step) > Math.abs(diff)) {
                        trolley.position.x = localTarget.x;
                        setAnimationPhase("init-final-hook-adjust");
                    } else {
                        trolley.position.x += step;
                    }
                } else {
                    trolley.position.x = localTarget.x;
                    setAnimationPhase("init-final-hook-adjust");
                }
                break;
            }

            case "init-final-hook-adjust": {
                const hookWorld = new THREE.Vector3();
                hook.getWorldPosition(hookWorld);

                const targetY = clampedPoints[0].y;
                const diff = targetY - hookWorld.y;

                if (Math.abs(diff) > threshold) {
                    const step = Math.sign(diff) * hookStep;
                    if (Math.abs(step) > Math.abs(diff)) {
                        const localTarget = hook.parent.worldToLocal(clampedPoints[0].clone());
                        hook.position.y = localTarget.y;
                        setAnimationPhase("first-hook-adjust");
                    } else {
                        hook.position.y += step;
                    }
                } else {
                    const localTarget = hook.parent.worldToLocal(clampedPoints[0].clone());
                    hook.position.y = localTarget.y;
                    setAnimationPhase("first-hook-adjust");
                }
                break;
            }

            case "first-hook-adjust": {
                const hookWorld = new THREE.Vector3();
                hook.getWorldPosition(hookWorld);

                const targetWorld = clampedPoints[1].clone().add(new THREE.Vector3(0, 0.5, 0));
                const targetY = targetWorld.y;
                const diff = targetY - hookWorld.y;

                if (Math.abs(diff) > threshold) {
                    const step = Math.sign(diff) * hookStep;
                    if (Math.abs(step) > Math.abs(diff)) {
                        const localTarget = hook.parent.worldToLocal(targetWorld.clone());
                        hook.position.y = localTarget.y;
                        setAnimationPhase("first-rotate-base");
                    } else {
                        hook.position.y += step;
                    }
                } else {
                    const localTarget = hook.parent.worldToLocal(targetWorld.clone());
                    hook.position.y = localTarget.y;
                    setAnimationPhase("first-rotate-base");
                }
                break;
            }

            case "first-rotate-base": {
                const baseWorld = new THREE.Vector3();
                base.getWorldPosition(baseWorld);

                const baseForward = new THREE.Vector3(1, 0, 0);
                base.localToWorld(baseForward);
                baseForward.sub(baseWorld);

                const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();

                const targetWorld = clampedPoints[1];
                const targetDir = new THREE.Vector2(targetWorld.x - baseWorld.x, targetWorld.z - baseWorld.z).normalize();

                const currentAngle = Math.atan2(currentDir.y, currentDir.x);
                const targetAngle = Math.atan2(targetDir.y, targetDir.x);
                let angleDiff = currentAngle - targetAngle;

                angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));

                if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) {
                    const step = Math.sign(angleDiff) * rotationStep;

                    if (Math.abs(step) > Math.abs(angleDiff)) {
                        base.rotation.y += angleDiff;
                        setAnimationPhase("first-move-trolley");
                    } else {
                        base.rotation.y += step;
                    }
                } else {
                    base.rotation.y += angleDiff;
                    setAnimationPhase("first-move-trolley");
                }
                break;
            }

            case "first-move-trolley": {
                const baseWorld = new THREE.Vector3();
                base.getWorldPosition(baseWorld);

                const targetWorld = clampedPoints[1].clone();
                const localTarget = trolley.parent.worldToLocal(targetWorld.clone());

                const diff = localTarget.x - trolley.position.x;

                if (Math.abs(diff) > threshold) {
                    const step = Math.sign(diff) * trolleyStep;
                    if (Math.abs(step) > Math.abs(diff)) {
                        trolley.position.x = localTarget.x;
                        setAnimationPhase("first-final-hook-adjust");
                    } else {
                        trolley.position.x += step;
                    }
                } else {
                    trolley.position.x = localTarget.x;
                    setAnimationPhase("first-final-hook-adjust");
                }
                break;
            }

            case "first-final-hook-adjust": {
                const hookWorld = new THREE.Vector3();
                hook.getWorldPosition(hookWorld);

                const targetY = clampedPoints[1].y;
                const diff = targetY - hookWorld.y;

                if (Math.abs(diff) > threshold) {
                    const step = Math.sign(diff) * hookStep;
                    if (Math.abs(step) > Math.abs(diff)) {
                        const localTarget = hook.parent.worldToLocal(clampedPoints[1].clone());
                        hook.position.y = localTarget.y;
                        if (crane.currentPhase === "init-pickup") {
                            setAnimationPhase("picking");
                            onAnimationComplete("picking");
                        } else if (crane.currentPhase === "pickup-drop") {
                            setAnimationPhase("dropping");
                            onAnimationComplete("dropping");
                        }
                    } else {
                        hook.position.y += step;
                    }
                } else {
                    const localTarget = hook.parent.worldToLocal(clampedPoints[1].clone());
                    hook.position.y = localTarget.y;
                    if (crane.currentPhase === "init-pickup") {
                        setAnimationPhase("picking");
                        onAnimationComplete("picking");
                    } else if (crane.currentPhase === "pickup-drop") {
                        setAnimationPhase("dropping");
                        onAnimationComplete("dropping");
                    }
                }
                break;
            }
        }
    }

    return <></>;
}

export default PillarJibAnimator;
