import { DragControls, Line } from "@react-three/drei";
import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import {
  useActiveTool,
  useCreatedPaths,
  useToolMode,
} from "../../../../store/builder/store";
import { CubicBezierCurve3, Plane, Quaternion, Vector3 } from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { handleCanvasCursors } from "../../../../utils/mouseUtils/handleCanvasCursors";
import { getPathsByPointId, setPathPosition } from "./function/getPaths";
import { aStar } from "../structuredPath/functions/aStar";
import {
  useAnimationPlaySpeed,
  usePlayButtonStore,
} from "../../../../store/usePlayButtonStore";
import { useSceneContext } from "../../../scene/sceneContext";
import { usePathManager } from "./function/usePathManager";
import { useProductContext } from "../../products/productContext";
import { useSelectedEventSphere } from "../../../../store/simulation/useSimulationStore";
type PointData = {
  pointId: string;
  position: [number, number, number];
  isCurved?: boolean;
  handleA?: [number, number, number] | null;
  handleB?: [number, number, number] | null;
  neighbors?: string[];
};
interface PathDataInterface {
  pathId: string;
  isActive?: boolean;
  isCurved?: boolean;
  pathPoints: [PointData, PointData];
}

type PathData = PathDataInterface[];
type PointHandlerProps = {
  point: PointData;
  hoveredPoint: PointData | null;
  setPaths: React.Dispatch<React.SetStateAction<PathData>>;
  paths: PathDataInterface[];
  setHoveredPoint: React.Dispatch<React.SetStateAction<PointData | null>>;
  hoveredLine: PathDataInterface | null;
  pointIndex: any;
  points: PointData[];
  selected: number[];
  setSelected: React.Dispatch<React.SetStateAction<number[]>>;
};
function dist(a: PointData, b: PointData): number {
  return Math.sqrt(
    (a.position[0] - b.position[0]) ** 2 +
      (a.position[1] - b.position[1]) ** 2 +
      (a.position[2] - b.position[2]) ** 2
  );
}
type SegmentPoint = {
  position: Vector3;
  originalPoint?: PointData;
  pathId?: string;
  startId?: string;
  endId?: string;
};

/** --- A* Algorithm --- */
type AStarResult = {
  pointIds: string[];
  distance: number;
};

function aStarShortestPath(
  startId: string,
  goalId: string,
  points: PointData[],
  paths: PathData
): AStarResult | null {
  const pointById = new Map(points.map((p) => [p.pointId, p]));
  const start = pointById.get(startId);
  const goal = pointById.get(goalId);
  if (!start || !goal) return null;

  const openSet = new Set<string>([startId]);
  const cameFrom: Record<string, string | null> = {};
  const gScore: Record<string, number> = {};
  const fScore: Record<string, number> = {};

  for (const p of points) {
    cameFrom[p.pointId] = null;
    gScore[p.pointId] = Infinity;
    fScore[p.pointId] = Infinity;
  }

  gScore[startId] = 0;
  fScore[startId] = dist(start, goal);

  const neighborsOf = (id: string): { id: string; cost: number }[] => {
    const me = pointById.get(id)!;
    const out: { id: string; cost: number }[] = [];
    for (const edge of paths) {
      const [a, b] = edge.pathPoints;
      if (a.pointId === id) out.push({ id: b.pointId, cost: dist(me, b) });
      else if (b.pointId === id) out.push({ id: a.pointId, cost: dist(me, a) });
    }
    return out;
  };

  while (openSet.size > 0) {
    let current: string = [...openSet].reduce((a, b) =>
      fScore[a] < fScore[b] ? a : b
    );

    if (current === goalId) {
      const ids: string[] = [];
      let node: string | null = current;
      while (node) {
        ids.unshift(node);
        node = cameFrom[node];
      }
      return { pointIds: ids, distance: gScore[goalId] };
    }

    openSet.delete(current);

    for (const nb of neighborsOf(current)) {
      const tentativeG = gScore[current] + nb.cost;
      if (tentativeG < gScore[nb.id]) {
        cameFrom[nb.id] = current;
        gScore[nb.id] = tentativeG;
        fScore[nb.id] = tentativeG + dist(pointById.get(nb.id)!, goal);
        openSet.add(nb.id);
      }
    }
  }

  return null;
}

/** --- Convert node path to edges --- */
function nodePathToEdges(
  pointIds: string[],
  points: PointData[],
  paths: PathData
): PathData {
  const byId = new Map(points.map((p) => [p.pointId, p]));
  const edges: PathData = [];

  for (let i = 0; i < pointIds.length - 1; i++) {
    const a = pointIds[i];
    const b = pointIds[i + 1];

    const edge = paths.find(
      (p) =>
        (p.pathPoints[0].pointId === a && p.pathPoints[1].pointId === b) ||
        (p.pathPoints[0].pointId === b && p.pathPoints[1].pointId === a)
    );

    if (edge) {
      const [p1, p2] = edge.pathPoints;
      edges.push({
        pathId: edge.pathId,
        pathPoints:
          p1.pointId === a
            ? ([p1, p2] as [PointData, PointData])
            : ([p2, p1] as [PointData, PointData]),
      });
    } else {
      const pa = byId.get(a)!;
      const pb = byId.get(b)!;
      edges.push({
        pathId: `synthetic-${a}-${b}`,
        pathPoints: [pa, pb],
      });
    }
  }

  return edges;
}
type VehicleDetails = {
  vehicleId: string;
  vehiclePosition: [number, number, number];
};
type Manager = {
  pathId: string;
  vehicleId: string;
};
export default function PointHandler({
  point,
  // setPaths,
  // paths,
  setHoveredPoint,
  hoveredLine,
  hoveredPoint,
  pointIndex,
  points,
  setSelected,
  selected,
}: PointHandlerProps) {
  const { isPlaying } = usePlayButtonStore();
  const [multiPaths, setMultiPaths] = useState<
    { id: number; path: PathData }[]
  >([]);
  const { vehicleStore, productStore } = useSceneContext();
  const { vehicles, getVehicleById } = vehicleStore();
  const { selectedProductStore } = useProductContext();
  const { selectedProduct } = selectedProductStore();
  const { updateEvent, updateAction } = productStore();
  const { selectedEventSphere } = useSelectedEventSphere();
  const pathIdRef = useRef(1); // To ensure unique incremental IDs
  const { toolMode } = useToolMode();
  const { activeTool } = useActiveTool();
  const { scene, raycaster } = useThree();
  const [isHovered, setIsHovered] = useState(false);
  const [dragOffset, setDragOffset] = useState<Vector3 | null>(null);
  const plane = useMemo(() => new Plane(new Vector3(0, 1, 0), 0), []);
  const [initialPositions, setInitialPositions] = useState<{
    paths?: any;
  }>({});
  const [shortestPaths, setShortestPaths] = useState<PathData>([]);
  const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
  const [vehicleUuids, setVehicleUuids] = useState<any>();
  const CAN_POINT_SNAP = true;
  const CAN_ANGLE_SNAP = true;
  const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5;
  const [selectedPointIndices, setSelectedPointIndices] = useState<number[]>(
    []
  );
  const [shortestEdges, setShortestEdges] = useState<PathData>([]);
  const { speed } = useAnimationPlaySpeed();
  const { assetStore } = useSceneContext();
  const { assets } = assetStore();
  const vehicleMovementState = useRef<any>({});
  const [activeVehicleIndex, setActiveVehicleIndex] = useState(0);
  const [vehicleData, setVehicleData] = useState<VehicleDetails[]>([]);
  const { paths, setPaths } = useCreatedPaths();
  const [managerData, setManagerData] = useState<Manager>();

  useEffect(() => {
    const findVehicle = assets
      .filter((val) => val.eventData?.type === "Vehicle")
      ?.map((val) => val.modelUuid);
    const findVehicleDatas = assets
      .filter((val) => val.eventData?.type === "Vehicle")
      ?.map((val) => val);
    findVehicleDatas.forEach((val) => {
      const vehicledId = val.modelUuid;
      const vehiclePosition: [number, number, number] = val.position;

      setVehicleData((prev) => [
        ...prev,
        { vehicleId: vehicledId, vehiclePosition },
      ]);
    });

    setVehicleUuids(findVehicle);
    setActiveVehicleIndex(0); // Reset to first vehicle

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

  const removePathByPoint = (pointId: string): PathDataInterface[] => {
    const removedPaths: PathDataInterface[] = [];

    const newPaths = paths.filter((path: PathDataInterface) => {
      const hasPoint = path.pathPoints.some(
        (p: PointData) => p.pointId === pointId
      );
      if (hasPoint) {
        removedPaths.push(JSON.parse(JSON.stringify(path))); // keep a copy
        return false; // remove this path
      }
      return true; // keep this path
    });

    setPaths(newPaths);

    return removedPaths;
  };

  const getConnectedPoints = (uuid: string): PointData[] => {
    const connected: PointData[] = [];

    for (const path of paths) {
      for (const point of path.pathPoints) {
        if (point.pointId === uuid) {
          connected.push(
            ...path.pathPoints.filter((p: PointData) => p.pointId !== uuid)
          );
        }
      }
    }

    return connected;
  };

  const snapPathAngle = useCallback(
    (
      newPosition: [number, number, number],
      pointId: string
    ): {
      position: [number, number, number];
      isSnapped: boolean;
      snapSources: Vector3[];
    } => {
      if (!pointId || !CAN_ANGLE_SNAP) {
        return { position: newPosition, isSnapped: false, snapSources: [] };
      }

      const connectedPoints: PointData[] = getConnectedPoints(pointId) || [];

      if (connectedPoints.length === 0) {
        return {
          position: newPosition,
          isSnapped: false,
          snapSources: [],
        };
      }

      const newPos = new Vector3(...newPosition);

      let closestX: { pos: Vector3; dist: number } | null = null;
      let closestZ: { pos: Vector3; dist: number } | null = null;

      for (const connectedPoint of connectedPoints) {
        const cPos = new Vector3(...connectedPoint.position);

        const xDist = Math.abs(newPos.x - cPos.x);
        const zDist = Math.abs(newPos.z - cPos.z);

        if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
          if (!closestX || xDist < closestX.dist) {
            closestX = { pos: cPos, dist: xDist };
          }
        }

        if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
          if (!closestZ || zDist < closestZ.dist) {
            closestZ = { pos: cPos, dist: zDist };
          }
        }
      }

      const snappedPos = newPos.clone();
      const snapSources: Vector3[] = [];

      if (closestX) {
        snappedPos.x = closestX.pos.x;
        snapSources.push(closestX.pos.clone());
      }

      if (closestZ) {
        snappedPos.z = closestZ.pos.z;
        snapSources.push(closestZ.pos.clone());
      }

      const isSnapped = snapSources.length > 0;

      return {
        position: [snappedPos.x, snappedPos.y, snappedPos.z],
        isSnapped,
        snapSources,
      };
    },
    []
  );

  const getAllOtherPathPoints = useCallback((): PointData[] => {
    return (
      paths?.flatMap((path: PathDataInterface) =>
        path.pathPoints.filter((pt: PointData) => pt.pointId !== point.pointId)
      ) ?? []
    );
  }, [paths]);

  const snapPathPoint = useCallback(
    (position: [number, number, number], pointId?: string) => {
      if (!CAN_POINT_SNAP)
        return { position: position, isSnapped: false, snappedPoint: null };

      const otherPoints = getAllOtherPathPoints();

      const currentVec = new Vector3(...position);
      for (const point of otherPoints) {
        const pointVec = new Vector3(...point.position);
        const distance = currentVec.distanceTo(pointVec);
        if (distance <= POINT_SNAP_THRESHOLD) {
          return {
            position: point.position,
            isSnapped: true,
            snappedPoint: point,
          };
        }
      }
      return { position: position, isSnapped: false, snappedPoint: null };
    },
    [getAllOtherPathPoints]
  );

  const handlePointClick = (e: any, point: PointData) => {
    e.stopPropagation();
    if (toolMode === "3D-Delete") {
      removePathByPoint(point.pointId);
      return;
    }
  };
  let selectedVehiclePaths: Array<
    Array<{
      vehicleId: string;
      pathId: string;
      isActive?: boolean;
      isCurved?: boolean;
      pathPoints: [PointData, PointData];
    }>
  > = [];

  function assignPathToSelectedVehicle(
    selectedVehicleId: string,
    currentPath: PathData
  ) {
    const vehiclePathSegments = currentPath.map((path) => ({
      vehicleId: selectedVehicleId,
      ...path,
    }));

    return selectedVehiclePaths.push(vehiclePathSegments);
  }

  const handleContextMenu = (e: any, point: PointData) => {
    // if (e.shiftKey && e.button === 2) {
    const pointIndex = points.findIndex((p) => p.pointId === point.pointId);
    if (pointIndex === -1) {
      return;
    }

    setSelected((prev) => {
      if (prev.length === 0) {
        return [pointIndex];
      }
      // if (prev.length === 1) {
      //   setTimeout(() => {
      //
      //     const prevPoint = points[prev[0]];
      //
      //     const newPoint = points[pointIndex];
      //
      //     const result = aStarShortestPath(
      //       prevPoint.pointId,
      //       newPoint.pointId,
      //       points,
      //       paths
      //     );

      //     if (result) {
      //       const edges = nodePathToEdges(result.pointIds, points, paths);
      //
      //       setShortestPaths(edges);
      //       setShortestEdges(edges);
      //     } else {
      //       setShortestPaths([]);
      //       setShortestEdges([]);
      //     }
      //     if (prevPoint.pointId === newPoint.pointId) {
      //       return prev;
      //     }
      //   }, 0);

      //   return [prev[0], pointIndex];
      // }

      // More than two points — reset
      if (prev.length === 1) {
        setTimeout(() => {
          const prevPoint = points[prev[0]];
          const newPoint = points[pointIndex];
          console.log(
            "selectedEventSphere?.userData.modelUuid: ",
            selectedEventSphere?.userData.modelUuid
          );
          if (selectedEventSphere?.userData.modelUuid) {
            const updatedVehicle = getVehicleById(
              selectedEventSphere.userData.modelUuid
            );

            const startPoint = new Vector3(...prevPoint.position);
            const endPoint = new Vector3(...newPoint.position);
            if (updatedVehicle && startPoint && endPoint) {
              if (updatedVehicle.type === "vehicle") {
                const event = updateAction(
                  selectedProduct.productUuid,
                  updatedVehicle.point?.action.actionUuid,
                  {
                    pickUpPoint: {
                      position: {
                        x: startPoint.x,
                        y: 0,
                        z: startPoint.z,
                      },
                      rotation: {
                        x:
                          updatedVehicle.point.action.pickUpPoint?.rotation.x ??
                          0,
                        y:
                          updatedVehicle.point.action.pickUpPoint?.rotation.y ??
                          0,
                        z:
                          updatedVehicle.point.action.pickUpPoint?.rotation.z ??
                          0,
                      },
                    },
                    unLoadPoint: {
                      position: {
                        x: endPoint.x,
                        y: endPoint.y,
                        z: endPoint.z,
                      },
                      rotation: {
                        x:
                          updatedVehicle.point.action.unLoadPoint?.rotation.x ??
                          0,
                        y:
                          updatedVehicle.point.action.unLoadPoint?.rotation.y ??
                          0,
                        z:
                          updatedVehicle.point.action.unLoadPoint?.rotation.z ??
                          0,
                      },
                    },
                  }
                );

                if (prevPoint.pointId === newPoint.pointId) return;

                const result = aStarShortestPath(
                  prevPoint.pointId,
                  newPoint.pointId,
                  points,
                  paths
                );

                if (result) {
                  const edges = nodePathToEdges(result.pointIds, points, paths);

                  // Create a new path object/
                  const newPathObj = {
                    id: pathIdRef.current++,
                    path: edges,
                  };
                  const shortPath = assignPathToSelectedVehicle(
                    updatedVehicle?.modelUuid,
                    edges
                  );
                  console.log("shortPath: ", shortPath);

                  setShortestPaths(edges);
                  setShortestEdges(edges);
                  // Append it to the list of paths
                  setMultiPaths((prevPaths) => [...prevPaths, newPathObj]);
                }
              }
            }
          }

          // Reset selection to allow new pair selection
        }, 0);

        return [prev[0], pointIndex];
      }

      setShortestPaths([]);
      return [pointIndex];
    });
    // }
  };

  const handleDragStart = (point: PointData) => {
    if (activeTool !== "cursor") return;
    const intersectionPoint = new Vector3();
    const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
    if (hit) {
      const currentPosition = new Vector3(...point.position);
      const offset = new Vector3().subVectors(currentPosition, hit);
      setDragOffset(offset);
      const pathIntersection = getPathsByPointId(point.pointId, paths);
      setInitialPositions({ paths: pathIntersection });
    }
  };

  const handleDrag = (point: PointData) => {
    if (isHovered && dragOffset) {
      const intersectionPoint = new Vector3();
      const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
      if (hit) {
        // handleCanvasCursors("grabbing");
        const positionWithOffset = new Vector3().addVectors(hit, dragOffset);
        const newPosition: [number, number, number] = [
          positionWithOffset.x,
          positionWithOffset.y,
          positionWithOffset.z,
        ];

        // ✅ Pass newPosition and pointId
        const pathSnapped = snapPathAngle(newPosition, point.pointId);
        const finalSnapped = snapPathPoint(pathSnapped.position);
        setPathPosition(point.pointId, finalSnapped.position, setPaths, paths);
      }
    }
  };

  const handleDragEnd = (point: PointData) => {
    const pathIntersection = getPathsByPointId(point.pointId, paths);
    if (pathIntersection && pathIntersection.length > 0) {
      pathIntersection.forEach((update) => {});
    }
  };

  const pathSegments = useMemo(() => {
    if (!shortestPaths || shortestPaths.length === 0) return [];

    const segments: SegmentPoint[] = [];

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

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

      segments.push(
        { position: startPos, originalPoint: start, startId: start.pointId },
        { position: endPos, originalPoint: end, endId: end.pointId }
      );
    });

    return segments.filter(
      (v, i, arr) => i === 0 || !v.position.equals(arr[i - 1].position)
    );
  }, [shortestPaths]);


  function getPathIdByPoints(
    startId: string | undefined,
    endId: string | undefined,
    shortestPaths: any[]
  ) {
    for (const path of shortestPaths) {
      for (let i = 0; i < path.pathPoints.length - 1; i++) {
        const s = path.pathPoints[i];
        const e = path.pathPoints[i + 1];

        if (
          (s.pointId === startId && e.pointId === endId) ||
          (s.pointId === endId && e.pointId === startId) // handle both directions
        ) {
          return path.pathId;
        }
      }
    }
    return null; // not found
  }

  return (
    <>
      <DragControls
        axisLock="y"
        autoTransform={false}
        onDragStart={() => handleDragStart(point)}
        onDrag={() => handleDrag(point)}
        onDragEnd={() => handleDragEnd(point)}
      >
        <mesh
          key={point.pointId}
          position={point.position}
          name="Path-Point"
          userData={point}
          onClick={(e) => {
            handlePointClick(e, point);
          }}
          // onContextMenu={(e) => handleContextMenu(e, point)}
          onPointerOver={(e) => {
            if (!hoveredPoint && e.buttons === 0 && !e.ctrlKey) {
              setHoveredPoint(point);
              setIsHovered(true);
              //   handleCanvasCursors("default");
            }
          }}
          onPointerOut={() => {
            if (hoveredPoint) {
              setHoveredPoint(null);
              if (!hoveredLine) {
                // handleCanvasCursors("default");
              }
            }
            setIsHovered(false);
          }}
        >
          <sphereGeometry args={[0.3, 16, 16]} />
          <meshBasicMaterial color="pink" />
        </mesh>
      </DragControls>
      {shortestEdges.map((edge) => (
        <Line
          key={`sp-${edge.pathId}`}
          points={edge.pathPoints.map((p) => p.position)}
          color="yellow"
          lineWidth={3}
        />
      ))}
    </>
  );
}
