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

type PointWithDegree = {
    position: [number, number, number];
    degree: number;
};

interface RoboticArmAnimatorProps {
    HandleCallback: () => void;
    restPosition: THREE.Vector3;
    ikSolver: any;
    targetBone: string;
    armBot: ArmBotStatus;
    path: [number, number, number][];
    currentPhase: string;
}

function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone, armBot, path, currentPhase }: RoboticArmAnimatorProps) {
    const progressRef = useRef(0);
    const curveRef = useRef<THREE.Vector3[] | null>(null);
    const totalDistanceRef = useRef(0);
    const startTimeRef = useRef<number | null>(null);
    const segmentDistancesRef = useRef<number[]>([]);
    const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
    const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
    const [circlePointsWithDegrees, setCirclePointsWithDegrees] = useState<PointWithDegree[]>([]);
    const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null);
    const { armBotStore, productStore, materialStore } = useSceneContext();
    const { getArmBotById } = armBotStore();
    const { getMaterialById, getMaterialPosition } = materialStore();
    const { getEventByModelUuid, getActionByUuid, getPointByUuid, getTriggeringModels } = productStore();
    const { selectedProductStore } = useProductContext();
    const { selectedProduct } = selectedProductStore();
    const { scene } = useThree();

    let curveHeight = 1.75
    const CIRCLE_RADIUS = 1.6

    // Zustand stores
    const { isPlaying } = usePlayButtonStore();
    const { isPaused } = usePauseButtonStore();
    const { isReset } = useResetButtonStore();
    const { speed } = useAnimationPlaySpeed();

    // Update path state whenever `path` prop changes
    useEffect(() => {
        setCurrentPath(path);
    }, [path]);

    // Handle circle points based on armBot position
    useEffect(() => {
        const points = generateRingPoints(CIRCLE_RADIUS, 64)
        setCirclePoints(points);
    }, [armBot.position]);

    //Handle Reset Animation
    useEffect(() => {
        if (isReset || !isPlaying) {
            progressRef.current = 0;
            curveRef.current = null;
            setCurrentPath([]);
            setCustomCurvePoints(null);
            totalDistanceRef.current = 0;
            startTimeRef.current = null;
            segmentDistancesRef.current = [];
            if (!ikSolver) return
            const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
            if (!bone) return;
            bone.position.copy(restPosition)
            ikSolver.update();
        }
    }, [isReset, isPlaying])

    //Generate Circle Points
    function generateRingPoints(radius: any, segments: any) {
        const points: [number, number, number][] = [];
        for (let i = 0; i < segments; i++) {
            // Calculate angle for current segment
            const angle = (i / segments) * Math.PI * 2;
            // Calculate x and z coordinates (y remains the same for a flat ring)
            const x = Math.cos(angle) * radius;
            const z = Math.sin(angle) * radius;
            points.push([x, 1.5, z]);
        }
        return points;
    }

    //Generate CirclePoints with Angle
    function generateRingPointsWithDegrees(radius: number, segments: number, initialRotation: [number, number, number]) {
        const points: { position: [number, number, number]; degree: number }[] = [];
        for (let i = 0; i < segments; i++) {
            const angleRadians = (i / segments) * Math.PI * 2;
            const x = Math.cos(angleRadians) * radius;
            const z = Math.sin(angleRadians) * radius;
            const degree = (angleRadians * 180) / Math.PI; // Convert radians to degrees
            points.push({
                position: [x, 1.5, z],
                degree,
            });
        }
        return points;
    }

    // Handle circle points based on armBot position
    useEffect(() => {
        const points = generateRingPointsWithDegrees(CIRCLE_RADIUS, 64, armBot.rotation);
        setCirclePointsWithDegrees(points)
    }, [armBot.rotation]);

    // Function for find nearest Circlepoints Index
    const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
        for (let i = 0; i < points.length; i++) {
            const [x, y, z] = points[i];
            if (
                Math.abs(x - nearestPoint[0]) < epsilon &&
                Math.abs(y - nearestPoint[1]) < epsilon &&
                Math.abs(z - nearestPoint[2]) < epsilon
            ) {
                return i; // Found the matching index
            }
        }
        return -1; // Not found
    };

    //function to find nearest Circlepoints 
    const findNearest = (target: [number, number, number]) => {
        return circlePoints.reduce((nearest, point) => {
            const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]);
            const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]);
            return distance < nearestDistance ? point : nearest;
        }, circlePoints[0]);
    };

    // Helper function to collect points and check forbidden degrees
    const collectArcPoints = (startIdx: number, endIdx: number, clockwise: boolean) => {
        const totalSegments = 64;
        const arcPoints: [number, number, number][] = [];
        let i = startIdx;

        while (i !== (endIdx + (clockwise ? 1 : -1) + totalSegments) % totalSegments) {
            const { degree, position } = circlePointsWithDegrees[i];
            // Skip over
            arcPoints.push(position);
            i = (i + (clockwise ? 1 : -1) + totalSegments) % totalSegments;
        }
        return arcPoints;
    };

    //Range to restrict angle 
    const hasForbiddenDegrees = (arc: [number, number, number][]) => {
        return arc.some(p => {
            const idx = findNearestIndex(p, circlePoints);
            const degree = circlePointsWithDegrees[idx]?.degree || 0;
            return degree >= 271 && degree <= 300;  // Forbidden range: 271° to 300°
        });
    };

    // Handle nearest points and final path (including arc points)
    useEffect(() => {
        if (circlePoints.length > 0 && currentPath.length > 0 && ikSolver.mesh) {

            let start = currentPath[0];
            let end = currentPath[currentPath.length - 1];
            const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);

            const armbotStatus = getArmBotById(armBot.modelUuid);
            const currentMaterial = armbotStatus?.currentAction?.materialId;
            const currentAction = getActionByUuid(selectedProduct.productUuid, armbotStatus?.currentAction?.actionUuid || '');

            if (armbotStatus && currentMaterial && currentAction && (currentPhase === 'rest-to-start' || currentPhase === 'start-to-end' || currentPhase === 'end-to-rest')) {
                const materialData = getMaterialById(currentMaterial);
                if (materialData) {
                    const triggeringModel = getTriggeringModels(selectedProduct.productUuid, currentAction.actionUuid);
                    const prevModel = triggeringModel[0] || null;
                    const nextModel = getEventByModelUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '');
                    const nextPoint = getPointByUuid(selectedProduct.productUuid, currentAction?.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid || '', currentAction?.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '');

                    if (prevModel && prevModel.type === 'transfer') {
                        const material = scene.getObjectByProperty("uuid", currentMaterial);
                        const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
                        if (material && armbotModel) {
                            const materialWorldPos = new THREE.Vector3();
                            material.getWorldPosition(materialWorldPos);

                            const armbotWorldPos = new THREE.Vector3();
                            armbotModel.getWorldPosition(armbotWorldPos);

                            const materialLocalPos = materialWorldPos.clone();
                            armbotModel.worldToLocal(materialLocalPos);

                            if (currentPhase === 'rest-to-start') {
                                end = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
                            } else if (currentPhase === 'start-to-end') {
                                start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
                            }
                        }
                    } else if (prevModel && prevModel.type === 'storageUnit') {
                        const position = getMaterialPosition(prevModel.modelUuid, currentMaterial);
                        const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);

                        if (armbotModel) {
                            const armbotWorldPos = new THREE.Vector3();

                            let materialWorldPos = new THREE.Vector3();

                            if (position) {
                                materialWorldPos.copy(position);
                            } else {
                                materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos));
                            }

                            const materialLocalPos = materialWorldPos.clone();
                            armbotModel.worldToLocal(materialLocalPos);

                            if (currentPhase === 'rest-to-start') {
                                end = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
                            } else if (currentPhase === 'start-to-end') {
                                start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
                            } else if (currentPhase === 'end-to-rest') {
                                start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
                            }
                        }
                    }

                    if (nextModel && nextPoint && nextModel.type === 'transfer') {
                        const conveyorModel = scene.getObjectByProperty("uuid", nextModel.modelUuid);
                        const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
                        if (conveyorModel && armbotModel) {
                            const localPoint = new THREE.Vector3(
                                nextPoint.position[0],
                                nextPoint.position[1],
                                nextPoint.position[2]
                            );

                            const worldPoint = conveyorModel.localToWorld(localPoint);

                            armbotModel.worldToLocal(worldPoint);

                            if (currentPhase === 'start-to-end') {
                                end = [worldPoint.x, worldPoint.y + 0.35, worldPoint.z];
                            }
                        }
                    }
                }

            }

            if (currentPhase === 'end-to-rest') {
                const armbotModel = scene.getObjectByProperty("uuid", armBot.modelUuid);
                const armbotWorldPos = new THREE.Vector3();

                if (armbotModel) {
                    let materialWorldPos = new THREE.Vector3();
                    materialWorldPos.copy(bone.getWorldPosition(armbotWorldPos));

                    const materialLocalPos = materialWorldPos.clone();
                    armbotModel.worldToLocal(materialLocalPos);
                    start = [materialLocalPos.x, materialLocalPos.y + 0.35, materialLocalPos.z];
                }
            }

            const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
            const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];

            const nearestToStart = findNearest(raisedStart);
            const nearestToEnd = findNearest(raisedEnd);

            const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
            const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);

            const totalSegments = 64;
            const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + totalSegments) % totalSegments;
            const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + totalSegments) % totalSegments;

            // Try both directions
            const arcClockwise = collectArcPoints(indexOfNearestStart, indexOfNearestEnd, true);
            const arcCounterClockwise = collectArcPoints(indexOfNearestStart, indexOfNearestEnd, false);

            const clockwiseForbidden = hasForbiddenDegrees(arcClockwise);
            const counterClockwiseForbidden = hasForbiddenDegrees(arcCounterClockwise);

            let arcPoints: [number, number, number][] = [];

            if (!clockwiseForbidden && (clockwiseDistance <= counterClockwiseDistance || counterClockwiseForbidden)) {
                arcPoints = arcClockwise;
            } else {
                arcPoints = arcCounterClockwise;
            }

            const pathVectors = [
                new THREE.Vector3(start[0], start[1], start[2]),
                new THREE.Vector3(start[0], curveHeight, start[2]),
                new THREE.Vector3(nearestToStart[0], curveHeight, nearestToStart[2]),
                ...arcPoints.map(point => new THREE.Vector3(point[0], curveHeight, point[2])),
                new THREE.Vector3(nearestToEnd[0], curveHeight, nearestToEnd[2]),
                new THREE.Vector3(end[0], curveHeight, end[2]),
                new THREE.Vector3(end[0], end[1], end[2])
            ];

            const pathSegments: [THREE.Vector3, THREE.Vector3][] = [];
            for (let i = 0; i < pathVectors.length - 1; i++) {
                pathSegments.push([pathVectors[i], pathVectors[i + 1]]);
            }

            const segmentDistances = pathSegments.map(([p1, p2]) => p1.distanceTo(p2));
            segmentDistancesRef.current = segmentDistances;
            const totalDistance = segmentDistances.reduce((sum, d) => sum + d, 0);
            totalDistanceRef.current = totalDistance;

            setCustomCurvePoints(pathVectors);
        }
    }, [circlePoints, currentPath]);

    // Frame update for animation
    useFrame((_, delta) => {
        if (!ikSolver) return;

        const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);

        if (!bone) return;
        if (isPlaying) {
            if (!isPaused && customCurvePoints && customCurvePoints.length > 0) {
                const distances = segmentDistancesRef.current;
                const totalDistance = totalDistanceRef.current;

                progressRef.current += delta * (speed * armBot.speed);
                const coveredDistance = progressRef.current;

                let index = 0;
                let accumulatedDistance = 0;

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

                if (index < distances.length) {
                    const startPoint = customCurvePoints[index];
                    const endPoint = customCurvePoints[index + 1];
                    const segmentDistance = distances[index];
                    const t = (coveredDistance - accumulatedDistance) / segmentDistance;
                    if (startPoint && endPoint) {
                        const position = startPoint.clone().lerp(endPoint, t);
                        bone.position.copy(position);
                    }
                }

                if (progressRef.current >= totalDistance) {
                    HandleCallback();
                    setCurrentPath([]);
                    setCustomCurvePoints([]);
                    curveRef.current = null;
                    progressRef.current = 0;
                    startTimeRef.current = null;
                }
                ikSolver.update();
            }
        } else if (!isPlaying && currentPath.length === 0) {
            progressRef.current = 0;
            startTimeRef.current = null;
            setCurrentPath([]);
            setCustomCurvePoints([]);
            bone.position.copy(restPosition);
            ikSolver.update();
        }
    });

    return (
        <>
            {/* {customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
                <mesh rotation={armBot.rotation} position={armBot.position} visible={false}>
                    <Line
                        points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
                        color="green"
                        lineWidth={5}
                        dashed={false}
                    />
                </mesh>
            )}
            <group
                position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]}
                rotation={[armBot.rotation[0], armBot.rotation[1], armBot.rotation[2]]}
                visible={false}
            >
                <mesh rotation={[-Math.PI / 2, 0, 0]} visible={false}>
                    <ringGeometry args={[CIRCLE_RADIUS, CIRCLE_RADIUS + 0.02, 64]} />
                    <meshBasicMaterial color="green" side={THREE.DoubleSide} />
                </mesh>

                {[90, 180, 270, 360].map((degree, index) => {
                    const rad = ((degree) * Math.PI) / 180;
                    const x = CIRCLE_RADIUS * Math.cos(rad);
                    const z = CIRCLE_RADIUS * Math.sin(rad);
                    const y = 0;
                    return (
                        <mesh key={index} position={[x, y, z]}>
                            <sphereGeometry args={[0.05, 16, 16]} />
                            <meshStandardMaterial color="red" />
                        </mesh>
                    );
                })}

                {[90, 180, 270, 360].map((degree, index) => {
                    const rad = ((degree) * Math.PI) / 180;
                    const x = CIRCLE_RADIUS * Math.cos(rad);
                    const z = CIRCLE_RADIUS * Math.sin(rad);
                    const y = 0.15;

                    return (
                        <Text
                            key={`text-${index}`}
                            position={[x, y, z]}
                            fontSize={0.2}
                            color="yellow"
                            anchorX="center"
                            anchorY="middle"
                        >
                            {degree}°
                        </Text>
                    );
                })}
            </group> */}

        </>
    );
}

export default RoboticArmAnimator;