import * as THREE from "three";
import { useRef, useState, useMemo, useEffect } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import { Line } from "@react-three/drei";
import { useSceneContext } from "../../../scene/sceneContext";
import {
  useAnimationPlaySpeed,
  usePlayButtonStore,
} from "../../../../store/usePlayButtonStore";
import PointHandles from "./pointHandlers";
import LineSegment from "./lineSegment";
import {
  handleContextMenu,
  handleMouseClick,
  handleMouseDown,
  handleMouseMove,
  handleMouseUp,
} from "./functions/pathMouseHandler";

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

interface PathDataInterface {
  pathId: string;
  isActive?: boolean;
  isCurved?: boolean;
  pathPoints: [PointData, PointData];
}

type PathData = PathDataInterface[];
type SegmentPoint = {
  position: THREE.Vector3;
  originalPoint?: PointData;
};

export default function StructuredPath() {
  const { scene, camera, raycaster, gl, pointer } = useThree();
  const plane = useMemo(
    () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0),
    []
  );
  const { speed } = useAnimationPlaySpeed();
  const { assetStore } = useSceneContext();
  const { assets } = assetStore();

  // --- State Variables ---
  const [pathPointsList, setPathPointsList] = useState<PointData[]>([]);
  const [allPaths, setAllPaths] = useState<PathData>([]);
  console.log("allPaths: ", allPaths);
  const [computedShortestPath, setComputedShortestPath] = useState<PathData>(
    []
  );
  const [currentTempPath, setCurrentTempPath] = useState<PointData[]>([]);
  const [currentMousePos, setCurrentMousePos] = useState<
    [number, number, number] | null
  >(null);
  const [selectedPointIndices, setSelectedPointIndices] = useState<number[]>(
    []
  );
  const [vehicleUuids, setVehicleUuids] = useState<any>();

  // --- Constants & Refs ---
  const isLeftClickDown = useRef<boolean>(false);
  const isDragging = useRef<boolean>(false);
  const vehicleMovementState = useRef<any>({});
  const activeVehicleIndexRef = useRef(0);

  const { isPlaying } = usePlayButtonStore();

  // --- Computed Path Segments ---
  const pathSegments = useMemo(() => {
    if (!computedShortestPath || computedShortestPath.length === 0) return [];

    const segments: SegmentPoint[] = [];

    computedShortestPath.forEach((path) => {
      const [start, end] = path.pathPoints;

      const startPos = new THREE.Vector3(...start.position);
      const endPos = new THREE.Vector3(...end.position);

      // Start point has curve handles
      if (start.isCurved && start.handleA && start.handleB) {
        const handleA = new THREE.Vector3(...start.handleA);
        const handleB = new THREE.Vector3(...start.handleB);

        const curve = new THREE.CubicBezierCurve3(
          startPos,
          handleA,
          handleB,
          endPos
        );
        const points = curve.getPoints(20).map((pos) => ({
          position: pos,
          originalPoint: start,
        }));
        segments.push(...points);
      }

      // End point has curve handles
      else if (end.isCurved && end.handleA && end.handleB) {
        const handleA = new THREE.Vector3(...end.handleA);
        const handleB = new THREE.Vector3(...end.handleB);

        const curve = new THREE.CubicBezierCurve3(
          startPos,
          handleA,
          handleB,
          endPos
        );
        const points = curve.getPoints(20).map((pos) => ({
          position: pos,
          originalPoint: end,
        }));
        segments.push(...points);
      }

      // No curves — just straight line
      else {
        segments.push(
          { position: startPos, originalPoint: start },
          { position: endPos, originalPoint: end }
        );
      }
    });

    // Filter out duplicate consecutive points
    return segments.filter(
      (v, i, arr) => i === 0 || !v.position.equals(arr[i - 1].position)
    );
  }, [computedShortestPath]);

  // --- Initialize Vehicles ---
  useEffect(() => {
    const findVehicle = assets
      .filter((val) => val.eventData?.type === "Vehicle")
      ?.map((val) => val.modelUuid);

    setVehicleUuids(findVehicle);

    vehicleMovementState.current = {};
    findVehicle.forEach((uuid) => {
      vehicleMovementState.current[uuid] = { index: 0, progress: 0 };
    });
  }, [assets]);

  // --- Vehicle Movement ---
  useFrame((_, delta) => {
    if (!isPlaying || pathSegments.length < 2) return;

    const object = scene.getObjectByProperty(
      "uuid",
      vehicleUuids[activeVehicleIndexRef.current]
    );
    if (!object) return;

    const state =
      vehicleMovementState.current[vehicleUuids[activeVehicleIndexRef.current]];
    if (!state) return;

    const startSeg = pathSegments[state.index];
    const endSeg = pathSegments[state.index + 1];

    const segmentDistance = startSeg.position.distanceTo(endSeg.position);
    state.progress += (speed * delta) / segmentDistance;

    if (state.progress >= 1) {
      state.progress = 0;
      state.index++;
      if (state.index >= pathSegments.length - 1) {
        state.index = 0;
        activeVehicleIndexRef.current =
          (activeVehicleIndexRef.current + 1) % vehicleUuids.length;
      }
    }

    const newPos = startSeg.position
      .clone()
      .lerp(endSeg.position, state.progress);
    object.position.copy(newPos);

    const direction = endSeg.position
      .clone()
      .sub(startSeg.position)
      .normalize();
    const forward = new THREE.Vector3(0, 0, 1);
    object.quaternion.setFromUnitVectors(forward, direction);
  });

  // --- Update Mouse Position ---
  useFrame(() => {
    if (currentTempPath.length === 0) return;
    raycaster.setFromCamera(pointer, camera);
    const intersect = new THREE.Vector3();
    if (raycaster.ray.intersectPlane(plane, intersect)) {
      setCurrentMousePos([intersect.x, intersect.y, intersect.z]);
    }
  });

  const addPointToCurrentTemp = (newPoint: PointData) => {
    setCurrentTempPath((prev) => {
      const updated = [...prev, newPoint];

      if (prev.length > 0) {
        const lastPoint = prev[prev.length - 1];
        const newPath: PathDataInterface = {
          pathId: THREE.MathUtils.generateUUID(),
          pathPoints: [lastPoint, newPoint],
        };
        setAllPaths((prevPaths) => [...prevPaths, newPath]);
      }

      return updated;
    });

    setPathPointsList((prev) => {
      if (!prev.find((p) => p.pointId === newPoint.pointId))
        return [...prev, newPoint];
      return prev;
    });
  };

  useEffect(() => {
    const canvas = gl.domElement;

    const onMouseDown = (evt: MouseEvent) =>
      handleMouseDown(evt, isLeftClickDown, isDragging);
    const onMouseUp = (evt: MouseEvent) => handleMouseUp(evt, isLeftClickDown);
    const onMouseMove = () => handleMouseMove(isLeftClickDown, isDragging);
    const onClick = (evt: MouseEvent) =>
      handleMouseClick({
        evt,
        isDragging,
        raycaster,
        plane,
        pointer,
        currentTempPath,
        setCurrentTempPath,
        pathPointsList,
        allPaths,
        setAllPaths,
        addPointToCurrentTemp,
      });
    const onContextMenu = (evt: MouseEvent) =>
      handleContextMenu(evt, setCurrentTempPath);

    canvas.addEventListener("mousedown", onMouseDown);
    canvas.addEventListener("mouseup", onMouseUp);
    canvas.addEventListener("mousemove", onMouseMove);
    canvas.addEventListener("click", onClick);
    canvas.addEventListener("contextmenu", onContextMenu);

    return () => {
      canvas.removeEventListener("mousedown", onMouseDown);
      canvas.removeEventListener("mouseup", onMouseUp);
      canvas.removeEventListener("mousemove", onMouseMove);
      canvas.removeEventListener("click", onClick);
      canvas.removeEventListener("contextmenu", onContextMenu);
    };
  }, [
    gl,
    camera,
    raycaster,
    pointer,
    plane,
    currentTempPath,
    pathPointsList,
    allPaths,
  ]);
  // --- Render ---
  return (
    <>
      {allPaths.map((path, pathIndex) => (
        <LineSegment
          key={path.pathId}
          index={pathIndex}
          pathIndex={pathIndex}
          paths={allPaths}
          setPaths={setAllPaths}
        />
      ))}

      {pathPointsList.map((point, index) => (
        <PointHandles
          key={point.pointId}
          point={point}
          pointIndex={index}
          points={pathPointsList}
          setPoints={setPathPointsList}
          paths={allPaths}
          setPaths={setAllPaths}
          setShortestPath={setComputedShortestPath}
          selected={selectedPointIndices}
          setSelected={setSelectedPointIndices}
        />
      ))}

      {currentTempPath.length > 0 && currentMousePos && (
        <Line
          points={[
            new THREE.Vector3(
              ...currentTempPath[currentTempPath.length - 1].position
            ),
            new THREE.Vector3(...currentMousePos),
          ]}
          color="orange"
          lineWidth={2}
          dashed
        />
      )}
    </>
  );
}
