import React, { useRef, useState, useEffect } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { Line } from "@react-three/drei";
type PointData = {
  pointId: string;
  position: [number, number, number];
  isCurved: boolean;
  handleA: [number, number, number] | null;
  handleB: [number, number, number] | null;
};
interface PointProps {
  point: any;
  pointIndex: number;
  groupIndex: number;
  selected: number[];
  mainShapeOnly?: PointData[];
  setSelected: React.Dispatch<React.SetStateAction<number[]>>;
  pointsGroups: any[][];
  setPointsGroups: React.Dispatch<React.SetStateAction<any[][]>>;
  shortestPath: number[]; // <- add this
  setShortestPath: React.Dispatch<React.SetStateAction<number[]>>; // <- add this
  setShortestDistance?: React.Dispatch<React.SetStateAction<number>>; // optional
}

export default function PointHandle({
  point,
  pointIndex,
  groupIndex,
  selected,
  setSelected,
  pointsGroups,
  setPointsGroups,
  setShortestDistance,
  shortestPath,
  setShortestPath,
}: PointProps) {
  const meshRef = useRef<THREE.Mesh>(null);
  const handleARef = useRef<THREE.Mesh>(null);
  const handleBRef = useRef<THREE.Mesh>(null);
  const lineRef = useRef<THREE.Line>(null!);
  //   const pathLineRef = useRef<THREE.Line>(null!);

  const { camera, gl, controls } = useThree();
  const [dragging, setDragging] = useState<
    null | "main" | "handleA" | "handleB"
  >(null);
  const dragOffset = useRef(new THREE.Vector3());
  //   const [shortestPath, setShortestPath] = useState<number[]>([]);

  /** Shift-click or ctrl-click handling */
  const onPointClick = (e: any) => {
    e.stopPropagation();

    if (e.ctrlKey) {
      // Toggle handles
      setPointsGroups((prev) => {
        const newGroups = [...prev];
        const group = [...newGroups[groupIndex]];
        const idx = group.findIndex((p) => p.pointId === point.pointId);
        const updated = { ...group[idx] };

        if (!updated.handleA && !updated.handleB) {
          updated.handleA = [
            updated.position[0] + 1,
            updated.position[1],
            updated.position[2],
          ];
          updated.handleB = [
            updated.position[0] - 1,
            updated.position[1],
            updated.position[2],
          ];
          updated.isCurved = true;
        } else {
          updated.handleA = null;
          updated.handleB = null;
          updated.isCurved = false;
        }

        group[idx] = updated;
        newGroups[groupIndex] = group;
        return newGroups;
      });
    } else if (e.shiftKey) {
      // Shift-click for multi-select
      setSelected((prev) => {
        if (prev.includes(pointIndex)) return prev; // keep selection
        const newSelection = [...prev, pointIndex];
        return newSelection.slice(-2); // keep only 2 points
      });
    } else {
      // Single selection
      setSelected([pointIndex]);
    }
  };

  /** Dragging logic */
  const startDrag = (target: "main" | "handleA" | "handleB", e: any) => {
    e.stopPropagation();
    setDragging(target);
    const targetRef =
      target === "main"
        ? meshRef.current
        : target === "handleA"
        ? handleARef.current
        : handleBRef.current;
    if (targetRef) dragOffset.current.copy(targetRef.position).sub(e.point);
    if (controls) (controls as any).enabled = false;
    gl.domElement.style.cursor = "grabbing";
  };

  const stopDrag = () => {
    setDragging(null);
    gl.domElement.style.cursor = "auto";
    if (controls) (controls as any).enabled = true;
  };

  useFrame(({ raycaster, mouse }) => {
    if (!dragging) return;

    const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
    raycaster.setFromCamera(mouse, camera);
    const intersection = new THREE.Vector3();

    if (raycaster.ray.intersectPlane(plane, intersection)) {
      const newPos = intersection.add(dragOffset.current);

      setPointsGroups((prev) => {
        const newGroups = [...prev];
        const group = [...newGroups[groupIndex]];
        const idx = group.findIndex((p) => p.pointId === point.pointId);
        const updated = { ...group[idx] };

        if (dragging === "main") {
          const delta = new THREE.Vector3()
            .fromArray(newPos.toArray())
            .sub(new THREE.Vector3().fromArray(updated.position));
          updated.position = newPos.toArray() as [number, number, number];

          if (updated.handleA) {
            updated.handleA = new THREE.Vector3()
              .fromArray(updated.handleA)
              .add(delta)
              .toArray() as [number, number, number];
          }
          if (updated.handleB) {
            updated.handleB = new THREE.Vector3()
              .fromArray(updated.handleB)
              .add(delta)
              .toArray() as [number, number, number];
          }
        } else {
          updated[dragging] = newPos.toArray() as [number, number, number];
          if (updated.isCurved) {
            const mainPos = new THREE.Vector3().fromArray(updated.position);
            const thisHandle = new THREE.Vector3().fromArray(
              updated[dragging]!
            );
            const mirrorHandle = mainPos
              .clone()
              .sub(thisHandle.clone().sub(mainPos));

            if (dragging === "handleA")
              updated.handleB = mirrorHandle.toArray() as [
                number,
                number,
                number
              ];
            if (dragging === "handleB")
              updated.handleA = mirrorHandle.toArray() as [
                number,
                number,
                number
              ];
          }
        }

        group[idx] = updated;
        newGroups[groupIndex] = group;
        return newGroups;
      });
    }
  });

  /** Update handle lines */
  useFrame(() => {
    if (lineRef.current && point.handleA && point.handleB) {
      const positions = lineRef.current.geometry.attributes.position
        .array as Float32Array;
      positions[0] = point.handleA[0];
      positions[1] = point.handleA[1];
      positions[2] = point.handleA[2];
      positions[3] = point.handleB[0];
      positions[4] = point.handleB[1];
      positions[5] = point.handleB[2];
      lineRef.current.geometry.attributes.position.needsUpdate = true;
    }
  });

  useEffect(() => {
    if (selected.length === 2) {
      const groupPoints = pointsGroups[groupIndex];
      if (!groupPoints) return;

      const pathPoints = selected
        .map((i) => groupPoints[i])
        .filter((p) => p !== undefined)
        .map((p) => p.position);

      setShortestPath(pathPoints);

      // compute distance
      let totalDistance = 0;
      for (let i = 0; i < pathPoints.length - 1; i++) {
        const p1 = new THREE.Vector3().fromArray(pathPoints[i]);
        const p2 = new THREE.Vector3().fromArray(pathPoints[i + 1]);
        totalDistance += p1.distanceTo(p2);
      }
      setShortestDistance?.(totalDistance);
    } else {
      setShortestPath([]);
      setShortestDistance?.(0);
    }
  }, [selected, pointsGroups]);

  return (
    <>
      {/* Main point */}
      <mesh
        ref={meshRef}
        position={point.position}
        onClick={onPointClick}
        onPointerDown={(e) => startDrag("main", e)}
        onPointerUp={stopDrag}
      >
        <sphereGeometry args={[0.3, 16, 16]} />
        <meshStandardMaterial
          color={selected.includes(pointIndex) ? "red" : "pink"}
        />
      </mesh>

      {/* Handles + line */}
      {point.isCurved && point.handleA && point.handleB && (
        <>
          <Line
            points={[point.handleA, point.handleB]}
            color="gray"
            lineWidth={1}
          />
          <mesh
            ref={handleARef}
            position={point.handleA}
            onPointerDown={(e) => startDrag("handleA", e)}
            onPointerUp={stopDrag}
          >
            <sphereGeometry args={[0.15, 8, 8]} />
            <meshStandardMaterial color="orange" />
          </mesh>
          <mesh
            ref={handleBRef}
            position={point.handleB}
            onPointerDown={(e) => startDrag("handleB", e)}
            onPointerUp={stopDrag}
          >
            <sphereGeometry args={[0.15, 8, 8]} />
            <meshStandardMaterial color="green" />
          </mesh>
        </>
      )}

      {/* Highlight shortest path */}
      {shortestPath.length > 1 && (
        <Line
          points={shortestPath} // <- just use the positions array
          color="blue"
          lineWidth={2}
        />
      )}
    </>
  );
}

/** Build adjacency list for shortest path */
//   const buildGraph = (points: any[]) => {
//     const graph: Record<number, { neighbor: number; distance: number }[]> = {};
//     points.forEach((p, idx) => {
//       graph[idx] = [];
//       points.forEach((q, j) => {
//         if (idx !== j) {
//           const d = new THREE.Vector3()
//             .fromArray(p.position)
//             .distanceTo(new THREE.Vector3().fromArray(q.position));
//           graph[idx].push({ neighbor: j, distance: d });
//         }
//       });
//     });
//     return graph;
//   };

//   /** Dijkstra shortest path */
//   const findShortestPath = (graph: any, startIdx: number, endIdx: number) => {
//     const distances: number[] = Array(Object.keys(graph).length).fill(Infinity);
//     const previous: (number | null)[] = Array(distances.length).fill(null);
//     distances[startIdx] = 0;
//     const queue = new Set(Object.keys(graph).map(Number));

//     while (queue.size) {
//       let current = [...queue].reduce((a, b) =>
//         distances[a] < distances[b] ? a : b
//       );
//       if (current === endIdx) break;
//       queue.delete(current);

//       for (const { neighbor, distance } of graph[current]) {
//         const alt = distances[current] + distance;
//         if (alt < distances[neighbor]) {
//           distances[neighbor] = alt;
//           previous[neighbor] = current;
//         }
//       }
//     }

//     const path: number[] = [];
//     let u: number | null = endIdx;
//     while (u !== null) {
//       path.unshift(u);
//       u = previous[u];
//     }
//
//     return path;
//   };

//   /** Calculate shortest path when 2 points are selected */
//   useEffect(() => {
//     if (selected.length === 2) {
//       const groupPoints = pointsGroups[groupIndex];
//       const graph = buildGraph(groupPoints);
//       const path = findShortestPath(graph, selected[0], selected[1]);
//       setShortestPath(path);

//       // Calculate distance
//       if (setShortestDistance) {
//         let totalDistance = 0;
//         for (let i = 0; i < path.length - 1; i++) {
//           const p1 = new THREE.Vector3().fromArray(
//             groupPoints[path[i]].position
//           );
//           const p2 = new THREE.Vector3().fromArray(
//             groupPoints[path[i + 1]].position
//           );
//           totalDistance += p1.distanceTo(p2);
//         }
//         setShortestDistance?.(totalDistance);
//       }
//     } else {
//       setShortestPath([]);
//       if (setShortestDistance) setShortestDistance(0);
//     }
//   }, [selected, pointsGroups]);
