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

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

    const [points, setPoints] = useState<THREE.Vector3[]>([]);
    const [linePoints, setLinePoints] = useState<THREE.Vector3[] | null>(null);
    const [axisLock, setAxisLock] = useState<"x" | "y" | "z" | null>(null);
    const groupRef = useRef<THREE.Group>(null);
    const keysPressed = useRef<Set<string>>(new Set());

    useEffect(() => {
        const handleKeyDown = (e: KeyboardEvent) => {
            if (e.altKey) {
                if (e.key.toLowerCase() === "x") keysPressed.current.add("x");
                else if (e.key.toLowerCase() === "y") keysPressed.current.add("y");
                else if (e.key.toLowerCase() === "z") keysPressed.current.add("z");

                if (keysPressed.current.has("x")) setAxisLock("x");
                else if (keysPressed.current.has("y")) setAxisLock("y");
                else if (keysPressed.current.has("z")) setAxisLock("z");
            } else if (e.key === "Escape") {
                setPoints([]);
                setLinePoints(null);
                setAxisLock(null);
            }
        };

        const handleKeyUp = (e: KeyboardEvent) => {
            keysPressed.current.delete(e.key.toLowerCase());
            if (keysPressed.current.has("x")) setAxisLock("x");
            else if (keysPressed.current.has("y")) setAxisLock("y");
            else if (keysPressed.current.has("z")) setAxisLock("z");
            else setAxisLock(null);
        };

        window.addEventListener("keydown", handleKeyDown);
        window.addEventListener("keyup", handleKeyUp);
        return () => {
            window.removeEventListener("keydown", handleKeyDown);
            window.removeEventListener("keyup", handleKeyUp);
        };
    }, []);

    const getLineColor = useCallback(() => {
        switch (axisLock) {
            case "x":
                return "#d94522"; // Red for X axis
            case "y":
                return "#22ab2e"; // Green for Y axis
            case "z":
                return "#227bd9"; // Blue for Z axis
            default:
                return "#b18ef1"; // Default purple
        }
    }, [axisLock]);

    const applyAxisLock = useCallback(
        (point: THREE.Vector3, referencePoint: THREE.Vector3) => {
            const lockedPoint = point.clone();

            switch (axisLock) {
                case "x":
                    lockedPoint.y = referencePoint.y;
                    lockedPoint.z = referencePoint.z;
                    break;
                case "y":
                    lockedPoint.x = referencePoint.x;
                    lockedPoint.z = referencePoint.z;
                    break;
                case "z":
                    lockedPoint.x = referencePoint.x;
                    lockedPoint.y = referencePoint.y;
                    break;
            }

            return lockedPoint;
        },
        [axisLock]
    );

    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.name.includes("zonePlane") &&
                            !intersect.object.name.includes("SelectionGroup") &&
                            !intersect.object.name.includes("selectionAssetGroup") &&
                            !intersect.object.name.includes("SelectionGroupBoundingBoxLine") &&
                            !intersect.object.name.includes("SelectionGroupBoundingBox") &&
                            !intersect.object.name.includes("SelectionGroupBoundingLine") &&
                            intersect.object.type !== "TransformControlsPlane" &&
                            intersect.object.type !== "GridHelper"
                    );

                if (intersects.length > 0) {
                    let intersectionPoint = intersects[0].point.clone();
                    if (axisLock && points.length > 0) {
                        intersectionPoint = applyAxisLock(intersectionPoint, points[points.length - 1]);
                    }
                    if (points.length < 2) {
                        setPoints([...points, intersectionPoint]);
                    } else {
                        setPoints([intersectionPoint]);
                    }
                }
            }
        };

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

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

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

        return () => {
            canvasElement.removeEventListener("pointerdown", onMouseDown);
            canvasElement.removeEventListener("pointermove", onMouseMove);
            canvasElement.removeEventListener("pointerup", onMouseUp);
            canvasElement.removeEventListener("contextmenu", onContextMenu);
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [toolMode, camera, raycaster, pointer, scene, points, axisLock]);

    useFrame(() => {
        if (points.length === 1) {
            // live preview for second point
            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.name.includes("zonePlane") &&
                        !intersect.object.name.includes("SelectionGroup") &&
                        !intersect.object.name.includes("selectionAssetGroup") &&
                        !intersect.object.name.includes("SelectionGroupBoundingBoxLine") &&
                        !intersect.object.name.includes("SelectionGroupBoundingBox") &&
                        !intersect.object.name.includes("SelectionGroupBoundingLine") &&
                        intersect.object.type !== "TransformControlsPlane" &&
                        intersect.object.type !== "GridHelper"
                );

            if (intersects.length > 0) {
                let tempEnd = intersects[0].point.clone();
                if (axisLock) {
                    tempEnd = applyAxisLock(tempEnd, points[0]);
                }
                updateMeasurement(points[0], tempEnd);
            }
        } else if (points.length === 2) {
            // second point already fixed
            updateMeasurement(points[0], points[1]);
        } else {
            setLinePoints(null);
        }
    });

    const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
        setLinePoints([start.clone(), end.clone()]);
    };

    return (
        <group ref={groupRef} name="MeasurementGroup">
            {linePoints && (
                <>
                    {/* Outline line */}
                    <Line
                        points={linePoints}
                        color="black"
                        lineWidth={6} // thicker than main line
                        depthTest={false}
                        depthWrite={false}
                        renderOrder={998} // render behind main line
                    />

                    {/* Main line */}
                    <Line
                        points={linePoints}
                        color={getLineColor()}
                        lineWidth={2} // actual line width
                        depthTest={false}
                        depthWrite={false}
                        transparent={false}
                        opacity={1}
                        renderOrder={999} // render on top
                    />
                </>
            )}

            {points.map((point, index) => (
                <Html key={index} position={point} scale={0.5} wrapperClass="measurement-label-wrapper" className="measurement-label" zIndexRange={[1, 0]} prepend sprite>
                    <div className="measurement-point"></div>
                </Html>
            ))}

            {linePoints && linePoints.length === 2 && (
                <Html
                    position={[(linePoints[0].x + linePoints[1].x) / 2, (linePoints[0].y + linePoints[1].y) / 2, (linePoints[0].z + linePoints[1].z) / 2]}
                    scale={0.5}
                    wrapperClass="distance-text-wrapper"
                    className="distance-text"
                    zIndexRange={[2, 1]}
                    prepend
                    sprite
                >
                    <div>{linePoints[0].distanceTo(linePoints[1]).toFixed(2)} m</div>
                </Html>
            )}
        </group>
    );
};

export default MeasurementTool;
