import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import { useToolMode } from "../../../store/store";
import { Html } from "@react-three/drei";

const MeasurementTool = () => {
    const { gl, raycaster, pointer, camera, scene } = useThree();
    const { toolMode } = useToolMode();

    const [points, setPoints] = useState<THREE.Vector3[]>([]);
    const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
        null
    );
    const groupRef = useRef<THREE.Group>(null);
    const [startConePosition, setStartConePosition] =
        useState<THREE.Vector3 | null>(null);
    const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
        null
    );
    const [startConeQuaternion, setStartConeQuaternion] = useState(
        new THREE.Quaternion()
    );
    const [endConeQuaternion, setEndConeQuaternion] = useState(
        new THREE.Quaternion()
    );
    const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });

    const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1;
    const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4;
    const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0;

    useEffect(() => {
        const canvasElement = gl.domElement;
        let drag = false;
        let isLeftMouseDown = false;

        const onMouseDown = () => {
            isLeftMouseDown = true;
            drag = false;
        };

        const onMouseUp = (evt: any) => {
            isLeftMouseDown = false;
            if (evt.button === 0 && !drag) {
                raycaster.setFromCamera(pointer, camera);
                const intersects = raycaster
                    .intersectObjects(scene.children, true)
                    .filter(
                        (intersect) =>
                            !intersect.object.name.includes("Roof") &&
                            !intersect.object.name.includes("MeasurementReference") &&
                            !intersect.object.name.includes("agv-collider") &&
                            !(intersect.object.type === "GridHelper")
                    );

                if (intersects.length > 0) {
                    const intersectionPoint = intersects[0].point.clone();
                    if (points.length < 2) {
                        setPoints([...points, intersectionPoint]);
                    } else {
                        setPoints([intersectionPoint]);
                    }
                }
            }
        };

        const onMouseMove = () => {
            if (isLeftMouseDown) drag = true;
        };

        const onContextMenu = (evt: any) => {
            evt.preventDefault();
            if (!drag) {
                evt.preventDefault();
                setPoints([]);
                setTubeGeometry(null);
            }
        };

        if (toolMode === "MeasurementScale") {
            canvasElement.addEventListener("pointerdown", onMouseDown);
            canvasElement.addEventListener("pointermove", onMouseMove);
            canvasElement.addEventListener("pointerup", onMouseUp);
            canvasElement.addEventListener("contextmenu", onContextMenu);
        } else {
            resetMeasurement();
            setPoints([]);
        }

        return () => {
            canvasElement.removeEventListener("pointerdown", onMouseDown);
            canvasElement.removeEventListener("pointermove", onMouseMove);
            canvasElement.removeEventListener("pointerup", onMouseUp);
            canvasElement.removeEventListener("contextmenu", onContextMenu);
        };
    }, [toolMode, camera, raycaster, pointer, scene, points]);

    useFrame(() => {
        if (points.length === 1) {
            raycaster.setFromCamera(pointer, camera);
            const intersects = raycaster
                .intersectObjects(scene.children, true)
                .filter(
                    (intersect) =>
                        !intersect.object.name.includes("Roof") &&
                        !intersect.object.name.includes("MeasurementReference") &&
                        !intersect.object.name.includes("agv-collider") &&
                        !(intersect.object.type === "GridHelper")
                );

            if (intersects.length > 0) {
                updateMeasurement(points[0], intersects[0].point);
            }
        } else if (points.length === 2) {
            updateMeasurement(points[0], points[1]);
        } else {
            resetMeasurement();
        }
    });

    const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
        const distance = start.distanceTo(end);

        const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS);
        const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS);
        const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT);

        setConeSize({ radius: coneRadius, height: coneHeight });

        const direction = new THREE.Vector3().subVectors(end, start).normalize();

        const offset = direction.clone().multiplyScalar(coneHeight * 0.5);

        let tubeStart = start.clone().add(offset);
        let tubeEnd = end.clone().sub(offset);

        tubeStart.y = Math.max(tubeStart.y, 0);
        tubeEnd.y = Math.max(tubeEnd.y, 0);

        const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
        setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));

        setStartConePosition(tubeStart);
        setEndConePosition(tubeEnd);
        setStartConeQuaternion(getArrowOrientation(start, end));
        setEndConeQuaternion(getArrowOrientation(end, start));
    };

    const resetMeasurement = () => {
        setTubeGeometry(null);
        setStartConePosition(null);
        setEndConePosition(null);
    };

    const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
        const direction = new THREE.Vector3()
            .subVectors(end, start)
            .normalize()
            .negate();
        const quaternion = new THREE.Quaternion();
        quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
        return quaternion;
    };

    useEffect(() => {
        if (points.length === 2) {
            // console.log(points[0].distanceTo(points[1]));
        }
    }, [points]);

    return (
        <group ref={groupRef} name="MeasurementGroup">
            {startConePosition && (
                <mesh
                    name="MeasurementReference"
                    position={startConePosition}
                    quaternion={startConeQuaternion}
                >
                    <coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
                    <meshBasicMaterial color="yellow" />
                </mesh>
            )}
            {endConePosition && (
                <mesh
                    name="MeasurementReference"
                    position={endConePosition}
                    quaternion={endConeQuaternion}
                >
                    <coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
                    <meshBasicMaterial color="yellow" />
                </mesh>
            )}
            {tubeGeometry && (
                <mesh name="MeasurementReference" geometry={tubeGeometry}>
                    <meshBasicMaterial color="yellow" />
                </mesh>
            )}

            {startConePosition && endConePosition && (
                <Html
                    scale={THREE.MathUtils.clamp(
                        startConePosition.distanceTo(endConePosition) * 0.25,
                        0,
                        10
                    )}
                    position={[
                        (startConePosition.x + endConePosition.x) / 2,
                        (startConePosition.y + endConePosition.y) / 2,
                        (startConePosition.z + endConePosition.z) / 2,
                    ]}
                    // class
                    wrapperClass="distance-text-wrapper"
                    className="distance-text"
                    // other
                    zIndexRange={[1, 0]}
                    prepend
                    sprite
                >
                    <div>
                        {startConePosition.distanceTo(endConePosition).toFixed(2)} m
                    </div>
                </Html>
            )}
        </group>
    );
};

export default MeasurementTool;
