import { useState, useEffect, useMemo } from 'react';
import { Map as MapLibreMap, Popup } from '@vis.gl/react-maplibre';
import { useBaseMapStyle } from '@common/hooks/useBaseMapStyle';
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 '@components/common/map/BaseMap';
import { DeckGLOverlay } from '@components/common/map/DeckGLOverlay';
import { MapBoundsTracker } from '@components/common/map/MapBoundsTracker';
import { buildVesselLayers, VESSEL_LEGEND } from '@components/common/map/VesselLayer';
import { useVesselSignals } from '@common/hooks/useVesselSignals';
import type { MapBounds, VesselPosition } from '@/types/vessel';
import { getVesselCacheStatus, type VesselCacheStatus } from '@common/services/vesselApi';
import { IncidentsLeftPanel } from './IncidentsLeftPanel';
import { IncidentsRightPanel, type ViewMode, type AnalysisSection } from './IncidentsRightPanel';
import { fetchIncidents } from '../services/incidentsApi';
import type { Incident, IncidentCompat } from '@interfaces/incidents/IncidentsInterface';
import { fetchHnsAnalyses } from '@components/hns/services/hnsApi';
import type { HnsAnalysisItem } from '@interfaces/hns/HnsInterface';
import { buildHnsDispersionLayers } from '../utils/hnsDispersionLayers';
import {
  fetchAnalysisTrajectory,
  fetchOilSpillSummary,
} from '@components/prediction/services/predictionApi';
import type {
  TrajectoryResponse,
  SensitiveResourceFeatureCollection,
  SensitiveResourceCategory,
  PredictionAnalysis,
  OilSpillSummaryResponse,
} from '@components/prediction/services/predictionApi';
import type { RescueOpsItem } from '@interfaces/rescue/RescueInterface';
import { DischargeZonePanel } from './DischargeZonePanel';
import {
  estimateDistanceFromCoast,
  determineZone,
  getDischargeZoneLines,
  loadTerritorialBaseline,
  getCachedBaseline,
  loadZoneGeoJSON,
  getCachedZones,
} from '../utils/dischargeZoneData';
import { useMapStore } from '@common/store/mapStore';
import { FlyToController } from './contents/FlyToController';
import { VesselPopupPanel } from './contents/VesselPopupPanel';
import { IncidentPopupContent } from './contents/IncidentPopupContent';
import { VesselDetailModal } from './contents/VesselDetailModal';
import { VesselTooltipContent } from './contents/VesselTooltipContent';
import { IncidentTooltipContent } from './contents/IncidentTooltipContent';
/* eslint-disable react-refresh/only-export-components */

// ── 민감자원 카테고리별 색상 (목록 순서 인덱스 기반 — 중복 없음) ────────────
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];
}

// ── 사고 상태 색상 ──────────────────────────────────────
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];
}

export 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(true);

  // 분할 뷰에서 사용할 체크된 원본 아이템들 (우측 패널에서 주입)
  const [checkedPredItems, setCheckedPredItems] = useState<PredictionAnalysis[]>([]);
  const [checkedHnsItems, setCheckedHnsItems] = useState<HnsAnalysisItem[]>([]);
  const [checkedRescueItems, setCheckedRescueItems] = useState<RescueOpsItem[]>([]);
  const [sensCategoriesFull, setSensCategoriesFull] = useState<SensitiveResourceCategory[]>([]);
  const [checkedSensCategoriesFull, setCheckedSensCategoriesFull] = useState<Set<string>>(
    new Set(),
  );

  // 2분할 좌/우 슬롯에 표시할 분석 종류
  const [split2Slots, setSplit2Slots] = useState<[SplitSlotKey | null, SplitSlotKey | null]>([
    null,
    null,
  ]);

  // 예측 trajectory & 민감자원 지도 표출
  const [trajectoryEntries, setTrajectoryEntries] = useState<
    Record<string, { data: TrajectoryResponse; occurredAt: string }>
  >({});
  // 유출유 확산 요약 (분할 패널용)
  const [oilSummaryEntries, setOilSummaryEntries] = useState<
    Record<string, OilSpillSummaryResponse>
  >({});

  // HNS 대기확산 분석 (선택 사고에 연결된 완료 분석들)
  const [hnsAnalyses, setHnsAnalyses] = useState<HnsAnalysisItem[]>([]);
  // null = 아직 패널에서 전달되지 않음 → 전체 표시 / Set = 체크된 항목만 표시
  const [checkedHnsIds, setCheckedHnsIds] = useState<Set<string> | null>(null);
  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));
  }, []);

  // 사고 전환 시 지도 레이어 즉시 초기화 + HNS 분석 자동 로드
  useEffect(() => {
    setTrajectoryEntries({});
    setSensitiveGeojson(null);
    setSensCheckedCategories(new Set());
    setSensColorMap(new Map());
    setHnsAnalyses([]);
    setCheckedHnsIds(null);

    if (!selectedIncidentId) return;
    const acdntSn = parseInt(selectedIncidentId, 10);
    if (Number.isNaN(acdntSn)) return;
    fetchHnsAnalyses({ acdntSn, status: 'COMPLETED' })
      .then((items) => setHnsAnalyses(items))
      .catch(() => {});
  }, [selectedIncidentId]);

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

  const handleRunAnalysis = (sections: AnalysisSection[], sensitiveCount: number) => {
    if (sections.length === 0 && sensitiveCount === 0) return;

    // 2분할 기본 슬롯 설정 — 체크된 분석 목록에 실제 아이템이 있는 종류로 자동 배치
    const available: SplitSlotKey[] = [];
    if (checkedPredItems.length > 0) available.push('oil');
    if (checkedHnsItems.length > 0) available.push('hns');
    if (checkedRescueItems.length > 0) available.push('rescue');
    setSplit2Slots([available[0] ?? null, available[1] ?? null]);

    setAnalysisActive(true);
  };

  const handleCloseAnalysis = () => {
    setAnalysisActive(false);
  };

  // 상단 분석 태그 — 현재 viewMode에 따라 동적으로 계산
  // overlay: 체크된 섹션 + 민감자원
  // split2: split2Slots에 선택된 2개 슬롯만
  // split3: oil/hns/rescue 3개 고정
  const analysisTags = useMemo<{ icon: string; label: string; color: string }[]>(() => {
    if (!analysisActive) return [];
    const OIL = { icon: '🛢', label: '유출유', color: 'var(--color-warning)' };
    const HNS = { icon: '🧪', label: 'HNS', color: 'var(--color-tertiary)' };
    const RESCUE = { icon: '🚨', label: '구난', color: 'var(--color-accent)' };

    if (viewMode === 'split2') {
      const tags: { icon: string; label: string; color: string }[] = [];
      split2Slots.forEach((key) => {
        if (key === 'oil') tags.push(OIL);
        else if (key === 'hns') tags.push(HNS);
        else if (key === 'rescue') tags.push(RESCUE);
      });
      return tags;
    }

    if (viewMode === 'split3') {
      return [OIL, HNS, RESCUE];
    }

    // overlay
    const tags: { icon: string; label: string; color: string }[] = [];
    if (checkedPredItems.length > 0) tags.push(OIL);
    if (checkedHnsItems.length > 0) tags.push(HNS);
    if (checkedRescueItems.length > 0) tags.push(RESCUE);
    const sensCount = checkedSensCategoriesFull.size;
    if (sensCount > 0)
      tags.push({
        icon: '🐟',
        label: `민감자원 ${sensCount}건`,
        color: 'var(--color-success)',
      });
    return tags;
  }, [
    analysisActive,
    viewMode,
    split2Slots,
    checkedPredItems.length,
    checkedHnsItems.length,
    checkedRescueItems.length,
    checkedSensCategoriesFull,
  ]);

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

  const handleCheckedHnsChange = (
    checked: Array<{ id: string; hnsAnlysSn: number; acdntSn: number | null }>,
  ) => {
    setCheckedHnsIds(new Set(checked.map((h) => String(h.hnsAnlysSn))));
  };

  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]);

  // ── HNS 대기확산 레이어 (히트맵 BitmapLayer + AEGL 원) ──
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const hnsZoneLayers: any[] = useMemo(() => {
    // null = 패널 초기화 전 → 전체 표시 / Set → 체크된 항목만
    const visibleAnalyses =
      checkedHnsIds === null
        ? hnsAnalyses
        : hnsAnalyses.filter((a) => checkedHnsIds.has(String(a.hnsAnlysSn)));
    return buildHnsDispersionLayers(visibleAnalyses, analysisActive);
  }, [hnsAnalyses, checkedHnsIds, analysisActive]);

  // ── 예측 결과 레이어 (입자 클라우드, 중심점 경로, 시간 라벨, 해안 부착 입자) ──────
  // 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,
              visible: analysisActive,
            }),
          );
        });

        // 해안 부착 입자: 모델별 색상 + 테두리 강조
        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,
              visible: analysisActive,
            }),
          );
        });
      }

      // 중심점 경로선 (모델별 그룹)
      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] as [number, number]) }],
              getPath: (d: { path: [number, number][] }) => d.path,
              getColor: [...color, 230] as [number, number, number, number],
              getWidth: 2,
              widthMinPixels: 2,
              widthMaxPixels: 4,
              visible: analysisActive,
            }),
          );
          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,
              visible: analysisActive,
            }),
          );
          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,
              visible: analysisActive,
            }),
          );
        });
      }
    }
    return layers;
  }, [trajectoryEntries, analysisActive]);

  // ── 민감자원 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',
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      data: filtered as any,
      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,
      visible: analysisActive,
      updateTriggers: {
        getFillColor: [sensColorMap],
        getLineColor: [sensColorMap],
      },
    });
  }, [sensitiveGeojson, sensCheckedCategories, sensColorMap, analysisActive]);

  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,
      ...hnsZoneLayers,
      ...trajectoryLayers,
      ...(sensLayer ? [sensLayer] : []),
    ],
    [incidentLayer, realVesselLayers, dischargeZoneLayers, hnsZoneLayers, 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 — 항상 마운트하여 모드 전환 시 pan/zoom 및 deck.gl 레이어 상태를 보존한다 */}
          <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>
              )}

              {/* 오염물 배출 규정 토글 */}
              <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="absolute inset-0 flex z-[5] bg-bg-base">
              {[0, 1].map((slotIndex) => {
                const slotKey = split2Slots[slotIndex];
                const otherKey = split2Slots[slotIndex === 0 ? 1 : 0];
                const tag = slotKey ? SLOT_TAG[slotKey] : undefined;
                return (
                  <div
                    key={slotIndex}
                    className="flex-1 flex flex-col overflow-hidden"
                    style={{
                      borderRight:
                        slotIndex === 0 ? '2px solid var(--stroke-default)' : undefined,
                    }}
                  >
                    <div
                      className="flex items-center shrink-0 bg-bg-surface border-b border-stroke gap-2"
                      style={{
                        height: 32,
                        padding: '0 10px',
                        background: tag
                          ? `linear-gradient(90deg, color-mix(in srgb, ${tag.color} 8%, transparent), var(--bg-surface))`
                          : undefined,
                      }}
                    >
                      <span
                        className="text-caption font-bold"
                        style={{ color: tag?.color ?? 'var(--fg-disabled)' }}
                      >
                        {tag ? `${tag.icon} ${tag.label}` : '분석 선택'}
                      </span>
                      <select
                        value={slotKey ?? ''}
                        onChange={(e) => {
                          const v = (e.target.value || null) as SplitSlotKey | null;
                          setSplit2Slots((prev) => {
                            const next: [SplitSlotKey | null, SplitSlotKey | null] = [
                              prev[0],
                              prev[1],
                            ];
                            next[slotIndex] = v;
                            // 다른 슬롯과 중복 방지 — 중복 시 다른 슬롯을 빈 값으로
                            const other = slotIndex === 0 ? 1 : 0;
                            if (v && next[other] === v) next[other] = null;
                            return next;
                          });
                        }}
                        className="ml-auto text-caption bg-bg-base border border-stroke rounded-sm"
                        style={{ padding: '2px 6px', color: 'var(--fg)' }}
                      >
                        <option value="">— 선택 —</option>
                        {(['oil', 'hns', 'rescue'] as const).map((k) => (
                          <option key={k} value={k} disabled={k === otherKey}>
                            {SLOT_TAG[k].icon} {SLOT_TAG[k].label}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div
                      className="flex-1 min-h-0 overflow-y-auto bg-bg-base scrollbar-thin"
                      style={{ padding: 12 }}
                    >
                      <div className="flex flex-col gap-2">
                        <SplitPanelContent
                          slotKey={slotKey}
                          incident={selectedIncident}
                          checkedPreds={checkedPredItems}
                          checkedHns={checkedHnsItems}
                          checkedRescues={checkedRescueItems}
                          sensCategories={sensCategoriesFull}
                          checkedSensCategories={checkedSensCategoriesFull}
                          trajectoryLayers={trajectoryLayers}
                          hnsZoneLayers={hnsZoneLayers}
                          sensLayer={sensLayer}
                          oilSummaries={oilSummaryEntries}
                        />
                      </div>
                    </div>
                  </div>
                );
              })}
            </div>
          )}

          {/* ── 3분할 View ─────────────────────────────── */}
          {analysisActive && viewMode === 'split3' && (
            <div className="absolute inset-0 flex z-[5] bg-bg-base">
              {(['oil', 'hns', 'rescue'] as const).map((slotKey, i) => {
                const tag = SLOT_TAG[slotKey];
                return (
                  <div
                    key={slotKey}
                    className="flex-1 flex flex-col overflow-hidden"
                    style={{ borderRight: i < 2 ? '1px solid var(--stroke-default)' : undefined }}
                  >
                    <div
                      className="flex items-center shrink-0 border-b border-stroke"
                      style={{
                        height: 28,
                        background: `linear-gradient(90deg, color-mix(in srgb, ${tag.color} 8%, transparent), var(--bg-surface))`,
                        padding: '0 10px',
                      }}
                    >
                      <span className="text-caption font-bold" style={{ color: tag.color }}>
                        {tag.icon} {tag.fullLabel}
                      </span>
                    </div>
                    <div
                      className="flex-1 min-h-0 overflow-y-auto bg-bg-base scrollbar-thin"
                      style={{ padding: 10 }}
                    >
                      <div className="flex flex-col gap-1.5">
                        <SplitPanelContent
                          slotKey={slotKey}
                          incident={selectedIncident}
                          checkedPreds={checkedPredItems}
                          checkedHns={checkedHnsItems}
                          checkedRescues={checkedRescueItems}
                          sensCategories={sensCategoriesFull}
                          checkedSensCategories={checkedSensCategoriesFull}
                          trajectoryLayers={trajectoryLayers}
                          hnsZoneLayers={hnsZoneLayers}
                          sensLayer={sensLayer}
                          oilSummaries={oilSummaryEntries}
                        />
                      </div>
                    </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}
          onCheckedHnsChange={handleCheckedHnsChange}
          onCheckedPredItemsChange={setCheckedPredItems}
          onCheckedHnsItemsChange={setCheckedHnsItems}
          onCheckedRescueItemsChange={setCheckedRescueItems}
          onSensitiveCategoriesChange={(cats, checked) => {
            setSensCategoriesFull(cats);
            setCheckedSensCategoriesFull(checked);
          }}
          onSensitiveDataChange={handleSensitiveDataChange}
          selectedVessel={
            selectedVessel
              ? {
                  lat: selectedVessel.lat,
                  lng: selectedVessel.lon,
                  name: selectedVessel.shipNm,
                }
              : null
          }
        />
      </div>
    </div>
  );
}

/* ════════════════════════════════════════════════════

   SplitPanelContent — 2/3분할 내부 렌더러
   ════════════════════════════════════════════════════ */
type SplitSlotKey = 'oil' | 'hns' | 'rescue';

const SLOT_TAG: Record<
  SplitSlotKey,
  { icon: string; label: string; fullLabel: string; color: string }
> = {
  oil: { icon: '🛢', label: '유출유', fullLabel: '유출유 확산예측', color: 'var(--color-warning)' },
  hns: { icon: '🧪', label: 'HNS', fullLabel: 'HNS 대기확산', color: 'var(--color-tertiary)' },
  rescue: { icon: '🚨', label: '구난', fullLabel: '긴급구난', color: 'var(--color-accent)' },
};

const SPLIT_CATEGORY_ICON: Record<string, string> = {
  어장정보: '🐟',
  양식장: '🦪',
  양식어업: '🦪',
  어류양식장: '🐟',
  패류양식장: '🦪',
  해조류양식장: '🌿',
  가두리양식장: '🔲',
  갑각류양식장: '🦐',
  수산시장: '🐟',
  해수욕장: '🏖',
  마리나항: '⛵',
  무역항: '🚢',
  연안항: '⛵',
  국가어항: '⚓',
  지방어항: '⚓',
  어항: '⚓',
  항만구역: '⚓',
  해수취수시설: '💧',
  '취수구·배수구': '🚰',
  LNG: '⚡',
  발전소: '🔌',
  저유시설: '🛢',
  갯벌: '🪨',
  해안선_ESI: '🏖',
  보호지역: '🛡',
  해양보호구역: '🌿',
  철새도래지: '🐦',
  습지보호구역: '🏖',
  보호종서식지: '🐢',
};

const SPLIT_MOCK_FALLBACK: Record<
  SplitSlotKey,
  {
    model: string;
    items: { label: string; value: string; color?: string }[];
    summary: string;
  }
> = {
  oil: {
    model: '-',
    items: [
      { label: '예측 시간', value: '-' },
      { label: '최대 확산거리', value: '-', color: 'var(--color-warning)' },
      { label: '해안 도달 시간', value: '-', color: 'var(--color-danger)' },
      { label: '영향 해안선', value: '-' },
      { label: '풍화율', value: '-' },
      { label: '잔존유량', value: '-', color: 'var(--color-warning)' },
    ],
    summary: '-',
  },
  hns: {
    model: '-',
    items: [
      { label: 'IDLH 범위', value: '-', color: 'var(--color-danger)' },
      { label: 'ERPG-2 범위', value: '-', color: 'var(--color-warning)' },
      { label: 'ERPG-1 범위', value: '-', color: 'var(--color-caution)' },
      { label: '풍향', value: '-' },
      { label: '대기 안정도', value: '-' },
      { label: '영향 인구', value: '-', color: 'var(--color-danger)' },
    ],
    summary: '-',
  },
  rescue: {
    model: '-',
    items: [
      { label: '95% 확률 범위', value: '-', color: 'var(--color-accent)' },
      { label: '최적 탐색 경로', value: '-' },
      { label: '예상 표류 속도', value: '-' },
      { label: '표류 방향', value: '-' },
      { label: '생존 가능 시간', value: '-', color: 'var(--color-danger)' },
      { label: '필요 자산', value: '-', color: 'var(--color-warning)' },
    ],
    summary: '-',
  },
};

function SplitPanelContent({
  slotKey,
  incident,
  checkedPreds,
  checkedHns,
  checkedRescues,
  sensCategories,
  checkedSensCategories,
  trajectoryLayers,
  hnsZoneLayers,
  sensLayer,
  oilSummaries,
}: {
  slotKey: SplitSlotKey | null;
  incident: Incident | null;
  checkedPreds: PredictionAnalysis[];
  checkedHns: HnsAnalysisItem[];
  checkedRescues: RescueOpsItem[];
  sensCategories: SensitiveResourceCategory[];
  checkedSensCategories: Set<string>;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  trajectoryLayers: any[];
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  hnsZoneLayers: any[];
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  sensLayer: any;
  oilSummaries: Record<string, OilSpillSummaryResponse>;
}) {
  if (!slotKey) {
    return (
      <div className="flex-1 flex items-center justify-center text-fg-disabled text-label-2">
        분석 결과를 선택하세요
      </div>
    );
  }

  const tag = SLOT_TAG[slotKey];
  const mock = SPLIT_MOCK_FALLBACK[slotKey];

  // 슬롯별 체크된 항목 리스트 행 (우측 패널 포맷과 유사)
  const listRows: { id: string; name: string; sub: string }[] =
    slotKey === 'oil'
      ? checkedPreds.map((p) => {
          const date = p.runDtm ? p.runDtm.slice(0, 10) : (p.occurredAt?.slice(0, 10) ?? '-');
          const oil = p.oilType || '유출유';
          const models = [
            p.kospsStatus && p.kospsStatus !== 'pending' && p.kospsStatus !== 'none'
              ? 'KOSPS'
              : null,
            p.poseidonStatus && p.poseidonStatus !== 'pending' && p.poseidonStatus !== 'none'
              ? 'POSEIDON'
              : null,
            p.opendriftStatus && p.opendriftStatus !== 'pending' && p.opendriftStatus !== 'none'
              ? 'OpenDrift'
              : null,
          ]
            .filter(Boolean)
            .join('+');
          return {
            id: String(p.predRunSn ?? p.acdntSn),
            name: `${date} ${oil} 확산예측`.trim(),
            sub: `${models || '-'}${p.volume != null ? ` · ${p.volume}kL` : ''}`,
          };
        })
      : slotKey === 'hns'
        ? checkedHns.map((h) => {
            const date = h.regDtm ? h.regDtm.slice(0, 10) : '-';
            const sbst = h.sbstNm || 'HNS';
            const sub =
              [h.algoCd, h.fcstHr != null ? `${h.fcstHr}h` : null].filter(Boolean).join(' · ') ||
              '-';
            return {
              id: String(h.hnsAnlysSn),
              name: `${date} ${sbst} 대기확산`.trim(),
              sub,
            };
          })
        : checkedRescues.map((r) => {
            const date = r.regDtm ? r.regDtm.slice(0, 10) : '-';
            const vessel = r.vesselNm || '선박';
            const sub = [r.acdntTpCd, r.commanderNm].filter(Boolean).join(' · ') || '-';
            return {
              id: String(r.rescueOpsSn),
              name: `${date} ${vessel} 긴급구난`.trim(),
              sub,
            };
          });

  // 첫 번째 체크된 항목을 기준으로 메트릭 실제 값으로 보정
  const first = listRows[0];
  const items = mock.items.map((m) => {
    if (!first) return m;
    if (slotKey === 'oil') {
      const p = checkedPreds[0];
      if (!p) return m;
      const summaryKey = String(p.predRunSn ?? p.acdntSn);
      const oilSummary = oilSummaries[summaryKey]?.primary;
      if (!oilSummary) return m;
      switch (m.label) {
        case '예측 시간':
          return oilSummary.forecastDurationHr != null
            ? { ...m, value: `${oilSummary.forecastDurationHr}시간` } : m;
        case '최대 확산거리':
          return oilSummary.maxSpreadDistanceKm != null
            ? { ...m, value: `${oilSummary.maxSpreadDistanceKm.toFixed(1)} km` } : m;
        case '해안 도달 시간':
          return oilSummary.coastArrivalTimeHr != null
            ? { ...m, value: `${oilSummary.coastArrivalTimeHr}시간` } : m;
        case '영향 해안선':
          return oilSummary.affectedCoastlineKm != null
            ? { ...m, value: `${oilSummary.affectedCoastlineKm.toFixed(1)} km` } : m;
        case '풍화율':
          return oilSummary.weatheringRatePct != null
            ? { ...m, value: `${oilSummary.weatheringRatePct.toFixed(1)}%` } : m;
        case '잔존유량':
          return oilSummary.remainingVolumeKl != null
            ? { ...m, value: `${oilSummary.remainingVolumeKl.toFixed(1)} kL` } : m;
        default:
          return m;
      }
    } else if (slotKey === 'hns') {
      const h = checkedHns[0];
      if (!h) return m;
      if (m.label === '풍향' && h.windDir) return { ...m, value: `${h.windDir}` };
    }
    return m;
  });

  const modelString =
    slotKey === 'oil' && checkedPreds[0]
      ? `${checkedPreds[0].oilType || '-'}${checkedPreds[0].volume != null ? ` · ${checkedPreds[0].volume}kL` : ''}`
      : slotKey === 'hns' && checkedHns[0]
        ? `${checkedHns[0].algoCd ?? '-'} · ${checkedHns[0].sbstNm ?? '-'}${checkedHns[0].spilQty != null ? ` ${checkedHns[0].spilQty}${checkedHns[0].spilUnitCd ?? ''}` : ''}`
        : slotKey === 'rescue' && checkedRescues[0]
          ? `${checkedRescues[0].acdntTpCd ?? '-'} · ${checkedRescues[0].vesselNm ?? '-'}`
          : mock.model;

  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} {tag.fullLabel} 결과
        </div>
        <div className="text-caption text-fg-disabled font-mono">{modelString}</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">
        <div
          className="text-caption font-bold"
          style={{
            padding: '5px 10px',
            background: 'var(--bg-elevated)',
            borderBottom: '1px solid var(--stroke-default)',
            color: tag.color,
          }}
        >
          선택된 분석 ({listRows.length})
        </div>
        {listRows.length === 0 ? (
          <div
            className="text-caption text-fg-disabled text-center"
            style={{ padding: '8px 10px' }}
          >
            선택된 분석이 없습니다
          </div>
        ) : (
          listRows.map((r, i) => (
            <div
              key={r.id}
              style={{
                padding: '5px 10px',
                borderBottom:
                  i < listRows.length - 1 ? '1px solid var(--stroke-default)' : 'none',
                background: i % 2 === 0 ? 'var(--bg-surface)' : 'var(--bg-elevated)',
              }}
            >
              <div className="text-caption font-semibold whitespace-nowrap overflow-hidden text-ellipsis">
                {r.name}
              </div>
              <div className="text-caption text-fg-disabled font-mono">{r.sub}</div>
            </div>
          ))
        )}
      </div>

      {/* 메트릭 테이블 */}
      <div className="rounded-sm border border-stroke overflow-hidden">
        {items.map((item, i) => (
          <div
            key={i}
            className="flex justify-between items-center"
            style={{
              padding: '6px 10px',
              borderBottom: i < 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>


      {/* 시각화 영역 — 실제 지도 캡처 (선택 분석 레이어만 표출, 4:3 고정 비율) */}
      <div
        className="rounded-sm bg-bg-base border border-stroke overflow-hidden relative w-full shrink-0"
        style={{ aspectRatio: '4 / 3' }}
      >
        {incident ? (
          <SplitResultMap
            incident={incident}
            instanceKey={slotKey}
            layers={
              slotKey === 'oil'
                ? [...trajectoryLayers, ...(sensLayer ? [sensLayer] : [])]
                : slotKey === 'hns'
                  ? hnsZoneLayers
                  : []
            }
          />
        ) : (
          <div className="w-full h-full flex items-center justify-center text-caption text-fg-disabled">
            사고를 선택하세요
          </div>
        )}
        <div
          className="absolute top-1.5 left-1.5 text-caption font-mono rounded-sm"
          style={{
            padding: '2px 6px',
            background: 'rgba(0,0,0,0.55)',
            color: tag.color,
            border: `1px solid color-mix(in srgb, ${tag.color} 40%, transparent)`,
          }}
        >
          {tag.icon} {tag.label} 지도
        </div>
      </div>

      {/* 민감자원 섹션 (유출유 전용) */}
      {slotKey === 'oil' && (
        <div className="rounded-sm border border-stroke overflow-hidden">
          <div
            className="text-caption font-bold"
            style={{
              padding: '5px 10px',
              background: 'var(--bg-elevated)',
              borderBottom: '1px solid var(--stroke-default)',
              color: 'var(--color-success)',
            }}
          >
            🐟 민감자원 ({sensCategories.length})
          </div>
          {sensCategories.length === 0 ? (
            <div
              className="text-caption text-fg-disabled text-center"
              style={{ padding: '8px 10px' }}
            >
              -
            </div>
          ) : (
            sensCategories.map((cat, i) => {
              const areaLabel =
                cat.totalArea != null
                  ? `${cat.totalArea.toLocaleString('ko-KR', { maximumFractionDigits: 0 })}ha`
                  : `${cat.count}개소`;
              const isChecked = checkedSensCategories.has(cat.category);
              return (
                <div
                  key={cat.category}
                  className="flex items-center gap-1.5"
                  style={{
                    padding: '4px 10px',
                    borderBottom:
                      i < sensCategories.length - 1
                        ? '1px solid var(--stroke-default)'
                        : 'none',
                    background: i % 2 === 0 ? 'var(--bg-surface)' : 'var(--bg-elevated)',
                    opacity: isChecked ? 1 : 0.5,
                  }}
                >
                  <span className="text-caption">
                    {SPLIT_CATEGORY_ICON[cat.category] ?? '📍'}
                  </span>
                  <span className="flex-1 text-caption">{cat.category}</span>
                  <span className="text-caption font-mono text-fg-disabled">{areaLabel}</span>
                </div>
              );
            })
          )}
        </div>
      )}
    </>
  );
}

/* ── 분할 패널 내부 미니 지도 — 실제 체크된 분석 레이어 표출 ────────────── */
function SplitResultMap({
  incident,
  layers,
  instanceKey,
}: {
  incident: Incident;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  layers: any[];
  instanceKey: string;
}) {
  const mapStyle = useBaseMapStyle();
  const center: [number, number] = [incident.location.lon, incident.location.lat];
  // deck.gl 레이어는 단일 Deck 인스턴스 소유를 가정 → 분할마다 고유 id로 clone
  const scopedLayers = useMemo(
    () =>
      layers
        .filter((l) => l != null)
        .map((l) => (l && typeof l.clone === 'function' ? l.clone({ id: `${l.id}__${instanceKey}` }) : l)),
    [layers, instanceKey],
  );
  return (
    <MapLibreMap
      key={`${incident.id}-${instanceKey}`}
      initialViewState={{ longitude: center[0], latitude: center[1], zoom: 9 }}
      mapStyle={mapStyle}
      attributionControl={false}
      style={{ width: '100%', height: '100%' }}
    >
      <DeckGLOverlay layers={scopedLayers} />
    </MapLibreMap>
  );
}

/* ── (미사용) 분석별 SVG placeholder — 참고용 보존 ────────────────── */
export function SplitVisualization({ slotKey, color }: { slotKey: SplitSlotKey; color: string }) {
  if (slotKey === 'oil') {
    return (
      <svg viewBox="0 0 320 200" className="w-full h-full">
        <defs>
          <radialGradient id="oilSpread" cx="35%" cy="55%" r="55%">
            <stop offset="0%" stopColor={color} stopOpacity="0.85" />
            <stop offset="60%" stopColor={color} stopOpacity="0.35" />
            <stop offset="100%" stopColor={color} stopOpacity="0" />
          </radialGradient>
          <linearGradient id="oilSea" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="#0e3a5f" />
            <stop offset="100%" stopColor="#072338" />
          </linearGradient>
        </defs>
        <rect width="320" height="200" fill="url(#oilSea)" />
        {/* 해안선 */}
        <path
          d="M 0,20 C 60,30 120,5 180,25 C 230,40 270,20 320,35 L 320,0 L 0,0 Z"
          fill="#334155"
          opacity="0.9"
        />
        <path
          d="M 0,20 C 60,30 120,5 180,25 C 230,40 270,20 320,35"
          stroke="#94a3b8"
          strokeWidth="1"
          fill="none"
          opacity="0.8"
        />
        {/* 확산 타원 */}
        <ellipse cx="120" cy="120" rx="90" ry="45" fill="url(#oilSpread)" />
        {/* 사고점 */}
        <circle cx="120" cy="120" r="4" fill="#ffffff" />
        <circle cx="120" cy="120" r="7" fill="none" stroke="#ffffff" strokeOpacity="0.6" />
        {/* 궤적 화살표 */}
        <path
          d="M 120,120 Q 170,100 210,85 L 200,80 M 210,85 L 205,93"
          stroke={color}
          strokeWidth="2"
          fill="none"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
        <text x="10" y="190" fill="#cbd5e1" fontSize="9" fontFamily="monospace">
          유출유 확산 시뮬레이션 (72h)
        </text>
      </svg>
    );
  }
  if (slotKey === 'hns') {
    return (
      <svg viewBox="0 0 320 200" className="w-full h-full">
        <defs>
          <linearGradient id="hnsSky" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="#1e293b" />
            <stop offset="100%" stopColor="#0f172a" />
          </linearGradient>
        </defs>
        <rect width="320" height="200" fill="url(#hnsSky)" />
        {/* AEGL 3단 동심원 (풍하방향 오프셋) */}
        <ellipse cx="140" cy="110" rx="130" ry="55" fill="#fde047" fillOpacity="0.18" stroke="#fde047" strokeOpacity="0.5" />
        <ellipse cx="130" cy="108" rx="90" ry="40" fill="#fb923c" fillOpacity="0.25" stroke="#fb923c" strokeOpacity="0.6" />
        <ellipse cx="120" cy="106" rx="50" ry="24" fill="#ef4444" fillOpacity="0.35" stroke="#ef4444" strokeOpacity="0.75" />
        {/* 누출점 */}
        <circle cx="90" cy="106" r="5" fill="#ffffff" />
        <circle cx="90" cy="106" r="8" fill="none" stroke="#ffffff" strokeOpacity="0.5" />
        {/* 풍향 콘 */}
        <path d="M 90,106 L 60,80 L 70,95 L 55,90 L 70,105 Z" fill="#38bdf8" fillOpacity="0.9" />
        <text x="30" y="75" fill="#38bdf8" fontSize="10" fontFamily="monospace">
          풍향
        </text>
        {/* 범례 */}
        <g fontFamily="monospace" fontSize="9">
          <circle cx="235" cy="35" r="5" fill="#ef4444" />
          <text x="245" y="38" fill="#cbd5e1">IDLH</text>
          <circle cx="235" cy="52" r="5" fill="#fb923c" />
          <text x="245" y="55" fill="#cbd5e1">ERPG-2</text>
          <circle cx="235" cy="69" r="5" fill="#fde047" />
          <text x="245" y="72" fill="#cbd5e1">ERPG-1</text>
        </g>
        <text x="10" y="190" fill="#cbd5e1" fontSize="9" fontFamily="monospace">
          HNS 대기 확산 (AEGL 등급)
        </text>
      </svg>
    );
  }
  // rescue
  return (
    <svg viewBox="0 0 320 200" className="w-full h-full">
      <defs>
        <linearGradient id="rescueSea" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#083244" />
          <stop offset="100%" stopColor="#041a24" />
        </linearGradient>
      </defs>
      <rect width="320" height="200" fill="url(#rescueSea)" />
      {/* 표류 확률 타원(Monte Carlo) */}
      <ellipse
        cx="170"
        cy="110"
        rx="95"
        ry="45"
        fill={color}
        fillOpacity="0.12"
        stroke={color}
        strokeOpacity="0.55"
        strokeDasharray="4 3"
      />
      <ellipse
        cx="165"
        cy="108"
        rx="60"
        ry="28"
        fill={color}
        fillOpacity="0.22"
        stroke={color}
        strokeOpacity="0.7"
      />
      {/* Sector Search 패턴 */}
      <g stroke="#ffffff" strokeOpacity="0.5" strokeWidth="1" fill="none">
        <path d="M 165,108 L 215,70" />
        <path d="M 215,70 L 235,120" />
        <path d="M 235,120 L 175,145" />
        <path d="M 175,145 L 115,125" />
        <path d="M 115,125 L 135,75" />
        <path d="M 135,75 L 165,108" />
      </g>
      {/* 사고점 */}
      <circle cx="100" cy="108" r="5" fill="#ef4444" />
      <circle cx="100" cy="108" r="9" fill="none" stroke="#ef4444" strokeOpacity="0.6" />
      <text x="85" y="98" fill="#ef4444" fontSize="10" fontFamily="monospace">
        사고점
      </text>
      {/* 표류 화살표 */}
      <path
        d="M 100,108 Q 130,100 165,108 L 155,103 M 165,108 L 156,113"
        stroke={color}
        strokeWidth="2"
        fill="none"
        strokeLinecap="round"
      />
      <text x="10" y="190" fill="#cbd5e1" fontSize="9" fontFamily="monospace">
        구조 시나리오 (Sector Search)
      </text>
    </svg>
  );
}

/* ════════════════════════════════════════════════════

   VesselPopupPanel / VesselDetailModal 공용 유틸
   ════════════════════════════════════════════════════ */
export 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())}`;
}

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