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 DistanceFindingControls = ({
  boundingBoxRef,
  object,
}: DistanceFindingControlsProps) => {
  const { camera, scene } = useThree();
  const [labelValues, setLabelValues] = useState<{
    textPosX: any;
    textNegX: any;
    textPosZ: any;
    textNegZ: any;
  }>({
    textPosX: "",
    textNegX: "",
    textPosZ: "",
    textNegZ: "",
  });

  // Refs for measurement lines
  const line1 = useRef<Line>(null);
  const line2 = useRef<Line>(null);
  const line3 = useRef<Line>(null);
  const line4 = useRef<Line>(null);
  const line5 = useRef<Line>(null);

  // Refs for measurement text labels
  const textPosX = useRef<Group>(null);
  const textNegX = useRef<Group>(null);
  const textPosZ = useRef<Group>(null);
  const textNegZ = useRef<Group>(null);
  const textPosY = useRef<Group>(null);

  // Store line geometries to avoid recreation
  const lineGeometries = useRef({
    posX: new BufferGeometry(),
    negX: new BufferGeometry(),
    posZ: new BufferGeometry(),
    negZ: new BufferGeometry(),
    posY: new BufferGeometry(),
  });

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

    boundingBoxRef.current.geometry.computeBoundingBox();
    const bbox = boundingBoxRef.current.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 vec = boundingBoxRef.current?.getWorldPosition(new Vector3()).clone();

    if (!vec) return;
    updateLine({
      line: line1.current,
      geometry: lineGeometries.current.posX,
      direction: new Vector3(1, 0, 0), // Positive X
      angle: "pos",
      mesh: textPosX,
      vec,
      size,
    });
    updateLine({
      line: line2.current,
      geometry: lineGeometries.current.negX,
      direction: new Vector3(-1, 0, 0), // Negative X
      angle: "neg",
      mesh: textNegX,
      vec,
      size,
    });
    updateLine({
      line: line3.current,
      geometry: lineGeometries.current.posZ,
      direction: new Vector3(0, 0, 1), // Positive Z
      angle: "pos",
      mesh: textPosZ,
      vec,
      size,
    });
    updateLine({
      line: line4.current,
      geometry: lineGeometries.current.negZ,
      direction: new Vector3(0, 0, -1), // Negative Z
      angle: "neg",
      mesh: textNegZ,
      vec,
      size,
    });
    updateLine({
      line: line5.current,
      geometry: lineGeometries.current.posY,
      direction: new Vector3(0, -1, 0), // Down (Y)
      angle: "posY",
      mesh: textPosY,
      vec,
      size,
    });
  });

  const updateLine = ({
    line,
    geometry,
    direction,
    angle,
    mesh,
    vec,
    size,
  }: {
    line: Line | null;
    geometry: BufferGeometry;
    direction: Vector3;
    angle: string;
    mesh: React.RefObject<Group>;
    vec: Vector3;
    size: { x: number; y: number; z: number };
  }) => {
    if (!line) return;

    const points = [];

    if (angle === "pos") {
      points[0] = new Vector3(vec.x, vec.y, vec.z).add(
        new Vector3((direction.x * size.x) / 2, 0, (direction.z * size.z) / 2)
      );
    } else if (angle === "neg") {
      points[0] = new Vector3(vec.x, vec.y, vec.z).sub(
        new Vector3((-direction.x * size.x) / 2, 0, (-direction.z * size.z) / 2)
      );
    } else if (angle === "posY") {
      points[0] = new Vector3(vec.x, vec.y, vec.z).sub(
        new Vector3(0, size.y / 2, 0)
      );
    }

    const ray = new Raycaster();
    if (camera) ray.camera = camera;
    ray.set(new Vector3(vec.x, vec.y, vec.z), direction);
    ray.params.Line.threshold = 0.1;

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

    // Find intersection point
    if (intersects[0]) {
      for (const intersect of intersects) {
        if (intersect.object.name.includes("Wall")) {
          points[1] =
            angle !== "posY" ? intersect.point : new Vector3(vec.x, 0, vec.z); // Floor
          break;
        }
      }
    }
    if (points[1]) {
      geometry.dispose();
      geometry.setFromPoints([points[0], points[1]]);
      line.geometry = geometry;

      // Calculate the distance only once
      const distance = points[0].distanceTo(points[1]).toFixed(2);

      // Update measurement text
      if (mesh?.current) {
        geometry.computeBoundingSphere();
        const center = geometry.boundingSphere?.center;
        if (center) {
          mesh.current.position.copy(center);
        }

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

          // Update specific label state based on the label ID
          switch (label.id) {
            case "textPosX":
              setLabelValues((prevState) => ({ ...prevState, textPosX: distance }));
              break;
            case "textNegX":
              setLabelValues((prevState) => ({ ...prevState, textNegX: distance }));
              break;
            case "textPosZ":
              setLabelValues((prevState) => ({ ...prevState, textPosZ: distance }));
              break;
            case "textNegZ":
              setLabelValues((prevState) => ({ ...prevState, textNegZ: distance }));
              break;
            default:
              break;
          }
        }
      }
    } else {
      // No intersection found - clear the line
      geometry.dispose();
      geometry.setFromPoints([new Vector3(), new Vector3()]);
      line.geometry = geometry;

      const label = document.getElementById(mesh?.current?.name ?? "");
      if (label) {
        label.innerText = "";

        // Clear the corresponding label value in the state
        switch (label.id) {
          case "textPosX":
            setLabelValues((prevState) => ({ ...prevState, textPosX: "" }));
            break;
          case "textNegX":
            setLabelValues((prevState) => ({ ...prevState, textNegX: "" }));
            break;
          case "textPosZ":
            setLabelValues((prevState) => ({ ...prevState, textPosZ: "" }));
            break;
          case "textNegZ":
            setLabelValues((prevState) => ({ ...prevState, textNegZ: "" }));
            break;
          default:
            break;
        }
      }
    }

  };

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

  return (
    <>
      {/* Measurement text labels */}
      {boundingBoxRef.current && object > 0 && (
        <>
          <group name="textPosX" ref={textPosX}>
            <Html
              wrapperClass="distance-text-wrapper"
              className="distance-text"
              zIndexRange={[1, 0]}
              style={{
                pointerEvents: "none",
                visibility: labelValues.textPosX === "" ? "hidden" : "visible",
              }}
            >
              <div className="distance-label" id="textPosX">{labelValues.textPosX}</div>
            </Html>
          </group>

          <group name="textNegX" ref={textNegX}>
            <Html
              wrapperClass="distance-text-wrapper"
              className="distance-text"
              zIndexRange={[1, 0]}
              style={{
                pointerEvents: "none",
                visibility: labelValues.textNegX === "" ? "hidden" : "visible",
              }}
            >
              <div className="distance-label" id="textNegX">{labelValues.textNegX}</div>
            </Html>
          </group>

          <group name="textPosZ" ref={textPosZ}>
            <Html
              wrapperClass="distance-text-wrapper"
              className="distance-text"
              zIndexRange={[2, 0]}
              style={{
                pointerEvents: "none",
                visibility: labelValues.textPosZ === "" ? "hidden" : "visible",
              }}
            >
              <div className="distance-label" id="textPosZ">{labelValues.textPosZ}</div>
            </Html>
          </group>

          <group name="textNegZ" ref={textNegZ}>
            <Html
              wrapperClass="distance-text-wrapper"
              className="distance-text"
              zIndexRange={[1, 0]}
              style={{
                pointerEvents: "none",
                visibility: labelValues.textNegZ === "" ? "hidden" : "visible",
              }}
            >
              <div className="distance-label" id="textNegZ">{labelValues.textNegZ}</div>
            </Html>
          </group>

          {/* Measurement lines */}
          <primitive
            object={new Line(new BufferGeometry(), Material)}
            ref={line1}
          />
          <primitive
            object={new Line(new BufferGeometry(), Material)}
            ref={line2}
          />
          <primitive
            object={new Line(new BufferGeometry(), Material)}
            ref={line3}
          />
          <primitive
            object={new Line(new BufferGeometry(), Material)}
            ref={line4}
          />
        </>
      )
      }
    </>
  );
};

export default DistanceFindingControls;
