import { useEffect, useRef, useState } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { gridToLatLon, greatCirclePoints } from '@/lib/maidenhead';
import { BASEMAPS, type BasemapKey } from '@/components/MainMap';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';

// FT Map — the live decode feed as geography: every station decoded in the
// last half hour, an arc from the operator's own square to theirs, coloured by
// band the way PSK Reporter taught everyone to read it. Wholly display: the
// decode list is the same one the FT decodes tab shows, and a station with no
// grid (never sent one in a CQ) simply cannot be placed and is not drawn.
//
// Performance is a design constraint, not an afterthought: the panel only
// exists while its tab is active (the parent unmounts it otherwise), the map
// renders with canvas (one <canvas>, not one DOM node per arc), the arcs are
// capped, and redraws happen when the DECODE LIST changes — every 15 s in FT8,
// not per frame.

export type FTMapDecode = {
  call: string;
  grid?: string;
  band?: string;
  snr: number;
  at: string;
};

// The band palette every PSK Reporter user already knows, near enough.
const BAND_COLOURS: Record<string, string> = {
  '160m': '#7f7f7f', '80m': '#e550e5', '60m': '#00008b', '40m': '#5555ff',
  '30m': '#62d962', '20m': '#f2c40c', '17m': '#f2f261', '15m': '#cca166',
  '12m': '#b22222', '10m': '#ff69b4', '6m': '#ff0000', '4m': '#cc0044',
  '2m': '#ff1493', '70cm': '#999900',
};
const bandColour = (b?: string) => BAND_COLOURS[(b ?? '').toLowerCase()] || '#9ca3af';

const MAX_ARCS = 300;
const MAX_AGE_MS = 30 * 60_000;

export function FTMapPanel({ decodes, myGrid }: { decodes: FTMapDecode[]; myGrid: string }) {
  const { t } = useI18n();
  const divRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<L.Map | null>(null);
  const layerRef = useRef<L.LayerGroup | null>(null);
  const baseRef = useRef<L.TileLayer | null>(null);
  const labelsRef = useRef<L.TileLayer | null>(null);
  const [basemap, setBasemap] = useState<BasemapKey>(() =>
    (localStorage.getItem('opslog.ftmapBase') as BasemapKey) || 'satellite');

  // The map itself, once.
  useEffect(() => {
    if (!divRef.current || mapRef.current) return;
    // ONE world: noWrap tiles inside hard bounds, no side-by-side copies —
    // and the space beyond the edge is the theme's own surface (style.css).
    const m = L.map(divRef.current, {
      zoomControl: true, attributionControl: true,
      worldCopyJump: false, preferCanvas: true,
      center: [25, 0], zoom: 2, minZoom: 2,
      maxBounds: L.latLngBounds(L.latLng(-85.0511, -180), L.latLng(85.0511, 180)),
      maxBoundsViscosity: 1,
    });
    mapRef.current = m;
    layerRef.current = L.layerGroup().addTo(m);
    return () => { m.remove(); mapRef.current = null; layerRef.current = null; };
  }, []);

  // Basemap follows the picker.
  useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    baseRef.current?.remove(); labelsRef.current?.remove();
    const bm = BASEMAPS[basemap];
    const opts: L.TileLayerOptions = {
      maxNativeZoom: bm.maxNativeZoom,
      noWrap: true,
      bounds: L.latLngBounds(L.latLng(-85.0511, -180), L.latLng(85.0511, 180)),
    };
    baseRef.current = L.tileLayer(bm.url, { ...opts, attribution: bm.attr, subdomains: bm.subdomains ?? 'abc' }).addTo(m);
    if (bm.labelsUrl) labelsRef.current = L.tileLayer(bm.labelsUrl, opts).addTo(m);
    localStorage.setItem('opslog.ftmapBase', basemap);
  }, [basemap]);

  // The arcs, redrawn when the decode list changes. Newest last so they paint
  // on top; opacity falls with age so the map reads as "now" with a memory.
  useEffect(() => {
    const layer = layerRef.current;
    if (!layer) return;
    layer.clearLayers();
    const from = gridToLatLon(myGrid);
    if (!from) return;
    const now = Date.now();
    const placed = decodes
      .filter((d) => d.grid && Date.parse(d.at) > now - MAX_AGE_MS)
      .slice(-MAX_ARCS);
    // One line per CALL (its freshest sighting): the same CQer decoded thirty
    // times in ten minutes is one path on the air, not thirty strokes of it.
    const byCall = new Map<string, FTMapDecode>();
    for (const d of placed) byCall.set(d.call.toUpperCase(), d);
    L.circleMarker([from.lat, from.lon], {
      radius: 5, color: '#fff', weight: 2, fillColor: '#e11d48', fillOpacity: 1,
    }).addTo(layer);
    for (const d of byCall.values()) {
      const to = gridToLatLon(d.grid!);
      if (!to) continue;
      const age = now - Date.parse(d.at);
      const fade = Math.max(0.15, 1 - age / MAX_AGE_MS);
      const colour = bandColour(d.band);
      const pts = greatCirclePoints(from.lat, from.lon, to.lat, to.lon, 48);
      L.polyline(pts as L.LatLngExpression[], {
        color: colour, weight: 1.3, opacity: 0.65 * fade, smoothFactor: 0,
      }).addTo(layer);
      L.circleMarker([to.lat, to.lon], {
        radius: 3, color: colour, weight: 1, fillColor: colour, fillOpacity: 0.9 * fade,
      }).bindTooltip(`${d.call} · ${d.grid} · ${d.snr > 0 ? '+' : ''}${d.snr} dB`, { direction: 'top' })
        .addTo(layer);
    }
  }, [decodes, myGrid]);

  const bands = [...new Set(decodes.map((d) => (d.band ?? '').toLowerCase()).filter(Boolean))];
  return (
    <div className="relative h-full w-full min-h-0">
      <div ref={divRef} className="absolute inset-0 rounded-lg overflow-hidden" />
      {/* Basemap picker, MainMap's own vocabulary. */}
      <div className="absolute top-2 left-12 z-[1000] flex gap-1 rounded-md bg-background/85 backdrop-blur px-1 py-1 border border-border">
        {(Object.keys(BASEMAPS) as BasemapKey[]).map((k) => (
          <button key={k} type="button" onClick={() => setBasemap(k)}
            className={cn('px-2 py-0.5 rounded text-[11px]',
              basemap === k ? 'bg-primary text-primary-foreground font-semibold' : 'text-muted-foreground hover:bg-muted')}>
            {BASEMAPS[k].label}
          </button>
        ))}
      </div>
      {/* Band legend — only the bands actually on screen. */}
      {bands.length > 0 && (
        <div className="absolute bottom-2 left-2 z-[1000] flex flex-wrap gap-x-2.5 gap-y-1 rounded-md bg-background/85 backdrop-blur px-2 py-1.5 border border-border">
          {bands.map((b) => (
            <span key={b} className="flex items-center gap-1 text-[11px] text-foreground">
              <span className="inline-block w-3 h-[3px] rounded" style={{ background: bandColour(b) }} />
              {b.toUpperCase()}
            </span>
          ))}
        </div>
      )}
      {!gridToLatLon(myGrid) && (
        <div className="absolute inset-0 z-[1000] flex items-center justify-center pointer-events-none">
          <span className="rounded-md bg-background/90 border border-border px-3 py-2 text-sm text-muted-foreground">
            {t('ftmap.noGrid')}
          </span>
        </div>
      )}
    </div>
  );
}
