import { useState, useMemo, useEffect, useCallback } from 'react'
import { Map, Marker, Popup, Source, Layer, useControl, useMap } from '@vis.gl/react-maplibre'
import { MapboxOverlay } from '@deck.gl/mapbox'
import { ScatterplotLayer, PathLayer, TextLayer } from '@deck.gl/layers'
import type { PickingInfo } from '@deck.gl/core'
import type { StyleSpecification } from 'maplibre-gl'
import type { MapLayerMouseEvent } from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { layerDatabase } from '@common/services/layerService'
import { decimalToDMS } from '@common/utils/coordinates'
import type { PredictionModel, SensitiveResource } from '@tabs/prediction/components/OilSpillView'
import type { BoomLine, BoomLineCoord } from '@common/types/boomLine'
import type { ReplayShip, CollisionEvent } from '@common/types/backtrack'
import { createBacktrackLayers } from './BacktrackReplayOverlay'
import { hexToRgba } from './mapUtils'

const GEOSERVER_URL = import.meta.env.VITE_GEOSERVER_URL || 'http://localhost:8080'

// 남해안 중심 좌표 (여수 앞바다)
const DEFAULT_CENTER: [number, number] = [34.5, 127.8]
const DEFAULT_ZOOM = 10

// CartoDB Dark Matter 스타일
const BASE_STYLE: StyleSpecification = {
  version: 8,
  sources: {
    'carto-dark': {
      type: 'raster',
      tiles: [
        'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}@2x.png',
        'https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}@2x.png',
        'https://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}@2x.png',
      ],
      tileSize: 256,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://carto.com/attributions">CARTO</a>',
    },
  },
  layers: [
    {
      id: 'carto-dark-layer',
      type: 'raster',
      source: 'carto-dark',
      minzoom: 0,
      maxzoom: 22,
    },
  ],
}

// 모델별 색상 매핑
const MODEL_COLORS: Record<PredictionModel, string> = {
  'KOSPS': '#06b6d4',
  'POSEIDON': '#ef4444',
  'OpenDrift': '#3b82f6',
}

// 오일펜스 우선순위별 색상/두께
const PRIORITY_COLORS: Record<string, string> = {
  'CRITICAL': '#ef4444',
  'HIGH': '#f97316',
  'MEDIUM': '#eab308',
}
const PRIORITY_WEIGHTS: Record<string, number> = {
  'CRITICAL': 4,
  'HIGH': 3,
  'MEDIUM': 2,
}
const PRIORITY_LABELS: Record<string, string> = {
  'CRITICAL': '긴급',
  'HIGH': '중요',
  'MEDIUM': '보통',
}

const SENSITIVE_COLORS: Record<string, string> = {
  'aquaculture': '#22c55e',
  'beach': '#0ea5e9',
  'ecology': '#eab308',
  'intake': '#a855f7',
}
const SENSITIVE_ICONS: Record<string, string> = {
  'aquaculture': '🐟',
  'beach': '🏖',
  'ecology': '🦅',
  'intake': '🚰',
}

interface DispersionZone {
  level: string
  color: string
  radius: number
  angle: number
}

interface DispersionResult {
  zones: DispersionZone[]
  timestamp: string
  windDirection: number
  substance: string
  concentration: Record<string, string>
}

interface MapViewProps {
  center?: [number, number]
  zoom?: number
  enabledLayers?: Set<string>
  incidentCoord?: { lon: number; lat: number }
  isSelectingLocation?: boolean
  onMapClick?: (lon: number, lat: number) => void
  oilTrajectory?: Array<{ lat: number; lon: number; time: number; particle?: number; model?: PredictionModel }>
  selectedModels?: Set<PredictionModel>
  dispersionResult?: DispersionResult | null
  boomLines?: BoomLine[]
  isDrawingBoom?: boolean
  drawingPoints?: BoomLineCoord[]
  layerOpacity?: number
  layerBrightness?: number
  backtrackReplay?: {
    isActive: boolean
    ships: ReplayShip[]
    collisionEvent: CollisionEvent | null
    replayFrame: number
    totalFrames: number
    incidentCoord: { lat: number; lon: number }
  }
  sensitiveResources?: SensitiveResource[]
}

// deck.gl 오버레이 컴포넌트 (MapLibre 컨트롤로 등록)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function DeckGLOverlay({ layers }: { layers: any[] }) {
  const overlay = useControl<MapboxOverlay>(() => new MapboxOverlay({ interleaved: true }))
  overlay.setProps({ layers })
  return null
}

// 팝업 정보
interface PopupInfo {
  longitude: number
  latitude: number
  content: React.ReactNode
}

export function MapView({
  center = DEFAULT_CENTER,
  zoom = DEFAULT_ZOOM,
  enabledLayers = new Set(),
  incidentCoord,
  isSelectingLocation = false,
  onMapClick,
  oilTrajectory = [],
  selectedModels = new Set(['OpenDrift'] as PredictionModel[]),
  dispersionResult = null,
  boomLines = [],
  isDrawingBoom = false,
  drawingPoints = [],
  layerOpacity = 50,
  layerBrightness = 50,
  backtrackReplay,
  sensitiveResources = [],
}: MapViewProps) {
  const [currentPosition, setCurrentPosition] = useState<[number, number]>(DEFAULT_CENTER)
  const [currentTime, setCurrentTime] = useState(0)
  const [isPlaying, setIsPlaying] = useState(false)
  const [playbackSpeed, setPlaybackSpeed] = useState(1)
  const [popupInfo, setPopupInfo] = useState<PopupInfo | null>(null)

  const handleMapClick = useCallback((e: MapLayerMouseEvent) => {
    const { lng, lat } = e.lngLat
    setCurrentPosition([lat, lng])
    if (onMapClick) {
      onMapClick(lng, lat)
    }
    setPopupInfo(null)
  }, [onMapClick])

  // 애니메이션 재생 로직
  useEffect(() => {
    if (!isPlaying || oilTrajectory.length === 0) return

    const maxTime = Math.max(...oilTrajectory.map(p => p.time))
    if (currentTime >= maxTime) {
      setIsPlaying(false)
      return
    }

    const interval = setInterval(() => {
      setCurrentTime(prev => {
        const next = prev + (1 * playbackSpeed)
        return next > maxTime ? maxTime : next
      })
    }, 200)

    return () => clearInterval(interval)
  }, [isPlaying, currentTime, playbackSpeed, oilTrajectory])

  // 시뮬레이션 시작 시 자동으로 애니메이션 재생
  useEffect(() => {
    if (oilTrajectory.length > 0) {
      setCurrentTime(0)
      setIsPlaying(true)
    }
  }, [oilTrajectory.length])

  // WMS 레이어 목록
  const wmsLayers = useMemo(() => {
    return Array.from(enabledLayers)
      .map(layerId => {
        const layer = layerDatabase.find(l => l.id === layerId)
        return layer?.wmsLayer ? { id: layerId, wmsLayer: layer.wmsLayer } : null
      })
      .filter((l): l is { id: string; wmsLayer: string } => l !== null)
  }, [enabledLayers])

  // WMS 밝기 값 (MapLibre raster paint)
  const wmsBrightnessMax = Math.min(layerBrightness / 50, 2)
  const wmsBrightnessMin = wmsBrightnessMax > 1 ? wmsBrightnessMax - 1 : 0
  const wmsOpacity = layerOpacity / 100

  // deck.gl 레이어 구축
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const deckLayers = useMemo((): any[] => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const result: any[] = []

    // --- 유류 확산 입자 (ScatterplotLayer) ---
    const visibleParticles = oilTrajectory.filter(p => p.time <= currentTime)
    if (visibleParticles.length > 0) {
      result.push(
        new ScatterplotLayer({
          id: 'oil-particles',
          data: visibleParticles,
          getPosition: (d: (typeof visibleParticles)[0]) => [d.lon, d.lat],
          getRadius: 3,
          getFillColor: (d: (typeof visibleParticles)[0]) => {
            const modelKey = d.model || Array.from(selectedModels)[0] || 'OpenDrift'
            return hexToRgba(MODEL_COLORS[modelKey] || '#3b82f6', 180)
          },
          radiusMinPixels: 2.5,
          radiusMaxPixels: 5,
          pickable: true,
          onClick: (info: PickingInfo) => {
            if (info.object) {
              const d = info.object as (typeof visibleParticles)[0]
              const modelKey = d.model || Array.from(selectedModels)[0] || 'OpenDrift'
              setPopupInfo({
                longitude: d.lon,
                latitude: d.lat,
                content: (
                  <div className="text-xs">
                    <strong>{modelKey} 입자 #{(d.particle ?? 0) + 1}</strong>
                    <br />
                    시간: +{d.time}h
                    <br />
                    위치: {d.lat.toFixed(4)}°, {d.lon.toFixed(4)}°
                  </div>
                ),
              })
            }
          },
          updateTriggers: {
            getFillColor: [selectedModels],
          },
        })
      )
    }

    // --- 오일펜스 라인 (PathLayer) ---
    if (boomLines.length > 0) {
      result.push(
        new PathLayer({
          id: 'boom-lines',
          data: boomLines,
          getPath: (d: BoomLine) => d.coords.map(c => [c.lon, c.lat] as [number, number]),
          getColor: (d: BoomLine) => hexToRgba(PRIORITY_COLORS[d.priority] || '#f59e0b', 230),
          getWidth: (d: BoomLine) => PRIORITY_WEIGHTS[d.priority] || 2,
          getDashArray: (d: BoomLine) => d.status === 'PLANNED' ? [10, 5] : null,
          dashJustified: true,
          widthMinPixels: 2,
          widthMaxPixels: 6,
          pickable: true,
          onClick: (info: PickingInfo) => {
            if (info.object) {
              const d = info.object as BoomLine
              setPopupInfo({
                longitude: info.coordinate?.[0] ?? 0,
                latitude: info.coordinate?.[1] ?? 0,
                content: (
                  <div className="text-xs" style={{ fontFamily: 'var(--fK)', minWidth: '140px' }}>
                    <strong style={{ color: PRIORITY_COLORS[d.priority] }}>{d.name}</strong>
                    <br />
                    우선순위: {PRIORITY_LABELS[d.priority] || d.priority}
                    <br />
                    길이: {d.length.toFixed(0)}m
                    <br />
                    각도: {d.angle.toFixed(0)}°
                    <br />
                    차단 효율: {d.efficiency}%
                  </div>
                ),
              })
            }
          },
        })
      )

      // 오일펜스 끝점 마커
      const endpoints: Array<{ position: [number, number]; color: [number, number, number, number] }> = []
      boomLines.forEach(line => {
        if (line.coords.length >= 2) {
          const c = hexToRgba(PRIORITY_COLORS[line.priority] || '#f59e0b', 230)
          endpoints.push({ position: [line.coords[0].lon, line.coords[0].lat], color: c })
          endpoints.push({ position: [line.coords[line.coords.length - 1].lon, line.coords[line.coords.length - 1].lat], color: c })
        }
      })
      if (endpoints.length > 0) {
        result.push(
          new ScatterplotLayer({
            id: 'boom-endpoints',
            data: endpoints,
            getPosition: (d: (typeof endpoints)[0]) => d.position,
            getRadius: 5,
            getFillColor: (d: (typeof endpoints)[0]) => d.color,
            getLineColor: [255, 255, 255, 255],
            getLineWidth: 2,
            stroked: true,
            radiusMinPixels: 5,
            radiusMaxPixels: 8,
          })
        )
      }
    }

    // --- 드로잉 미리보기 ---
    if (isDrawingBoom && drawingPoints.length > 0) {
      result.push(
        new PathLayer({
          id: 'drawing-preview',
          data: [{ path: drawingPoints.map(c => [c.lon, c.lat] as [number, number]) }],
          getPath: (d: { path: [number, number][] }) => d.path,
          getColor: [245, 158, 11, 200],
          getWidth: 3,
          getDashArray: [10, 6],
          dashJustified: true,
          widthMinPixels: 3,
        })
      )
      result.push(
        new ScatterplotLayer({
          id: 'drawing-points',
          data: drawingPoints.map(c => ({ position: [c.lon, c.lat] as [number, number] })),
          getPosition: (d: { position: [number, number] }) => d.position,
          getRadius: 4,
          getFillColor: [245, 158, 11, 255],
          getLineColor: [255, 255, 255, 255],
          getLineWidth: 2,
          stroked: true,
          radiusMinPixels: 4,
          radiusMaxPixels: 6,
        })
      )
    }

    // --- HNS 대기확산 구역 (ScatterplotLayer, meters 단위) ---
    if (dispersionResult && incidentCoord) {
      const zones = dispersionResult.zones.map((zone, idx) => ({
        position: [incidentCoord.lon, incidentCoord.lat] as [number, number],
        radius: zone.radius,
        fillColor: hexToRgba(zone.color, 100),
        lineColor: hexToRgba(zone.color, 180),
        level: zone.level,
        idx,
      }))

      result.push(
        new ScatterplotLayer({
          id: 'hns-zones',
          data: zones,
          getPosition: (d: (typeof zones)[0]) => d.position,
          getRadius: (d: (typeof zones)[0]) => d.radius,
          getFillColor: (d: (typeof zones)[0]) => d.fillColor,
          getLineColor: (d: (typeof zones)[0]) => d.lineColor,
          getLineWidth: 2,
          stroked: true,
          radiusUnits: 'meters' as const,
          pickable: true,
          onClick: (info: PickingInfo) => {
            if (info.object) {
              const d = info.object as (typeof zones)[0]
              setPopupInfo({
                longitude: incidentCoord.lon,
                latitude: incidentCoord.lat,
                content: (
                  <div className="text-xs" style={{ fontFamily: 'var(--fK)' }}>
                    <strong style={{ color: 'var(--orange)' }}>{d.level}</strong>
                    <br />
                    물질: {dispersionResult.substance}
                    <br />
                    농도: {dispersionResult.concentration[d.level]}
                    <br />
                    반경: {d.radius}m
                  </div>
                ),
              })
            }
          },
        })
      )
    }

    // --- 역추적 리플레이 ---
    if (backtrackReplay?.isActive) {
      result.push(...createBacktrackLayers({
        replayShips: backtrackReplay.ships,
        collisionEvent: backtrackReplay.collisionEvent,
        replayFrame: backtrackReplay.replayFrame,
        totalFrames: backtrackReplay.totalFrames,
        incidentCoord: backtrackReplay.incidentCoord,
      }))
    }

    // --- 민감자원 영역 (ScatterplotLayer) ---
    if (sensitiveResources.length > 0) {
      result.push(
        new ScatterplotLayer({
          id: 'sensitive-zones',
          data: sensitiveResources,
          getPosition: (d: SensitiveResource) => [d.lon, d.lat],
          getRadius: (d: SensitiveResource) => d.radiusM,
          getFillColor: (d: SensitiveResource) => hexToRgba(SENSITIVE_COLORS[d.type] || '#22c55e', 40),
          getLineColor: (d: SensitiveResource) => hexToRgba(SENSITIVE_COLORS[d.type] || '#22c55e', 150),
          getLineWidth: 2,
          stroked: true,
          radiusUnits: 'meters' as const,
          pickable: true,
          onClick: (info: PickingInfo) => {
            if (info.object) {
              const d = info.object as SensitiveResource
              setPopupInfo({
                longitude: d.lon,
                latitude: d.lat,
                content: (
                  <div className="text-xs" style={{ fontFamily: 'var(--fK)', minWidth: '130px' }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '4px' }}>
                      <span>{SENSITIVE_ICONS[d.type]}</span>
                      <strong style={{ color: SENSITIVE_COLORS[d.type] }}>{d.name}</strong>
                    </div>
                    <div style={{ fontSize: '10px', color: '#666' }}>
                      반경: {d.radiusM}m<br />
                      도달 예상: <strong style={{ color: d.arrivalTimeH <= 6 ? '#ef4444' : '#f97316' }}>{d.arrivalTimeH}h</strong>
                    </div>
                  </div>
                ),
              })
            }
          },
        })
      )

      // 민감자원 중심 마커
      result.push(
        new ScatterplotLayer({
          id: 'sensitive-centers',
          data: sensitiveResources,
          getPosition: (d: SensitiveResource) => [d.lon, d.lat],
          getRadius: 6,
          getFillColor: (d: SensitiveResource) => hexToRgba(SENSITIVE_COLORS[d.type] || '#22c55e', 220),
          getLineColor: [255, 255, 255, 200],
          getLineWidth: 2,
          stroked: true,
          radiusMinPixels: 6,
          radiusMaxPixels: 10,
        })
      )

      // 민감자원 라벨
      result.push(
        new TextLayer({
          id: 'sensitive-labels',
          data: sensitiveResources,
          getPosition: (d: SensitiveResource) => [d.lon, d.lat],
          getText: (d: SensitiveResource) => `${SENSITIVE_ICONS[d.type]} ${d.name} (${d.arrivalTimeH}h)`,
          getSize: 12,
          getColor: [255, 255, 255, 200],
          getPixelOffset: [0, -20],
          fontFamily: 'NanumSquare, Malgun Gothic, 맑은 고딕, sans-serif',
          fontWeight: 'bold',
          characterSet: 'auto',
          outlineWidth: 2,
          outlineColor: [15, 21, 36, 200],
          billboard: true,
          sizeUnits: 'pixels' as const,
        })
      )
    }

    // --- 해류 화살표 (TextLayer) ---
    if (incidentCoord) {
      const currentArrows: Array<{ lon: number; lat: number; bearing: number; speed: number }> = []
      const gridSize = 5
      const spacing = 0.04  // 약 4km 간격
      const mainBearing = 200 // SSW 방향 (도)

      for (let row = -gridSize; row <= gridSize; row++) {
        for (let col = -gridSize; col <= gridSize; col++) {
          const lat = incidentCoord.lat + row * spacing
          const lon = incidentCoord.lon + col * spacing / Math.cos(incidentCoord.lat * Math.PI / 180)
          // 사고 지점에서 멀어질수록 해류 방향 약간 변화
          const distFactor = Math.sqrt(row * row + col * col) / gridSize
          const localBearing = mainBearing + (col * 3) + (row * 2)
          const speed = 0.3 + (1 - distFactor) * 0.2
          currentArrows.push({ lon, lat, bearing: localBearing, speed })
        }
      }

      result.push(
        new TextLayer({
          id: 'current-arrows',
          data: currentArrows,
          getPosition: (d: (typeof currentArrows)[0]) => [d.lon, d.lat],
          getText: () => '➤',
          getAngle: (d: (typeof currentArrows)[0]) => -d.bearing + 90,
          getSize: 22,
          getColor: [6, 182, 212, 100],
          characterSet: 'auto',
          sizeUnits: 'pixels' as const,
          billboard: true,
        })
      )
    }

    return result
  }, [
    oilTrajectory, currentTime, selectedModels,
    boomLines, isDrawingBoom, drawingPoints,
    dispersionResult, incidentCoord, backtrackReplay,
    sensitiveResources,
  ])

  return (
    <div className="w-full h-full relative">
      <Map
        initialViewState={{
          longitude: center[1],
          latitude: center[0],
          zoom: zoom,
        }}
        mapStyle={BASE_STYLE}
        style={{ width: '100%', height: '100%', cursor: isSelectingLocation ? 'crosshair' : 'grab' }}
        onClick={handleMapClick}
        attributionControl={false}
      >
        {/* WMS 레이어 */}
        {wmsLayers.map(layer => (
          <Source
            key={layer.id}
            id={`wms-${layer.id}`}
            type="raster"
            tiles={[
              `${GEOSERVER_URL}/geoserver/gwc/service/wms?service=WMS&version=1.1.0&request=GetMap&layers=${layer.wmsLayer}&styles=&bbox={bbox-epsg-3857}&width=256&height=256&srs=EPSG:3857&format=image/png&transparent=true`
            ]}
            tileSize={256}
          >
            <Layer
              id={`wms-layer-${layer.id}`}
              type="raster"
              paint={{
                'raster-opacity': wmsOpacity,
                'raster-brightness-min': wmsBrightnessMin,
                'raster-brightness-max': Math.min(wmsBrightnessMax, 1),
              }}
            />
          </Source>
        ))}

        {/* deck.gl 오버레이 */}
        <DeckGLOverlay layers={deckLayers} />

        {/* 사고 위치 마커 (MapLibre Marker) */}
        {incidentCoord && !isNaN(incidentCoord.lat) && !isNaN(incidentCoord.lon) && (
          <Marker longitude={incidentCoord.lon} latitude={incidentCoord.lat} anchor="bottom">
            <div
              style={{
                width: 24, height: 24, background: 'var(--cyan)', borderRadius: '50% 50% 50% 0',
                transform: 'rotate(-45deg)', border: '2px solid #fff',
                boxShadow: '0 2px 8px rgba(6,182,212,0.5)',
              }}
            />
          </Marker>
        )}

        {/* 사고 위치 팝업 (클릭 시) */}
        {incidentCoord && !isNaN(incidentCoord.lat) && !isNaN(incidentCoord.lon) && !popupInfo && (
          <Popup longitude={incidentCoord.lon} latitude={incidentCoord.lat} anchor="bottom" offset={30} closeButton={false} closeOnClick={false}>
            <div className="text-sm" style={{ color: '#333' }}>
              <strong>사고 지점</strong>
              <br />
              <span className="text-xs" style={{ color: '#666' }}>
                {decimalToDMS(incidentCoord.lat, true)}
                <br />
                {decimalToDMS(incidentCoord.lon, false)}
              </span>
              <br />
              <span className="text-xs" style={{ fontFamily: 'monospace', color: '#888' }}>
                ({incidentCoord.lat.toFixed(4)}°, {incidentCoord.lon.toFixed(4)}°)
              </span>
            </div>
          </Popup>
        )}

        {/* deck.gl 객체 클릭 팝업 */}
        {popupInfo && (
          <Popup
            longitude={popupInfo.longitude}
            latitude={popupInfo.latitude}
            anchor="bottom"
            onClose={() => setPopupInfo(null)}
          >
            <div style={{ color: '#333' }}>{popupInfo.content}</div>
          </Popup>
        )}

        {/* 커스텀 줌 컨트롤 */}
        <MapControls center={center} zoom={zoom} />
      </Map>

      {/* 드로잉 모드 안내 */}
      {isDrawingBoom && (
        <div className="boom-drawing-indicator">
          오일펜스 배치 모드 — 지도를 클릭하여 포인트를 추가하세요 ({drawingPoints.length}개 포인트)
        </div>
      )}

      {/* 기상청 연계 정보 */}
      <WeatherInfoPanel position={currentPosition} />

      {/* 범례 */}
      <MapLegend dispersionResult={dispersionResult} incidentCoord={incidentCoord} oilTrajectory={oilTrajectory} boomLines={boomLines} selectedModels={selectedModels} />

      {/* 좌표 표시 */}
      <CoordinateDisplay
        position={incidentCoord ? [incidentCoord.lat, incidentCoord.lon] : currentPosition}
      />

      {/* 타임라인 컨트롤 */}
      {oilTrajectory.length > 0 && (
        <TimelineControl
          currentTime={currentTime}
          maxTime={Math.max(...oilTrajectory.map(p => p.time))}
          isPlaying={isPlaying}
          playbackSpeed={playbackSpeed}
          onTimeChange={setCurrentTime}
          onPlayPause={() => setIsPlaying(!isPlaying)}
          onSpeedChange={setPlaybackSpeed}
        />
      )}

      {/* 역추적 리플레이 바 */}
      {backtrackReplay?.isActive && (
        <BacktrackReplayBar
          replayFrame={backtrackReplay.replayFrame}
          totalFrames={backtrackReplay.totalFrames}
          ships={backtrackReplay.ships}
        />
      )}
    </div>
  )
}

// 지도 컨트롤 (줌, 위치 초기화)
function MapControls({ center, zoom }: { center: [number, number]; zoom: number }) {
  const { current: map } = useMap()

  return (
    <div style={{ position: 'absolute', top: 16, left: 16, zIndex: 10 }}>
      <div className="flex flex-col gap-2">
        <button
          onClick={() => map?.zoomIn()}
          className="w-[38px] h-[38px] bg-[rgba(18,25,41,0.9)] backdrop-blur-xl border border-border rounded-sm text-text-2 flex items-center justify-center hover:bg-bg-hover hover:text-text-1 transition-all text-base"
        >
          +
        </button>
        <button
          onClick={() => map?.zoomOut()}
          className="w-[38px] h-[38px] bg-[rgba(18,25,41,0.9)] backdrop-blur-xl border border-border rounded-sm text-text-2 flex items-center justify-center hover:bg-bg-hover hover:text-text-1 transition-all text-base"
        >
          −
        </button>
        <button
          onClick={() => map?.flyTo({ center: [center[1], center[0]], zoom, duration: 1000 })}
          className="w-[38px] h-[38px] bg-[rgba(18,25,41,0.9)] backdrop-blur-xl border border-border rounded-sm text-text-2 flex items-center justify-center hover:text-text-1 transition-all text-sm"
        >
          &#x1F3AF;
        </button>
      </div>
    </div>
  )
}

// 지도 범례
interface MapLegendProps {
  dispersionResult?: DispersionResult | null
  incidentCoord?: { lon: number; lat: number }
  oilTrajectory?: Array<{ lat: number; lon: number; time: number }>
  boomLines?: BoomLine[]
  selectedModels?: Set<PredictionModel>
}

function MapLegend({ dispersionResult, incidentCoord, oilTrajectory = [], boomLines = [], selectedModels = new Set(['OpenDrift'] as PredictionModel[]) }: MapLegendProps) {
  if (dispersionResult && incidentCoord) {
    return (
      <div className="absolute top-4 right-4 bg-[rgba(18,25,41,0.95)] backdrop-blur-xl border border-border rounded-lg p-3.5 min-w-[200px]" style={{ fontFamily: 'var(--fK)', zIndex: 20 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '10px' }}>
          <div style={{ fontSize: '16px' }}>📍</div>
          <div>
            <h4 className="text-[11px] font-bold text-primary-orange">사고 위치</h4>
            <div className="text-[8px] text-text-3" style={{ fontFamily: 'var(--fM)' }}>
              {incidentCoord.lat.toFixed(4)}°N, {incidentCoord.lon.toFixed(4)}°E
            </div>
          </div>
        </div>
        <div style={{ background: 'rgba(249,115,22,0.08)', padding: '8px', borderRadius: '6px', marginBottom: '8px', fontSize: '9px', color: 'var(--t2)' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '3px' }}>
            <span style={{ color: 'var(--t3)' }}>물질</span>
            <span style={{ fontWeight: 600, color: 'var(--orange)' }}>{dispersionResult.substance}</span>
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '3px' }}>
            <span style={{ color: 'var(--t3)' }}>풍향</span>
            <span style={{ fontWeight: 600, fontFamily: 'var(--fM)' }}>SW {dispersionResult.windDirection}°</span>
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between' }}>
            <span style={{ color: 'var(--t3)' }}>확산 구역</span>
            <span style={{ fontWeight: 600, color: 'var(--cyan)' }}>{dispersionResult.zones.length}개</span>
          </div>
        </div>
        <div>
          <h5 className="text-[9px] font-bold text-text-3 mb-2">위험 구역</h5>
          <div className="flex flex-col gap-1.5">
            <div className="flex items-center gap-2 text-[10px] text-text-2">
              <div className="w-3 h-3 rounded-full" style={{ background: 'rgba(239,68,68,0.7)' }} />
              <span>치명적 위험 구역 (AEGL-3)</span>
            </div>
            <div className="flex items-center gap-2 text-[10px] text-text-2">
              <div className="w-3 h-3 rounded-full" style={{ background: 'rgba(249,115,22,0.7)' }} />
              <span>높은 위험 구역 (AEGL-2)</span>
            </div>
            <div className="flex items-center gap-2 text-[10px] text-text-2">
              <div className="w-3 h-3 rounded-full" style={{ background: 'rgba(234,179,8,0.7)' }} />
              <span>중간 위험 구역 (AEGL-1)</span>
            </div>
          </div>
        </div>
        <div style={{ marginTop: '8px', padding: '6px', background: 'rgba(168,85,247,0.08)', borderRadius: '4px', display: 'flex', alignItems: 'center', gap: '6px' }}>
          <div style={{ fontSize: '12px' }}>🧭</div>
          <span className="text-[9px] text-text-3">풍향 (방사형)</span>
        </div>
      </div>
    )
  }

  if (oilTrajectory.length > 0) {
    return (
      <div className="absolute top-4 right-4 bg-[rgba(18,25,41,0.9)] backdrop-blur-xl border border-border rounded-md p-3.5 min-w-[180px]" style={{ zIndex: 20 }}>
        <h4 className="text-[11px] font-bold uppercase tracking-wider text-text-3 mb-2.5">범례</h4>
        <div className="flex flex-col gap-1.5">
          {Array.from(selectedModels).map(model => (
            <div key={model} className="flex items-center gap-2 text-xs text-text-2">
              <div className="w-3.5 h-3.5 rounded-full" style={{ background: MODEL_COLORS[model] }} />
              <span className="font-korean">{model}</span>
            </div>
          ))}
          {selectedModels.size === 3 && (
            <div className="flex items-center gap-2 text-xs text-text-3" style={{ fontSize: '9px' }}>
              <span className="font-korean">(앙상블 모드)</span>
            </div>
          )}
          <div style={{ height: '1px', background: 'var(--bd)', margin: '4px 0' }} />
          <div className="flex items-center gap-2 text-xs text-text-2">
            <div className="w-3.5 h-3.5 rounded-full bg-primary-cyan" />
            <span className="font-korean">사고 지점</span>
          </div>
          {boomLines.length > 0 && (
            <>
              <div style={{ height: '1px', background: 'var(--bd)', margin: '4px 0' }} />
              <div className="flex items-center gap-2 text-xs text-text-2">
                <div style={{ width: '14px', height: '3px', background: '#ef4444', borderRadius: '1px' }} />
                <span className="font-korean">긴급 오일펜스</span>
              </div>
              <div className="flex items-center gap-2 text-xs text-text-2">
                <div style={{ width: '14px', height: '3px', background: '#f97316', borderRadius: '1px' }} />
                <span className="font-korean">중요 오일펜스</span>
              </div>
              <div className="flex items-center gap-2 text-xs text-text-2">
                <div style={{ width: '14px', height: '3px', background: '#eab308', borderRadius: '1px' }} />
                <span className="font-korean">보통 오일펜스</span>
              </div>
            </>
          )}
        </div>
      </div>
    )
  }

  return null
}

// 좌표 표시
function CoordinateDisplay({ position }: { position: [number, number] }) {
  const [lat, lng] = position
  const latDirection = lat >= 0 ? 'N' : 'S'
  const lngDirection = lng >= 0 ? 'E' : 'W'

  return (
    <div className="cod">
      <span>위도 <span className="cov">{Math.abs(lat).toFixed(4)}°{latDirection}</span></span>
      <span>경도 <span className="cov">{Math.abs(lng).toFixed(4)}°{lngDirection}</span></span>
      <span>축척 <span className="cov">1:50,000</span></span>
    </div>
  )
}

// 타임라인 컨트롤
interface TimelineControlProps {
  currentTime: number
  maxTime: number
  isPlaying: boolean
  playbackSpeed: number
  onTimeChange: (time: number) => void
  onPlayPause: () => void
  onSpeedChange: (speed: number) => void
}

function TimelineControl({
  currentTime, maxTime, isPlaying, playbackSpeed,
  onTimeChange, onPlayPause, onSpeedChange
}: TimelineControlProps) {
  const progressPercent = (currentTime / maxTime) * 100

  const handleRewind = () => onTimeChange(Math.max(0, currentTime - 6))
  const handleForward = () => onTimeChange(Math.min(maxTime, currentTime + 6))
  const handleStart = () => onTimeChange(0)
  const handleEnd = () => onTimeChange(maxTime)

  const toggleSpeed = () => {
    const speeds = [1, 2, 4]
    const currentIndex = speeds.indexOf(playbackSpeed)
    onSpeedChange(speeds[(currentIndex + 1) % speeds.length])
  }

  const handleTimelineClick = (e: React.MouseEvent<HTMLDivElement>) => {
    const rect = e.currentTarget.getBoundingClientRect()
    const percent = (e.clientX - rect.left) / rect.width
    onTimeChange(Math.max(0, Math.min(maxTime, Math.round(percent * maxTime))))
  }

  const timeLabels = []
  for (let t = 0; t <= maxTime; t += 6) {
    timeLabels.push(t)
  }

  return (
    <div className="tlb">
      <div className="tlc">
        <div className="tb" onClick={handleStart}>⏮</div>
        <div className="tb" onClick={handleRewind}>◀</div>
        <div className={`tb ${isPlaying ? 'on' : ''}`} onClick={onPlayPause}>
          {isPlaying ? '⏸' : '▶'}
        </div>
        <div className="tb" onClick={handleForward}>▶▶</div>
        <div className="tb" onClick={handleEnd}>⏭</div>
        <div style={{ width: '8px' }} />
        <div className="tb" onClick={toggleSpeed}>{playbackSpeed}×</div>
      </div>
      <div className="tlt">
        <div className="tlls">
          {timeLabels.map(t => (
            <span key={t} className={`tll ${Math.abs(currentTime - t) < 1 ? 'on' : ''}`} style={{ left: `${(t / maxTime) * 100}%` }}>
              {t}h
            </span>
          ))}
        </div>
        <div className="tlsw" onClick={handleTimelineClick}>
          <div className="tlr">
            <div className="tlp" style={{ width: `${progressPercent}%` }} />
            {timeLabels.map(t => (
              <div key={`marker-${t}`} className={`tlm ${t % 12 === 0 ? 'mj' : ''}`} style={{ left: `${(t / maxTime) * 100}%` }} />
            ))}
          </div>
          <div className="tlth" style={{ left: `${progressPercent}%` }} />
        </div>
      </div>
      <div className="tli">
        {/* eslint-disable-next-line react-hooks/purity */}
        <div className="tlct">+{currentTime.toFixed(0)}h — {new Date(Date.now() + currentTime * 3600000).toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })} KST</div>
        <div className="tlss">
          <div className="tls"><span className="tlsl">진행률</span><span className="tlsv">{progressPercent.toFixed(0)}%</span></div>
          <div className="tls"><span className="tlsl">속도</span><span className="tlsv">{playbackSpeed}×</span></div>
          <div className="tls"><span className="tlsl">시간</span><span className="tlsv">{currentTime.toFixed(0)}/{maxTime}h</span></div>
        </div>
      </div>
    </div>
  )
}

// 기상 데이터 Mock
function getWeatherData(position: [number, number]) {
  const [lat, lng] = position
  const latSeed = Math.abs(lat * 100) % 10
  const lngSeed = Math.abs(lng * 100) % 10
  const directions = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
  return {
    windSpeed: Number((5 + latSeed).toFixed(1)),
    windDirection: directions[Math.floor(lngSeed * 0.8)],
    waveHeight: Number((1 + latSeed * 0.2).toFixed(1)),
    waterTemp: Number((8 + (lngSeed - 5) * 0.5).toFixed(1)),
    currentSpeed: Number((0.3 + lngSeed * 0.05).toFixed(2)),
    currentDirection: directions[Math.floor(latSeed * 0.8)],
  }
}

function WeatherInfoPanel({ position }: { position: [number, number] }) {
  const weather = getWeatherData(position)
  return (
    <div className="wip">
      <div className="wii">
        <div className="wii-icon">💨</div>
        <div className="wii-value">{weather.windSpeed} m/s</div>
        <div className="wii-label">풍속 ({weather.windDirection})</div>
      </div>
      <div className="wii">
        <div className="wii-icon">🌊</div>
        <div className="wii-value">{weather.waveHeight} m</div>
        <div className="wii-label">파고</div>
      </div>
      <div className="wii">
        <div className="wii-icon">🌡</div>
        <div className="wii-value">{weather.waterTemp}°C</div>
        <div className="wii-label">수온</div>
      </div>
      <div className="wii">
        <div className="wii-icon">🔄</div>
        <div className="wii-value">{weather.currentSpeed} m/s</div>
        <div className="wii-label">해류 ({weather.currentDirection})</div>
      </div>
    </div>
  )
}

// 역추적 리플레이 컨트롤 바 (HTML 오버레이)
function BacktrackReplayBar({ replayFrame, totalFrames, ships }: { replayFrame: number; totalFrames: number; ships: ReplayShip[] }) {
  const progress = (replayFrame / totalFrames) * 100
  return (
    <div style={{
      position: 'absolute', bottom: 80, left: '50%', transform: 'translateX(-50%)',
      background: 'rgba(10,14,26,0.92)', backdropFilter: 'blur(12px)',
      border: '1px solid var(--bdL)', borderRadius: '10px',
      padding: '12px 18px', zIndex: 50,
      display: 'flex', alignItems: 'center', gap: '16px', minWidth: '340px',
    }}>
      <div style={{ fontSize: '14px', color: 'var(--purple)', fontFamily: 'var(--fM)', fontWeight: 700 }}>
        {progress.toFixed(0)}%
      </div>
      <div style={{ flex: 1, height: '4px', background: 'var(--bd)', borderRadius: '2px', position: 'relative' }}>
        <div style={{ width: `${progress}%`, height: '100%', background: 'linear-gradient(90deg, var(--purple), var(--cyan))', borderRadius: '2px', transition: 'width 0.05s' }} />
      </div>
      <div style={{ display: 'flex', gap: '6px' }}>
        {ships.map(s => (
          <div key={s.vesselName} style={{
            width: 8, height: 8, borderRadius: '50%', background: s.color,
            border: '1px solid rgba(255,255,255,0.3)',
          }} title={s.vesselName} />
        ))}
      </div>
    </div>
  )
}
