import { useState, useMemo, useEffect, useCallback } from 'react';
import type { Ship, ChnPrmShipInfo, VesselAnalysisDto, RiskLevel } from '../../types';
import { FONT_MONO } from '../../styles/fonts';
import { getMarineTrafficCategory } from '../../utils/marineTraffic';
import { lookupPermittedShip } from '../../services/chnPrmShip';
import { fetchVesselTrack } from '../../services/vesselTrack';
import type { UseVesselAnalysisResult } from '../../hooks/useVesselAnalysis';
import { RISK_TO_ALERT } from '../../constants/riskMapping';
import { Map as MapGL, Source, Layer, Marker } from 'react-map-gl/maplibre';

// MarineTraffic 사진 캐시 (null = 없음, undefined = 미조회)
const mtPhotoCache = new Map<string, string | null>();

async function loadMarineTrafficPhoto(mmsi: string): Promise<string | null> {
  if (mtPhotoCache.has(mmsi)) return mtPhotoCache.get(mmsi) ?? null;
  return new Promise(resolve => {
    const url = `https://photos.marinetraffic.com/ais/showphoto.aspx?mmsi=${mmsi}&size=thumb300`;
    const img = new Image();
    img.onload = () => { mtPhotoCache.set(mmsi, url); resolve(url); };
    img.onerror = () => { mtPhotoCache.set(mmsi, null); resolve(null); };
    img.src = url;
  });
}

// S&P Global 이미지 캐시
const spgCache = new Map<string, string | null>();

async function loadSpgPhoto(imo: string, shipImagePath: string): Promise<string | null> {
  if (spgCache.has(imo)) return spgCache.get(imo) ?? null;
  try {
    const res = await fetch(`/signal-batch/api/v1/shipimg/${imo}`);
    if (!res.ok) throw new Error();
    const data: Array<{ picId: number; path: string }> = await res.json();
    const url = data.length > 0 ? `${data[0].path}_2.jpg` : `${shipImagePath.replace(/_[12]\.\w+$/, '')}_2.jpg`;
    spgCache.set(imo, url);
    return url;
  } catch {
    const fallback = `${shipImagePath.replace(/_[12]\.\w+$/, '')}_2.jpg`;
    spgCache.set(imo, fallback);
    return fallback;
  }
}

// ── 항상 다크 테마 색상 팔레트
const C = {
  bg: '#07101A',
  bg2: '#0C1825',
  bg3: '#112033',
  panel: '#040C14',
  green: '#00E676',
  cyan: '#18FFFF',
  amber: '#FFD740',
  red: '#FF5252',
  purple: '#E040FB',
  ink: '#CFE2F3',
  ink2: '#7EA8C4',
  ink3: '#3D6480',
  border: '#1A3350',
  border2: '#0E2035',
} as const;

const MINIMAP_STYLE = {
  version: 8 as const,
  sources: {
    'carto-dark': {
      type: 'raster' as const,
      tiles: ['https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}@2x.png'],
      tileSize: 256,
    },
  },
  layers: [{ id: 'carto-dark', type: 'raster' as const, source: 'carto-dark' }],
};

function stateLabel(s: string): string {
  const map: Record<string, string> = {
    FISHING: '조업중', SAILING: '항행중', STATIONARY: '정박', AIS_LOSS: 'AIS소실',
  };
  return map[s] ?? s;
}

function zoneLabel(z: string): string {
  const map: Record<string, string> = {
    TERRITORIAL: '영해(침범!)', CONTIGUOUS: '접속수역', EEZ: 'EEZ', BEYOND: 'EEZ외측',
  };
  return map[z] ?? z;
}


interface ProcessedVessel {
  ship: Ship;
  zone: string;
  state: string;
  alert: 'CRITICAL' | 'WATCH' | 'MONITOR' | 'NORMAL';
  vtype: string;
  cluster: string;
}

interface LogEntry {
  ts: string;
  mmsi: string;
  name: string;
  type: string;
  level: 'critical' | 'watch' | 'info';
}

interface Props {
  ships: Ship[];
  vesselAnalysis?: UseVesselAnalysisResult;
  onClose: () => void;
  onShowReport?: () => void;
}

const PIPE_STEPS = [
  { num: '01', name: 'AIS 전처리' },
  { num: '02', name: '행동 상태 탐지' },
  { num: '03', name: '궤적 리샘플링' },
  { num: '04', name: '특징 벡터 추출' },
  { num: '05', name: '규칙 기반 분류' },
  { num: '06', name: 'BIRCH 군집화' },
  { num: '07', name: '계절 활동 분석' },
];

const ALERT_ORDER: Record<string, number> = { CRITICAL: 0, WATCH: 1, MONITOR: 2, NORMAL: 3 };

export function FieldAnalysisModal({ ships, vesselAnalysis, onClose, onShowReport }: Props) {
  const emptyMap = useMemo(() => new Map<string, VesselAnalysisDto>(), []);
  const analysisMap = vesselAnalysis?.analysisMap ?? emptyMap;
  const [activeFilter, setActiveFilter] = useState('ALL');
  const [search, setSearch] = useState('');
  const [selectedMmsi, setSelectedMmsi] = useState<string | null>(null);
  const [logs, setLogs] = useState<LogEntry[]>([]);
  // pipeStep 제거 — 파이프라인 상태는 analysisMap 존재 여부로 판단
  const [tick, setTick] = useState(0);

  // 중국 어선만 필터
  const cnFishing = useMemo(() => ships.filter(s => {
    if (s.flag !== 'CN') return false;
    const cat = getMarineTrafficCategory(s.typecode, s.category);
    return cat === 'fishing' || s.category === 'fishing';
  }), [ships]);

  // 선박 데이터 처리 — Python 분석 결과 기반 (경량 분석 포함)
  const processed = useMemo((): ProcessedVessel[] => {
    return cnFishing
      .filter(ship => analysisMap.has(ship.mmsi))
      .map(ship => {
        const dto = analysisMap.get(ship.mmsi)!;
        const zone = dto.algorithms.location.zone;
        const state = dto.algorithms.activity.state;
        const alert = RISK_TO_ALERT[dto.algorithms.riskScore.level as RiskLevel];
        const vtype = dto.classification.vesselType ?? 'UNKNOWN';
        const clusterId = dto.algorithms.cluster.clusterId ?? -1;
        const cluster = clusterId >= 0 ? `C-${String(clusterId).padStart(2, '0')}` : '—';
        return { ship, zone, state, alert, vtype, cluster };
      });
  }, [cnFishing, analysisMap]);

  // 필터 + 정렬
  const displayed = useMemo(() => {
    return processed
      .filter(v => {
        if (activeFilter === 'CRITICAL' && v.alert !== 'CRITICAL') return false;
        if (activeFilter === 'FISHING' && v.state !== 'FISHING') return false;
        if (activeFilter === 'AIS_LOSS' && v.state !== 'AIS_LOSS') return false;
        if (activeFilter === 'TERRITORIAL' && v.zone !== 'TERRITORIAL') return false;
        if (search && !v.ship.mmsi.includes(search) && !v.ship.name.toLowerCase().includes(search)) return false;
        return true;
      })
      .sort((a, b) => ALERT_ORDER[a.alert] - ALERT_ORDER[b.alert]);
  }, [processed, activeFilter, search]);

  // 통계 — Python 분석 결과 기반
  const stats = useMemo(() => {
    let gpsAnomaly = 0;
    let bd09Detected = 0;
    for (const v of processed) {
      const dto = analysisMap.get(v.ship.mmsi);
      if (dto) {
        if (dto.algorithms.gpsSpoofing.spoofingScore > 0.5) gpsAnomaly++;
        if (dto.algorithms.gpsSpoofing.bd09OffsetM > 100) bd09Detected++;
      }
    }
    return {
      total: processed.length,
      territorial: processed.filter(v => v.zone === 'TERRITORIAL_SEA' || v.zone === 'ZONE_I').length,
      fishing: processed.filter(v => v.state === 'FISHING').length,
      aisLoss: processed.filter(v => v.state === 'AIS_LOSS' || v.state === 'UNKNOWN').length,
      gpsAnomaly,
      bd09Detected,
      clusters: new Set(processed.filter(v => v.cluster !== '—').map(v => v.cluster)).size,
      trawl: processed.filter(v => v.vtype === 'TRAWL').length,
      purse: processed.filter(v => v.vtype === 'PURSE').length,
    };
  }, [processed, analysisMap]);

  // 구역별 카운트 — Python zone 분류 기반
  const zoneCounts = useMemo(() => ({
    terr: processed.filter(v => v.zone === 'TERRITORIAL_SEA' || v.zone === 'ZONE_I').length,
    cont: processed.filter(v => v.zone === 'CONTIGUOUS_ZONE' || v.zone === 'ZONE_II').length,
    eez: processed.filter(v => v.zone === 'EEZ_OR_BEYOND' || v.zone === 'ZONE_III' || v.zone === 'ZONE_IV').length,
    beyond: processed.filter(v => !['TERRITORIAL_SEA', 'CONTIGUOUS_ZONE', 'EEZ_OR_BEYOND', 'ZONE_I', 'ZONE_II', 'ZONE_III', 'ZONE_IV'].includes(v.zone)).length,
  }), [processed]);

  // 초기 경보 로그 생성
  useEffect(() => {
    const initLogs: LogEntry[] = processed
      .filter(v => v.alert === 'CRITICAL' || v.alert === 'WATCH')
      .slice(0, 10)
      .map((v, i) => {
        const t = new Date(Date.now() - i * 4 * 60000);
        const ts = t.toTimeString().slice(0, 8);
        const type =
          v.zone === 'TERRITORIAL' ? '영해 내 불법조업 탐지' :
          v.state === 'AIS_LOSS' ? 'AIS 신호 소실 — Dark Vessel 의심' :
          '접속수역 조업 행위 감지';
        return { ts, mmsi: v.ship.mmsi, name: v.ship.name || '(Unknown)', type, level: v.alert === 'CRITICAL' ? 'critical' : 'watch' };
      });
    setLogs(initLogs);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // 시계 tick
  useEffect(() => {
    const t = setInterval(() => setTick(s => s + 1), 1000);
    return () => clearInterval(t);
  }, []);
  void tick; // used to force re-render for clock

  // Escape 키 닫기
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const selectedVessel = useMemo(() =>
    selectedMmsi ? processed.find(v => v.ship.mmsi === selectedMmsi) ?? null : null,
    [selectedMmsi, processed],
  );

  // 항적 미니맵
  const [trackCoords, setTrackCoords] = useState<[number, number][]>([]);

  useEffect(() => {
    if (!selectedVessel) { setTrackCoords([]); return; }
    fetchVesselTrack(selectedVessel.ship.mmsi, 72).then(setTrackCoords);
  }, [selectedVessel]);

  // 허가 정보
  const [permitStatus, setPermitStatus] = useState<'idle' | 'loading' | 'found' | 'not-found'>('idle');
  const [permitData, setPermitData] = useState<ChnPrmShipInfo | null>(null);

  // 선박 사진
  const [photoUrl, setPhotoUrl] = useState<string | null | undefined>(undefined); // undefined=로딩, null=없음

  useEffect(() => {
    if (!selectedVessel) return;
    const { ship } = selectedVessel;

    // 허가 조회
    setPermitStatus('loading');
    setPermitData(null);
    lookupPermittedShip(ship.mmsi).then(data => {
      setPermitData(data);
      setPermitStatus(data ? 'found' : 'not-found');
    });

    // 사진 로드: S&P Global 우선, 없으면 MarineTraffic
    setPhotoUrl(undefined);
    if (ship.imo && ship.shipImagePath) {
      loadSpgPhoto(ship.imo, ship.shipImagePath).then(url => {
        if (url) { setPhotoUrl(url); return; }
        loadMarineTrafficPhoto(ship.mmsi).then(setPhotoUrl);
      });
    } else {
      loadMarineTrafficPhoto(ship.mmsi).then(setPhotoUrl);
    }
  }, [selectedMmsi]); // eslint-disable-line react-hooks/exhaustive-deps

  const addLog = useCallback((mmsi: string, name: string, type: string, level: 'critical' | 'watch') => {
    const ts = new Date().toTimeString().slice(0, 8);
    setLogs(prev => [{ ts, mmsi, name, type, level }, ...prev].slice(0, 60));
  }, []);

  const downloadCsv = useCallback(() => {
    const headers = ['MMSI', '선명', '위도', '경도', 'SOG(kt)', '침로(°)', '상태', '선종', '구역', '클러스터', '경보등급', '마지막수신(분전)'];
    const rows = processed.map(v => {
      const ageMins = Math.floor((Date.now() - v.ship.lastSeen) / 60000);
      return [
        v.ship.mmsi,
        v.ship.name || '',
        v.ship.lat.toFixed(5),
        v.ship.lng.toFixed(5),
        v.state === 'AIS_LOSS' ? '' : v.ship.speed.toFixed(1),
        v.state === 'AIS_LOSS' ? '' : String(v.ship.course),
        stateLabel(v.state),
        v.vtype,
        zoneLabel(v.zone),
        v.cluster,
        v.alert,
        String(ageMins),
      ].map(s => `"${s}"`).join(',');
    });
    const csv = [headers.join(','), ...rows].join('\n');
    const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `cn_fishing_vessels_${new Date().toISOString().slice(0, 10)}.csv`;
    a.click();
    URL.revokeObjectURL(url);
  }, [processed]);

  // 색상 헬퍼
  const alertColor = (al: string) => ({ CRITICAL: C.red, WATCH: C.amber, MONITOR: C.cyan, NORMAL: C.green }[al] ?? C.ink3);
  const zoneColor = (z: string) => ({ TERRITORIAL: C.red, CONTIGUOUS: C.amber, EEZ: C.cyan, BEYOND: C.green }[z] ?? C.ink3);
  const stateColor = (s: string) => ({ FISHING: C.amber, SAILING: C.cyan, STATIONARY: C.green, AIS_LOSS: C.red }[s] ?? C.ink3);

  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 2000,
      background: 'rgba(2,6,14,0.96)',
      display: 'flex', flexDirection: 'column',
      fontFamily: FONT_MONO,
    }}>
      {/* ── 헤더 */}
      <div style={{
        background: C.panel,
        borderBottom: `1px solid ${C.border}`,
        padding: '10px 20px',
        display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0,
      }}>
        <span style={{ color: C.green, fontSize: 9, letterSpacing: 3 }}>▶ FIELD ANALYSIS</span>
        <span style={{ color: '#fff', fontSize: 14, fontWeight: 700, letterSpacing: 1 }}>중국 불법어업 현장분석 대시보드</span>
        <span style={{ color: C.ink3, fontSize: 10 }}>AIS · 규칙분류 · BIRCH · Shepperson(2017) · Yan et al.(2022)</span>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 16, alignItems: 'center' }}>
          <span style={{ color: C.green, fontSize: 10, display: 'flex', alignItems: 'center', gap: 4 }}>
            <span style={{ width: 6, height: 6, borderRadius: '50%', background: C.green, display: 'inline-block', animation: 'pulse 1.5s ease-in-out infinite' }} />
            LIVE
          </span>
          <span style={{ color: C.ink2, fontSize: 10 }}>{new Date().toLocaleTimeString('ko-KR')}</span>
          {onShowReport && (
            <button
              type="button"
              onClick={onShowReport}
              style={{
                background: 'rgba(99,179,237,0.1)', border: '1px solid rgba(99,179,237,0.4)',
                color: '#63b3ed', padding: '4px 14px', cursor: 'pointer',
                fontSize: 11, borderRadius: 2, fontFamily: 'inherit',
              }}
            >
              📋 보고서
            </button>
          )}
          <button
            type="button"
            onClick={onClose}
            style={{
              background: 'rgba(255,82,82,0.1)', border: `1px solid rgba(255,82,82,0.4)`,
              color: C.red, padding: '4px 14px', cursor: 'pointer',
              fontSize: 11, borderRadius: 2, fontFamily: 'inherit',
            }}
          >
            ✕ 닫기
          </button>
        </div>
      </div>

      {/* ── 통계 스트립 */}
      <div style={{
        display: 'flex', gap: 8, padding: '8px 12px',
        background: C.bg, flexShrink: 0,
        borderBottom: `1px solid ${C.border}`,
      }}>
        {[
          { label: '총 탐지 어선', val: stats.total, color: C.cyan, sub: 'AIS 수신 기준' },
          { label: '영해 침범', val: stats.territorial, color: C.red, sub: '12NM 이내' },
          { label: '조업 중', val: stats.fishing, color: C.amber, sub: 'SOG 0.5–5.0kt' },
          { label: 'AIS 소실', val: stats.aisLoss, color: C.red, sub: '>20분 미수신' },
          { label: 'GPS 이상', val: stats.gpsAnomaly, color: C.purple, sub: 'BD-09 스푸핑 50%↑' },
          { label: '집단 클러스터', val: stats.clusters, color: C.amber, sub: 'DBSCAN 군집' },
          { label: '트롤어선', val: stats.trawl, color: C.purple, sub: 'Python 분류' },
          { label: '선망어선', val: stats.purse, color: C.cyan, sub: 'Python 분류' },
        ].map(({ label, val, color, sub }) => (
          <div key={label} style={{
            flex: 1, background: C.bg2, border: `1px solid ${C.border}`,
            borderRadius: 3, padding: '8px 10px', textAlign: 'center',
            borderTop: `2px solid ${color}`,
          }}>
            <div style={{ fontSize: 9, color: C.ink3, letterSpacing: 1 }}>{label}</div>
            <div style={{ fontSize: 22, fontWeight: 700, color, lineHeight: 1.2 }}>{val}</div>
            <div style={{ fontSize: 9, color: C.ink3 }}>{sub}</div>
          </div>
        ))}
      </div>

      {/* ── 메인 그리드 */}
      <div style={{
        display: 'flex', flex: 1, overflow: 'hidden',
        background: C.bg,
      }}>
        {/* ── 좌측 패널: 구역 현황 + AI 파이프라인 */}
        <div style={{
          width: 240, flexShrink: 0,
          background: C.panel, borderRight: `1px solid ${C.border}`,
          overflow: 'auto', padding: '10px 12px',
        }}>
          <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, marginBottom: 8, paddingBottom: 6, borderBottom: `1px solid ${C.border}` }}>
            구역별 현황
            <span style={{ float: 'right', color: C.green, fontSize: 8 }}>●</span>
          </div>

          {([
            { label: '영해 (12NM)', count: zoneCounts.terr, color: C.red, sub: '즉시 퇴거 명령 필요' },
            { label: '접속수역 (24NM)', count: zoneCounts.cont, color: C.amber, sub: '조업 행위 집중 모니터링' },
            { label: 'EEZ 내측', count: zoneCounts.eez, color: C.amber, sub: '조업밀도 핫스팟 포함' },
            { label: 'EEZ 외측', count: zoneCounts.beyond, color: C.green, sub: '정상 모니터링' },
          ] as const).map(({ label, count, color, sub }) => {
            const max = Math.max(processed.length, 1);
            return (
              <div key={label} style={{ marginBottom: 10 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 3 }}>
                  <span style={{ fontSize: 10, color }}>{label}</span>
                  <span style={{ fontSize: 11, fontWeight: 700, color }}>{count}</span>
                </div>
                <div style={{ height: 4, background: C.border2, borderRadius: 2, overflow: 'hidden' }}>
                  <div style={{ height: '100%', width: `${Math.min((count / max) * 100, 100)}%`, background: color, borderRadius: 2, transition: 'width 0.5s' }} />
                </div>
                <div style={{ fontSize: 9, color: C.ink3, marginTop: 2 }}>{sub}</div>
              </div>
            );
          })}

          <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, margin: '12px 0 8px', paddingBottom: 6, borderBottom: `1px solid ${C.border}` }}>
            AI 파이프라인 상태
            <span style={{ float: 'right', color: analysisMap.size > 0 ? C.green : C.red, fontSize: 8 }}>●</span>
          </div>

          {PIPE_STEPS.map((step) => {
            const connected = analysisMap.size > 0;
            return (
              <div key={step.num} style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5 }}>
                <span style={{ fontSize: 9, color: C.ink3, width: 20 }}>{step.num}</span>
                <span style={{ fontSize: 10, color: C.ink, flex: 1 }}>{step.name}</span>
                <span style={{
                  fontSize: 8, padding: '1px 6px', borderRadius: 2,
                  background: connected ? 'rgba(0,230,118,0.1)' : 'rgba(255,82,82,0.1)',
                  border: `1px solid ${connected ? C.green : C.red}`,
                  color: connected ? C.green : C.red,
                  fontWeight: 400,
                }}>
                  {connected ? 'ON' : 'OFF'}
                </span>
              </div>
            );
          })}

          {[
            {
              num: 'GPS', name: 'BD-09 변환',
              status: stats.bd09Detected > 0 ? `${stats.bd09Detected}척 탐지` : 'CLEAR',
              color: stats.bd09Detected > 0 ? C.amber : C.green,
              active: stats.bd09Detected > 0,
            },
            { num: 'NRD', name: '레이더 교차검증', status: '미연동', color: C.ink3, active: false },
          ].map(step => (
            <div key={step.num} style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5 }}>
              <span style={{ fontSize: 9, color: C.ink3, width: 20 }}>{step.num}</span>
              <span style={{ fontSize: 10, color: C.ink, flex: 1 }}>{step.name}</span>
              <span style={{
                fontSize: 8, padding: '1px 6px', borderRadius: 2,
                background: step.active ? 'rgba(255,215,64,0.12)' : 'rgba(24,255,255,0.08)',
                border: `1px solid ${step.active ? C.amber : C.border}`,
                color: step.color,
                fontWeight: step.active ? 700 : 400,
              }}>
                {step.status}
              </span>
            </div>
          ))}

          {/* 알고리즘 기준 요약 */}
          <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, margin: '12px 0 8px', paddingBottom: 6, borderBottom: `1px solid ${C.border}` }}>
            알고리즘 기준
          </div>
          {[
            { label: '위치 판정', val: 'Haversine + 기선', color: C.ink2 },
            { label: '조업 패턴', val: 'UCAF/UCFT SOG', color: C.ink2 },
            { label: 'AIS 소실', val: '>20분 미수신', color: C.amber },
            { label: 'GPS 조작', val: 'BD-09 좌표계', color: C.purple },
            { label: '클러스터', val: 'DBSCAN 3NM (Python)', color: C.ink2 },
            { label: '선종 분류', val: 'Python 7단계 파이프라인', color: C.green },
          ].map(({ label, val, color }) => (
            <div key={label} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
              <span style={{ fontSize: 9, color: C.ink3 }}>{label}</span>
              <span style={{ fontSize: 9, color }}>{val}</span>
            </div>
          ))}

          {/* 위험도 점수 기준 */}
          <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, margin: '12px 0 8px', paddingBottom: 6, borderBottom: `1px solid ${C.border}` }}>
            위험도 점수 기준
          </div>
          <div style={{ fontSize: 9, color: C.ink3, lineHeight: 1.8 }}>
            {[
              { title: '■ 위치 (최대 40점)', items: ['영해 내: 40 / 접속수역: 10'] },
              { title: '■ 조업 행위 (최대 30점)', items: ['영해 내 조업: 20 / 기타 조업: 5', 'U-turn 패턴: 10'] },
              { title: '■ AIS 조작 (최대 35점)', items: ['순간이동: 20 / 장시간 갭: 15', '단시간 갭: 5'] },
              { title: '■ 허가 이력 (최대 20점)', items: ['미허가 어선: 20'] },
            ].map(({ title, items }) => (
              <div key={title} style={{ marginBottom: 6 }}>
                <div style={{ color: C.ink2 }}>{title}</div>
                {items.map(item => <div key={item} style={{ paddingLeft: 8 }}>{item}</div>)}
              </div>
            ))}
            <div style={{ marginTop: 6, borderTop: `1px solid ${C.border}`, paddingTop: 6, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2px 8px' }}>
              <span style={{ color: C.red }}>CRITICAL ≥70</span>
              <span style={{ color: C.amber }}>WATCH ≥50</span>
              <span style={{ color: C.cyan }}>MONITOR ≥30</span>
              <span style={{ color: C.green }}>NORMAL {'<'}30</span>
            </div>
            <div style={{ marginTop: 6, color: C.ink3 }}>
              UCAF: 어구별 조업속도 매칭 비율<br />
              UCFT: 조업-항행 구분 신뢰도<br />
              스푸핑: 순간이동+SOG급변+BD09 종합
            </div>
          </div>
        </div>

        {/* ── 중앙 패널: 선박 테이블 */}
        <div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
          {/* 필터 바 */}
          <div style={{
            display: 'flex', gap: 6, padding: '8px 12px', alignItems: 'center',
            background: C.bg2, borderBottom: `1px solid ${C.border}`, flexShrink: 0,
            flexWrap: 'wrap',
          }}>
            {[
              { key: 'ALL', label: '전체' },
              { key: 'CRITICAL', label: '긴급 경보' },
              { key: 'FISHING', label: '조업 중' },
              { key: 'AIS_LOSS', label: 'AIS 소실' },
              { key: 'TERRITORIAL', label: '영해 내' },
            ].map(({ key, label }) => (
              <button
                key={key}
                type="button"
                onClick={() => setActiveFilter(key)}
                style={{
                  padding: '3px 10px', fontSize: 10, cursor: 'pointer',
                  borderRadius: 2, fontFamily: 'inherit',
                  background: activeFilter === key ? 'rgba(0,230,118,0.15)' : C.bg3,
                  border: `1px solid ${activeFilter === key ? C.green : C.border}`,
                  color: activeFilter === key ? C.green : C.ink2,
                }}
              >
                {label}
              </button>
            ))}
            <input
              value={search}
              onChange={e => setSearch(e.target.value.toLowerCase())}
              placeholder="MMSI / 선명 검색..."
              style={{
                flex: 1, minWidth: 120,
                background: C.bg3, border: `1px solid ${C.border}`,
                color: C.ink, padding: '3px 10px', fontSize: 10,
                borderRadius: 2, outline: 'none', fontFamily: 'inherit',
              }}
            />
            <span style={{ color: C.ink3, fontSize: 10, whiteSpace: 'nowrap' }}>
              표시: <span style={{ color: C.cyan }}>{displayed.length}</span> 척
            </span>
            <button
              type="button"
              onClick={downloadCsv}
              title="CSV 다운로드"
              style={{
                padding: '3px 10px', fontSize: 10, cursor: 'pointer',
                borderRadius: 2, fontFamily: 'inherit', whiteSpace: 'nowrap',
                background: 'rgba(24,255,255,0.08)', border: `1px solid ${C.border}`, color: C.cyan,
              }}
            >
              ↓ CSV
            </button>
          </div>

          {/* 테이블 */}
          <div style={{ flex: 1, overflow: 'auto' }}>
            <table style={{ width: '100%', borderCollapse: 'separate', borderSpacing: 0 }}>
              <thead>
                <tr style={{ position: 'sticky', top: 0, background: C.panel, zIndex: 1 }}>
                  {['AIS', 'MMSI', '선명', '위도', '경도', 'SOG', '침로', '상태', '선종', '구역', '클러스터', '경보', '수신'].map(h => (
                    <th key={h} style={{
                      padding: '6px 8px', fontSize: 9, color: C.ink3, fontWeight: 600,
                      letterSpacing: 1, textAlign: 'left',
                      borderBottom: `1px solid ${C.border}`, whiteSpace: 'nowrap',
                    }}>{h}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {displayed.slice(0, 120).map(v => {
                  const rowBg =
                    v.alert === 'CRITICAL' ? 'rgba(255,82,82,0.08)' :
                    v.alert === 'WATCH' ? 'rgba(255,215,64,0.05)' :
                    v.alert === 'MONITOR' ? 'rgba(24,255,255,0.04)' :
                    'transparent';
                  const isSelected = v.ship.mmsi === selectedMmsi;
                  const ageMins = Math.floor((Date.now() - v.ship.lastSeen) / 60000);
                  return (
                    <tr
                      key={v.ship.mmsi}
                      onClick={() => setSelectedMmsi(v.ship.mmsi)}
                      style={{
                        background: isSelected ? 'rgba(0,230,118,0.08)' : rowBg,
                        cursor: 'pointer',
                        outline: isSelected ? `1px solid ${C.green}` : undefined,
                      }}
                    >
                      <td style={{ padding: '5px 8px' }}>
                        <span style={{
                          display: 'inline-block', width: 7, height: 7, borderRadius: '50%',
                          background: v.state === 'AIS_LOSS' ? C.red : C.green,
                        }} />
                      </td>
                      <td style={{ fontSize: 10, color: C.cyan, whiteSpace: 'nowrap', padding: '5px 8px' }}>{v.ship.mmsi}</td>
                      <td style={{ fontSize: 10, color: '#fff', padding: '5px 8px', maxWidth: 90, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {v.ship.name || '(Unknown)'}
                      </td>
                      <td style={{ fontSize: 10, color: C.ink2, padding: '5px 8px', whiteSpace: 'nowrap' }}>{v.ship.lat.toFixed(3)}°N</td>
                      <td style={{ fontSize: 10, color: C.ink2, padding: '5px 8px', whiteSpace: 'nowrap' }}>{v.ship.lng.toFixed(3)}°E</td>
                      <td style={{ fontSize: 10, color: C.amber, padding: '5px 8px', whiteSpace: 'nowrap' }}>
                        {v.state === 'AIS_LOSS' ? '—' : `${v.ship.speed.toFixed(1)}kt`}
                      </td>
                      <td style={{ fontSize: 10, color: C.ink2, padding: '5px 8px', whiteSpace: 'nowrap' }}>
                        {v.state !== 'AIS_LOSS' ? `${v.ship.course}°` : '—'}
                      </td>
                      <td style={{ padding: '5px 8px' }}>
                        <span style={{
                          fontSize: 9, padding: '2px 5px', borderRadius: 2,
                          background: `${stateColor(v.state)}22`,
                          border: `1px solid ${stateColor(v.state)}66`,
                          color: stateColor(v.state),
                        }}>
                          {stateLabel(v.state)}
                        </span>
                      </td>
                      <td style={{ padding: '5px 8px' }}>
                        <span style={{
                          fontSize: 9, padding: '2px 5px', borderRadius: 2,
                          background: 'rgba(24,255,255,0.08)', border: `1px solid ${C.border}`, color: C.cyan,
                        }}>
                          {v.vtype}
                        </span>
                      </td>
                      <td style={{ padding: '5px 8px' }}>
                        <span style={{
                          fontSize: 9, padding: '2px 5px', borderRadius: 2, whiteSpace: 'nowrap',
                          background: `${zoneColor(v.zone)}15`,
                          border: `1px solid ${zoneColor(v.zone)}55`,
                          color: zoneColor(v.zone),
                        }}>
                          {zoneLabel(v.zone)}
                        </span>
                      </td>
                      <td style={{ padding: '5px 8px', fontSize: 10, color: v.cluster !== '—' ? C.purple : C.ink3 }}>
                        {v.cluster}
                      </td>
                      <td style={{ padding: '5px 8px' }}>
                        <span style={{
                          fontSize: 9, padding: '2px 5px', borderRadius: 2,
                          background: `${alertColor(v.alert)}15`,
                          border: `1px solid ${alertColor(v.alert)}55`,
                          color: alertColor(v.alert),
                        }}>
                          {v.alert}
                        </span>
                      </td>
                      <td style={{ fontSize: 9, color: C.ink3, padding: '5px 8px', whiteSpace: 'nowrap' }}>
                        {ageMins < 60 ? `${ageMins}분전` : `${Math.floor(ageMins / 60)}시간전`}
                      </td>
                    </tr>
                  );
                })}
                {displayed.length === 0 && (
                  <tr>
                    <td colSpan={13} style={{ padding: 32, textAlign: 'center', color: C.ink3, fontSize: 11 }}>
                      탐지된 중국 어선 없음
                    </td>
                  </tr>
                )}
              </tbody>
            </table>
          </div>

          {/* 하단 범례 */}
          <div style={{
            display: 'flex', gap: 16, padding: '6px 12px', alignItems: 'center',
            background: C.bg2, borderTop: `1px solid ${C.border}`,
            fontSize: 10, flexShrink: 0, flexWrap: 'wrap',
          }}>
            {[
              { color: C.red, label: 'CRITICAL — 즉시대응' },
              { color: C.amber, label: 'WATCH — 집중모니터링' },
              { color: C.cyan, label: 'MONITOR — 주시' },
              { color: C.green, label: 'NORMAL — 정상' },
            ].map(({ color, label }) => (
              <span key={label} style={{ display: 'flex', alignItems: 'center', gap: 5, color: C.ink2 }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block' }} />
                {label}
              </span>
            ))}
            <span style={{ marginLeft: 'auto', color: C.ink3, fontSize: 9 }}>
              AIS 4분 갱신 | Python 7단계 파이프라인 | DBSCAN 3NM 클러스터 | GeoJSON 수역 분류
            </span>
          </div>
        </div>

        {/* ── 우측 패널: 선박 상세 + 허가 정보 + 사진 + 경보 로그 */}
        <div style={{
          width: 280, flexShrink: 0,
          background: C.panel, borderLeft: `1px solid ${C.border}`,
          overflow: 'hidden', display: 'flex', flexDirection: 'column',
        }}>
          {/* 패널 헤더 */}
          <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, padding: '10px 12px 6px', borderBottom: `1px solid ${C.border}`, flexShrink: 0 }}>
            선박 상세 정보
            <span style={{ float: 'right', color: C.green, fontSize: 8 }}>●</span>
          </div>

          {/* 스크롤 영역: 상세 + 허가 + 사진 */}
          <div style={{ flex: 1, overflow: 'auto', minHeight: 0 }}>
            {selectedVessel ? (
              <>
                {/* 기본 상세 필드 */}
                <div style={{ padding: '8px 12px' }}>
                  {[
                    { label: 'MMSI', val: selectedVessel.ship.mmsi, color: C.cyan },
                    { label: '선명', val: selectedVessel.ship.name || '(Unknown)', color: '#fff' },
                    { label: '위치', val: `${selectedVessel.ship.lat.toFixed(4)}°N  ${selectedVessel.ship.lng.toFixed(4)}°E`, color: C.ink },
                    { label: '속도 / 침로', val: `${selectedVessel.ship.speed.toFixed(1)}kt  ${selectedVessel.ship.course}°`, color: C.amber },
                    { label: '행동 상태', val: stateLabel(selectedVessel.state), color: stateColor(selectedVessel.state) },
                    { label: '선종 (Python)', val: selectedVessel.vtype, color: C.ink },
                    { label: '현재 구역', val: zoneLabel(selectedVessel.zone), color: zoneColor(selectedVessel.zone) },
                    { label: '클러스터', val: selectedVessel.cluster, color: selectedVessel.cluster !== '—' ? C.purple : C.ink3 },
                    { label: '위험도', val: selectedVessel.alert, color: alertColor(selectedVessel.alert) },
                    ...(() => {
                      const dto = analysisMap.get(selectedVessel.ship.mmsi);
                      if (!dto) return [{ label: 'AI 분석', val: '미분석', color: C.ink3 }];
                      return [
                        { label: '위험 점수', val: `${dto.algorithms.riskScore.score}점`, color: alertColor(selectedVessel.alert) },
                        { label: 'GPS 스푸핑', val: `${Math.round(dto.algorithms.gpsSpoofing.spoofingScore * 100)}%`, color: dto.algorithms.gpsSpoofing.spoofingScore > 0.5 ? C.red : C.green },
                        { label: 'AIS 공백', val: dto.algorithms.darkVessel.isDark ? `${Math.round(dto.algorithms.darkVessel.gapDurationMin)}분` : '정상', color: dto.algorithms.darkVessel.isDark ? C.red : C.green },
                        { label: '선단 역할', val: dto.algorithms.fleetRole.role, color: dto.algorithms.fleetRole.isLeader ? C.amber : C.ink2 },
                      ];
                    })(),
                  ].map(({ label, val, color }) => (
                    <div key={label} style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderBottom: `1px solid ${C.border2}` }}>
                      <span style={{ fontSize: 9, color: C.ink3 }}>{label}</span>
                      <span style={{ fontSize: 10, color, fontWeight: 600, textAlign: 'right', maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{val}</span>
                    </div>
                  ))}
                  <div style={{ display: 'flex', gap: 6, marginTop: 8 }}>
                    <button
                      type="button"
                      onClick={() => addLog(selectedVessel.ship.mmsi, selectedVessel.ship.name || '', '대응 명령 발령', 'critical')}
                      style={{ flex: 1, padding: '5px 0', fontSize: 9, cursor: 'pointer', background: 'rgba(255,82,82,0.1)', border: `1px solid rgba(255,82,82,0.4)`, color: C.red, borderRadius: 2, fontFamily: 'inherit' }}
                    >대응 명령</button>
                    <button
                      type="button"
                      onClick={() => addLog(selectedVessel.ship.mmsi, selectedVessel.ship.name || '', 'ENG/드론 투입 명령', 'watch')}
                      style={{ flex: 1, padding: '5px 0', fontSize: 9, cursor: 'pointer', background: 'rgba(255,215,64,0.08)', border: `1px solid rgba(255,215,64,0.3)`, color: C.amber, borderRadius: 2, fontFamily: 'inherit' }}
                    >ENG/드론</button>
                  </div>
                </div>

                {/* ── 허가 정보 */}
                <div style={{ borderTop: `1px solid ${C.border}`, padding: '8px 12px' }}>
                  <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, marginBottom: 7 }}>허가 정보</div>

                  {/* 허가 여부 배지 */}
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
                    <span style={{ fontSize: 9, color: C.ink3 }}>허가 여부</span>
                    {permitStatus === 'loading' && (
                      <span style={{ fontSize: 9, color: C.ink3 }}>조회 중...</span>
                    )}
                    {permitStatus === 'found' && (
                      <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 10px', borderRadius: 2, background: 'rgba(0,230,118,0.15)', border: `1px solid ${C.green}`, color: C.green }}>
                        ✓ 허가 선박
                      </span>
                    )}
                    {permitStatus === 'not-found' && (
                      <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 10px', borderRadius: 2, background: 'rgba(255,82,82,0.12)', border: `1px solid ${C.red}`, color: C.red }}>
                        ✕ 미등록 선박
                      </span>
                    )}
                  </div>

                  {/* 허가 내역 (데이터 있을 때) */}
                  {permitStatus === 'found' && permitData && (
                    <div style={{ background: C.bg2, border: `1px solid ${C.border}`, borderRadius: 3, padding: '7px 10px' }}>
                      {[
                        { label: '선명', val: permitData.name },
                        { label: '선종', val: permitData.vesselType },
                        { label: 'IMO', val: String(permitData.imo || '—') },
                        { label: '호출부호', val: permitData.callsign || '—' },
                        { label: '길이/폭', val: `${permitData.length ?? 0}m / ${permitData.width ?? 0}m` },
                        { label: '흘수', val: permitData.draught ? `${permitData.draught}m` : '—' },
                        { label: '목적지', val: permitData.destination || '—' },
                        { label: '상태', val: permitData.status || '—' },
                      ].map(({ label, val }) => (
                        <div key={label} style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', borderBottom: `1px solid ${C.border2}` }}>
                          <span style={{ fontSize: 9, color: C.ink3 }}>{label}</span>
                          <span style={{ fontSize: 9, color: C.ink, textAlign: 'right', maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{val}</span>
                        </div>
                      ))}
                    </div>
                  )}

                  {/* 미등록 안내 */}
                  {permitStatus === 'not-found' && (
                    <div style={{ background: 'rgba(255,82,82,0.06)', border: `1px solid rgba(255,82,82,0.2)`, borderRadius: 3, padding: '7px 10px' }}>
                      <div style={{ fontSize: 9, color: '#FF8A80', lineHeight: 1.6 }}>
                        한중어업협정 허가 DB에 등록되지 않은 선박입니다.<br />
                        불법어업 의심 — 추가 조사 및 조치 필요
                      </div>
                    </div>
                  )}
                </div>

                {/* ── 선박 사진 */}
                <div style={{ borderTop: `1px solid ${C.border}`, padding: '8px 12px 12px' }}>
                  <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, marginBottom: 7 }}>선박 사진</div>
                  <div style={{
                    width: '100%', height: 140,
                    background: C.bg3, border: `1px solid ${C.border}`,
                    borderRadius: 3, overflow: 'hidden',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                  }}>
                    {photoUrl === undefined && (
                      <span style={{ fontSize: 9, color: C.ink3 }}>로딩 중...</span>
                    )}
                    {photoUrl === null && (
                      <span style={{ fontSize: 9, color: C.ink3 }}>사진 없음</span>
                    )}
                    {photoUrl && (
                      <img
                        src={photoUrl}
                        alt={selectedVessel.ship.name || '선박'}
                        style={{ width: '100%', height: '100%', objectFit: 'cover' }}
                        onError={() => setPhotoUrl(null)}
                      />
                    )}
                  </div>
                  {photoUrl && (
                    <div style={{ fontSize: 8, color: C.ink3, marginTop: 4, textAlign: 'right' }}>
                      © MarineTraffic / S&P Global
                    </div>
                  )}
                </div>

                {/* ── 항적 미니맵 */}
                <div style={{ borderTop: `1px solid ${C.border}`, padding: '8px 12px 12px' }}>
                  <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, marginBottom: 6 }}>항적 미니맵</div>
                  <div style={{ height: 180, borderRadius: 4, overflow: 'hidden', border: `1px solid ${C.border}` }}>
                    <MapGL
                      key={selectedVessel.ship.mmsi}
                      initialViewState={{ longitude: selectedVessel.ship.lng, latitude: selectedVessel.ship.lat, zoom: 3 }}
                      style={{ width: '100%', height: '100%' }}
                      mapStyle={MINIMAP_STYLE}
                      attributionControl={false}
                      interactive={false}
                    >
                      {trackCoords.length > 1 && (
                        <Source id="minimap-track" type="geojson" data={{
                          type: 'Feature', properties: {},
                          geometry: { type: 'LineString', coordinates: trackCoords },
                        }}>
                          <Layer id="minimap-track-line" type="line" paint={{
                            'line-color': '#fbbf24', 'line-width': 2, 'line-opacity': 0.8,
                          }} />
                        </Source>
                      )}
                      <Marker longitude={selectedVessel.ship.lng} latitude={selectedVessel.ship.lat}>
                        <div style={{ width: 8, height: 8, borderRadius: '50%', background: C.red, border: '2px solid #fff' }} />
                      </Marker>
                    </MapGL>
                  </div>
                  <div style={{ fontSize: 8, color: C.ink3, marginTop: 4 }}>
                    최근 72시간 항적 · {trackCoords.length}포인트
                  </div>
                </div>
              </>
            ) : (
              <div style={{ padding: '24px 0', textAlign: 'center', color: C.ink3, fontSize: 10 }}>
                테이블에서 선박을 선택하세요
              </div>
            )}
          </div>

          {/* 경보 로그 — 하단 고정 */}
          <div style={{ fontSize: 9, letterSpacing: 2, color: C.cyan, padding: '6px 12px', borderTop: `1px solid ${C.border}`, borderBottom: `1px solid ${C.border}`, flexShrink: 0, display: 'flex', justifyContent: 'space-between' }}>
            <span>실시간 경보 로그</span>
            <span style={{ color: C.ink3, fontSize: 8 }}>{logs.length}건</span>
          </div>
          <div style={{ flex: '0 0 160px', overflow: 'auto' }}>
            {logs.map((log, i) => (
              <div key={i} style={{
                padding: '5px 12px',
                borderBottom: `1px solid ${C.border2}`,
                borderLeft: `2px solid ${log.level === 'critical' ? C.red : log.level === 'watch' ? C.amber : C.cyan}`,
              }}>
                <div style={{ fontSize: 9, color: C.ink3 }}>{log.ts}</div>
                <div style={{ fontSize: 10, lineHeight: 1.4, color: log.level === 'critical' ? '#FF8A80' : log.level === 'watch' ? '#FFE57F' : '#80DEEA' }}>
                  <span style={{ color: C.cyan }}>{log.mmsi}</span> {log.name} — {log.type}
                </div>
              </div>
            ))}
            {logs.length === 0 && (
              <div style={{ padding: 16, textAlign: 'center', color: C.ink3, fontSize: 10 }}>경보 없음</div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
