import React, { useRef, useState } from "react";
import {
    Vector3,
    Raycaster,
    BufferGeometry,
    LineBasicMaterial,
    Line,
    Mesh,
    Group,
} from "three";
import { useThree, useFrame } from "@react-three/fiber";
import { Html } from "@react-three/drei";

interface DistanceFindingControlsProps {
    boundingBoxRef: React.RefObject<Mesh>;
    object: number;
}

const material = new LineBasicMaterial({ color: "#d2baff" });

const DIRECTION_LABEL_MAP = {
    textPosX: "textPosX",
    textNegX: "textNegX",
    textPosZ: "textPosZ",
    textNegZ: "textNegZ",
} as const;

const DistanceFindingControls = ({
    boundingBoxRef,
    object,
}: DistanceFindingControlsProps) => {
    const { camera, scene } = useThree();
    const [labelValues, setLabelValues] = useState<Record<string, string>>({
        textPosX: "",
        textNegX: "",
        textPosZ: "",
        textNegZ: "",
    });

    const lineRefs = {
        posX: useRef<Line>(null),
        negX: useRef<Line>(null),
        posZ: useRef<Line>(null),
        negZ: useRef<Line>(null),
        posY: useRef<Line>(null),
    };

    const textRefs = {
        textPosX: useRef<Group>(null),
        textNegX: useRef<Group>(null),
        textPosZ: useRef<Group>(null),
        textNegZ: useRef<Group>(null),
        textPosY: useRef<Group>(null),
    };

    const lineGeometries = useRef({
        posX: new BufferGeometry(),
        negX: new BufferGeometry(),
        posZ: new BufferGeometry(),
        negZ: new BufferGeometry(),
        posY: new BufferGeometry(),
    });

    useFrame(() => {
        const bboxMesh = boundingBoxRef?.current;
        if (!bboxMesh) return;

        bboxMesh.geometry.computeBoundingBox();
        const bbox = bboxMesh.geometry.boundingBox;
        if (!bbox) return;

        const size = {
            x: bbox.max.x - bbox.min.x,
            y: bbox.max.y - bbox.min.y,
            z: bbox.max.z - bbox.min.z,
        };

        const center = bboxMesh.getWorldPosition(new Vector3()).clone();
        if (!center) return;

        updateLine("posX", new Vector3(1, 0, 0), "pos", size, center);
        updateLine("negX", new Vector3(-1, 0, 0), "neg", size, center);
        updateLine("posZ", new Vector3(0, 0, 1), "pos", size, center);
        updateLine("negZ", new Vector3(0, 0, -1), "neg", size, center);
        updateLine("posY", new Vector3(0, -1, 0), "posY", size, center);
    });

    const updateLine = (
        key: keyof typeof lineRefs,
        direction: Vector3,
        angle: string,
        size: { x: number; y: number; z: number },
        origin: Vector3
    ) => {
        const line = lineRefs[key].current;
        const geometry = lineGeometries.current[key];
        const mesh = textRefs[`text${key[0].toUpperCase() + key.slice(1)}` as keyof typeof textRefs];

        if (!line) return;

        const points: Vector3[] = [];
        const halfSize = new Vector3(size.x / 2, size.y / 2, size.z / 2);

        if (angle === "pos") {
            points[0] = origin.clone().add(direction.clone().multiply(halfSize));
        } else if (angle === "neg") {
            points[0] = origin.clone().sub(direction.clone().multiply(halfSize));
        } else if (angle === "posY") {
            points[0] = origin.clone().sub(new Vector3(0, size.y / 2, 0));
        }

        const ray = new Raycaster();
        ray.camera = camera;
        ray.set(origin, direction);
        ray.params.Line.threshold = 0.1;

        const wallsGroup = scene.children.find((val) =>
            val?.name.includes("Walls")
        );
        const intersects = wallsGroup
            ? ray.intersectObjects([wallsGroup], true)
            : [];

        const intersect = intersects.find((i) =>
            i.object.name.includes("Wall")
        );

        if (intersect) {
            points[1] = angle !== "posY" ? intersect.point : new Vector3(origin.x, 0, origin.z);
        }

        if (points[1]) {
            geometry.dispose();
            geometry.setFromPoints(points);
            line.geometry = geometry;

            const distance = points[0].distanceTo(points[1]).toFixed(2);

            if (mesh?.current) {
                geometry.computeBoundingSphere();
                mesh.current.position.copy(geometry.boundingSphere!.center);

                const labelEl = document.getElementById(mesh.current.name);
                if (labelEl) {
                    labelEl.innerText = `${distance}m`;

                    if (DIRECTION_LABEL_MAP[labelEl.id as keyof typeof DIRECTION_LABEL_MAP]) {
                        setLabelValues((prev) => ({
                            ...prev,
                            [labelEl.id]: distance,
                        }));
                    }
                }
            }
        } else {
            geometry.dispose();
            geometry.setFromPoints([new Vector3(), new Vector3()]);
            line.geometry = geometry;

            const labelEl = document.getElementById(mesh?.current?.name ?? "");
            if (labelEl && DIRECTION_LABEL_MAP[labelEl.id as keyof typeof DIRECTION_LABEL_MAP]) {
                labelEl.innerText = "";
                setLabelValues((prev) => ({
                    ...prev,
                    [labelEl.id]: "",
                }));
            }
        }
    };

    const renderLabel = (id: keyof typeof textRefs) => (
        <group name={id} ref={textRefs[id]}>
            <Html
                wrapperClass="distance-text-wrapper"
                className="distance-text"
                zIndexRange={[1, 0]}
                style={{
                    pointerEvents: "none",
                    visibility: labelValues[id] === "" ? "hidden" : "visible",
                }}
            >
                <div className="distance-label" id={id}>{labelValues[id]}</div>
            </Html>
        </group>
    );

    return (
        <>
            {boundingBoxRef.current && object > 0 && (
                <group
                    name="DistanceFindingControls"
                >
                    {renderLabel("textPosX")}
                    {renderLabel("textNegX")}
                    {renderLabel("textPosZ")}
                    {renderLabel("textNegZ")}

                    <primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.posX} />
                    <primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.negX} />
                    <primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.posZ} />
                    <primitive object={new Line(new BufferGeometry(), material)} ref={lineRefs.negZ} />
                </group>
            )}
        </>
    );
};

export default DistanceFindingControls;
