"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import {
  FeatureGroup,
  LayersControl,
  MapContainer,
  Marker,
  Popup,
  TileLayer,
  useMap,
} from "react-leaflet";
import L from "leaflet";
import type { CallRecord, IncidentRecord, NodeRecord } from "@/lib/types";

// ── Leaflet icon fix ──────────────────────────────────────────────────────────
delete (L.Icon.Default.prototype as unknown as Record<string, unknown>)._getIconUrl;
L.Icon.Default.mergeOptions({
  iconRetinaUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png",
  iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png",
  shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png",
});

// ── Colors ────────────────────────────────────────────────────────────────────
const INCIDENT_COLORS: Record<string, string> = {
  fire: "#ef4444",
  police: "#3b82f6",
  ems: "#eab308",
  accident: "#f97316",
  other: "#6b7280",
};

function statusColor(status: string): string {
  if (status === "online" || status === "recording") return "#4ade80";
  if (status === "unconfigured") return "#818cf8";
  return "#6b7280";
}

// ── Single-node icon (with optional pulsing ring for recording) ───────────────
function nodeIcon(status: string): L.DivIcon {
  const isRec = status === "recording";
  const color = statusColor(status);
  const ring = isRec
    ? `<div class="node-pulse-ring" style="position:absolute;width:28px;height:28px;border-radius:50%;border:2px solid #fb923c;top:-7px;left:-7px;pointer-events:none;"></div>`
    : "";
  return L.divIcon({
    className: "",
    html: `<div style="position:relative;width:14px;height:14px">${ring}<div style="width:14px;height:14px;border-radius:50%;background:${color};border:2px solid #111827;box-shadow:0 0 6px ${isRec ? "#fb923c" : "transparent"};"></div></div>`,
    iconSize: [14, 14],
    iconAnchor: [7, 7],
  });
}

function incidentIcon(type: string | null): L.DivIcon {
  const color = INCIDENT_COLORS[type ?? "other"] ?? INCIDENT_COLORS.other;
  return L.divIcon({
    className: "",
    html: `<div style="width:16px;height:16px;border-radius:3px;background:${color};border:2px solid #111827;display:flex;align-items:center;justify-content:center;font-size:9px;color:#fff;font-weight:bold;line-height:1;">!</div>`,
    iconSize: [16, 16],
    iconAnchor: [8, 8],
  });
}

// ── Fan / hand-of-cards icons for clustered markers ───────────────────────────
function nodeFanIcon(members: NodeRecord[]): L.DivIcon {
  const n = members.length;
  const CARD = 13;
  const STEP = 5;
  const totalW = CARD + (n - 1) * STEP;
  const maxRot = Math.min(28, n * 7);
  const cards = members
    .map((m, i) => {
      const ratio = n === 1 ? 0 : i / (n - 1) - 0.5;
      const rot = ratio * maxRot;
      const left = i * STEP;
      return `<div style="position:absolute;width:${CARD}px;height:${CARD}px;border-radius:3px;background:${statusColor(m.status)};border:1.5px solid #111827;left:${left}px;top:0;transform:rotate(${rot}deg);transform-origin:bottom center;box-shadow:0 1px 3px rgba(0,0,0,0.7);"></div>`;
    })
    .join("");
  return L.divIcon({
    className: "",
    html: `<div style="position:relative;width:${totalW}px;height:${CARD + 6}px">${cards}</div>`,
    iconSize: [totalW, CARD + 6],
    iconAnchor: [totalW / 2, CARD + 6],
  });
}

function incidentFanIcon(members: IncidentRecord[]): L.DivIcon {
  const n = members.length;
  const CARD = 14;
  const STEP = 8;
  const totalW = CARD + (n - 1) * STEP;
  const maxRot = Math.min(28, n * 7);
  const cards = members
    .map((m, i) => {
      const ratio = n === 1 ? 0 : i / (n - 1) - 0.5;
      const rot = ratio * maxRot;
      const left = i * STEP;
      const color = INCIDENT_COLORS[m.type ?? "other"] ?? INCIDENT_COLORS.other;
      return `<div style="position:absolute;width:${CARD}px;height:${CARD}px;border-radius:2px;background:${color};border:1.5px solid #111827;left:${left}px;top:0;transform:rotate(${rot}deg);transform-origin:bottom center;box-shadow:0 1px 3px rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;font-size:8px;color:#fff;font-weight:bold;">!</div>`;
    })
    .join("");
  return L.divIcon({
    className: "",
    html: `<div style="position:relative;width:${totalW}px;height:${CARD + 6}px">${cards}</div>`,
    iconSize: [totalW, CARD + 6],
    iconAnchor: [totalW / 2, CARD + 6],
  });
}

// ── Fan cluster grouping ──────────────────────────────────────────────────────
const CLUSTER_PX = 32;

function computeGroups<T extends { id: string; lat: number; lng: number }>(
  items: T[],
  map: L.Map
): Map<string, T[]> {
  if (!items.length) return new Map();
  const withPx = items.map((item) => ({
    item,
    px: map.latLngToContainerPoint([item.lat, item.lng]),
  }));
  const parent: number[] = items.map((_, i) => i);
  function find(x: number): number {
    if (parent[x] !== x) parent[x] = find(parent[x]);
    return parent[x];
  }
  for (let i = 0; i < withPx.length; i++) {
    for (let j = i + 1; j < withPx.length; j++) {
      const dx = withPx[i].px.x - withPx[j].px.x;
      const dy = withPx[i].px.y - withPx[j].px.y;
      if (Math.sqrt(dx * dx + dy * dy) < CLUSTER_PX) {
        const ri = find(i), rj = find(j);
        if (ri !== rj) parent[ri] = rj;
      }
    }
  }
  const groups = new Map<number, T[]>();
  withPx.forEach(({ item }, i) => {
    const root = find(i);
    if (!groups.has(root)) groups.set(root, []);
    groups.get(root)!.push(item);
  });
  const result = new Map<string, T[]>();
  Array.from(groups.values()).forEach((members) => result.set(members[0].id, members));
  return result;
}

// ── MapRefCapture — exposes L.Map instance to parent ─────────────────────────
function MapRefCapture({ onReady }: { onReady: (m: L.Map) => void }) {
  const map = useMap();
  useEffect(() => {
    onReady(map);
  }, [map, onReady]);
  return null;
}

// ── FanNodeLayer ──────────────────────────────────────────────────────────────
function FanNodeLayer({
  nodes,
  activeCalls,
}: {
  nodes: NodeRecord[];
  activeCalls: CallRecord[];
}) {
  const map = useMap();
  const [tick, setTick] = useState(0);
  useEffect(() => {
    const h = () => setTick((t: number) => t + 1);
    map.on("zoomend moveend", h);
    return () => { map.off("zoomend moveend", h); };
  }, [map]);

  const activeByNode = useMemo(
    () => Object.fromEntries(activeCalls.map((c) => [c.node_id, c])),
    [activeCalls]
  );
  const nodeById = useMemo(() => new Map(nodes.map((n) => [n.node_id, n])), [nodes]);

  const groups = useMemo(() => {
    const items = nodes.map((n) => ({ id: n.node_id, lat: n.lat, lng: n.lon }));
    return computeGroups(items, map);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [nodes, map, tick]);

  return (
    <>
      {(Array.from(groups.entries()) as Array<[string, { id: string; lat: number; lng: number }[]]>).map(([repId, raw]) => {
        const members = raw.map((r) => nodeById.get(r.id)!).filter(Boolean);
        const rep = nodeById.get(repId);
        if (!rep) return null;
        return (
          <Marker
            key={repId}
            position={[rep.lat, rep.lon]}
            icon={members.length > 1 ? nodeFanIcon(members) : nodeIcon(rep.status)}
          >
            <Popup className="font-mono" minWidth={160}>
              <div className="text-gray-900 space-y-2">
                {members.map((node, idx) => (
                  <div
                    key={node.node_id}
                    className={idx < members.length - 1 ? "border-b border-gray-200 pb-2" : ""}
                  >
                    <p className="font-bold text-sm">{node.name}</p>
                    <p className="text-xs text-gray-500">{node.node_id}</p>
                    <p className="text-xs capitalize">{node.status}</p>
                    {activeByNode[node.node_id] && (
                      <p className="text-xs text-orange-600 mt-0.5">
                        ● TG {activeByNode[node.node_id].talkgroup_id ?? "—"}{" "}
                        {activeByNode[node.node_id].talkgroup_name}
                      </p>
                    )}
                  </div>
                ))}
              </div>
            </Popup>
          </Marker>
        );
      })}
    </>
  );
}

// ── FanIncidentLayer ──────────────────────────────────────────────────────────
function FanIncidentLayer({
  incidents,
  onSelect,
}: {
  incidents: IncidentRecord[];
  onSelect: (inc: IncidentRecord) => void;
}) {
  const map = useMap();
  const [tick, setTick] = useState(0);
  useEffect(() => {
    const h = () => setTick((t: number) => t + 1);
    map.on("zoomend moveend", h);
    return () => { map.off("zoomend moveend", h); };
  }, [map]);

  const plotted = useMemo(
    () =>
      incidents
        .filter((i) => i.location_coords)
        .map((i) => ({
          id: i.incident_id,
          lat: i.location_coords!.lat,
          lng: i.location_coords!.lng,
          inc: i,
        })),
    [incidents]
  );

  const incById = useMemo(
    () => new Map(plotted.map((p: { id: string; lat: number; lng: number; inc: IncidentRecord }) => [p.id, p.inc])),
    [plotted]
  );

  const groups = useMemo(
    () => computeGroups(plotted, map),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [plotted, map, tick]
  );

  return (
    <>
      {(Array.from(groups.entries()) as Array<[string, { id: string; lat: number; lng: number }[]]>).map(([repId, raw]) => {
        const members = raw.map((r) => incById.get(r.id)!).filter(Boolean);
        const repPlot = plotted.find((p: { id: string }) => p.id === repId);
        if (!repPlot) return null;
        return (
          <Marker
            key={repId}
            position={[repPlot.lat, repPlot.lng]}
            icon={members.length > 1 ? incidentFanIcon(members) : incidentIcon(repPlot.inc.type)}
            eventHandlers={{ click: () => onSelect(repPlot.inc) }}
          >
            <Popup className="font-mono" minWidth={180}>
              <div className="text-gray-900 space-y-2">
                {members.map((inc, idx) => (
                  <div
                    key={inc.incident_id}
                    className={idx < members.length - 1 ? "border-b border-gray-200 pb-2" : ""}
                  >
                    <p className="font-bold text-sm">{inc.title ?? "Incident"}</p>
                    <p
                      className="text-xs capitalize"
                      style={{ color: INCIDENT_COLORS[inc.type ?? "other"] ?? INCIDENT_COLORS.other }}
                    >
                      {inc.type ?? "other"}
                    </p>
                    {inc.location && <p className="text-xs text-gray-600">{inc.location}</p>}
                    <a
                      href={`/incidents/${inc.incident_id}`}
                      className="text-xs text-blue-600 hover:underline block mt-0.5"
                    >
                      View incident →
                    </a>
                  </div>
                ))}
              </div>
            </Popup>
          </Marker>
        );
      })}
    </>
  );
}

// ── Helpers ───────────────────────────────────────────────────────────────────
function timeAgo(date: Date): string {
  const s = Math.floor((Date.now() - date.getTime()) / 1000);
  if (s < 60) return `${s}s ago`;
  if (s < 3600) return `${Math.floor(s / 60)}m ago`;
  return `${Math.floor(s / 3600)}h ago`;
}

// ── Main MapView ──────────────────────────────────────────────────────────────
interface Props {
  nodes: NodeRecord[];
  activeCalls: CallRecord[];
  incidents?: IncidentRecord[];
  lastUpdated?: Date | null;
}

export default function MapView({ nodes, activeCalls, incidents = [], lastUpdated }: Props) {
  const [mapInstance, setMapInstance] = useState<L.Map | null>(null);
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [agoClock, setAgoClock] = useState(0);
  const [radarEpoch, setRadarEpoch] = useState(() => Date.now());
  const [clockStr, setClockStr] = useState(() =>
    new Date().toLocaleTimeString([], { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })
  );

  useEffect(() => {
    const id = setInterval(() => setAgoClock((t: number) => t + 1), 10_000);
    return () => clearInterval(id);
  }, []);

  // Radar tiles are static once loaded — force remount every 5 min to refresh
  useEffect(() => {
    const id = setInterval(() => setRadarEpoch(Date.now()), 5 * 60 * 1000);
    return () => clearInterval(id);
  }, []);

  // Live clock for TOC situational awareness
  useEffect(() => {
    const id = setInterval(() =>
      setClockStr(new Date().toLocaleTimeString([], { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })),
      1000
    );
    return () => clearInterval(id);
  }, []);

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const ago = useMemo(() => (lastUpdated ? timeAgo(lastUpdated) : null), [lastUpdated, agoClock]);

  const allPositions = useMemo(
    () => [
      ...nodes.map((n) => [n.lat, n.lon] as [number, number]),
      ...incidents
        .filter((i) => i.location_coords)
        .map((i) => [i.location_coords!.lat, i.location_coords!.lng] as [number, number]),
    ],
    [nodes, incidents]
  );

  const center: [number, number] =
    nodes.length > 0
      ? [nodes[0].lat, nodes[0].lon]
      : allPositions.length > 0
      ? allPositions[0]
      : [39.5, -98.35];

  const zoom = nodes.length > 0 ? 10 : allPositions.length > 0 ? 14 : 4;

  const handleFitAll = useCallback(() => {
    if (!mapInstance || allPositions.length === 0) return;
    if (allPositions.length === 1) {
      mapInstance.setView(allPositions[0], 14);
    } else {
      mapInstance.fitBounds(L.latLngBounds(allPositions), { padding: [40, 40] });
    }
  }, [mapInstance, allPositions]);

  const handleIncidentSelect = useCallback(
    (inc: IncidentRecord) => {
      if (!mapInstance || !inc.location_coords) return;
      mapInstance.flyTo([inc.location_coords.lat, inc.location_coords.lng], 15, { duration: 1.2 });
    },
    [mapInstance]
  );

  const onMapReady = useCallback((m: L.Map) => setMapInstance(m), []);

  return (
    <div className="relative w-full h-full">
      {/* ── Map container ───────────────────────────────────────────────────── */}
      <MapContainer
        center={center}
        zoom={zoom}
        className="w-full h-full rounded-lg"
        style={{ background: "#111827" }}
      >
        <MapRefCapture onReady={onMapReady} />

        <LayersControl position="topright">
          {/* Base layers */}
          <LayersControl.BaseLayer checked name="Dark">
            <TileLayer
              url="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
              attribution='&copy; <a href="https://carto.com/">CARTO</a>'
            />
          </LayersControl.BaseLayer>
          <LayersControl.BaseLayer name="Light">
            <TileLayer
              url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"
              attribution='&copy; <a href="https://carto.com/">CARTO</a>'
            />
          </LayersControl.BaseLayer>
          <LayersControl.BaseLayer name="Streets">
            <TileLayer
              url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
              attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
            />
          </LayersControl.BaseLayer>

          {/* Overlay: Nodes */}
          <LayersControl.Overlay checked name="Nodes">
            <FeatureGroup>
              <FanNodeLayer nodes={nodes} activeCalls={activeCalls} />
            </FeatureGroup>
          </LayersControl.Overlay>

          {/* Overlay: Active Incidents */}
          <LayersControl.Overlay checked name="Active Incidents">
            <FeatureGroup>
              <FanIncidentLayer incidents={incidents} onSelect={handleIncidentSelect} />
            </FeatureGroup>
          </LayersControl.Overlay>

          {/* Overlay: Traffic — Google Maps traffic layer */}
          <LayersControl.Overlay name="Traffic">
            <TileLayer
              url="https://mt{s}.google.com/vt?lyrs=traffic&x={x}&y={y}&z={z}"
              subdomains={["0", "1", "2", "3"]}
              attribution='Traffic &copy; <a href="https://maps.google.com/">Google</a>'
              opacity={0.8}
            />
          </LayersControl.Overlay>

          {/* Overlay: Weather Radar — NEXRAD via Iowa Env Mesonet; key forces remount on refresh */}
          <LayersControl.Overlay name="Weather Radar">
            <TileLayer
              key={radarEpoch}
              url="https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0r-900913/{z}/{x}/{y}.png"
              attribution='Radar &copy; <a href="https://mesonet.agron.iastate.edu/">IEM/NWS</a>'
              opacity={0.65}
            />
          </LayersControl.Overlay>

          {/* Overlay: ADS-B — placeholder for future integration */}
          <LayersControl.Overlay name="ADS-B">
            <FeatureGroup />
          </LayersControl.Overlay>

          {/* Overlay: Meshtastic — placeholder for future integration */}
          <LayersControl.Overlay name="Meshtastic">
            <FeatureGroup />
          </LayersControl.Overlay>
        </LayersControl>
      </MapContainer>

      {/* ── Live timestamp ───────────────────────────────────────────────────── */}
      {ago && (
        <div className="absolute top-3 left-1/2 -translate-x-1/2 z-[1001] pointer-events-none">
          <span className="bg-gray-950/90 border border-gray-700 rounded-full px-3 py-1 text-xs font-mono text-green-400 whitespace-nowrap">
            ● Live · {ago}
          </span>
        </div>
      )}

      {/* ── Auto-fit button ──────────────────────────────────────────────────── */}
      {mapInstance && allPositions.length > 0 && (
        <button
          onClick={handleFitAll}
          title="Fit all markers in view"
          className="absolute bottom-[9.5rem] right-3 z-[1001] w-8 h-8 bg-gray-950/90 border border-gray-700 rounded text-white text-base leading-none hover:bg-gray-800 transition-colors flex items-center justify-center select-none"
        >
          ⤢
        </button>
      )}

      {/* ── Clock — bottom-left for TOC situational awareness ───────────────── */}
      <div className="absolute bottom-8 left-3 z-[1001] bg-gray-950/90 border border-gray-800 rounded-lg px-3 py-2 pointer-events-none">
        <span className="text-white text-sm font-mono tabular-nums">{clockStr}</span>
      </div>

      {/* ── Legend — bottom-right to avoid incident panel on left ────────────── */}
      <div className="absolute bottom-8 right-3 z-[1001] bg-gray-950/90 border border-gray-800 rounded-lg px-3 py-2 text-xs font-mono pointer-events-none space-y-1">
        <div className="flex items-center gap-2"><span className="text-green-400">●</span> Online</div>
        <div className="flex items-center gap-2"><span className="text-orange-400">●</span> Recording</div>
        <div className="flex items-center gap-2"><span className="text-indigo-400">●</span> Unconfigured</div>
        <div className="flex items-center gap-2"><span className="text-gray-500">●</span> Offline</div>
        <div className="border-t border-gray-800 my-0.5" />
        <div className="flex items-center gap-2"><span className="text-red-500">■</span> Fire</div>
        <div className="flex items-center gap-2"><span className="text-blue-500">■</span> Police</div>
        <div className="flex items-center gap-2"><span className="text-yellow-500">■</span> EMS</div>
        <div className="flex items-center gap-2"><span className="text-orange-500">■</span> Accident</div>
      </div>

      {/* ── Incident overlay panel ───────────────────────────────────────────── */}
      {incidents.length > 0 && (
        <>
          {/* Desktop: left sidebar — kept away from the LayersControl (topright) */}
          <div className="absolute top-3 left-3 bottom-[4.5rem] z-[1001] hidden md:flex flex-col w-56 gap-1.5 overflow-y-auto">
            {incidents.map((inc) => {
              const color = INCIDENT_COLORS[inc.type ?? "other"] ?? INCIDENT_COLORS.other;
              const age = inc.started_at ? timeAgo(new Date(inc.started_at)) : null;
              const unitCount = inc.units?.length ?? 0;
              return (
                <button
                  key={inc.incident_id}
                  onClick={() => handleIncidentSelect(inc)}
                  className="w-full text-left bg-gray-950/85 backdrop-blur-sm border rounded-lg px-3 py-2 text-xs font-mono hover:brightness-110 transition-all"
                  style={{ borderColor: color + "55" }}
                >
                  <div className="flex items-center gap-1.5 mb-0.5">
                    <span
                      className="inline-block w-2 h-2 rounded-sm flex-shrink-0"
                      style={{ background: color }}
                    />
                    <span
                      className="uppercase tracking-wide font-semibold text-[10px]"
                      style={{ color }}
                    >
                      {inc.type ?? "other"}
                    </span>
                  </div>
                  <p className="text-white font-semibold leading-snug truncate">
                    {inc.title ?? "Incident"}
                  </p>
                  {inc.location && (
                    <p className="text-gray-500 truncate mt-0.5">{inc.location}</p>
                  )}
                  <div className="flex items-center justify-between mt-0.5">
                    {age && <span className="text-gray-600">{age}</span>}
                    {unitCount > 0 && (
                      <span className="text-gray-600">{unitCount} unit{unitCount !== 1 ? "s" : ""}</span>
                    )}
                  </div>
                  {!inc.location_coords && (
                    <p className="text-gray-700 italic mt-0.5">no coords</p>
                  )}
                </button>
              );
            })}
          </div>

          {/* Mobile: bottom drawer */}
          <div className="absolute bottom-0 left-0 right-0 z-[1001] md:hidden">
            <button
              onClick={() => setDrawerOpen((v: boolean) => !v)}
              className="w-full bg-gray-950/95 border-t border-gray-800 px-4 py-2 text-xs font-mono text-gray-300 flex items-center justify-between"
            >
              <span>Incidents ({incidents.length})</span>
              <span>{drawerOpen ? "▼" : "▲"}</span>
            </button>
            {drawerOpen && (
              <div className="bg-gray-950/95 border-t border-gray-800 max-h-52 overflow-y-auto px-3 py-2 space-y-1.5">
                {incidents.map((inc) => {
                  const color = INCIDENT_COLORS[inc.type ?? "other"] ?? INCIDENT_COLORS.other;
                  return (
                    <button
                      key={inc.incident_id}
                      onClick={() => {
                        setDrawerOpen(false);
                        handleIncidentSelect(inc);
                      }}
                      className="w-full text-left border rounded px-2 py-1.5 text-xs font-mono"
                      style={{ borderColor: color + "55" }}
                    >
                      <span className="font-semibold" style={{ color }}>
                        {inc.type ?? "other"}
                      </span>
                      {" — "}
                      <span className="text-white">{inc.title ?? "Incident"}</span>
                    </button>
                  );
                })}
              </div>
            )}
          </div>
        </>
      )}
    </div>
  );
}
