"use client";

import type { Map as MapLibreMap } from "maplibre-gl";
import { type RefObject, useEffect, useState } from "react";
import { cn } from "@/lib/utils";
import { MAP_CONTROL_SURFACE_CLASS_NAME } from "./map-control-styles";

type ScaleLineState = {
  label: string;
  width: number;
  zoom: string;
};

type MapScaleLineProps = {
  mapRef: RefObject<MapLibreMap | null>;
  mapReady: boolean;
  maxWidth?: number;
  coordinatePrecision?: number;
  className?: string;
};

type CoordinateState = {
  lng: number;
  lat: number;
};

const DEFAULT_SCALE: ScaleLineState = {
  label: "500 m",
  width: 80,
  zoom: "Z --"
};

const MIN_SCALE_WIDTH = 34;
const COORDINATE_READOUT_WIDTH_CLASS_NAME = "w-[160px]";

export function MapScaleLine({
  mapRef,
  mapReady,
  maxWidth = 96,
  coordinatePrecision = 5,
  className = ""
}: MapScaleLineProps) {
  const [scale, setScale] = useState<ScaleLineState>(DEFAULT_SCALE);
  const [coordinate, setCoordinate] = useState<CoordinateState | null>(null);
  const coordinateReadout = coordinate
    ? createCoordinateReadout(coordinate, coordinatePrecision)
    : null;
  const coordinateLabel = coordinateReadout
    ? `${coordinateReadout.mercatorText} · ${coordinateReadout.lngLatText}`
    : "移动鼠标读取坐标";

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

    const updateScale = () => {
      setScale(createScaleLineState(map, maxWidth));
    };

    updateScale();
    map.on("move", updateScale);
    map.on("zoom", updateScale);
    map.on("resize", updateScale);

    return () => {
      map.off("move", updateScale);
      map.off("zoom", updateScale);
      map.off("resize", updateScale);
    };
  }, [mapReady, mapRef, maxWidth]);

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

    const handleMove = (event: { lngLat: { lng: number; lat: number } }) => {
      setCoordinate({
        lng: event.lngLat.lng,
        lat: event.lngLat.lat
      });
    };

    const handleLeave = () => {
      setCoordinate(null);
    };

    map.on("mousemove", handleMove);
    map.on("mouseout", handleLeave);

    return () => {
      map.off("mousemove", handleMove);
      map.off("mouseout", handleLeave);
    };
  }, [mapReady, mapRef]);

  return (
    <div
      aria-label={`比例尺 ${scale.label}，${coordinateLabel}`}
      className={cn(
        "pointer-events-none inline-flex w-auto max-w-full select-none items-center gap-2.5 overflow-hidden rounded-none rounded-tl-xl border-b-0 border-r-0 px-2.5 py-1.5 text-xs font-semibold leading-none text-slate-800",
        MAP_CONTROL_SURFACE_CLASS_NAME,
        className
      )}
    >
      <div className="flex shrink-0 items-center gap-2">
        <span className="min-w-11 whitespace-nowrap text-right tabular-nums">{scale.label}</span>
        <div
          className="relative h-2 shrink-0 border-x-2 border-b-2 border-slate-800 transition-[width,border-color] duration-200 ease-out motion-reduce:transition-none"
          style={{ width: `${scale.width}px` }}
          aria-hidden="true"
        />
      </div>

      <ScaleLineDivider />

      <div className="flex min-w-0 items-center justify-end gap-2.5 overflow-hidden whitespace-nowrap text-slate-700">
        <span className="tabular-nums text-slate-800">{scale.zoom}</span>
        <span className="text-slate-600">EPSG:3857</span>
        {coordinateReadout ? (
          <span className={cn(COORDINATE_READOUT_WIDTH_CLASS_NAME, "min-w-0 truncate text-right tabular-nums text-slate-800")} title={coordinateReadout.lngLatText}>
            {coordinateReadout.mercatorText}
          </span>
        ) : (
          <span className={cn(COORDINATE_READOUT_WIDTH_CLASS_NAME, "min-w-0 truncate text-right tabular-nums")}>{coordinateLabel}</span>
        )}
        <span className="text-slate-500">Mapbox/OSM</span>
      </div>
    </div>
  );
}

function ScaleLineDivider() {
  return (
    <div
      className="h-3.5 w-px shrink-0 bg-slate-300/70"
      aria-hidden="true"
    />
  );
}

function createScaleLineState(map: MapLibreMap, maxWidth: number): ScaleLineState {
  const canvas = map.getCanvas();
  const canvasWidth = canvas.clientWidth || canvas.width;
  const center = map.project(map.getCenter());
  const halfWidth = Math.min(maxWidth / 2, Math.max(1, canvasWidth / 2));
  const y = Math.round(center.y);
  const start = map.unproject([Math.max(0, center.x - halfWidth), y]);
  const end = map.unproject([Math.min(canvasWidth, center.x + halfWidth), y]);
  const measuredWidth = Math.max(1, Math.min(canvasWidth, center.x + halfWidth) - Math.max(0, center.x - halfWidth));
  const metersPerMaxWidth = distanceInMeters(start.lng, start.lat, end.lng, end.lat);
  if (!Number.isFinite(metersPerMaxWidth) || metersPerMaxWidth <= 0) {
    return {
      ...DEFAULT_SCALE,
      zoom: `Z ${map.getZoom().toFixed(1)}`
    };
  }

  const niceMeters = getNiceDistance(metersPerMaxWidth);
  const widthPx = Math.min(
    measuredWidth,
    Math.max(Math.min(MIN_SCALE_WIDTH, measuredWidth), Math.round((niceMeters / metersPerMaxWidth) * measuredWidth))
  );

  return {
    label: formatDistance(niceMeters),
    width: widthPx,
    zoom: `Z ${map.getZoom().toFixed(1)}`
  };
}

function getNiceDistance(maxMeters: number) {
  const target = Math.max(1, maxMeters);
  const magnitude = 10 ** Math.floor(Math.log10(target));
  const normalized = target / magnitude;
  const multiplier = normalized >= 5 ? 5 : normalized >= 2 ? 2 : 1;

  return multiplier * magnitude;
}

function formatDistance(meters: number) {
  if (meters >= 1000) {
    return `${Number((meters / 1000).toFixed(meters >= 10000 ? 0 : 1))} km`;
  }

  return `${Math.round(meters)} m`;
}

function createCoordinateReadout(coordinate: CoordinateState, precision: number) {
  return {
    lngLatText: `Lng ${coordinate.lng.toFixed(precision)} Lat ${coordinate.lat.toFixed(precision)}`,
    mercatorText: formatWebMercator(coordinate.lng, coordinate.lat)
  };
}

function formatWebMercator(lng: number, lat: number) {
  const earthRadius = 6378137;
  const clampedLat = Math.max(-85.05112878, Math.min(85.05112878, lat));
  const x = earthRadius * toRadians(lng);
  const y = earthRadius * Math.log(Math.tan(Math.PI / 4 + toRadians(clampedLat) / 2));

  return `X ${Math.round(x).toLocaleString("en-US")} Y ${Math.round(y).toLocaleString("en-US")}`;
}

function distanceInMeters(startLng: number, startLat: number, endLng: number, endLat: number) {
  const earthRadius = 6371008.8;
  const startPhi = toRadians(startLat);
  const endPhi = toRadians(endLat);
  const deltaPhi = toRadians(endLat - startLat);
  const deltaLambda = toRadians(endLng - startLng);
  const a =
    Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +
    Math.cos(startPhi) * Math.cos(endPhi) * Math.sin(deltaLambda / 2) * Math.sin(deltaLambda / 2);

  return 2 * earthRadius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}

function toRadians(degrees: number) {
  return (degrees * Math.PI) / 180;
}
