import { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import { fetchAircraftFromBackend } from '../services/aircraftApi'; import { fetchSatelliteTLEKorea, propagateAll } from '../services/celestrak'; import { fetchShipsKorea } from '../services/ships'; import { fetchOsintFeed } from '../services/osint'; import type { OsintItem } from '../services/osint'; import { propagateAircraft, propagateShips } from '../services/propagation'; import { getMarineTrafficCategory } from '../utils/marineTraffic'; import type { Aircraft, Ship, Satellite, SatellitePosition } from '../types'; interface UseKoreaDataArgs { currentTime: number; isLive: boolean; hiddenAcCategories: Set; hiddenShipCategories: Set; hiddenNationalities: Set; refreshKey: number; } interface UseKoreaDataResult { aircraft: Aircraft[]; ships: Ship[]; visibleAircraft: Aircraft[]; visibleShips: Ship[]; satPositions: SatellitePosition[]; osintFeed: OsintItem[]; koreaKoreanShips: Ship[]; koreaChineseShips: Ship[]; shipsByCategory: Record; shipsByNationality: Record; fishingByNationality: Record; aircraftByCategory: Record; militaryCount: number; } const SHIP_POLL_INTERVAL = 300_000; // 5 min const SHIP_STALE_MS = 3_600_000; // 60 min /** Map flag code to nationality group key */ export function getNationalityGroup(flag?: string): string { if (!flag) return 'unclassified'; if (flag === 'KR') return 'KR'; if (flag === 'CN') return 'CN'; if (flag === 'KP') return 'KP'; if (flag === 'JP') return 'JP'; return 'unclassified'; } export function useKoreaData({ currentTime, isLive, hiddenAcCategories, hiddenShipCategories, hiddenNationalities, refreshKey, }: UseKoreaDataArgs): UseKoreaDataResult { const [baseAircraftKorea, setBaseAircraftKorea] = useState([]); const [baseShipsKorea, setBaseShipsKorea] = useState([]); const [satellitesKorea, setSatellitesKorea] = useState([]); const [satPositionsKorea, setSatPositionsKorea] = useState([]); const [osintFeed, setOsintFeed] = useState([]); const satTimeKoreaRef = useRef(0); const shipMapRef = useRef>(new Map()); // Fetch Korea satellite TLE data useEffect(() => { fetchSatelliteTLEKorea().then(setSatellitesKorea).catch(() => {}); }, [refreshKey]); // Fetch Korea aircraft data useEffect(() => { const load = async () => { const result = await fetchAircraftFromBackend('korea'); if (result.length > 0) setBaseAircraftKorea(result); }; load(); const interval = setInterval(load, 60_000); return () => clearInterval(interval); }, [refreshKey]); // Ship merge with stale cleanup const mergeShips = useCallback((newShips: Ship[]) => { const map = shipMapRef.current; for (const s of newShips) { map.set(s.mmsi, s); } const cutoff = Date.now() - SHIP_STALE_MS; for (const [mmsi, ship] of map) { if (ship.lastSeen < cutoff) map.delete(mmsi); } setBaseShipsKorea(Array.from(map.values())); }, []); // Fetch Korea region ship data: initial 60min, then 5min polling with 6min window useEffect(() => { let initialDone = false; const loadInitial = async () => { try { const data = await fetchShipsKorea(60); // 초기: 60분 데이터 if (data.length > 0) { shipMapRef.current = new Map(data.map(s => [s.mmsi, s])); setBaseShipsKorea(data); initialDone = true; } } catch { /* keep previous */ } }; const loadIncremental = async () => { if (!initialDone) return; try { const data = await fetchShipsKorea(6); // polling: 6분 데이터 if (data.length > 0) mergeShips(data); } catch { /* keep previous */ } }; loadInitial(); const interval = setInterval(loadIncremental, SHIP_POLL_INTERVAL); return () => clearInterval(interval); }, [refreshKey, mergeShips]); // Fetch OSINT feed for Korea tab useEffect(() => { const load = async () => { try { const data = await fetchOsintFeed('korea'); if (data.length > 0) setOsintFeed(data); } catch { /* keep previous */ } }; load(); const interval = setInterval(load, 120_000); return () => clearInterval(interval); }, [refreshKey]); // Propagate Korea satellite positions useEffect(() => { if (satellitesKorea.length === 0) return; const now = Date.now(); if (now - satTimeKoreaRef.current < 2000) return; satTimeKoreaRef.current = now; const positions = propagateAll(satellitesKorea, new Date(currentTime)); setSatPositionsKorea(positions); }, [satellitesKorea, currentTime]); // Propagate Korea aircraft (live only — no waypoint propagation needed) const aircraft = useMemo(() => propagateAircraft(baseAircraftKorea, currentTime), [baseAircraftKorea, currentTime]); // Korea region ships const ships = useMemo( () => propagateShips(baseShipsKorea, currentTime, isLive), [baseShipsKorea, currentTime, isLive], ); // Category-filtered data for map rendering const visibleAircraft = useMemo( () => aircraft.filter(a => !hiddenAcCategories.has(a.category)), [aircraft, hiddenAcCategories], ); const visibleShips = useMemo( () => ships.filter(s => !hiddenShipCategories.has(getMarineTrafficCategory(s.typecode, s.category)) && !hiddenNationalities.has(getNationalityGroup(s.flag)), ), [ships, hiddenShipCategories, hiddenNationalities], ); // Korea region stats const koreaKoreanShips = useMemo(() => ships.filter(s => s.flag === 'KR'), [ships]); const koreaChineseShips = useMemo(() => ships.filter(s => s.flag === 'CN'), [ships]); const shipsByCategory = useMemo(() => { const counts: Record = {}; for (const s of ships) { const mtCat = getMarineTrafficCategory(s.typecode, s.category); counts[mtCat] = (counts[mtCat] || 0) + 1; } return counts; }, [ships]); const shipsByNationality = useMemo(() => { const counts: Record = {}; for (const s of ships) { const nat = getNationalityGroup(s.flag); counts[nat] = (counts[nat] || 0) + 1; } return counts; }, [ships]); const fishingByNationality = useMemo(() => { const counts: Record = {}; for (const s of ships) { if (getMarineTrafficCategory(s.typecode, s.category) !== 'fishing') continue; const flag = s.flag || 'unknown'; const group = flag === 'CN' ? 'CN' : flag === 'KR' ? 'KR' : flag === 'JP' ? 'JP' : 'other'; counts[group] = (counts[group] || 0) + 1; } return counts; }, [ships]); // Korea aircraft stats const aircraftByCategory = useMemo(() => { const counts: Record = {}; for (const ac of aircraft) { counts[ac.category] = (counts[ac.category] || 0) + 1; } return counts; }, [aircraft]); const militaryCount = useMemo( () => aircraft.filter(a => a.category !== 'civilian' && a.category !== 'unknown').length, [aircraft], ); return { aircraft, ships, visibleAircraft, visibleShips, satPositions: satPositionsKorea, osintFeed, koreaKoreanShips, koreaChineseShips, shipsByCategory, shipsByNationality, fishingByNationality, aircraftByCategory, militaryCount, }; }