import { useState, useEffect, useMemo, useRef } from 'react';
import { Popup, useMap } from '@vis.gl/react-maplibre';
import { ScatterplotLayer, PathLayer, TextLayer, GeoJsonLayer } from '@deck.gl/layers';
import { PathStyleExtension } from '@deck.gl/extensions';
import 'maplibre-gl/dist/maplibre-gl.css';
import { BaseMap } from '@common/components/map/BaseMap';
import { DeckGLOverlay } from '@common/components/map/DeckGLOverlay';
import { MapBoundsTracker } from '@common/components/map/MapBoundsTracker';
import { buildVesselLayers, VESSEL_LEGEND, getShipKindLabel } from '@common/components/map/VesselLayer';
import { useVesselSignals } from '@common/hooks/useVesselSignals';
import type { MapBounds, VesselPosition } from '@common/types/vessel';
import { getVesselCacheStatus, type VesselCacheStatus } from '@common/services/vesselApi';
import { IncidentsLeftPanel, type Incident } from './IncidentsLeftPanel';
import { IncidentsRightPanel, type ViewMode, type AnalysisSection } from './IncidentsRightPanel';
import { fetchIncidents } from '../services/incidentsApi';
import type { IncidentCompat } from '../services/incidentsApi';
import { fetchAnalysisTrajectory } from '@tabs/prediction/services/predictionApi';
import type {
  TrajectoryResponse,
  SensitiveResourceFeatureCollection,
} from '@tabs/prediction/services/predictionApi';
import { DischargeZonePanel } from './DischargeZonePanel';
import {
  estimateDistanceFromCoast,
  determineZone,
  getDischargeZoneLines,
  loadTerritorialBaseline,
  getCachedBaseline,
  loadZoneGeoJSON,
  getCachedZones,
} from '../utils/dischargeZoneData';
import { useMapStore } from '@common/store/mapStore';

// ── 민감자원 카테고리별 색상 (목록 순서 인덱스 기반 — 중복 없음) ────────────
const CATEGORY_PALETTE: [number, number, number][] = [
  [239, 68, 68], // red
  [249, 115, 22], // orange
  [234, 179, 8], // yellow
  [132, 204, 22], // lime
  [20, 184, 166], // teal
  [6, 182, 212], // cyan
  [59, 130, 246], // blue
  [99, 102, 241], // indigo
  [168, 85, 247], // purple
  [236, 72, 153], // pink
  [244, 63, 94], // rose
  [16, 185, 129], // emerald
  [14, 165, 233], // sky
  [139, 92, 246], // violet
  [217, 119, 6], // amber
  [45, 212, 191], // turquoise
];

function getCategoryColor(index: number): [number, number, number] {
  return CATEGORY_PALETTE[index % CATEGORY_PALETTE.length];
}

// ── FlyToController: 사고 선택 시 지도 이동 ──────────
function FlyToController({ incident }: { incident: IncidentCompat | null }) {
  const { current: map } = useMap();
  const prevIdRef = useRef<string | null>(null);

  useEffect(() => {
    if (!map || !incident) return;
    if (prevIdRef.current === incident.id) return;
    prevIdRef.current = incident.id;
    map.flyTo({
      center: [incident.location.lon, incident.location.lat],
      zoom: 10,
      duration: 800,
    });
  }, [map, incident]);

  return null;
}

// ── 사고 상태 색상 ──────────────────────────────────────
function getMarkerColor(s: string): [number, number, number, number] {
  if (s === 'active') return [239, 68, 68, 204];
  if (s === 'investigating') return [245, 158, 11, 204];
  return [107, 114, 128, 204];
}

function getMarkerStroke(s: string): [number, number, number, number] {
  if (s === 'active') return [220, 38, 38, 255];
  if (s === 'investigating') return [217, 119, 6, 255];
  return [75, 85, 99, 255];
}

const getStatusLabel = (s: string) =>
  s === 'active' ? '대응중' : s === 'investigating' ? '조사중' : s === 'closed' ? '종료' : '';

// 팝업 정보
interface VesselPopupInfo {
  longitude: number;
  latitude: number;
  vessel: VesselPosition;
}

interface IncidentPopupInfo {
  longitude: number;
  latitude: number;
  incident: IncidentCompat;
}

// 호버 툴팁 정보
interface HoverInfo {
  x: number;
  y: number;
  object: VesselPosition | IncidentCompat;
  type: 'vessel' | 'incident';
}

/* ════════════════════════════════════════════════════
   IncidentsView
   ════════════════════════════════════════════════════ */
export function IncidentsView() {
  const [incidents, setIncidents] = useState<IncidentCompat[]>([]);
  const [filteredIncidents, setFilteredIncidents] = useState<IncidentCompat[]>([]);
  const [selectedIncidentId, setSelectedIncidentId] = useState<string | null>(null);
  const [selectedVessel, setSelectedVessel] = useState<VesselPosition | null>(null);
  const [detailVessel, setDetailVessel] = useState<VesselPosition | null>(null);
  const [vesselPopup, setVesselPopup] = useState<VesselPopupInfo | null>(null);
  const [incidentPopup, setIncidentPopup] = useState<IncidentPopupInfo | null>(null);
  const [hoverInfo, setHoverInfo] = useState<HoverInfo | null>(null);

  const [mapBounds, setMapBounds] = useState<MapBounds | null>(null);
  const [mapZoom, setMapZoom] = useState<number>(10);
  const realVessels = useVesselSignals(mapBounds);

  const [vesselStatus, setVesselStatus] = useState<VesselCacheStatus | null>(null);
  useEffect(() => {
    let cancelled = false;
    const load = async () => {
      try {
        const status = await getVesselCacheStatus();
        if (!cancelled) setVesselStatus(status);
      } catch {
        // 무시 — 다음 폴링에서 재시도
      }
    };
    load();
    const id = setInterval(load, 30_000);
    return () => {
      cancelled = true;
      clearInterval(id);
    };
  }, []);

  const [dischargeMode, setDischargeMode] = useState(false);
  const [leftCollapsed, setLeftCollapsed] = useState(false);
  const [rightCollapsed, setRightCollapsed] = useState(false);
  const [dischargeInfo, setDischargeInfo] = useState<{
    lat: number;
    lon: number;
    distanceNm: number;
    zoneIndex: number;
  } | null>(null);
  const [baselineLoaded, setBaselineLoaded] = useState(
    () => getCachedBaseline() !== null && getCachedZones() !== null,
  );

  // Measure mode (cursor 결정용 — 측정 클릭/레이어는 BaseMap이 처리)
  const measureMode = useMapStore((s) => s.measureMode);

  // Analysis view mode
  const [viewMode, setViewMode] = useState<ViewMode>('overlay');
  const [analysisActive, setAnalysisActive] = useState(false);
  const [analysisTags, setAnalysisTags] = useState<
    { icon: string; label: string; color: string }[]
  >([]);

  // 예측 trajectory & 민감자원 지도 표출
  const [trajectoryEntries, setTrajectoryEntries] = useState<
    Record<string, { data: TrajectoryResponse; occurredAt: string }>
  >({});
  const [sensitiveGeojson, setSensitiveGeojson] =
    useState<SensitiveResourceFeatureCollection | null>(null);
  const [sensCheckedCategories, setSensCheckedCategories] = useState<Set<string>>(new Set());
  const [sensColorMap, setSensColorMap] = useState<Map<string, [number, number, number]>>(
    new Map(),
  );

  useEffect(() => {
    fetchIncidents().then((data) => {
      setIncidents(data);
    });
    Promise.all([loadTerritorialBaseline(), loadZoneGeoJSON()]).then(() => setBaselineLoaded(true));
  }, []);

  // 사고 전환 시 지도 레이어 즉시 초기화
  useEffect(() => {
    setTrajectoryEntries({});
    setSensitiveGeojson(null);
    setSensCheckedCategories(new Set());
    setSensColorMap(new Map());
  }, [selectedIncidentId]);

  const selectedIncident = incidents.find((i) => i.id === selectedIncidentId) ?? null;

  const handleRunAnalysis = (sections: AnalysisSection[], sensitiveCount: number) => {
    if (sections.length === 0) return;
    const tags: { icon: string; label: string; color: string }[] = [];
    sections.forEach((s) => {
      if (s.key === 'oil')
        tags.push({ icon: '🛢', label: '유출유', color: 'var(--color-warning)' });
      if (s.key === 'hns') tags.push({ icon: '🧪', label: 'HNS', color: 'var(--color-tertiary)' });
      if (s.key === 'rsc') tags.push({ icon: '🚨', label: '구난', color: 'var(--color-accent)' });
    });
    if (sensitiveCount > 0)
      tags.push({
        icon: '🐟',
        label: `민감자원 ${sensitiveCount}건`,
        color: 'var(--color-success)',
      });
    setAnalysisTags(tags);
    setAnalysisActive(true);
  };

  const handleCloseAnalysis = () => {
    setAnalysisActive(false);
    setAnalysisTags([]);
  };

  const handleCheckedPredsChange = async (
    checked: Array<{ id: string; acdntSn: number; predRunSn: number | null; occurredAt: string }>,
  ) => {
    const newEntries: Record<string, { data: TrajectoryResponse; occurredAt: string }> = {};
    await Promise.all(
      checked.map(async ({ id, acdntSn, predRunSn, occurredAt }) => {
        const existing = trajectoryEntries[id];
        if (existing) {
          newEntries[id] = existing;
          return;
        }
        try {
          const data = await fetchAnalysisTrajectory(acdntSn, predRunSn ?? undefined);
          newEntries[id] = { data, occurredAt };
        } catch {
          /* 조용히 실패 */
        }
      }),
    );
    setTrajectoryEntries(newEntries);
  };

  const handleSensitiveDataChange = (
    geojson: SensitiveResourceFeatureCollection | null,
    checkedCategories: Set<string>,
    categoryOrder: string[],
  ) => {
    setSensitiveGeojson(geojson);
    setSensCheckedCategories(checkedCategories);
    const colorMap = new Map<string, [number, number, number]>();
    categoryOrder.forEach((cat, i) => colorMap.set(cat, getCategoryColor(i)));
    setSensColorMap(colorMap);
  };

  // ── 사고 마커 (ScatterplotLayer) ──────────────────────
  const incidentLayer = useMemo(
    () =>
      new ScatterplotLayer({
        id: 'incidents',
        data: filteredIncidents,
        getPosition: (d: IncidentCompat) => [d.location.lon, d.location.lat],
        getRadius: (d: IncidentCompat) => (selectedIncidentId === d.id ? 16 : 12),
        getFillColor: (d: IncidentCompat) => getMarkerColor(d.status),
        getLineColor: (d: IncidentCompat) =>
          selectedIncidentId === d.id ? [6, 182, 212, 255] : getMarkerStroke(d.status),
        getLineWidth: (d: IncidentCompat) => (selectedIncidentId === d.id ? 3 : 2),
        stroked: true,
        radiusMinPixels: 6,
        radiusMaxPixels: 20,
        radiusUnits: 'pixels',
        pickable: true,
        onClick: (info: { object?: IncidentCompat; coordinate?: number[] }) => {
          if (info.object && info.coordinate) {
            const newId = selectedIncidentId === info.object.id ? null : info.object.id;
            setSelectedIncidentId(newId);
            if (newId) {
              setIncidentPopup({
                longitude: info.coordinate[0],
                latitude: info.coordinate[1],
                incident: info.object,
              });
            } else {
              setIncidentPopup(null);
            }
            setVesselPopup(null);
          }
        },
        onHover: (info: { object?: IncidentCompat; x?: number; y?: number }) => {
          if (info.object && info.x !== undefined && info.y !== undefined) {
            setHoverInfo({ x: info.x, y: info.y, object: info.object, type: 'incident' });
          } else {
            setHoverInfo((h) => (h?.type === 'incident' ? null : h));
          }
        },
        updateTriggers: {
          getRadius: [selectedIncidentId],
          getLineColor: [selectedIncidentId],
          getLineWidth: [selectedIncidentId],
        },
      }),
    [filteredIncidents, selectedIncidentId],
  );


  // ── 배출 구역 경계선 레이어 ──
  const dischargeZoneLayers = useMemo(() => {
    if (!dischargeMode || !baselineLoaded) return [];
    const zoneLines = getDischargeZoneLines();
    return zoneLines.map(
      (line, i) =>
        new PathLayer({
          id: `discharge-zone-${i}`,
          data: [line],
          getPath: (d: typeof line) => d.path,
          getColor: (d: typeof line) => d.color,
          getWidth: 2,
          widthUnits: 'pixels',
          getDashArray: [6, 3],
          dashJustified: true,
          extensions: [new PathStyleExtension({ dash: true })],
          pickable: false,
        }),
    );
  }, [dischargeMode, baselineLoaded]);

  // ── 예측 결과 레이어 (입자 클라우드, 중심점 경로, 시간 라벨, 해안 부착 입자) ──────
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const trajectoryLayers: any[] = useMemo(() => {
    const layers: unknown[] = [];

    // 모델별 색상 (prediction 탭과 동일)
    const MODEL_COLORS: Record<string, [number, number, number]> = {
      KOSPS: [6, 182, 212], // cyan
      POSEIDON: [239, 68, 68], // red
      OpenDrift: [59, 130, 246], // blue
      default: [249, 115, 22], // orange
    };

    const pad = (n: number) => String(n).padStart(2, '0');

    let runIdx = 0;
    for (const [runId, entry] of Object.entries(trajectoryEntries)) {
      const { data: traj, occurredAt } = entry;
      const { trajectory, centerPoints } = traj;
      const startDt = new Date(occurredAt);
      runIdx++;

      if (trajectory && trajectory.length > 0) {
        const maxTime = Math.max(...trajectory.map((p) => p.time));

        // 최종 스텝 부유 입자: 모델별로 그룹핑하여 각각 다른 색
        const lastStepByModel: Record<string, typeof trajectory> = {};
        trajectory.forEach((p) => {
          if (p.time === maxTime && p.stranded !== 1) {
            const m = p.model ?? 'default';
            if (!lastStepByModel[m]) lastStepByModel[m] = [];
            lastStepByModel[m].push(p);
          }
        });
        Object.entries(lastStepByModel).forEach(([model, particles]) => {
          const color = MODEL_COLORS[model] ?? MODEL_COLORS['default'];
          layers.push(
            new ScatterplotLayer({
              id: `traj-particles-${runId}-${model}`,
              data: particles,
              getPosition: (d: { lon: number; lat: number }) => [d.lon, d.lat],
              getFillColor: [...color, 180] as [number, number, number, number],
              getRadius: 3,
              radiusMinPixels: 2,
              radiusMaxPixels: 5,
            }),
          );
        });

        // 해안 부착 입자: 모델별 색상 + 테두리 강조
        const beachedByModel: Record<string, typeof trajectory> = {};
        trajectory.forEach((p) => {
          if (p.stranded === 1) {
            const m = p.model ?? 'default';
            if (!beachedByModel[m]) beachedByModel[m] = [];
            beachedByModel[m].push(p);
          }
        });
        Object.entries(beachedByModel).forEach(([model, particles]) => {
          const color = MODEL_COLORS[model] ?? MODEL_COLORS['default'];
          layers.push(
            new ScatterplotLayer({
              id: `traj-beached-${runId}-${model}`,
              data: particles,
              getPosition: (d: { lon: number; lat: number }) => [d.lon, d.lat],
              getFillColor: [...color, 220] as [number, number, number, number],
              getRadius: 4,
              radiusMinPixels: 3,
              radiusMaxPixels: 6,
              stroked: true,
              getLineColor: [255, 255, 255, 160] as [number, number, number, number],
              getLineWidth: 1,
              lineWidthMinPixels: 1,
            }),
          );
        });
      }

      // 중심점 경로선 (모델별 그룹)
      if (centerPoints && centerPoints.length >= 2) {
        const byModel: Record<string, typeof centerPoints> = {};
        centerPoints.forEach((cp) => {
          const m = cp.model ?? 'default';
          if (!byModel[m]) byModel[m] = [];
          byModel[m].push(cp);
        });
        Object.entries(byModel).forEach(([model, pts]) => {
          const color = MODEL_COLORS[model] ?? MODEL_COLORS['default'];
          const sorted = [...pts].sort((a, b) => a.time - b.time);
          const pathId = `${runIdx}-${model}`;
          layers.push(
            new PathLayer({
              id: `traj-path-${pathId}`,
              data: [{ path: sorted.map((p) => [p.lon, p.lat]) }],
              getPath: (d: { path: number[][] }) => d.path,
              getColor: [...color, 230] as [number, number, number, number],
              getWidth: 2,
              widthMinPixels: 2,
              widthMaxPixels: 4,
            }),
          );
          layers.push(
            new ScatterplotLayer({
              id: `traj-centers-${pathId}`,
              data: sorted,
              getPosition: (d: { lon: number; lat: number }) => [d.lon, d.lat],
              getFillColor: [...color, 230] as [number, number, number, number],
              getRadius: 5,
              radiusMinPixels: 4,
              radiusMaxPixels: 8,
            }),
          );
          layers.push(
            new TextLayer({
              id: `traj-labels-${pathId}`,
              data: sorted,
              getPosition: (d: { lon: number; lat: number }) => [d.lon, d.lat],
              getText: (d: { time: number }) => {
                const dt = new Date(startDt.getTime() + d.time * 3600 * 1000);
                return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
              },
              getSize: 11,
              getColor: [...color, 240] as [number, number, number, number],
              getPixelOffset: [0, -14] as [number, number],
              outlineWidth: 2,
              outlineColor: [0, 0, 0, 180] as [number, number, number, number],
              fontSettings: { sdf: true },
              billboard: true,
            }),
          );
        });
      }
    }
    return layers;
  }, [trajectoryEntries]);

  // ── 민감자원 GeoJSON 레이어 ──────────────────────────
  const sensLayer = useMemo(() => {
    if (!sensitiveGeojson || sensCheckedCategories.size === 0) return null;
    const filtered = {
      ...sensitiveGeojson,
      features: sensitiveGeojson.features.filter((f) =>
        sensCheckedCategories.has(
          ((f.properties as Record<string, unknown>)?.['category'] as string) ?? '',
        ),
      ),
    };
    if (filtered.features.length === 0) return null;
    return new GeoJsonLayer({
      id: 'incidents-sensitive-geojson',
      data: filtered,
      pickable: false,
      stroked: true,
      filled: true,
      pointRadiusMinPixels: 8,
      lineWidthMinPixels: 1,
      getFillColor: (f: { properties: Record<string, unknown> }) => {
        const color = sensColorMap.get((f.properties['category'] as string) ?? '') ?? [
          128, 128, 128,
        ];
        return [...color, 60] as [number, number, number, number];
      },
      getLineColor: (f: { properties: Record<string, unknown> }) => {
        const color = sensColorMap.get((f.properties['category'] as string) ?? '') ?? [
          128, 128, 128,
        ];
        return [...color, 180] as [number, number, number, number];
      },
      getLineWidth: 1,
      updateTriggers: {
        getFillColor: [sensColorMap],
        getLineColor: [sensColorMap],
      },
    });
  }, [sensitiveGeojson, sensCheckedCategories, sensColorMap]);

  const realVesselLayers = useMemo(
    () =>
      buildVesselLayers(
        realVessels,
        {
          onClick: (vessel, coordinate) => {
            setSelectedVessel(vessel);
            setVesselPopup({
              longitude: coordinate[0],
              latitude: coordinate[1],
              vessel,
            });
            setIncidentPopup(null);
            setDetailVessel(null);
          },
          onHover: (vessel, x, y) => {
            if (vessel) {
              setHoverInfo({ x, y, object: vessel, type: 'vessel' });
            } else {
              setHoverInfo((h) => (h?.type === 'vessel' ? null : h));
            }
          },
        },
        mapZoom,
      ),
    [realVessels, mapZoom],
  );

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const deckLayers: any[] = useMemo(
    () => [
      incidentLayer,
      ...realVesselLayers,
      ...dischargeZoneLayers,
      ...trajectoryLayers,
      ...(sensLayer ? [sensLayer] : []),
    ],
    [incidentLayer, realVesselLayers, dischargeZoneLayers, trajectoryLayers, sensLayer],
  );

  return (
    <div className="flex flex-1 overflow-hidden">
      {/* Left Panel */}
      <div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 360 }}>
        <IncidentsLeftPanel
          incidents={incidents}
          selectedIncidentId={selectedIncidentId}
          onIncidentSelect={setSelectedIncidentId}
          onFilteredChange={setFilteredIncidents}
        />
      </div>

      {/* Center - Map + Analysis Views */}
      <div className="flex-1 flex flex-col overflow-hidden">
        {/* Analysis Bar */}
        {analysisActive && (
          <div
            className="shrink-0 flex items-center justify-between border-b border-stroke"
            style={{
              height: 36,
              padding: '0 16px',
              background:
                'linear-gradient(90deg, color-mix(in srgb, var(--color-accent) 6%, transparent), var(--bg-surface))',
            }}
          >
            <div className="flex items-center gap-2">
              <span className="text-caption font-bold">🔬 통합 분석 비교</span>
              <span className="text-caption text-fg-disabled">{selectedIncident?.name}</span>
              <div className="flex gap-1">
                {analysisTags.map((t, i) => (
                  <span
                    key={i}
                    className="text-caption font-semibold rounded-md"
                    style={{
                      padding: '2px 8px',
                      background: `color-mix(in srgb, ${t.color} 18%, transparent)`,
                      border: `1px solid color-mix(in srgb, ${t.color} 40%, transparent)`,
                      color: t.color,
                    }}
                  >
                    {t.icon} {t.label}
                  </span>
                ))}
              </div>
            </div>
            <div className="flex gap-1 ml-auto">
              {(
                [
                  { mode: 'overlay' as ViewMode, icon: '🗂', label: '오버레이' },
                  { mode: 'split2' as ViewMode, icon: '◫', label: '2분할' },
                  { mode: 'split3' as ViewMode, icon: '⊞', label: '3분할' },
                ] as const
              ).map((v) => (
                <button
                  key={v.mode}
                  onClick={() => setViewMode(v.mode)}
                  className="text-caption font-semibold cursor-pointer rounded-sm"
                  style={{
                    padding: '3px 10px',
                    background:
                      viewMode === v.mode
                        ? 'color-mix(in srgb, var(--color-accent) 12%, transparent)'
                        : 'var(--bg-card)',
                    border:
                      viewMode === v.mode
                        ? '1px solid color-mix(in srgb, var(--color-accent) 30%, transparent)'
                        : '1px solid var(--stroke-default)',
                    color: viewMode === v.mode ? 'var(--color-accent)' : 'var(--fg-disabled)',
                  }}
                >
                  {v.icon} {v.label}
                </button>
              ))}
              <button
                onClick={handleCloseAnalysis}
                className="text-caption font-semibold cursor-pointer rounded-sm"
                style={{
                  padding: '3px 8px',
                  background: 'color-mix(in srgb, var(--color-danger) 6%, transparent)',
                  border: '1px solid color-mix(in srgb, var(--color-danger) 20%, transparent)',
                  color: 'var(--color-danger)',
                }}
              >
                ✕ 닫기
              </button>
            </div>
          </div>
        )}

        {/* Map / Analysis Content Area */}
        <div className="flex-1 relative overflow-hidden">
          {/* Left panel toggle button */}
          <button
            onClick={() => setLeftCollapsed((v) => !v)}
            className="absolute z-[50] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
            style={{
              left: 0,
              width: 18,
              height: 40,
              background: 'var(--bg-elevated)',
              border: '1px solid var(--stroke-default)',
              borderLeft: 'none',
              borderRadius: '0 6px 6px 0',
              color: 'var(--fg-sub)',
              cursor: 'pointer',
            }}
          >
            {leftCollapsed ? '▶' : '◀'}
          </button>

          {/* Right panel toggle button */}
          <button
            onClick={() => setRightCollapsed((v) => !v)}
            className="absolute z-[50] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
            style={{
              right: 0,
              width: 18,
              height: 40,
              background: 'var(--bg-elevated)',
              border: '1px solid var(--stroke-default)',
              borderRight: 'none',
              borderRadius: '6px 0 0 6px',
              color: 'var(--fg-sub)',
              cursor: 'pointer',
            }}
          >
            {rightCollapsed ? '◀' : '▶'}
          </button>

          {/* Default Map (visible when not in analysis or in overlay mode) */}
          {(!analysisActive || viewMode === 'overlay') && (
            <div className="absolute inset-0">
              <BaseMap
                center={[35.0, 127.8]}
                zoom={7}
                cursor={measureMode !== null || dischargeMode ? 'crosshair' : undefined}
                onMapClick={(lon, lat) => {
                  if (dischargeMode) {
                    const distanceNm = estimateDistanceFromCoast(lat, lon);
                    const zoneIndex = determineZone(lat, lon);
                    setDischargeInfo({ lat, lon, distanceNm, zoneIndex });
                  }
                }}
              >
                <DeckGLOverlay layers={deckLayers} />
                <MapBoundsTracker onBoundsChange={setMapBounds} onZoomChange={setMapZoom} />
                <FlyToController incident={selectedIncident} />

                {/* 사고 팝업 */}
                {incidentPopup && (
                  <Popup
                    longitude={incidentPopup.longitude}
                    latitude={incidentPopup.latitude}
                    anchor="bottom"
                    onClose={() => setIncidentPopup(null)}
                    closeButton={false}
                    closeOnClick={false}
                    className="incident-popup"
                    maxWidth="none"
                  >
                    <IncidentPopupContent
                      incident={incidentPopup.incident}
                      onClose={() => setIncidentPopup(null)}
                    />
                  </Popup>
                )}
              </BaseMap>

              {/* 호버 툴팁 */}
              {hoverInfo && (
                <div
                  className="absolute z-[1000] pointer-events-none rounded-md"
                  style={{
                    left: hoverInfo.x + 12,
                    top: hoverInfo.y - 12,
                    background: 'var(--bg-elevated)',
                    border: '1px solid var(--stroke-default)',
                    padding: '8px 12px',
                    boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
                    minWidth: 150,
                  }}
                >
                  {hoverInfo.type === 'vessel' ? (
                    <VesselTooltipContent vessel={hoverInfo.object as VesselPosition} />
                  ) : (
                    <IncidentTooltipContent incident={hoverInfo.object as IncidentCompat} />
                  )}
                </div>
              )}

              {/* 분석 오버레이 (지도 위 시각효과) */}
              {analysisActive && viewMode === 'overlay' && (
                <div className="absolute inset-0 z-[500] pointer-events-none">
                  {analysisTags.some((t) => t.label === '유출유') && (
                    <div
                      style={{
                        position: 'absolute',
                        top: '30%',
                        left: '45%',
                        width: 180,
                        height: 120,
                        background:
                          'radial-gradient(ellipse, color-mix(in srgb, var(--color-warning) 35%, transparent) 0%, color-mix(in srgb, var(--color-warning) 10%, transparent) 50%, transparent 70%)',
                        borderRadius: '50%',
                        transform: 'rotate(-15deg)',
                      }}
                    />
                  )}
                  {analysisTags.some((t) => t.label === 'HNS') && (
                    <div
                      style={{
                        position: 'absolute',
                        top: '25%',
                        left: '50%',
                        width: 150,
                        height: 100,
                        background:
                          'radial-gradient(ellipse, color-mix(in srgb, var(--color-tertiary) 30%, transparent) 0%, color-mix(in srgb, var(--color-tertiary) 8%, transparent) 50%, transparent 70%)',
                        borderRadius: '50%',
                        transform: 'rotate(20deg)',
                      }}
                    />
                  )}
                  {analysisTags.some((t) => t.label === '구난') && (
                    <div
                      style={{
                        position: 'absolute',
                        top: '35%',
                        left: '42%',
                        width: 200,
                        height: 200,
                        border:
                          '2px dashed color-mix(in srgb, var(--color-accent) 40%, transparent)',
                        borderRadius: '50%',
                      }}
                    />
                  )}
                </div>
              )}

              {/* 오염물 배출 규정 토글 */}
              <button
                onClick={() => {
                  setDischargeMode(!dischargeMode);
                  if (dischargeMode) setDischargeInfo(null);
                }}
                className="absolute z-[500] cursor-pointer rounded-md text-caption font-bold font-korean"
                style={{
                  top: 10,
                  right: 180,
                  padding: '6px 10px',
                  background: 'var(--bg-base)',
                  border: '1px solid var(--stroke-default)',
                  color: dischargeMode ? 'var(--color-accent)' : 'var(--fg-disabled)',
                  backdropFilter: 'blur(8px)',
                  transition: 'color 0.2s',
                }}
              >
                배출규정 {dischargeMode ? 'ON' : 'OFF'}
              </button>

              {/* 오염물 배출 규정 패널 */}
              {dischargeMode && dischargeInfo && (
                <DischargeZonePanel
                  lat={dischargeInfo.lat}
                  lon={dischargeInfo.lon}
                  distanceNm={dischargeInfo.distanceNm}
                  zoneIndex={dischargeInfo.zoneIndex}
                  onClose={() => setDischargeInfo(null)}
                />
              )}

              {/* 배출규정 모드 안내 */}
              {dischargeMode && !dischargeInfo && (
                <div
                  className="absolute z-[500] rounded-md text-label-2 font-korean font-semibold"
                  style={{
                    top: '50%',
                    left: '50%',
                    transform: 'translate(-50%, -50%)',
                    padding: '12px 20px',
                    background: 'var(--bg-base)',
                    border: '1px solid color-mix(in srgb, var(--color-accent) 30%, transparent)',
                    color: 'var(--color-accent)',
                    backdropFilter: 'blur(8px)',
                    pointerEvents: 'none',
                  }}
                >
                  📍 지도를 클릭하여 배출 규정을 확인하세요
                </div>
              )}

              {/* AIS Live Badge */}
              <div
                className="absolute top-[10px] right-[10px] z-[500] rounded-md"
                style={{
                  background: 'var(--bg-base)',
                  border: '1px solid var(--stroke-default)',
                  padding: '8px 12px',
                  backdropFilter: 'blur(8px)',
                }}
              >
                <div className="flex items-center gap-1.5 mb-[5px]">
                  {/* <div
                    style={{
                      width: 6,
                      height: 6,
                      borderRadius: '50%',
                      background: 'var(--color-success)',
                      animation: 'pd 1.5s infinite',
                    }}
                  /> */}
                  <span className="text-caption">AIS Live</span>
                </div>
                <div className="flex gap-2.5 text-caption font-mono">
                  <div className="text-fg-sub">선박 {vesselStatus?.count ?? 0}</div>
                  <div className="text-fg-sub">사고 {filteredIncidents.length}</div>
                  <div className="text-fg-sub">방제선 {vesselStatus?.bangjeCount ?? 0}</div>
                </div>
              </div>

              {/* Legend */}
              <div
                className="absolute bottom-[10px] left-[10px] z-[500] rounded-md flex flex-col gap-1.5"
                style={{
                  background: 'var(--bg-base)',
                  border: '1px solid var(--stroke-default)',
                  padding: '8px 12px',
                  backdropFilter: 'blur(8px)',
                }}
              >
                <div className="text-caption font-bold text-fg-sub">사고 상태</div>
                <div className="grid grid-cols-3 gap-1.5">
                  {[
                    { c: 'var(--color-danger)', l: '대응중' },
                    { c: 'var(--color-warning)', l: '조사중' },
                    { c: 'var(--fg-disabled)', l: '종료' },
                  ].map((s) => (
                    <div key={s.l} className="flex flex-col items-center gap-0.5">
                      <div style={{ width: 8, height: 8, borderRadius: '50%', background: s.c }} />
                      <span className="text-caption text-fg-disabled">{s.l}</span>
                    </div>
                  ))}
                </div>
                <div className="text-caption font-bold text-fg-sub mt-0.5">AIS 선박</div>
                <div className="grid grid-cols-3 gap-1.5">
                  {VESSEL_LEGEND.map((vl) => (
                    <div key={vl.type} className="flex flex-col items-center gap-0.5">
                      <div
                        style={{
                          width: 0,
                          height: 0,
                          borderLeft: '3px solid transparent',
                          borderRight: '3px solid transparent',
                          borderBottom: `7px solid ${vl.color}`,
                        }}
                      />
                      <span className="text-caption text-fg-disabled text-center">{vl.type}</span>
                    </div>
                  ))}
                </div>
              </div>

              {/* 선박 팝업 패널 */}
              {vesselPopup && selectedVessel && !detailVessel && (
                <VesselPopupPanel
                  vessel={selectedVessel}
                  onClose={() => {
                    setVesselPopup(null);
                    setSelectedVessel(null);
                  }}
                  onDetail={() => {
                    setDetailVessel(selectedVessel);
                    setVesselPopup(null);
                    setSelectedVessel(null);
                  }}
                />
              )}
              {detailVessel && (
                <VesselDetailModal vessel={detailVessel} onClose={() => setDetailVessel(null)} />
              )}
            </div>
          )}

          {/* ── 2분할 View ─────────────────────────────── */}
          {analysisActive && viewMode === 'split2' && (
            <div className="flex h-full">
              <div
                className="flex-1 flex flex-col overflow-hidden"
                style={{ borderRight: '2px solid var(--color-accent)' }}
              >
                <div
                  className="flex items-center shrink-0 bg-bg-surface border-b border-stroke"
                  style={{ height: 28, padding: '0 10px' }}
                >
                  <span className="text-caption font-bold text-color-accent">
                    {analysisTags[0]
                      ? `${analysisTags[0].icon} ${analysisTags[0].label}`
                      : '— 분석 결과를 선택하세요 —'}
                  </span>
                </div>
                <div
                  className="flex-1 overflow-y-auto bg-bg-base flex flex-col gap-2"
                  style={{ padding: 12 }}
                >
                  <SplitPanelContent tag={analysisTags[0]} incident={selectedIncident} />
                </div>
              </div>
              <div className="flex-1 flex flex-col overflow-hidden">
                <div
                  className="flex items-center shrink-0 bg-bg-surface border-b border-stroke"
                  style={{ height: 28, padding: '0 10px' }}
                >
                  <span className="text-caption font-bold text-color-accent">
                    {analysisTags[1]
                      ? `${analysisTags[1].icon} ${analysisTags[1].label}`
                      : '— 분석 결과를 선택하세요 —'}
                  </span>
                </div>
                <div
                  className="flex-1 overflow-y-auto bg-bg-base flex flex-col gap-2"
                  style={{ padding: 12 }}
                >
                  <SplitPanelContent tag={analysisTags[1]} incident={selectedIncident} />
                </div>
              </div>
            </div>
          )}

          {/* ── 3분할 View ─────────────────────────────── */}
          {analysisActive && viewMode === 'split3' && (
            <div className="flex h-full">
              <div className="flex-1 flex flex-col overflow-hidden border-r border-stroke">
                <div
                  className="flex items-center shrink-0 border-b border-stroke"
                  style={{
                    height: 28,
                    background:
                      'linear-gradient(90deg, color-mix(in srgb, var(--color-warning) 8%, transparent), var(--bg-surface))',
                    padding: '0 10px',
                  }}
                >
                  <span
                    className="text-caption font-bold"
                    style={{ color: 'var(--color-warning)' }}
                  >
                    🛢 유출유 확산예측
                  </span>
                </div>
                <div
                  className="flex-1 overflow-y-auto bg-bg-base flex flex-col gap-1.5"
                  style={{ padding: 10 }}
                >
                  <SplitPanelContent
                    tag={{ icon: '🛢', label: '유출유', color: 'var(--color-warning)' }}
                    incident={selectedIncident}
                  />
                </div>
              </div>
              <div className="flex-1 flex flex-col overflow-hidden border-r border-stroke">
                <div
                  className="flex items-center shrink-0 border-b border-stroke"
                  style={{
                    height: 28,
                    background:
                      'linear-gradient(90deg, color-mix(in srgb, var(--color-tertiary) 8%, transparent), var(--bg-surface))',
                    padding: '0 10px',
                  }}
                >
                  <span
                    className="text-caption font-bold"
                    style={{ color: 'var(--color-tertiary)' }}
                  >
                    🧪 HNS 대기확산
                  </span>
                </div>
                <div
                  className="flex-1 overflow-y-auto bg-bg-base flex flex-col gap-1.5"
                  style={{ padding: 10 }}
                >
                  <SplitPanelContent
                    tag={{ icon: '🧪', label: 'HNS', color: 'var(--color-tertiary)' }}
                    incident={selectedIncident}
                  />
                </div>
              </div>
              <div className="flex-1 flex flex-col overflow-hidden">
                <div
                  className="flex items-center shrink-0 border-b border-stroke"
                  style={{
                    height: 28,
                    background:
                      'linear-gradient(90deg, color-mix(in srgb, var(--color-accent) 8%, transparent), var(--bg-surface))',
                    padding: '0 10px',
                  }}
                >
                  <span className="text-caption font-bold text-color-accent">🚨 긴급구난</span>
                </div>
                <div
                  className="flex-1 overflow-y-auto bg-bg-base flex flex-col gap-1.5"
                  style={{ padding: 10 }}
                >
                  <SplitPanelContent
                    tag={{ icon: '🚨', label: '구난', color: 'var(--color-accent)' }}
                    incident={selectedIncident}
                  />
                </div>
              </div>
            </div>
          )}
        </div>

        {/* Decision Bar */}
        {analysisActive && (
          <div
            className="shrink-0 flex items-center justify-between bg-bg-surface border-t border-stroke"
            style={{ padding: '6px 16px' }}
          >
            <div className="text-caption text-fg-disabled">
              📊 {selectedIncident?.name} · {analysisTags.map((t) => t.label).join(' + ')} 분석 결과
              비교
            </div>
            <div className="flex gap-[6px]">
              <button
                className="cursor-pointer rounded text-caption font-semibold"
                style={{
                  padding: '4px 12px',
                  background: 'color-mix(in srgb, var(--color-info) 10%, transparent)',
                  border: '1px solid color-mix(in srgb, var(--color-info) 20%, transparent)',
                  color: 'var(--color-info)',
                }}
              >
                📋 보고서 생성
              </button>
              <button
                className="cursor-pointer rounded text-caption font-semibold"
                style={{
                  padding: '4px 12px',
                  background: 'color-mix(in srgb, var(--color-tertiary) 10%, transparent)',
                  border: '1px solid color-mix(in srgb, var(--color-tertiary) 20%, transparent)',
                  color: 'var(--color-tertiary)',
                }}
              >
                🔗 R&D 연계
              </button>
            </div>
          </div>
        )}
      </div>

      {/* Right Panel */}
      <div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 280 }}>
        <IncidentsRightPanel
          incident={selectedIncident}
          viewMode={viewMode}
          onViewModeChange={setViewMode}
          onRunAnalysis={handleRunAnalysis}
          analysisActive={analysisActive}
          onCloseAnalysis={handleCloseAnalysis}
          onCheckedPredsChange={handleCheckedPredsChange}
          onSensitiveDataChange={handleSensitiveDataChange}
          selectedVessel={
            selectedVessel
              ? {
                  lat: selectedVessel.lat,
                  lng: selectedVessel.lon,
                  name: selectedVessel.shipNm,
                }
              : null
          }
        />
      </div>
    </div>
  );
}

/* ════════════════════════════════════════════════════
   SplitPanelContent
   ════════════════════════════════════════════════════ */
function SplitPanelContent({
  tag,
  incident,
}: {
  tag?: { icon: string; label: string; color: string };
  incident: Incident | null;
}) {
  if (!tag) {
    return (
      <div className="flex-1 flex items-center justify-center text-fg-disabled text-label-2">
        R&D 분석 결과를 선택하세요
      </div>
    );
  }

  const mockData: Record<
    string,
    {
      title: string;
      model: string;
      items: { label: string; value: string; color?: string }[];
      summary: string;
    }
  > = {
    유출유: {
      title: '유출유 확산예측 결과',
      model: 'KOSPS + OpenDrift · BUNKER-C 150kL',
      items: [
        { label: '예측 시간', value: '72시간 (3일)' },
        { label: '최대 확산거리', value: '12.3 NM', color: 'var(--color-warning)' },
        { label: '해안 도달 시간', value: '18시간 후', color: 'var(--color-danger)' },
        { label: '영향 해안선', value: '27.5 km' },
        { label: '풍화율', value: '32.4%' },
        { label: '잔존유량', value: '101.4 kL', color: 'var(--color-warning)' },
      ],
      summary:
        '여수항 남동쪽 방향 확산, 18시간 후 돌산도 해안 도달 예상. 조류 영향으로 남서쪽 이동.',
    },
    HNS: {
      title: 'HNS 대기확산 결과',
      model: 'ALOHA + PHAST · 톨루엔 5톤',
      items: [
        { label: 'IDLH 범위', value: '1.2 km', color: 'var(--color-danger)' },
        { label: 'ERPG-2 범위', value: '2.8 km', color: 'var(--color-warning)' },
        { label: 'ERPG-1 범위', value: '5.1 km', color: 'var(--color-caution)' },
        { label: '풍향', value: 'SW → NE 방향' },
        { label: '대기 안정도', value: 'D등급 (중립)' },
        { label: '영향 인구', value: '약 2,400명', color: 'var(--color-danger)' },
      ],
      summary: '남서풍에 의해 북동쪽 내륙 방향 확산. IDLH 1.2km 이내 즉시 대피 필요.',
    },
    구난: {
      title: '긴급구난 SAR 결과',
      model: 'SAROPS · Monte Carlo 10,000회 시뮬레이션',
      items: [
        { label: '95% 확률 범위', value: '8.5 NM²', color: 'var(--color-accent)' },
        { label: '최적 탐색 경로', value: 'Sector Search' },
        { label: '예상 표류 속도', value: '1.8 kn' },
        { label: '표류 방향', value: 'NE (045°)' },
        { label: '생존 가능 시간', value: '36시간', color: 'var(--color-danger)' },
        { label: '필요 자산', value: '헬기 2 + 경비정 3', color: 'var(--color-warning)' },
      ],
      summary: '북동쪽 방향 표류 예상. 06:00 기준 최적 탐색 패턴: 섹터탐색(Sector Search).',
    },
  };

  const data = mockData[tag.label] || mockData['유출유'];

  return (
    <>
      <div
        className="rounded-sm"
        style={{
          padding: '10px 12px',
          background: `color-mix(in srgb, ${tag.color} 8%, transparent)`,
          border: `1px solid color-mix(in srgb, ${tag.color} 20%, transparent)`,
        }}
      >
        <div className="text-label-2 font-bold" style={{ color: tag.color, marginBottom: 4 }}>
          {tag.icon} {data.title}
        </div>
        <div className="text-caption text-fg-disabled font-mono">{data.model}</div>
        {incident && (
          <div className="text-caption text-fg-disabled" style={{ marginTop: 2 }}>
            사고: {incident.name} · {incident.date} {incident.time}
          </div>
        )}
      </div>

      <div className="rounded-sm border border-stroke overflow-hidden">
        {data.items.map((item, i) => (
          <div
            key={i}
            className="flex justify-between items-center"
            style={{
              padding: '6px 10px',
              borderBottom: i < data.items.length - 1 ? '1px solid var(--stroke-default)' : 'none',
              background: i % 2 === 0 ? 'var(--bg-surface)' : 'var(--bg-elevated)',
            }}
          >
            <span className="text-caption text-fg-disabled">{item.label}</span>
            <span
              className="text-caption font-semibold font-mono"
              style={{ color: item.color || 'var(--fg)' }}
            >
              {item.value}
            </span>
          </div>
        ))}
      </div>

      <div
        className="rounded-sm text-caption text-fg-sub"
        style={{
          padding: '8px 10px',
          background: `color-mix(in srgb, ${tag.color} 6%, transparent)`,
          border: `1px solid color-mix(in srgb, ${tag.color} 15%, transparent)`,
          lineHeight: 1.6,
        }}
      >
        💡 {data.summary}
      </div>

      <div
        className="rounded-sm bg-bg-base border border-stroke flex items-center justify-center flex-col gap-1"
        style={{ height: 120 }}
      >
        <div className="text-[32px] opacity-30">{tag.icon}</div>
        <div className="text-caption text-fg-disabled">시각화 영역</div>
      </div>
    </>
  );
}

/* ════════════════════════════════════════════════════
   VesselPopupPanel / VesselDetailModal 공용 유틸
   ════════════════════════════════════════════════════ */
function formatDateTime(iso: string): string {
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '-';
  const pad = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

function displayVal(v: unknown): string {
  if (v === undefined || v === null || v === '') return '-';
  return String(v);
}


function VesselPopupPanel({
  vessel: v,
  onClose,
  onDetail,
}: {
  vessel: VesselPosition;
  onClose: () => void;
  onDetail: () => void;
}) {
  const statusText = v.status ?? '-';
  const isAccident = (v.status ?? '').includes('사고');
  const statusColor = isAccident ? 'var(--color-danger)' : 'var(--color-success)';
  const statusBg = isAccident
    ? 'color-mix(in srgb, var(--color-danger) 15%, transparent)'
    : 'color-mix(in srgb, var(--color-success) 10%, transparent)';
  const speed = v.sog !== undefined ? `${v.sog.toFixed(1)} kn` : '-';
  const heading = v.heading ?? v.cog;
  const headingText = heading !== undefined ? `${Math.round(heading)}°` : '-';
  const receivedAt = v.lastUpdate ? formatDateTime(v.lastUpdate) : '-';

  return (
    <div
      style={{
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%,-50%)',
        zIndex: 9995,
        width: 300,
        background: 'rgba(13,17,23,0.97)',
        border: '1px solid rgba(48,54,61,0.8)',
        borderRadius: 12,
        boxShadow: '0 16px 48px rgba(0,0,0,0.7)',
        overflow: 'hidden',
      }}
    >
      {/* Header */}
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          padding: '10px 14px',
          background: 'rgba(22,27,34,0.97)',
          borderBottom: '1px solid rgba(48,54,61,0.8)',
        }}
      >
        <div
          className="flex items-center justify-center text-title-2"
          style={{ width: 28, height: 20 }}
        >
          {v.nationalCode ?? '🚢'}
        </div>
        <div className="flex-1 min-w-0">
          <div
            className="text-label-1 font-[800] whitespace-nowrap overflow-hidden text-ellipsis"
            style={{ color: '#e6edf3' }}
          >
            {v.shipNm ?? '(이름 없음)'}
          </div>
          <div className="text-caption font-mono" style={{ color: '#8b949e' }}>
            MMSI: {v.mmsi}
          </div>
        </div>
        <span
          onClick={onClose}
          className="text-title-3 cursor-pointer p-[2px]"
          style={{ color: '#8b949e' }}
        >
          ✕
        </span>
      </div>

      {/* Ship Image */}
      <div
        className="w-full flex items-center justify-center text-[40px]"
        style={{
          height: 120,
          background: 'rgba(22,27,34,0.97)',
          borderBottom: '1px solid rgba(48,54,61,0.6)',
          color: '#484f58',
        }}
      >
        🚢
      </div>

      {/* Tags */}
      <div
        className="flex gap-2"
        style={{ padding: '6px 14px', borderBottom: '1px solid rgba(48,54,61,0.6)' }}
      >
        <span
          className="text-caption font-bold rounded text-color-info"
          style={{
            padding: '2px 8px',
            background: 'color-mix(in srgb, var(--color-info) 12%, transparent)',
            border: '1px solid color-mix(in srgb, var(--color-info) 25%, transparent)',
          }}
        >
          {getShipKindLabel(v.shipKindCode) ?? v.shipTy ?? '-'}
        </span>
        <span
          className="text-caption font-bold rounded"
          style={{
            padding: '2px 8px',
            background: statusBg,
            border: `1px solid color-mix(in srgb, ${statusColor} 40%, transparent)`,
            color: statusColor,
          }}
        >
          {statusText}
        </span>
      </div>

      {/* Data rows */}
      <div style={{ padding: '4px 0' }}>
        <PopupRow label="속도/항로" value={`${speed} / ${headingText}`} accent />
        <PopupRow label="흘수" value={v.draught !== undefined ? `${v.draught.toFixed(2)} m` : '-'} />
        <div
          className="flex flex-col gap-1"
          style={{
            padding: '6px 14px',
            borderBottom: '1px solid rgba(48,54,61,0.4)',
          }}
        >
          <div className="flex justify-between">
            <span className="text-caption" style={{ color: '#8b949e' }}>
              출항지
            </span>
            <span className="text-caption font-semibold font-mono" style={{ color: '#c9d1d9' }}>
              -
            </span>
          </div>
          <div className="flex justify-between">
            <span className="text-caption" style={{ color: '#8b949e' }}>
              입항지
            </span>
            <span className="text-caption font-semibold font-mono" style={{ color: '#c9d1d9' }}>
              {v.destination ?? '-'}
            </span>
          </div>
        </div>
        <PopupRow label="데이터 수신" value={receivedAt} muted />
      </div>

      {/* Buttons */}
      <div className="flex gap-1.5" style={{ padding: '10px 14px' }}>
        <button
          onClick={onDetail}
          className="flex-1 text-caption font-bold cursor-pointer text-center rounded-sm"
          style={{
            padding: 6,
            color: '#58a6ff',
            background: 'rgba(88,166,255,0.12)',
            border: '1px solid rgba(88,166,255,0.3)',
          }}
        >
          📋 상세정보
        </button>
        <button
          className="flex-1 text-caption font-bold cursor-pointer text-center rounded-sm"
          style={{
            padding: 6,
            color: '#a5d6ff',
            background: 'rgba(165,214,255,0.1)',
            border: '1px solid rgba(165,214,255,0.25)',
          }}
        >
          🔍 항적조회
        </button>
        <button
          className="flex-1 text-caption font-bold cursor-pointer text-center rounded-sm"
          style={{
            padding: 6,
            color: '#22d3ee',
            background: 'rgba(34,211,238,0.1)',
            border: '1px solid rgba(34,211,238,0.25)',
          }}
        >
          📐 항로예측
        </button>
      </div>
    </div>
  );
}

function PopupRow({
  label,
  value,
  accent,
  muted,
}: {
  label: string;
  value: string;
  accent?: boolean;
  muted?: boolean;
}) {
  return (
    <div
      className="flex justify-between text-caption"
      style={{
        padding: '6px 14px',
        borderBottom: '1px solid rgba(48,54,61,0.4)',
      }}
    >
      <span style={{ color: '#8b949e' }}>{label}</span>
      <span
        className="font-semibold font-mono"
        style={{
          color: muted ? '#8b949e' : accent ? '#22d3ee' : '#c9d1d9',
          fontSize: muted ? 9 : 10,
        }}
      >
        {value}
      </span>
    </div>
  );
}

/* ════════════════════════════════════════════════════
   IncidentPopupContent – 사고 마커 클릭 팝업
   ════════════════════════════════════════════════════ */
function IncidentPopupContent({
  incident: inc,
  onClose,
}: {
  incident: IncidentCompat;
  onClose: () => void;
}) {
  const dotColor: Record<string, string> = {
    active: 'var(--color-danger)',
    investigating: 'var(--color-warning)',
    closed: 'var(--fg-disabled)',
  };
  const stBg: Record<string, string> = {
    active: 'rgba(239,68,68,0.15)',
    investigating: 'rgba(249,115,22,0.15)',
    closed: 'rgba(100,116,139,0.15)',
  };
  const stColor: Record<string, string> = {
    active: 'var(--color-danger)',
    investigating: 'var(--color-warning)',
    closed: 'var(--fg-disabled)',
  };

  return (
    <div
      style={{
        width: 260,
        background: 'var(--bg-elevated)',
        border: '1px solid var(--stroke-default)',
        borderRadius: 10,
        boxShadow: '0 12px 40px rgba(0,0,0,0.5)',
        overflow: 'hidden',
      }}
    >
      {/* Header */}
      <div
        className="flex items-center gap-2 border-b border-stroke"
        style={{ padding: '10px 14px' }}
      >
        <span
          className="shrink-0"
          style={{
            width: 8,
            height: 8,
            borderRadius: '50%',
            background: dotColor[inc.status],
            boxShadow: inc.status !== 'closed' ? `0 0 6px ${dotColor[inc.status]}` : 'none',
          }}
        />
        <div className="flex-1 min-w-0 text-label-1 font-bold text-fg whitespace-nowrap overflow-hidden text-ellipsis">
          {inc.name}
        </div>
        <span
          onClick={onClose}
          className="cursor-pointer text-fg-disabled hover:text-fg flex items-center justify-center"
          style={{
            width: 22,
            height: 22,
            borderRadius: 'var(--radius-sm)',
            fontSize: 14,
            lineHeight: 1,
            transition: '0.15s',
          }}
        >
          ✕
        </span>
      </div>

      {/* Tags */}
      <div
        className="flex flex-wrap gap-1.5 border-b border-stroke"
        style={{ padding: '8px 14px' }}
      >
        <span
          className="text-caption font-semibold rounded-sm"
          style={{
            padding: '2px 8px',
            background: stBg[inc.status],
            border: `1px solid ${stColor[inc.status]}`,
            color: stColor[inc.status],
          }}
        >
          {getStatusLabel(inc.status)}
        </span>
        {inc.causeType && (
          <span
            className="text-caption font-medium text-fg-sub rounded-sm"
            style={{
              padding: '2px 8px',
              background: 'rgba(100,116,139,0.08)',
              border: '1px solid rgba(100,116,139,0.2)',
            }}
          >
            {inc.causeType}
          </span>
        )}
        {inc.oilType && (
          <span
            className="text-caption font-medium text-color-warning rounded-sm"
            style={{
              padding: '2px 8px',
              background: 'rgba(249,115,22,0.08)',
              border: '1px solid rgba(249,115,22,0.2)',
            }}
          >
            {inc.oilType}
          </span>
        )}
      </div>

      {/* Info rows */}
      <div style={{ padding: '4px 0' }}>
        <div
          className="flex justify-between text-caption"
          style={{ padding: '5px 14px', borderBottom: '1px solid rgba(48,54,61,0.4)' }}
        >
          <span className="text-fg-disabled">일시</span>
          <span className="text-fg-sub font-semibold font-mono">
            {inc.date} {inc.time}
          </span>
        </div>
        <div
          className="flex justify-between text-caption"
          style={{ padding: '5px 14px', borderBottom: '1px solid rgba(48,54,61,0.4)' }}
        >
          <span className="text-fg-disabled">관할</span>
          <span className="text-fg-sub font-semibold">{inc.office}</span>
        </div>
        <div className="flex justify-between text-caption" style={{ padding: '5px 14px' }}>
          <span className="text-fg-disabled">지역</span>
          <span className="text-fg-sub font-semibold">{inc.region}</span>
        </div>
      </div>

      {/* Prediction badge */}
      {inc.prediction && (
        <div className="border-t border-stroke" style={{ padding: '8px 14px' }}>
          <span
            className="text-caption font-semibold text-color-accent rounded-sm"
            style={{
              padding: '3px 10px',
              background: 'rgba(6,182,212,0.1)',
              border: '1px solid rgba(6,182,212,0.25)',
            }}
          >
            {inc.prediction}
          </span>
        </div>
      )}
    </div>
  );
}

/* ════════════════════════════════════════════════════
   VesselDetailModal
   ════════════════════════════════════════════════════ */
type DetTab = 'info' | 'nav' | 'spec' | 'ins' | 'dg';
const TAB_LABELS: { key: DetTab; label: string }[] = [
  { key: 'info', label: '상세정보' },
  { key: 'nav', label: '항해정보' },
  { key: 'spec', label: '선박제원' },
  { key: 'ins', label: '보험정보' },
  { key: 'dg', label: '위험물정보' },
];

function VesselDetailModal({
  vessel: v,
  onClose,
}: {
  vessel: VesselPosition;
  onClose: () => void;
}) {
  const [tab, setTab] = useState<DetTab>('info');

  return (
    <div
      onClick={(e) => {
        if (e.target === e.currentTarget) onClose();
      }}
      className="fixed inset-0 z-[10000] flex items-center justify-center"
      style={{
        background: 'rgba(0,0,0,0.65)',
        backdropFilter: 'blur(6px)',
      }}
    >
      <div
        className="flex flex-col overflow-hidden bg-bg-surface border border-stroke"
        style={{
          width: 560,
          height: '85vh',
          borderRadius: 14,
          boxShadow: '0 24px 64px rgba(0,0,0,0.7)',
        }}
      >
        {/* Header */}
        <div
          className="shrink-0 flex items-center justify-between bg-bg-surface border-b border-stroke"
          style={{ padding: '14px 18px' }}
        >
          <div className="flex items-center gap-[10px]">
            <span className="text-lg">{v.nationalCode ?? '🚢'}</span>
            <div>
              <div className="text-title-3 font-[800] text-fg">
                {v.shipNm ?? '(이름 없음)'}
              </div>
              <div className="text-caption text-fg-disabled font-mono">
                MMSI: {v.mmsi} · IMO: {displayVal(v.imo)}
              </div>
            </div>
          </div>
          <span onClick={onClose} className="text-title-2 cursor-pointer text-fg-disabled">
            ✕
          </span>
        </div>

        {/* Tabs */}
        <div
          className="shrink-0 flex gap-0.5 overflow-x-auto bg-bg-base border-b border-stroke"
          style={{ padding: '0 18px' }}
        >
          {TAB_LABELS.map((t) => (
            <button
              key={t.key}
              onClick={() => setTab(t.key)}
              className="text-label-2 cursor-pointer whitespace-nowrap"
              style={{
                padding: '8px 11px',
                fontWeight: tab === t.key ? 600 : 400,
                color: tab === t.key ? 'var(--color-accent)' : 'var(--fg-disabled)',
                borderBottom:
                  tab === t.key ? '2px solid var(--color-accent)' : '2px solid transparent',
                background: 'none',
                border: 'none',
                transition: '0.15s',
              }}
            >
              {t.label}
            </button>
          ))}
        </div>

        {/* Body */}
        <div
          className="flex-1 overflow-y-auto flex flex-col"
          style={{
            padding: '16px 18px',
            gap: 14,
            scrollbarWidth: 'thin',
            scrollbarColor: 'var(--stroke-default) transparent',
          }}
        >
          {tab === 'info' && <TabInfo v={v} />}
          {tab === 'nav' && <TabNav v={v} />}
          {tab === 'spec' && <TabSpec v={v} />}
          {tab === 'ins' && <TabInsurance v={v} />}
          {tab === 'dg' && <TabDangerous v={v} />}
        </div>
      </div>
    </div>
  );
}

/* ── shared section helpers ──────────────────────── */
function Sec({
  title,
  borderColor,
  bgColor,
  badge,
  children,
}: {
  title: string;
  borderColor?: string;
  bgColor?: string;
  badge?: React.ReactNode;
  children: React.ReactNode;
}) {
  return (
    <div
      style={{
        border: `1px solid ${borderColor || 'var(--stroke-default)'}`,
        borderRadius: 8,
        overflow: 'hidden',
      }}
    >
      <div
        className="text-label-2 font-bold text-fg-sub flex items-center justify-between"
        style={{
          padding: '8px 12px',
          background: bgColor || 'var(--bg-base)',
          borderBottom: `1px solid ${borderColor || 'var(--stroke-default)'}`,
        }}
      >
        <span>{title}</span>
        {badge}
      </div>
      {children}
    </div>
  );
}

function Grid({ children }: { children: React.ReactNode }) {
  return <div className="grid grid-cols-2">{children}</div>;
}

function Cell({
  label,
  value,
  span,
  color,
}: {
  label: string;
  value: string;
  span?: boolean;
  color?: string;
}) {
  return (
    <div
      style={{
        padding: '8px 12px',
        borderBottom: '1px solid color-mix(in srgb, var(--stroke-default) 60%, transparent)',
        borderRight: span
          ? 'none'
          : '1px solid color-mix(in srgb, var(--stroke-default) 60%, transparent)',
        gridColumn: span ? '1 / -1' : undefined,
      }}
    >
      <div className="text-caption text-fg-disabled" style={{ marginBottom: 2 }}>
        {label}
      </div>
      <div className="text-label-2 font-semibold font-mono" style={{ color: color || 'var(--fg)' }}>
        {value}
      </div>
    </div>
  );
}

function StatusBadge({ label, color }: { label: string; color: string }) {
  return (
    <span
      className="text-caption font-bold"
      style={{
        padding: '2px 6px',
        borderRadius: 8,
        marginLeft: 'auto',
        background: `color-mix(in srgb, ${color} 25%, transparent)`,
        color,
      }}
    >
      {label}
    </span>
  );
}

/* ── Tab 0: 상세정보 ─────────────────────────────── */
function TabInfo({ v }: { v: VesselPosition }) {
  const speed = v.sog !== undefined ? `${v.sog.toFixed(1)} kn` : '-';
  const heading = v.heading ?? v.cog;
  const headingText = heading !== undefined ? `${Math.round(heading)}°` : '-';
  return (
    <>
      <div
        className="w-full rounded-lg overflow-hidden flex items-center justify-center text-[60px] text-fg-disabled bg-bg-base"
        style={{ height: 160 }}
      >
        🚢
      </div>

      <Sec title="📡 실시간 현황">
        <Grid>
          <Cell label="선박상태" value={displayVal(v.status)} />
          <Cell
            label="속도 / 항로"
            value={`${speed} / ${headingText}`}
            color="var(--color-accent)"
          />
          <Cell label="위도" value={`${v.lat.toFixed(4)}°N`} />
          <Cell label="경도" value={`${v.lon.toFixed(4)}°E`} />
          <Cell label="흘수" value={v.draught !== undefined ? `${v.draught} m` : '-'} />
          <Cell label="수신시간" value={v.lastUpdate ? formatDateTime(v.lastUpdate) : '-'} />
        </Grid>
      </Sec>

      <Sec title="🚢 항해 일정">
        <Grid>
          <Cell label="출항지" value="-" />
          <Cell label="입항지" value={displayVal(v.destination)} />
          <Cell label="출항일시" value="-" />
          <Cell label="입항일시(ETA)" value="-" />
        </Grid>
      </Sec>
    </>
  );
}

/* ── Tab 1: 항해정보 ─────────────────────────────── */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function TabNav(_props: { v: VesselPosition }) {
  const hours = ['08', '09', '10', '11', '12', '13', '14'];
  const heights = [45, 60, 78, 82, 70, 85, 75];
  const colors = [
    'color-mix(in srgb, var(--color-success) 30%, transparent)',
    'color-mix(in srgb, var(--color-success) 40%, transparent)',
    'color-mix(in srgb, var(--color-info) 40%, transparent)',
    'color-mix(in srgb, var(--color-info) 50%, transparent)',
    'color-mix(in srgb, var(--color-info) 50%, transparent)',
    'color-mix(in srgb, var(--color-info) 60%, transparent)',
    'color-mix(in srgb, var(--color-accent) 50%, transparent)',
  ];

  return (
    <>
      <Sec title="🗺 최근 항적 (24h)">
        <div
          className="flex items-center justify-center relative overflow-hidden bg-bg-base"
          style={{ height: 180 }}
        >
          <svg
            width="100%"
            height="100%"
            viewBox="0 0 400 180"
            style={{ position: 'absolute', inset: 0 }}
          >
            <path
              d="M50,150 C80,140 120,100 160,95 S240,70 280,50 S340,30 370,20"
              fill="none"
              stroke="var(--color-accent)"
              strokeWidth="2"
              strokeDasharray="6,3"
              opacity=".6"
            />
            <circle cx="50" cy="150" r="4" fill="var(--fg-disabled)" />
            <circle cx="160" cy="95" r="3" fill="var(--color-accent)" opacity=".5" />
            <circle cx="280" cy="50" r="3" fill="var(--color-accent)" opacity=".5" />
            <circle cx="370" cy="20" r="5" fill="var(--color-accent)" />
            <text
              x="45"
              y="168"
              fill="var(--fg-disabled)"
              fontSize="9"
              fontFamily="var(--font-mono)"
            >
              08:00
            </text>
            <text
              x="150"
              y="113"
              fill="var(--fg-disabled)"
              fontSize="9"
              fontFamily="var(--font-mono)"
            >
              10:30
            </text>
            <text
              x="270"
              y="68"
              fill="var(--fg-disabled)"
              fontSize="9"
              fontFamily="var(--font-mono)"
            >
              12:45
            </text>
            <text
              x="350"
              y="16"
              fill="var(--color-accent)"
              fontSize="9"
              fontFamily="var(--font-mono)"
            >
              현재
            </text>
          </svg>
        </div>
      </Sec>

      <Sec title="📊 속도 이력">
        <div className="p-3 bg-bg-base">
          <div className="flex items-end gap-1.5" style={{ height: 80 }}>
            {hours.map((h, i) => (
              <div key={h} className="flex-1 flex flex-col items-center gap-0.5">
                <div
                  className="w-full"
                  style={{
                    background: colors[i],
                    borderRadius: '2px 2px 0 0',
                    height: `${heights[i]}%`,
                  }}
                />
                <span className="text-caption text-fg-disabled">{h}</span>
              </div>
            ))}
          </div>
          <div className="text-center text-caption text-fg-disabled" style={{ marginTop: 6 }}>
            평균: <b className="text-color-info">8.4 kn</b> · 최대:{' '}
            <b className="text-color-accent">11.2 kn</b>
          </div>
        </div>
      </Sec>

      <div className="flex gap-[8px]">
        <ActionBtn
          icon="🔍"
          label="전체 항적 조회"
          bg="color-mix(in srgb, var(--color-tertiary) 10%, transparent)"
          bd="color-mix(in srgb, var(--color-tertiary) 25%, transparent)"
          fg="var(--color-tertiary)"
        />
        <ActionBtn
          icon="📐"
          label="항로 예측"
          bg="color-mix(in srgb, var(--color-accent) 10%, transparent)"
          bd="color-mix(in srgb, var(--color-accent) 25%, transparent)"
          fg="var(--color-accent)"
        />
      </div>
    </>
  );
}

/* ── Tab 2: 선박제원 ─────────────────────────────── */
function TabSpec({ v }: { v: VesselPosition }) {
  const loa = v.length !== undefined ? `${v.length} m` : '-';
  const beam = v.width !== undefined ? `${v.width} m` : '-';
  return (
    <>
      <Sec title="📐 선체 제원">
        <Grid>
          <Cell label="선종" value={displayVal(getShipKindLabel(v.shipKindCode) ?? v.shipTy)} />
          <Cell label="선적국" value={displayVal(v.nationalCode)} />
          <Cell label="총톤수 (GT)" value="-" />
          <Cell label="재화중량 (DWT)" value="-" />
          <Cell label="전장 (LOA)" value={loa} />
          <Cell label="선폭" value={beam} />
          <Cell label="건조년도" value="-" />
          <Cell label="건조 조선소" value="-" />
        </Grid>
      </Sec>

      <Sec title="📡 통신 / 식별">
        <Grid>
          <Cell label="MMSI" value={String(v.mmsi)} />
          <Cell label="IMO" value={displayVal(v.imo)} />
          <Cell label="호출부호" value="-" />
          <Cell label="선급" value="-" />
        </Grid>
      </Sec>

      <Sec title="⚠ 위험물 적재 정보">
        <div className="p-[10px_12px] bg-bg-base">
          <div
            className="flex items-center gap-2 rounded"
            style={{
              padding: '5px 8px',
              background: 'color-mix(in srgb, var(--color-danger) 6%, transparent)',
              border: '1px solid color-mix(in srgb, var(--color-danger) 12%, transparent)',
            }}
          >
            <span className="text-label-1">🛢</span>
            <div className="flex-1">
              <div className="text-caption font-semibold text-fg">-</div>
              <div className="text-caption text-fg-disabled">정보 없음</div>
            </div>
          </div>
        </div>
      </Sec>
    </>
  );
}

/* ── Tab 3: 보험정보 ─────────────────────────────── */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function TabInsurance(_props: { v: VesselPosition }) {
  return (
    <>
      <Sec title="🏢 선주 / 운항사">
        <Grid>
          <Cell label="선주" value="대한해운(주)" />
          <Cell label="운항사" value="대한해운(주)" />
          <Cell label="P&I Club" value="한국선주상호보험" span />
        </Grid>
      </Sec>

      <Sec
        title="🚢 선체보험 (H&M)"
        borderColor="color-mix(in srgb, var(--color-accent) 20%, transparent)"
        bgColor="color-mix(in srgb, var(--color-accent) 6%, transparent)"
        badge={<StatusBadge label="유효" color="var(--color-success)" />}
      >
        <Grid>
          <Cell label="보험사" value="삼성화재해상보험" />
          <Cell label="보험가액" value="USD 28,500,000" color="var(--color-accent)" />
          <Cell label="보험기간" value="2025.01 ~ 2026.01" color="var(--color-success)" />
          <Cell label="면책금" value="USD 150,000" />
        </Grid>
      </Sec>

      <Sec
        title="📦 화물보험 (Cargo)"
        borderColor="color-mix(in srgb, var(--color-tertiary) 20%, transparent)"
        bgColor="color-mix(in srgb, var(--color-tertiary) 6%, transparent)"
        badge={<StatusBadge label="유효" color="var(--color-success)" />}
      >
        <Grid>
          <Cell label="보험사" value="DB손해보험" />
          <Cell label="보험가액" value="USD 42,100,000" color="var(--color-tertiary)" />
          <Cell label="적하물" value="벙커C유 72,850톤" />
          <Cell label="조건" value="ICC(A) All Risks" />
        </Grid>
      </Sec>

      <Sec
        title="🛢 유류오염배상 (CLC/IOPC)"
        borderColor="color-mix(in srgb, var(--color-danger) 20%, transparent)"
        bgColor="color-mix(in srgb, var(--color-danger) 6%, transparent)"
        badge={<StatusBadge label="유효" color="var(--color-success)" />}
      >
        <Grid>
          <Cell label="배상보증서" value="유효 (2025-12-31)" color="var(--color-success)" />
          <Cell label="발급기관" value="한국선주상호보험" />
          <Cell label="CLC 한도" value="89.77M SDR" color="var(--color-danger)" />
          <Cell label="IOPC 기금" value="203M SDR" />
          <Cell label="추가기금" value="750M SDR" />
          <Cell label="총 배상한도" value="약 1,042.77M SDR" color="var(--color-warning)" />
        </Grid>
      </Sec>

      <div
        className="rounded-sm text-caption text-fg-disabled"
        style={{
          padding: '8px 10px',
          background: 'color-mix(in srgb, var(--color-info) 4%, transparent)',
          border: '1px solid color-mix(in srgb, var(--color-info) 10%, transparent)',
          lineHeight: 1.6,
        }}
      >
        💡 보험정보는 한국해운조합(KSA) Open API 및 해양수산부 선박정보시스템 연동 데이터입니다.
        실시간 갱신 주기: 24시간
      </div>
    </>
  );
}

/* ── Tab 4: 위험물정보 ───────────────────────────── */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function TabDangerous(_props: { v: VesselPosition }) {
  return (
    <>
      <Sec
        title="⚠ 위험물 화물 신고정보"
        bgColor="color-mix(in srgb, var(--color-warning) 6%, transparent)"
        badge={
          <span
            className="text-caption font-bold text-color-danger"
            style={{
              padding: '2px 6px',
              background: 'color-mix(in srgb, var(--color-danger) 15%, transparent)',
              borderRadius: 8,
            }}
          >
            PORT-MIS
          </span>
        }
      >
        <Grid>
          <Cell label="화물명" value="-" color="var(--color-warning)" />
          <Cell label="컨테이너갯수/총량" value="— / 72,850 톤" />
          <Cell label="하역업체코드" value="KRY-2847" />
          <Cell label="하역기간" value="02-26 ~ 02-28" />
          <Cell label="신고업체코드" value="DHW-0412" />
          <Cell label="사용장소(부두)" value="여수 1부두 2선석" />
          <Cell label="신고일시" value="2026-02-24 09:30" />
          <Cell label="전출항지" value="울산항" />
          <Cell label="EDI ID" value="EDI-2026022400187" />
          <Cell label="수리일시" value="2026-02-24 10:15" />
        </Grid>
      </Sec>

      <Sec title="📋 화물창 및 첨부">
        <div
          className="flex items-center justify-between gap-2 bg-bg-base"
          style={{ padding: '12px' }}
        >
          <div className="flex items-center gap-2 text-label-2 whitespace-nowrap">
            <span className="text-fg-disabled">화물창 2개이상 여부</span>
            <span className="inline-flex items-center gap-1">
              <span
                className="flex items-center justify-center text-caption text-color-accent"
                style={{
                  width: 14,
                  height: 14,
                  borderRadius: '50%',
                  border: '2px solid var(--color-accent)',
                }}
              >
                ✓
              </span>
              <span className="font-semibold text-caption text-color-accent">예</span>
            </span>
          </div>
          <button
            className="text-caption font-semibold text-color-info cursor-pointer whitespace-nowrap shrink-0 rounded"
            style={{
              padding: '6px 14px',
              background: 'color-mix(in srgb, var(--color-info) 10%, transparent)',
              border: '1px solid color-mix(in srgb, var(--color-info) 20%, transparent)',
            }}
          >
            📎 첨부[화물적부도]
          </button>
        </div>
      </Sec>

      <Sec
        title="🔥 IMO 위험물 분류"
        borderColor="color-mix(in srgb, var(--color-danger) 20%, transparent)"
        bgColor="color-mix(in srgb, var(--color-danger) 6%, transparent)"
      >
        <Grid>
          <Cell label="IMO Class" value="Class 3" color="var(--color-danger)" />
          <Cell label="분류" value="인화성 액체" />
          <Cell label="UN No." value="UN 1993" />
          <Cell label="포장등급" value="III" />
          <Cell label="인화점" value="60°C 이상" color="var(--color-warning)" />
          <Cell label="해양오염물질" value="해당 (P)" color="var(--color-danger)" />
        </Grid>
      </Sec>

      <Sec
        title="🚨 비상 대응 요약 (EmS)"
        bgColor="color-mix(in srgb, var(--color-caution) 6%, transparent)"
      >
        <div className="flex flex-col gap-1.5 bg-bg-base" style={{ padding: '10px 12px' }}>
          <EmsRow
            icon="🔥"
            label="화재시"
            value="포말소화제, CO₂ 소화기 사용 · 물분무 냉각"
            bg="color-mix(in srgb, var(--color-danger) 5%, transparent)"
            bd="color-mix(in srgb, var(--color-danger) 12%, transparent)"
          />
          <EmsRow
            icon="🌊"
            label="유출시"
            value="오일펜스 전개 · 유흡착재 투입 · 해상 기름 회수"
            bg="color-mix(in srgb, var(--color-info) 5%, transparent)"
            bd="color-mix(in srgb, var(--color-info) 12%, transparent)"
          />
          <EmsRow
            icon="🫁"
            label="보호장비"
            value="내화학장갑, 보안경, 방독마스크 · 레벨C 보호복"
            bg="color-mix(in srgb, var(--color-tertiary) 5%, transparent)"
            bd="color-mix(in srgb, var(--color-tertiary) 12%, transparent)"
          />
        </div>
      </Sec>

      <div
        className="rounded-sm text-caption text-fg-disabled"
        style={{
          padding: '8px 10px',
          background: 'color-mix(in srgb, var(--color-warning) 4%, transparent)',
          border: '1px solid color-mix(in srgb, var(--color-warning) 10%, transparent)',
          lineHeight: 1.6,
        }}
      >
        💡 위험물정보는 PORT-MIS(항만운영정보시스템) 위험물 반입신고 데이터 연동입니다. IMDG Code
        최신 개정판(Amendment 42-24) 기준.
      </div>
    </>
  );
}

function EmsRow({
  icon,
  label,
  value,
  bg,
  bd,
}: {
  icon: string;
  label: string;
  value: string;
  bg: string;
  bd: string;
}) {
  return (
    <div
      className="flex items-center gap-2 rounded"
      style={{
        padding: '6px 10px',
        background: bg,
        border: `1px solid ${bd}`,
      }}
    >
      <span className="text-title-4">{icon}</span>
      <div>
        <div className="text-caption text-fg-disabled">{label}</div>
        <div className="text-caption font-semibold text-fg">{value}</div>
      </div>
    </div>
  );
}

function ActionBtn({
  icon,
  label,
  bg,
  bd,
  fg,
}: {
  icon: string;
  label: string;
  bg: string;
  bd: string;
  fg: string;
}) {
  return (
    <button
      className="flex-1 text-caption font-bold cursor-pointer text-center rounded-sm"
      style={{
        padding: 6,
        background: bg,
        border: `1px solid ${bd}`,
        color: fg,
      }}
    >
      {icon} {label}
    </button>
  );
}

/* ════════════════════════════════════════════════════
   호버 툴팁 컴포넌트
   ════════════════════════════════════════════════════ */
function VesselTooltipContent({ vessel: v }: { vessel: VesselPosition }) {
  const speed = v.sog !== undefined ? `${v.sog.toFixed(1)} kn` : '- kn';
  const heading = v.heading ?? v.cog;
  const headingText = heading !== undefined ? `HDG ${Math.round(heading)}°` : 'HDG -';
  const typeText = [getShipKindLabel(v.shipKindCode) ?? v.shipTy ?? '-', v.nationalCode].filter(Boolean).join(' · ');
  return (
    <>
      <div className="text-label-2 font-bold text-fg" style={{ marginBottom: 3 }}>
        {v.shipNm ?? '(이름 없음)'}
      </div>
      <div className="text-caption text-fg-disabled" style={{ marginBottom: 4 }}>
        {typeText}
      </div>
      <div className="flex justify-between text-caption">
        <span className="text-color-accent font-semibold">{speed}</span>
        <span className="text-fg-disabled">{headingText}</span>
      </div>
    </>
  );
}

function IncidentTooltipContent({ incident: i }: { incident: IncidentCompat }) {
  const statusColor =
    i.status === 'active'
      ? 'var(--color-danger)'
      : i.status === 'investigating'
        ? 'var(--color-warning)'
        : 'var(--fg-disabled)';

  return (
    <>
      <div className="text-label-2 font-bold text-fg" style={{ marginBottom: 3 }}>
        {i.name}
      </div>
      <div className="text-caption text-fg-disabled" style={{ marginBottom: 4 }}>
        {i.date} {i.time}
      </div>
      <div className="flex justify-between items-center">
        <span className="text-caption font-semibold" style={{ color: statusColor }}>
          {getStatusLabel(i.status)}
        </span>
        <span className="text-caption text-color-info font-mono">
          {i.location.lat.toFixed(3)}°N, {i.location.lon.toFixed(3)}°E
        </span>
      </div>
    </>
  );
}
