import maplibregl, { type Map as MapLibreMap, type StyleSpecification } from "maplibre-gl";
import type { MapFeatureInteractionState } from "../hooks/use-map-interactions";

const MAX_EXPORT_DIMENSION = 4096;
const EXPORT_IDLE_TIMEOUT_MS = 6000;

export type ExportMapViewOptions = {
  scale?: number;
  targetLongEdge?: number;
  filename?: string;
  selectedFeature?: Pick<MapFeatureInteractionState, "source" | "sourceLayer" | "id"> | null;
};

export async function exportMapViewImage(map: MapLibreMap, { scale, targetLongEdge, filename, selectedFeature }: ExportMapViewOptions = {}) {
  const canvas = map.getCanvas();
  const sourceWidth = canvas.clientWidth || canvas.width;
  const sourceHeight = canvas.clientHeight || canvas.height;

  if (!sourceWidth || !sourceHeight) {
    throw new Error("地图画布尺寸不可用。");
  }

  const requestedScale = targetLongEdge ? targetLongEdge / Math.max(sourceWidth, sourceHeight) : scale ?? 1;
  const exportScale = Math.max(1, Math.min(requestedScale, MAX_EXPORT_DIMENSION / sourceWidth, MAX_EXPORT_DIMENSION / sourceHeight));
  const exportWidth = Math.round(sourceWidth * exportScale);
  const exportHeight = Math.round(sourceHeight * exportScale);
  const container = createExportContainer(exportWidth, exportHeight);
  const style = cloneMapStyle(map.getStyle());
  const bounds = map.getBounds();

  document.body.appendChild(container);

  const exportMap = new maplibregl.Map({
    container,
    style,
    center: map.getCenter(),
    zoom: map.getZoom(),
    pitch: map.getPitch(),
    bearing: map.getBearing(),
    preserveDrawingBuffer: true,
    interactive: false,
    attributionControl: false,
    fadeDuration: 0
  });

  try {
    await onceMapEvent(exportMap, "load");
    exportMap.fitBounds(bounds, {
      padding: 0,
      duration: 0
    });
    if (selectedFeature) {
      exportMap.setFeatureState(
        { source: selectedFeature.source, sourceLayer: selectedFeature.sourceLayer, id: selectedFeature.id },
        { selected: true, hovered: false }
      );
    }
    await waitForMapIdle(exportMap);

    const link = document.createElement("a");
    link.download = filename ?? getDefaultExportFilename(getExportLabel(targetLongEdge, exportScale));
    link.href = exportMap.getCanvas().toDataURL("image/png");
    link.click();

    return {
      width: exportWidth,
      height: exportHeight,
      scale: exportScale
    };
  } finally {
    exportMap.remove();
    container.remove();
  }
}

function createExportContainer(width: number, height: number) {
  const container = document.createElement("div");
  container.style.position = "fixed";
  container.style.left = "-10000px";
  container.style.top = "0";
  container.style.width = `${width}px`;
  container.style.height = `${height}px`;
  container.style.pointerEvents = "none";
  container.style.opacity = "0";
  return container;
}

function cloneMapStyle(style: StyleSpecification) {
  if (typeof structuredClone === "function") {
    return structuredClone(style);
  }

  return JSON.parse(JSON.stringify(style)) as StyleSpecification;
}

function onceMapEvent(map: MapLibreMap, eventName: "load" | "idle") {
  return new Promise<void>((resolve, reject) => {
    map.once(eventName, () => resolve());
    map.once("error", (event) => reject(event.error ?? new Error("地图导出渲染失败。")));
  });
}

function waitForMapIdle(map: MapLibreMap) {
  return Promise.race([
    onceMapEvent(map, "idle"),
    new Promise<void>((resolve) => {
      window.setTimeout(resolve, EXPORT_IDLE_TIMEOUT_MS);
    })
  ]);
}

function getExportLabel(targetLongEdge: number | undefined, scale: number) {
  if (targetLongEdge === 3840) {
    return "4k";
  }

  return `${scale.toFixed(1)}x`;
}

function getDefaultExportFilename(label: string) {
  const timestamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-");
  return `supply-network-view-${label}-${timestamp}.png`;
}
