import type { Map as MapLibreMap, MapGeoJSONFeature, MapMouseEvent } from "maplibre-gl";
import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react";
import {
  createMeasurementFeatureCollection,
  emptyMeasurementFeatureCollection,
  ensureMeasurementLayers,
  setSelectedMeasurementPipeIds,
  setMeasurementSourceData
} from "../map/measurement-layers";
import { getFeatureId } from "../map/feature-adapter";
import {
  getLastSegmentDistanceMeters,
  getLineDistanceMeters,
  getPolygonAreaSquareMeters,
  isSameCoordinate,
  type LngLatTuple
} from "../map/geo";

export type WorkbenchMeasureMode = "distance" | "area" | "segment";
export type WorkbenchMeasureUnit = "m" | "km";
export type WorkbenchMeasurementResult = {
  label: string;
  value: string;
  unit: string;
  metrics: Array<{ label: string; value: string }>;
};

type UseWorkbenchMeasurementOptions = {
  mapRef: RefObject<MapLibreMap | null>;
  mapReady: boolean;
  mode: WorkbenchMeasureMode;
  unit: WorkbenchMeasureUnit;
};

const SNAP_RADIUS_PIXELS = 14;
const SNAP_LAYER_IDS = ["pipes-flow"];

type SelectedPipe = {
  id: string;
  lengthMeters?: number;
};

export function useWorkbenchMeasurement({
  mapRef,
  mapReady,
  mode,
  unit
}: UseWorkbenchMeasurementOptions) {
  const [coordinates, setCoordinates] = useState<LngLatTuple[]>([]);
  const [selectedPipes, setSelectedPipes] = useState<SelectedPipe[]>([]);
  const [measuring, setMeasuring] = useState(false);
  const coordinatesRef = useRef(coordinates);
  const selectedPipesRef = useRef(selectedPipes);

  useEffect(() => {
    coordinatesRef.current = coordinates;
  }, [coordinates]);

  useEffect(() => {
    selectedPipesRef.current = selectedPipes;
  }, [selectedPipes]);

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

    ensureMeasurementLayers(map);
    setMeasurementSourceData(map, emptyMeasurementFeatureCollection);
  }, [mapReady, mapRef]);

  const syncMeasurementSource = useCallback(
    (nextCoordinates: LngLatTuple[], nextMode = mode) => {
      const map = mapRef.current;
      if (!map) {
        return;
      }

      setMeasurementSourceData(
        map,
        nextMode !== "segment" && nextCoordinates.length > 0
          ? createMeasurementFeatureCollection(nextCoordinates, nextMode)
          : emptyMeasurementFeatureCollection
      );
    },
    [mapRef, mode]
  );

  useEffect(() => {
    syncMeasurementSource(coordinates, mode);
  }, [coordinates, mode, syncMeasurementSource]);

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

    setSelectedMeasurementPipeIds(
      map,
      mode === "segment" ? selectedPipes.map((pipe) => pipe.id) : []
    );
  }, [mapReady, mapRef, mode, selectedPipes]);

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

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

    function handleClick(event: MapMouseEvent) {
      event.preventDefault();
      if (mode === "segment") {
        const selectedPipe = getSelectedPipe(activeMap, event);
        if (selectedPipe) {
          setSelectedPipes((current) => addUniquePipe(current, selectedPipe));
        }
        return;
      }

      const coordinate: LngLatTuple = [event.lngLat.lng, event.lngLat.lat];
      setCoordinates((current) => {
        if (isSameCoordinate(current[current.length - 1], coordinate)) {
          return current;
        }

        return [...current, coordinate];
      });
    }

    map.on("click", handleClick);

    return () => {
      map.off("click", handleClick);
      canvas.style.cursor = previousCursor;
    };
  }, [mapReady, mapRef, measuring, mode]);

  const clear = useCallback(() => {
    setCoordinates([]);
    setSelectedPipes([]);
    setMeasuring(false);
    syncMeasurementSource([]);
  }, [syncMeasurementSource]);

  const copyResult = useCallback(() => {
    if (!navigator.clipboard) {
      return;
    }

    void navigator.clipboard.writeText(getResultText(mode, unit, coordinatesRef.current, selectedPipesRef.current));
  }, [mode, unit]);

  const result = useMemo<WorkbenchMeasurementResult>(() => {
    const distanceMeters = getLineDistanceMeters(coordinates);
    const areaSquareMeters = mode === "area" ? getPolygonAreaSquareMeters(coordinates) : 0;
    const segmentCount = Math.max(coordinates.length - 1, 0);
    const lastSegmentMeters = getLastSegmentDistanceMeters(coordinates);
    const selectedPipeLengthMeters = selectedPipes.reduce((total, pipe) => total + (pipe.lengthMeters ?? 0), 0);

    if (mode === "area") {
      return {
        label: "当前面积",
        value: formatArea(areaSquareMeters, unit),
        unit: unit === "km" ? "km²" : "m²",
        metrics: [
          { label: "顶点", value: String(coordinates.length) },
          { label: "周长", value: `${formatDistance(distanceMeters, unit)} ${unit}` }
        ]
      };
    }

    if (mode === "segment") {
      return {
        label: "资产长度",
        value: formatDistance(selectedPipeLengthMeters, unit),
        unit,
        metrics: [
          { label: "管段", value: `${selectedPipes.length} 段` }
        ]
      };
    }

    return {
      label: "当前长度",
      value: formatDistance(distanceMeters, unit),
      unit,
      metrics: [
        { label: "段数", value: String(segmentCount) },
        { label: "末段", value: `${formatDistance(lastSegmentMeters, unit)} ${unit}` }
      ]
    };
  }, [coordinates, mode, selectedPipes, unit]);

  return {
    measuring,
    result,
    hasPoints: mode === "segment" ? selectedPipes.length > 0 : coordinates.length > 0,
    start: () => setMeasuring(true),
    stop: () => setMeasuring(false),
    clear,
    copyResult
  };
}

function getResultText(
  mode: WorkbenchMeasureMode,
  unit: WorkbenchMeasureUnit,
  coordinates: LngLatTuple[],
  selectedPipes: SelectedPipe[]
) {
  const distanceMeters = getLineDistanceMeters(coordinates);

  if (mode === "area") {
    return `面积：${formatArea(getPolygonAreaSquareMeters(coordinates), unit)} ${unit === "km" ? "km²" : "m²"}`;
  }

  if (mode === "segment") {
    const lengthMeters = selectedPipes.reduce((total, pipe) => total + (pipe.lengthMeters ?? 0), 0);
    return `资产长度：${formatDistance(lengthMeters, unit)} ${unit}`;
  }

  return `长度：${formatDistance(distanceMeters, unit)} ${unit}`;
}

function getSelectedPipe(map: MapLibreMap, event: MapMouseEvent): SelectedPipe | null {
  const point = event.point;
  const features = map.queryRenderedFeatures(
    [
      [point.x - SNAP_RADIUS_PIXELS, point.y - SNAP_RADIUS_PIXELS],
      [point.x + SNAP_RADIUS_PIXELS, point.y + SNAP_RADIUS_PIXELS]
    ],
    { layers: SNAP_LAYER_IDS }
  );

  for (const feature of features) {
    const selectedPipe = toSelectedPipe(feature);
    if (selectedPipe) {
      return selectedPipe;
    }
  }

  return null;
}

function toSelectedPipe(feature: MapGeoJSONFeature): SelectedPipe | null {
  const id = getFeatureId(feature);
  if (!id || feature.layer.id !== "pipes-flow") {
    return null;
  }

  return {
    id,
    lengthMeters: getNumericProperty(feature.properties?.length)
  };
}

function getNumericProperty(value: unknown) {
  const numberValue = Number(value);
  return Number.isFinite(numberValue) && numberValue > 0 ? numberValue : undefined;
}

function addUniquePipe(current: SelectedPipe[], next: SelectedPipe) {
  if (current.some((pipe) => pipe.id === next.id)) {
    return current;
  }

  return [...current, next];
}

function formatDistance(valueMeters: number, unit: WorkbenchMeasureUnit) {
  const value = unit === "km" ? valueMeters / 1000 : valueMeters;
  return value.toLocaleString("zh-CN", {
    maximumFractionDigits: unit === "km" ? 2 : 1,
    minimumFractionDigits: 0
  });
}

function formatArea(valueSquareMeters: number, unit: WorkbenchMeasureUnit) {
  const value = unit === "km" ? valueSquareMeters / 1_000_000 : valueSquareMeters;
  return value.toLocaleString("zh-CN", {
    maximumFractionDigits: unit === "km" ? 3 : 1,
    minimumFractionDigits: 0
  });
}
