import { useState } from "react";
import {
  closestCenter,
  pointerWithin,
  type CollisionDetection,
  type DragEndEvent,
  type DragStartEvent,
} from "@dnd-kit/core";
import { arrayMove } from "@dnd-kit/sortable";
import type { APIGraphCreationContext } from "../../services/medical";
import type { GraphDataset } from "../../types/medical";
import { createDatasetInstanceId, getRangeForPreset, type DateRange } from "./graphHelpers.ts";
import type { DateFilterMode } from "./useGraphDateFilter.ts";

export type GraphItem = {
  id: string;
  datasets: GraphDataset[];
  creation: APIGraphCreationContext;
  normalizeByReference?: boolean;
};

type UseGraphActionsParams = {
  setGraphs: React.Dispatch<React.SetStateAction<GraphItem[]>>;
  availableDatasets: GraphDataset[];
  dateOptions: string[];
  dateFilterMode: DateFilterMode;
  selectedPreset: Exclude<import("./graphHelpers.ts").TimeframePreset, "custom">;
  appliedDateRange: DateRange;
  sliderStartDate: string;
  sliderEndDate: string;
  searchQuery: string;
  setSearchQuery: React.Dispatch<React.SetStateAction<string>>;
  setShowResults: React.Dispatch<React.SetStateAction<boolean>>;
};

export function useGraphActions({
  setGraphs,
  availableDatasets,
  dateOptions,
  dateFilterMode,
  selectedPreset,
  appliedDateRange,
  sliderStartDate,
  sliderEndDate,
  searchQuery,
  setSearchQuery,
  setShowResults,
}: UseGraphActionsParams) {
  const [activeId, setActiveId] = useState<string | null>(null);

  const customCollisionDetection: CollisionDetection = (args) => {
    const pointerCollisions = pointerWithin(args);
    const mergeCollision = pointerCollisions.find((collision) =>
      (collision.id as string).startsWith("merge-")
    );

    if (mergeCollision) {
      return [mergeCollision];
    }

    return closestCenter(args);
  };

  const addGraphFromDatasets = (datasets: GraphDataset[], sourceQuery?: string) => {
    if (datasets.length === 0) return;

    const MAX_PARAMETERS_PER_PLOT = 6;

    // Split datasets into chunks of at most MAX_PARAMETERS_PER_PLOT
    const chunks: GraphDataset[][] = [];
    for (let i = 0; i < datasets.length; i += MAX_PARAMETERS_PER_PLOT) {
      chunks.push(datasets.slice(i, i + MAX_PARAMETERS_PER_PLOT));
    }

    const rangeSnapshot =
      dateFilterMode === "custom"
        ? { startDate: sliderStartDate || undefined, endDate: sliderEndDate || undefined }
        : appliedDateRange;

    // Create a graph for each chunk
    const newGraphs: GraphItem[] = chunks.map((chunk) => {
      const instanceDatasets = chunk.map((dataset) => ({
        ...dataset,
        instanceId: createDatasetInstanceId(dataset.id),
      }));

      const newGraphId = `graph-${Date.now()}-${chunk[0].id}-${Math.random().toString(36).slice(2, 8)}`;

      const newGraph: GraphItem = {
        id: newGraphId,
        datasets: instanceDatasets,
        normalizeByReference: false,
        creation: {
          datasetIds: instanceDatasets.map((dataset) => dataset.id),
          datasetNames: instanceDatasets.map((dataset) => dataset.name),
          dateFilterMode,
          selectedPreset: dateFilterMode === "preset" ? selectedPreset : undefined,
          appliedDateRange: rangeSnapshot,
          datasetOrigins: Object.fromEntries(
            instanceDatasets.map((dataset) => [
              dataset.id,
              {
                dateFilterMode,
                selectedPreset: dateFilterMode === "preset" ? selectedPreset : undefined,
                appliedDateRange: rangeSnapshot,
              },
            ])
          ),
          searchQuery: (sourceQuery ?? searchQuery.trim()) || undefined,
        },
      };

      return newGraph;
    });

    // Add all graphs at once
    setGraphs((prev) => [...newGraphs, ...prev]);
    setSearchQuery("");
    setShowResults(false);
  };

  const addGraph = (datasetId: string) => {
    const dataset = availableDatasets.find((candidate) => candidate.id === datasetId);
    if (!dataset) return;

    addGraphFromDatasets([dataset]);
  };

  const removeGraph = (id: string) => {
    setGraphs((prev) => prev.filter((graph) => graph.id !== id));
  };

  const clearAllGraphs = () => {
    setGraphs([]);
  };

  const moveGraphUp = (id: string) => {
    setGraphs((items) => {
      const index = items.findIndex((item) => item.id === id);
      if (index > 0) {
        return arrayMove(items, index, index - 1);
      }
      return items;
    });
  };

  const moveGraphDown = (id: string) => {
    setGraphs((items) => {
      const index = items.findIndex((item) => item.id === id);
      if (index !== -1 && index < items.length - 1) {
        return arrayMove(items, index, index + 1);
      }
      return items;
    });
  };

  const updateDatasetColor = (graphId: string, datasetId: string, color: string) => {
    setGraphs((prev) =>
      prev.map((graph) => {
        if (graph.id !== graphId) return graph;

        return {
          ...graph,
          datasets: graph.datasets.map((dataset) =>
            (dataset.instanceId ?? dataset.id) === datasetId ? { ...dataset, color } : dataset
          ),
        };
      })
    );
  };

  const updateDatasetStyle = (
    graphId: string,
    datasetId: string,
    styleUpdate: Partial<Pick<GraphDataset, "lineStyle" | "pointStyle">>
  ) => {
    setGraphs((prev) =>
      prev.map((graph) => {
        if (graph.id !== graphId) return graph;

        return {
          ...graph,
          datasets: graph.datasets.map((dataset) =>
            (dataset.instanceId ?? dataset.id) === datasetId ? { ...dataset, ...styleUpdate } : dataset
          ),
        };
      })
    );
  };

  const updateGraphNormalization = (graphId: string, enabled: boolean) => {
    setGraphs((prev) =>
      prev.map((graph) => (graph.id === graphId ? { ...graph, normalizeByReference: enabled } : graph))
    );
  };

  const splitGraph = (id: string) => {
    setGraphs((prev) => {
      const index = prev.findIndex((graph) => graph.id === id);
      if (index === -1) return prev;

      const graph = prev[index];
      if (graph.datasets.length <= 1) return prev;

      const primaryDataset = graph.datasets[0];
      const fallbackOrigin = {
        dateFilterMode,
        selectedPreset: dateFilterMode === "preset" ? selectedPreset : undefined,
        appliedDateRange:
          dateFilterMode === "custom"
            ? { startDate: sliderStartDate || undefined, endDate: sliderEndDate || undefined }
            : getRangeForPreset(dateOptions, selectedPreset),
      };

      const remaining = {
        ...graph,
        datasets: [primaryDataset],
        normalizeByReference: graph.normalizeByReference ?? false,
        creation: {
          datasetIds: [primaryDataset.id],
          datasetNames: [primaryDataset.name],
          dateFilterMode: graph.creation.datasetOrigins?.[primaryDataset.id]?.dateFilterMode,
          selectedPreset: graph.creation.datasetOrigins?.[primaryDataset.id]?.selectedPreset,
          appliedDateRange: graph.creation.datasetOrigins?.[primaryDataset.id]?.appliedDateRange,
          datasetOrigins: {
            [primaryDataset.id]:
              graph.creation.datasetOrigins?.[primaryDataset.id] || fallbackOrigin,
          },
          searchQuery: graph.creation.searchQuery,
        },
      };

      const newGraphs: GraphItem[] = graph.datasets.slice(1).map((dataset) => {
        const dsOrigin = graph.creation.datasetOrigins?.[dataset.id];
        return {
          id: `graph-${Date.now()}-${dataset.id}`,
          datasets: [dataset],
          normalizeByReference: graph.normalizeByReference ?? false,
          creation: {
            datasetIds: [dataset.id],
            datasetNames: [dataset.name],
            dateFilterMode: dsOrigin?.dateFilterMode,
            selectedPreset: dsOrigin?.selectedPreset,
            appliedDateRange: dsOrigin?.appliedDateRange,
            datasetOrigins: {
              [dataset.id]:
                dsOrigin || fallbackOrigin,
            },
            searchQuery: graph.creation.searchQuery,
          },
        };
      });

      const next = [...prev];
      next.splice(index, 1, remaining, ...newGraphs);
      return next;
    });
  };

  const splitDatasetFromGraph = (graphId: string, datasetId: string) => {
    setGraphs((prev) => {
      const graphIndex = prev.findIndex((graph) => graph.id === graphId);
      if (graphIndex === -1) return prev;

      const graph = prev[graphIndex];
      if (graph.datasets.length <= 1) return prev;

      const datasetToSplit = graph.datasets.find((dataset) => (dataset.instanceId ?? dataset.id) === datasetId);
      if (!datasetToSplit) return prev;

      const remainingDatasets = graph.datasets.filter((dataset) => (dataset.instanceId ?? dataset.id) !== datasetId);
      if (remainingDatasets.length === 0) return prev;

      const fallbackOrigin = {
        dateFilterMode,
        selectedPreset: dateFilterMode === "preset" ? selectedPreset : undefined,
        appliedDateRange:
          dateFilterMode === "custom"
            ? { startDate: sliderStartDate || undefined, endDate: sliderEndDate || undefined }
            : getRangeForPreset(dateOptions, selectedPreset),
      };

      const splitOrigin = graph.creation.datasetOrigins?.[datasetToSplit.id] || fallbackOrigin;
      const remainingOrigins: APIGraphCreationContext["datasetOrigins"] = {};
      remainingDatasets.forEach((dataset) => {
        remainingOrigins[dataset.id] = graph.creation.datasetOrigins?.[dataset.id] || fallbackOrigin;
      });

      const updatedGraph: GraphItem = {
        ...graph,
        datasets: remainingDatasets,
        normalizeByReference: graph.normalizeByReference ?? false,
        creation: {
          datasetIds: remainingDatasets.map((dataset) => dataset.id),
          datasetNames: remainingDatasets.map((dataset) => dataset.name),
          datasetOrigins: remainingOrigins,
          searchQuery: graph.creation.searchQuery,
        },
      };

      if (remainingDatasets.length === 1) {
        const onlyDataset = remainingDatasets[0];
        const onlyOrigin = remainingOrigins[onlyDataset.id];
        updatedGraph.creation.dateFilterMode = onlyOrigin?.dateFilterMode;
        updatedGraph.creation.selectedPreset = onlyOrigin?.selectedPreset;
        updatedGraph.creation.appliedDateRange = onlyOrigin?.appliedDateRange;
      }

      const newGraph: GraphItem = {
        id: `graph-${Date.now()}-${datasetToSplit.id}`,
        datasets: [datasetToSplit],
        normalizeByReference: graph.normalizeByReference ?? false,
        creation: {
          datasetIds: [datasetToSplit.id],
          datasetNames: [datasetToSplit.name],
          dateFilterMode: splitOrigin.dateFilterMode,
          selectedPreset: splitOrigin.selectedPreset,
          appliedDateRange: splitOrigin.appliedDateRange,
          datasetOrigins: {
            [datasetToSplit.id]: splitOrigin,
          },
          searchQuery: graph.creation.searchQuery,
        },
      };

      const next = [...prev];
      next.splice(graphIndex, 1, updatedGraph, newGraph);
      return next;
    });
  };

  const handleDragStart = (event: DragStartEvent) => {
    setActiveId(event.active.id as string);
  };

  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;
    setActiveId(null);

    if (!over) return;

    const activeGraphId = active.id as string;
    const overId = over.id as string;

    if (overId.startsWith("merge-")) {
      const targetGraphId = overId.replace("merge-", "");

      setGraphs((prev) => {
        const activeGraph = prev.find((graph) => graph.id === activeGraphId);
        const targetGraphIndex = prev.findIndex((graph) => graph.id === targetGraphId);
        if (!activeGraph || targetGraphIndex === -1) return prev;

        const targetGraph = prev[targetGraphIndex];
        const existingIds = new Set(targetGraph.datasets.map((dataset) => dataset.id));
        const datasetsToAdd = activeGraph.datasets.filter((dataset) => !existingIds.has(dataset.id));
        if (datasetsToAdd.length === 0) return prev;

        const mergedDatasets = [...targetGraph.datasets, ...datasetsToAdd];
        const targetOrigins = targetGraph.creation.datasetOrigins || {};
        const activeOrigins = activeGraph.creation.datasetOrigins || {};
        const mergedOrigins: APIGraphCreationContext["datasetOrigins"] = { ...targetOrigins };

        datasetsToAdd.forEach((dataset) => {
          mergedOrigins[dataset.id] =
            activeOrigins[dataset.id] || {
              dateFilterMode: activeGraph.creation.dateFilterMode || dateFilterMode || "preset",
              selectedPreset: activeGraph.creation.selectedPreset,
              appliedDateRange: activeGraph.creation.appliedDateRange || appliedDateRange || {},
            };
        });

        const mergedGraph: GraphItem = {
          ...targetGraph,
          datasets: mergedDatasets,
          normalizeByReference: targetGraph.normalizeByReference ?? false,
          creation: {
            datasetIds: mergedDatasets.map((dataset) => dataset.id),
            datasetNames: mergedDatasets.map((dataset) => dataset.name),
            datasetOrigins: mergedOrigins,
            searchQuery: targetGraph.creation.searchQuery,
          },
        };

        const next = [...prev];
        next[targetGraphIndex] = mergedGraph;
        return next.filter((graph) => graph.id !== activeGraphId);
      });
      return;
    }

    if (activeGraphId !== overId) {
      setGraphs((items) => {
        const oldIndex = items.findIndex((item) => item.id === activeGraphId);
        const newIndex = items.findIndex((item) => item.id === overId);
        return arrayMove(items, oldIndex, newIndex);
      });
    }
  };

  return {
    activeId,
    customCollisionDetection,
    handleDragStart,
    handleDragEnd,
    addGraphFromDatasets,
    addGraph,
    removeGraph,
    clearAllGraphs,
    moveGraphUp,
    moveGraphDown,
    updateDatasetColor,
    updateDatasetStyle,
    updateGraphNormalization,
    splitGraph,
    splitDatasetFromGraph,
  };
}