import { Line, Plane } from "@react-three/drei";
import { ThreeEvent, useFrame, useThree } from "@react-three/fiber";
import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { Matrix4, Mesh, Quaternion, Vector3 } from "three";
import {
  useAnimationPlaySpeed,
  usePlayButtonStore,
} from "../../../../store/usePlayButtonStore";
import { useSceneContext } from "../../../scene/sceneContext";
import { findShortestPath } from "./functions/findShortestPath";
import * as THREE from "three";
import PointHandle from "./PointHandle";
import LineSegment from "./lineSegment";

type PointData = {
  pointId: string;
  position: [number, number, number];
  isCurved: boolean;
  handleA: [number, number, number] | null;
  handleB: [number, number, number] | null;
};

interface PathDataInterface {
  pathId: string;
  pathPoints: [PointData, PointData];
}

type PathData = PathDataInterface[];

export default function PreDefinedPath() {
  const { gl, raycaster } = useThree();
  const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0));

  const [mainShapeOnly, setMainShapeOnly] = useState<PointData[][]>([]);
  const [pointsGroups, setPointsGroups] = useState<PointData[][]>([[]]);
  const [definedPath, setDefinedPath] = useState<PointData[][] | PointData[]>(
    []
  );
  const [selected, setSelected] = useState<number[]>([]);

  const downPosition = useRef<{ x: number; y: number } | null>(null);
  const hasClicked = useRef(false);

  const handleMouseDown = useCallback((e: any) => {
    hasClicked.current = false;
    downPosition.current = { x: e.clientX, y: e.clientY };
  }, []);

  const SNAP_DISTANCE = 0.5;

  const handleClick = useCallback(
    (e: any) => {
      console.log("e.ctrlKey: ", e.ctrlKey);
      if (e.ctrlKey) return;
      if (e.button === 2) {
        setPointsGroups((prev) => [...prev, []]);
        setSelected([]);
        return;
      }
      if (e.button !== 0) return;
      if (hasClicked.current) return;
      hasClicked.current = true;

      if (
        !downPosition.current ||
        Math.abs(downPosition.current.x - e.clientX) > 2 ||
        Math.abs(downPosition.current.y - e.clientY) > 2
      )
        return;

      const intersection = new THREE.Vector3();
      if (raycaster.ray.intersectPlane(plane.current, intersection)) {
        const pointArray = intersection.toArray() as [number, number, number];

        setPointsGroups((prev) => {
          const newGroups = [...prev];
          const currentGroup = [...newGroups[newGroups.length - 1]];

          // 1️⃣ Find nearest existing point
          let nearestPos: [number, number, number] | null = null;
          newGroups.forEach((group) => {
            group.forEach((p) => {
              const dist = Math.sqrt(
                Math.pow(p.position[0] - pointArray[0], 2) +
                  Math.pow(p.position[1] - pointArray[1], 2) +
                  Math.pow(p.position[2] - pointArray[2], 2)
              );
              if (dist <= SNAP_DISTANCE && !nearestPos) {
                nearestPos = p.position; // take only the position
              }
            });
          });

          if (nearestPos) {
            // 2️⃣ Reuse the position, but create NEW pointId
            const snapPoint: PointData = {
              pointId: crypto.randomUUID(),
              position: nearestPos,
              isCurved: false,
              handleA: null,
              handleB: null,
            };
            currentGroup.push(snapPoint);
            newGroups[newGroups.length - 1] = currentGroup;
            return newGroups;
          }

          // 3️⃣ Otherwise, create brand new point
          const newPoint: PointData = {
            pointId: crypto.randomUUID(),
            position: pointArray,
            isCurved: false,
            handleA: null,
            handleB: null,
          };
          currentGroup.push(newPoint);
          newGroups[newGroups.length - 1] = currentGroup;
          return newGroups;
        });
      }
    },
    [raycaster]
  );

  function findConnectedComponents(groups: PointData[][]) {
    const visited = new Set<string>();
    const components: PointData[][] = [];

    const arePointsEqual = (p1: PointData, p2: PointData) =>
      Math.abs(p1.position[0] - p2.position[0]) < 0.001 &&
      Math.abs(p1.position[1] - p2.position[1]) < 0.001 &&
      Math.abs(p1.position[2] - p2.position[2]) < 0.001;

    const dfs = (point: PointData, component: PointData[][]) => {
      if (visited.has(point.pointId)) return;
      visited.add(point.pointId);

      for (const group of groups) {
        if (group.some((gp) => arePointsEqual(gp, point))) {
          if (!component.includes(group)) {
            component.push(group);
            for (const gp of group) dfs(gp, component);
          }
        }
      }
    };

    for (const group of groups) {
      for (const point of group) {
        if (!visited.has(point.pointId)) {
          const newComponent: PointData[][] = [];
          dfs(point, newComponent);
          if (newComponent.length > 0) components.push(newComponent.flat());
        }
      }
    }
    return components;
  }

  useEffect(() => {
    const newDefinedPath = pointsGroups.filter((g) => g.length > 0);
    setDefinedPath(newDefinedPath);

    const connected = findConnectedComponents(newDefinedPath);
    if (connected.length > 0) {
      let mainShape = [...connected[0]];
      const isolatedPoints = connected
        .slice(1)
        .filter((arr) => arr.length === 1);
      const updatedMainShapeOnly = [mainShape, ...isolatedPoints];
      setMainShapeOnly(updatedMainShapeOnly);
    } else {
      setMainShapeOnly([]);
    }
  }, [pointsGroups]);

  useEffect(() => {
    setDefinedPath(() => {
      if (pointsGroups.length === 1) {
        return [...pointsGroups[0]];
      } else {
        return pointsGroups.filter((group) => group.length > 0);
      }
    });
  }, [pointsGroups]);
  const [shortestPath, setShortestPath] = useState<number[]>([]);
  const [shortestDistance, setShortestDistance] = useState<number>(0);

  useEffect(() => {
    const domElement = gl.domElement;
    domElement.addEventListener("contextmenu", (e) => e.preventDefault());
    domElement.addEventListener("mousedown", handleMouseDown);
    domElement.addEventListener("mouseup", handleClick);
    return () => {
      domElement.removeEventListener("mousedown", handleMouseDown);
      domElement.removeEventListener("mouseup", handleClick);
    };
  }, [handleClick, handleMouseDown]);

  return (
    <>
      {pointsGroups.map((group, gIdx) => (
        <React.Fragment key={gIdx}>
          {group.map((point, idx) => (
            <PointHandle
              key={point.pointId}
              point={point}
              groupIndex={gIdx}
              pointIndex={idx}
              // mainShapeOnly={mainShapeOnly}
              setPointsGroups={setPointsGroups}
              pointsGroups={pointsGroups} // <-- pass the full groups
              selected={selected}
              setSelected={setSelected} // <-- pass setter for multi-selection
              shortestPath={shortestPath}
              setShortestPath={setShortestPath}
              setShortestDistance={setShortestDistance}
            />
          ))}

          {group.map((point, i) => {
            if (i < group.length - 1) {
              return (
                <LineSegment
                  key={i}
                  index={i}
                  createdPoints={group} // pass the whole group here
                  updatePoints={(i0, p0, i1, p1) => {
                    setPointsGroups((prev) => {
                      const newGroups = [...prev];
                      const newGroup = [...newGroups[gIdx]];
                      newGroup[i0] = {
                        ...newGroup[i0],
                        position: p0.toArray() as [number, number, number],
                      };
                      newGroup[i1] = {
                        ...newGroup[i1],
                        position: p1.toArray() as [number, number, number],
                      };
                      newGroups[gIdx] = newGroup;
                      return newGroups;
                    });
                  }}
                  insertPoint={(index, pointVec) => {
                    setPointsGroups((prev) => {
                      const newGroups = [...prev];
                      const groupToSplit = newGroups[gIdx];

                      // Create the new point
                      const newPoint = {
                        pointId: crypto.randomUUID(),
                        position: pointVec.toArray() as [
                          number,
                          number,
                          number
                        ],
                        isCurved: false,
                        handleA: null,
                        handleB: null,
                      };

                      // First half: everything from start to clicked segment
                      const firstHalf = [
                        ...groupToSplit.slice(0, index),
                        newPoint,
                      ];

                      // Second half: new point + everything after clicked segment
                      const secondHalf = [
                        newPoint,
                        ...groupToSplit.slice(index),
                      ];

                      // Replace the original group with the first half
                      newGroups[gIdx] = firstHalf;

                      // Insert the second half as a new group right after
                      newGroups.splice(gIdx + 1, 0, secondHalf);

                      return newGroups;
                    });
                  }}
                />
              );
            }
            return null;
          })}
        </React.Fragment>
      ))}
    </>
  );
}
