import type { Map as MapLibreMap, MapMouseEvent } from "maplibre-gl";
import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react";
import {
  DRAWING_INTERACTIVE_LAYER_IDS,
  DRAWING_SOURCE_IDS,
  createCircleFeature,
  createLineFeature,
  createPointFeature,
  createPolygonFeature,
  emptyDrawingFeatureCollection,
  ensureDrawingLayers,
  setSelectedDrawingFeatureId,
  setDrawingSourceData,
  type DrawingFeatureCollection
} from "../map/drawing-layers";
import {
  createDrawingId,
  createDrawingLabel,
  createFeatureFromDraft,
  formatDrawingTime,
  getDraftPointCount,
  getDrawingKindLabel,
  getVisibleFeatureCollection,
  type DrawingDraftState,
  type WorkbenchDrawMode
} from "../map/drawing-model";
import { getDistanceMeters, isSameCoordinate } from "../map/geo";

export type { WorkbenchDrawMode };

export type WorkbenchDrawingAnnotation = {
  id: string;
  label: string;
  description: string;
  kind: WorkbenchDrawMode;
  hidden: boolean;
  selected: boolean;
  onToggleVisibility: () => void;
  onSelect: () => void;
};

type UseWorkbenchDrawingOptions = {
  mapRef: RefObject<MapLibreMap | null>;
  mapReady: boolean;
  activeMode: WorkbenchDrawMode | null;
  onModeChange: (mode: WorkbenchDrawMode | null) => void;
};

export function useWorkbenchDrawing({
  mapRef,
  mapReady,
  activeMode,
  onModeChange
}: UseWorkbenchDrawingOptions) {
  const [featureCollection, setFeatureCollection] = useState<DrawingFeatureCollection>(emptyDrawingFeatureCollection);
  const [hiddenFeatureIds, setHiddenFeatureIds] = useState<Set<string>>(() => new Set());
  const [selectedFeatureId, setSelectedFeatureId] = useState<string | null>(null);
  const [, setHistoryVersion] = useState(0);
  const [draftPointCount, setDraftPointCount] = useState(0);
  const featureCollectionRef = useRef(featureCollection);
  const hiddenFeatureIdsRef = useRef(hiddenFeatureIds);
  const undoStackRef = useRef<DrawingFeatureCollection[]>([]);
  const redoStackRef = useRef<DrawingFeatureCollection[]>([]);
  const draftRef = useRef<DrawingDraftState | null>(null);
  const featureCounterRef = useRef(0);

  useEffect(() => {
    featureCollectionRef.current = featureCollection;
  }, [featureCollection]);

  useEffect(() => {
    hiddenFeatureIdsRef.current = hiddenFeatureIds;
  }, [hiddenFeatureIds]);

  useEffect(() => {
    const map = mapRef.current;
    if (!mapReady || !map) {
      return;
    }

    ensureDrawingLayers(map);
    setDrawingSourceData(map, DRAWING_SOURCE_IDS.features, getVisibleFeatureCollection(featureCollectionRef.current, hiddenFeatureIdsRef.current));
    setDrawingSourceData(map, DRAWING_SOURCE_IDS.draft, emptyDrawingFeatureCollection);
  }, [mapReady, mapRef]);

  const clearDraft = useCallback(() => {
    draftRef.current = null;
    const map = mapRef.current;
    if (map) {
      setDrawingSourceData(map, DRAWING_SOURCE_IDS.draft, emptyDrawingFeatureCollection);
    }
  }, [mapRef]);

  const updateFeatures = useCallback(
    (nextCollection: DrawingFeatureCollection) => {
      featureCollectionRef.current = nextCollection;
      setFeatureCollection(nextCollection);

      const map = mapRef.current;
      if (map) {
        setDrawingSourceData(map, DRAWING_SOURCE_IDS.features, getVisibleFeatureCollection(nextCollection, hiddenFeatureIdsRef.current));
      }
    },
    [mapRef]
  );

  const commitFeatures = useCallback(
    (nextCollection: DrawingFeatureCollection) => {
      undoStackRef.current.push(featureCollectionRef.current);
      redoStackRef.current = [];
      updateFeatures(nextCollection);
      setHistoryVersion((current) => current + 1);
    },
    [updateFeatures]
  );

  useEffect(() => {
    const map = mapRef.current;
    if (!mapReady || !map) {
      return;
    }

    setDrawingSourceData(map, DRAWING_SOURCE_IDS.features, getVisibleFeatureCollection(featureCollection, hiddenFeatureIds));
  }, [featureCollection, hiddenFeatureIds, mapReady, mapRef]);

  useEffect(() => {
    const map = mapRef.current;
    if (!mapReady || !map) {
      return;
    }

    setSelectedDrawingFeatureId(map, selectedFeatureId);
  }, [mapReady, mapRef, selectedFeatureId]);

  useEffect(() => {
    const map = mapRef.current;
    if (!mapReady || !map || activeMode) {
      return;
    }

    const activeMap = map;

    function handleDrawingClick(event: MapMouseEvent) {
      const feature = activeMap.queryRenderedFeatures(event.point, {
        layers: DRAWING_INTERACTIVE_LAYER_IDS
      })[0];
      const id = feature?.properties?.id;
      if (typeof id === "string" && id) {
        setSelectedFeatureId((current) => (current === id ? null : id));
      }
    }

    function handleMouseEnter() {
      activeMap.getCanvas().style.cursor = "pointer";
    }

    function handleMouseLeave() {
      activeMap.getCanvas().style.cursor = "";
    }

    DRAWING_INTERACTIVE_LAYER_IDS.forEach((layerId) => {
      if (activeMap.getLayer(layerId)) {
        activeMap.on("click", layerId, handleDrawingClick);
        activeMap.on("mouseenter", layerId, handleMouseEnter);
        activeMap.on("mouseleave", layerId, handleMouseLeave);
      }
    });

    return () => {
      DRAWING_INTERACTIVE_LAYER_IDS.forEach((layerId) => {
        if (activeMap.getLayer(layerId)) {
          activeMap.off("click", layerId, handleDrawingClick);
          activeMap.off("mouseenter", layerId, handleMouseEnter);
          activeMap.off("mouseleave", layerId, handleMouseLeave);
        }
      });
    };
  }, [activeMode, mapReady, mapRef]);

  const updateDraft = useCallback(
    (draft: DrawingDraftState | null) => {
      draftRef.current = draft;

      const map = mapRef.current;
      if (!map) {
        return;
      }

      if (!draft || getDraftPointCount(draft) === 0) {
        setDraftPointCount(0);
        setDrawingSourceData(map, DRAWING_SOURCE_IDS.draft, emptyDrawingFeatureCollection);
        return;
      }

      setDraftPointCount(getDraftPointCount(draft));

      const draftFeatures: DrawingFeatureCollection["features"] =
        draft.mode === "circle"
          ? [createPointFeature("draft-circle-center", draft.center, "圆心")]
          : draft.coordinates.map((coordinates, index) =>
              createPointFeature(`draft-vertex-${index}`, coordinates, `顶点 ${index + 1}`)
            );

      if (draft.mode !== "circle" && draft.coordinates.length >= 2) {
        draftFeatures.push(createLineFeature("draft-line", draft.coordinates, "绘制草稿"));
      }

      if (draft.mode === "polygon" && draft.coordinates.length >= 3) {
        draftFeatures.push(createPolygonFeature("draft-polygon", draft.coordinates, "范围草稿"));
      }

      if (draft.mode === "circle" && draft.edge) {
        draftFeatures.push(createPointFeature("draft-circle-edge", draft.edge, "半径"));
        draftFeatures.push(createLineFeature("draft-circle-radius", [draft.center, draft.edge], "圆形半径"));
        draftFeatures.push(createCircleFeature("draft-circle", draft.center, getDistanceMeters(draft.center, draft.edge), "圆形草稿"));
      }

      setDrawingSourceData(map, DRAWING_SOURCE_IDS.draft, {
        type: "FeatureCollection",
        features: draftFeatures
      });
    },
    [mapRef]
  );

  const finishDraft = useCallback(() => {
    const draft = draftRef.current;
    if (!draft) {
      return;
    }

    const minimumPoints = draft.mode === "line" ? 2 : draft.mode === "polygon" ? 3 : 2;
    const draftPointCount = getDraftPointCount(draft);
    if (draftPointCount < minimumPoints) {
      return;
    }

    const id = createDrawingId(draft.mode, featureCounterRef.current + 1);
    featureCounterRef.current += 1;
    const label = createDrawingLabel(draft.mode, featureCounterRef.current);
    const feature = createFeatureFromDraft(id, draft, label);

    if (!feature) {
      return;
    }

    commitFeatures({
      type: "FeatureCollection",
      features: [...featureCollectionRef.current.features, feature]
    });
    updateDraft(null);
    onModeChange(null);
  }, [commitFeatures, onModeChange, updateDraft]);

  useEffect(() => {
    const map = mapRef.current;
    if (!mapReady || !map || !activeMode) {
      clearDraft();
      return;
    }

    const drawingMode = activeMode;
    const canvas = map.getCanvas();
    const previousCursor = canvas.style.cursor;
    canvas.style.cursor = "crosshair";

    if (drawingMode !== "point") {
      map.doubleClickZoom.disable();
    }

    function handleClick(event: MapMouseEvent) {
      event.preventDefault();
      const coordinates: [number, number] = [event.lngLat.lng, event.lngLat.lat];

      if (drawingMode === "point") {
        const nextNumber = featureCounterRef.current + 1;
        const id = createDrawingId(drawingMode, nextNumber);
        featureCounterRef.current = nextNumber;
        const feature = createPointFeature(id, coordinates, createDrawingLabel("point", nextNumber));
        commitFeatures({
          type: "FeatureCollection",
          features: [...featureCollectionRef.current.features, feature]
        });
        return;
      }

      if (drawingMode === "circle") {
        const currentDraft = draftRef.current?.mode === "circle" ? draftRef.current : null;
        if (!currentDraft) {
          updateDraft({
            mode: "circle",
            center: coordinates
          });
          return;
        }

        updateDraft({
          mode: "circle",
          center: currentDraft.center,
          edge: coordinates
        });
        finishDraft();
        return;
      }

      const draftMode: DrawingDraftState["mode"] = drawingMode === "polygon" ? "polygon" : "line";
      const currentDraft = draftRef.current?.mode === draftMode ? draftRef.current : { mode: draftMode, coordinates: [] };
      if (isSameCoordinate(currentDraft.coordinates[currentDraft.coordinates.length - 1], coordinates)) {
        return;
      }

      updateDraft({
        mode: draftMode,
        coordinates: [...currentDraft.coordinates, coordinates]
      });
    }

    function handleDoubleClick(event: MapMouseEvent) {
      event.preventDefault();
      finishDraft();
    }

    function handleMouseMove(event: MapMouseEvent) {
      if (drawingMode !== "circle") {
        return;
      }

      const currentDraft = draftRef.current;
      if (currentDraft?.mode !== "circle") {
        return;
      }

      updateDraft({
        mode: "circle",
        center: currentDraft.center,
        edge: [event.lngLat.lng, event.lngLat.lat]
      });
    }

    map.on("click", handleClick);
    map.on("dblclick", handleDoubleClick);
    map.on("mousemove", handleMouseMove);

    return () => {
      map.off("click", handleClick);
      map.off("dblclick", handleDoubleClick);
      map.off("mousemove", handleMouseMove);
      canvas.style.cursor = previousCursor;

      if (drawingMode !== "point") {
        map.doubleClickZoom.enable();
      }
    };
  }, [activeMode, clearDraft, commitFeatures, finishDraft, mapReady, mapRef, updateDraft]);

  const undo = useCallback(() => {
    const draft = draftRef.current;
    if (draft && getDraftPointCount(draft) > 0) {
      if (draft.mode === "circle") {
        updateDraft(draft.edge ? { mode: "circle", center: draft.center } : null);
      } else {
        updateDraft({
          mode: draft.mode,
          coordinates: draft.coordinates.slice(0, -1)
        });
      }
      return;
    }

    const previousCollection = undoStackRef.current.pop();
    if (!previousCollection) {
      return;
    }

    redoStackRef.current.push(featureCollectionRef.current);
    updateFeatures(previousCollection);
    setHistoryVersion((current) => current + 1);
  }, [updateDraft, updateFeatures]);

  const redo = useCallback(() => {
    const nextCollection = redoStackRef.current.pop();
    if (!nextCollection) {
      return;
    }

    undoStackRef.current.push(featureCollectionRef.current);
    updateFeatures(nextCollection);
    setHistoryVersion((current) => current + 1);
  }, [updateFeatures]);

  const deleteSelected = useCallback(() => {
    if (!selectedFeatureId) {
      return;
    }

    const nextFeatures = featureCollectionRef.current.features.filter((feature) => feature.properties.id !== selectedFeatureId);
    if (nextFeatures.length === featureCollectionRef.current.features.length) {
      return;
    }

    setSelectedFeatureId(null);
    setHiddenFeatureIds((current) => {
      const next = new Set(current);
      next.delete(selectedFeatureId);
      return next;
    });
    commitFeatures({
      type: "FeatureCollection",
      features: nextFeatures
    });
  }, [commitFeatures, selectedFeatureId]);

  const clear = useCallback(() => {
    clearDraft();
    setHiddenFeatureIds(new Set());
    setSelectedFeatureId(null);
    commitFeatures(emptyDrawingFeatureCollection);
    onModeChange(null);
  }, [clearDraft, commitFeatures, onModeChange]);

  const toggleVisibility = useCallback((featureId: string) => {
    setHiddenFeatureIds((current) => {
      const next = new Set(current);
      if (next.has(featureId)) {
        next.delete(featureId);
      } else {
        next.add(featureId);
      }

      return next;
    });
  }, []);

  const exportGeoJSON = useCallback(() => {
    const data = JSON.stringify(featureCollectionRef.current, null, 2);
    const blob = new Blob([data], { type: "application/geo+json" });
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = "workbench-annotations.geojson";
    document.body.appendChild(link);
    link.click();
    link.remove();
    URL.revokeObjectURL(url);
  }, []);

  const annotations = useMemo<WorkbenchDrawingAnnotation[]>(
    () =>
      featureCollection.features.map((feature) => ({
        id: feature.properties.id,
        label: feature.properties.label,
        description: `${getDrawingKindLabel(feature.properties.kind)} · ${formatDrawingTime(feature.properties.createdAt)}`,
        kind: feature.properties.kind,
        hidden: hiddenFeatureIds.has(feature.properties.id),
        selected: selectedFeatureId === feature.properties.id,
        onToggleVisibility: () => toggleVisibility(feature.properties.id),
        onSelect: () => setSelectedFeatureId(feature.properties.id)
      })),
    [featureCollection, hiddenFeatureIds, selectedFeatureId, toggleVisibility]
  );

  return {
    annotations,
    canUndo: Boolean(draftPointCount || undoStackRef.current.length),
    canRedo: Boolean(redoStackRef.current.length),
    canDeleteSelected: Boolean(selectedFeatureId),
    hasFeatures: featureCollection.features.length > 0,
    undo,
    redo,
    deleteSelected,
    clear,
    exportGeoJSON
  };
}
