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

function PillarJibAnimator({
    crane,
    points,
    setPoints,
    animationPhase,
    setAnimationPhase,
    onAnimationComplete
}: {
    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 } = productStore();
    const { resetAsset } = assetStore();
    const { setIsVisible } = materialStore();
    const { selectedProductStore } = useProductContext();
    const { selectedProduct } = selectedProductStore();
    const { isPaused } = usePauseButtonStore();
    const { isPlaying } = usePlayButtonStore();
    const { isReset } = useResetButtonStore();
    const { speed } = useAnimationPlaySpeed();

    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 || !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 model = scene.getObjectByProperty('uuid', crane.modelUuid);
        if (!model) return;

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

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

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

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

        if (!model.userData.animationData) {
            model.userData.animationData = {
                originalHookY: hook.position.y,
                targetHookY: clampedPoints[0].y - baseWorld.y,
                targetDirection: new THREE.Vector2(),
                targetTrolleyX: 0,
                targetWorldPosition: clampedPoints[0].clone(),
                finalHookTargetY: 0,
            };
        }

        const { animationData } = model.userData;
        const hookSpeed = (model.userData.hookSpeed || 0.01) * speed;
        const rotationSpeed = (model.userData.rotationSpeed || 0.005) * speed;
        const trolleySpeed = (model.userData.trolleySpeed || 0.01) * speed;

        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 direction = Math.sign(targetY - hookWorld.y);

                hook.position.y = THREE.MathUtils.lerp(
                    hook.position.y,
                    hook.position.y + direction * 0.1,
                    Math.min(hookSpeed, 0.9)
                );

                if (Math.abs(hookWorld.y - targetY) < threshold) {
                    hook.position.y = targetY - baseWorld.y;
                    setAnimationPhase('init-rotate-base');
                }
                break;
            }

            case 'init-rotate-base': {
                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 (Math.abs(angleDiff) > threshold) {
                    base.rotation.y = THREE.MathUtils.lerp(
                        base.rotation.y,
                        base.rotation.y - angleDiff,
                        Math.min(rotationSpeed, 0.9)
                    );
                } else {
                    base.rotation.y -= angleDiff;
                    const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone());
                    animationData.targetTrolleyX = localTarget?.x;
                    setAnimationPhase('init-move-trolley');
                }
                break;
            }

            case 'init-move-trolley': {
                const dx = animationData.targetTrolleyX - trolley.position.x;

                trolley.position.x = THREE.MathUtils.lerp(
                    trolley.position.x,
                    animationData.targetTrolleyX,
                    Math.min(trolleySpeed, 0.9)
                );

                if (Math.abs(dx) < threshold) {
                    trolley.position.x = animationData.targetTrolleyX;
                    animationData.finalHookTargetY = clampedPoints[0].y - baseWorld.y;
                    setAnimationPhase('init-final-hook-adjust');
                }
                break;
            }

            case 'init-final-hook-adjust': {
                const targetY = clampedPoints[0].y - baseWorld.y;

                hook.position.y = THREE.MathUtils.lerp(
                    hook.position.y,
                    targetY,
                    Math.min(hookSpeed, 0.9)
                );

                if (Math.abs(hook.position.y - targetY) < threshold) {
                    hook.position.y = targetY;

                    model.userData.animationData = {
                        originalHookY: hook.position.y,
                        targetHookY: clampedPoints[1].y - baseWorld.y + 0.5,
                        targetDirection: new THREE.Vector2(),
                        targetTrolleyX: 0,
                        targetWorldPosition: clampedPoints[1].clone(),
                        finalHookTargetY: 0,
                    };

                    setAnimationPhase('starting');
                    onAnimationComplete('starting');
                }
                break;
            }

            case 'first-hook-adjust': {
                const direction = Math.sign(animationData.targetHookY - hook.position.y);
                hook.position.y += direction * hookSpeed;

                if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) {
                    hook.position.y = animationData.targetHookY;
                    setAnimationPhase('first-rotate-base');
                }
                break;
            }

            case 'first-rotate-base': {
                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) {
                    base.rotation.y += Math.sign(angleDiff) * rotationSpeed;
                } else {
                    base.rotation.y += angleDiff;
                    const localTarget = trolley.parent.worldToLocal(clampedPoints[1].clone());
                    animationData.targetTrolleyX = localTarget?.x;
                    setAnimationPhase('first-move-trolley');
                }
                break;
            }

            case 'first-move-trolley': {
                const dx = animationData.targetTrolleyX - trolley.position.x;
                const direction = Math.sign(dx);
                trolley.position.x += direction * trolleySpeed;

                if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) {
                    trolley.position.x = animationData.targetTrolleyX;

                    animationData.finalHookTargetY = hook.position.y - 0.5;
                    setAnimationPhase('first-final-hook-adjust');
                }
                break;
            }

            case 'first-final-hook-adjust': {
                const dy = animationData.finalHookTargetY - hook.position.y;
                const direction = Math.sign(dy);
                hook.position.y += direction * hookSpeed;

                if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) {
                    hook.position.y = animationData.finalHookTargetY;
                    if (crane.currentPhase === 'init-pickup') {
                        setAnimationPhase('picking');
                        onAnimationComplete('picking');
                    } else if (crane.currentPhase === 'pickup-drop') {
                        setAnimationPhase('dropping');
                        onAnimationComplete('dropping');
                    }
                }
                break;
            }
        }
    });

    return (
        <>
        </>
    );
}

export default PillarJibAnimator;