import { useEffect, useMemo, useRef, useState } from "react";
import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import { Box, Sphere } from "@react-three/drei";

function PillarJibHelper({
    crane,
    points,
    isHelperNeeded
}: {
    crane: CraneStatus,
    points: [THREE.Vector3, THREE.Vector3] | null;
    isHelperNeeded: boolean;
}) {
    const { scene } = useThree();
    const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
    const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);

    const baseWorldRef = useRef<THREE.Vector3 | null>(null);
    const trolleyWorldRef = useRef<THREE.Vector3 | null>(null);
    const hookWorldRef = useRef<THREE.Vector3 | null>(null);

    useEffect(() => {
        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) return;

        const bw = new THREE.Vector3();
        const tw = new THREE.Vector3();
        const hw = new THREE.Vector3();

        base.getWorldPosition(bw);
        trolley.getWorldPosition(tw);
        hook.getWorldPosition(hw);

        baseWorldRef.current = bw;
        trolleyWorldRef.current = tw;
        hookWorldRef.current = hw;
    }, [scene, crane.modelUuid]);

    const { geometry, position } = useMemo(() => {
        const model = scene.getObjectByProperty('uuid', crane.modelUuid);
        if (!model || !baseWorldRef.current || !trolleyWorldRef.current || !hookWorldRef.current) return { geometry: null, position: null };

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

        if (!base || !trolley || !hook || !points) return { geometry: null, position: null };

        const baseWorld = baseWorldRef.current;

        const trolleyWorld = trolleyWorldRef.current;

        const hookWorld = hookWorldRef.current;

        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 outerRadius = distFromBase + trolleyMaxOffset;
        const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05);
        const height = (hookMinOffset - (hookMaxOffset));
        const cylinderYPosition = hookWorld.y + (height / 2) + (hookMaxOffset + hookMinOffset) / 2;

        const shape = new THREE.Shape();
        shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false);

        const hole = new THREE.Path();
        hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true);
        shape.holes.push(hole);

        const extrudeSettings = {
            depth: height,
            bevelEnabled: false,
            steps: 1
        };

        const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
        const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];

        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);
        setIsInside(newIsInside);

        return { geometry, position };
    }, [scene, crane.modelUuid, points]);

    if (!geometry || !position) return null;

    return (
        <>
            {isHelperNeeded &&
                <>
                    <mesh
                        geometry={geometry}
                        position={position}
                        rotation={[Math.PI / 2, 0, 0]}
                    >
                        <meshStandardMaterial
                            color={0x888888}
                            metalness={0.5}
                            roughness={0.4}
                            side={THREE.DoubleSide}
                            transparent={true}
                            opacity={0.3}
                        />
                    </mesh>

                    {points && points.map((point, i) => (
                        <Box
                            key={`original-${i}`}
                            position={point}
                            args={[0.15, 0.15, 0.15]}
                        >
                            <meshStandardMaterial color="yellow" />
                        </Box>
                    ))}

                    {clampedPoints && clampedPoints.map((point, i) => (
                        <Sphere
                            key={`clamped-${i}`}
                            position={point}
                            args={[0.1, 16, 16]}
                        >
                            <meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
                        </Sphere>
                    ))}
                </>
            }
        </>
    );
}

export default PillarJibHelper;