import { useCallback, useEffect, useRef, useState } from 'react';
import maplibregl from 'maplibre-gl';
import { config as maptilerConfig } from '@maptiler/sdk';
import {
  WindLayer,
  TemperatureLayer,
  PrecipitationLayer,
  PressureLayer,
  RadarLayer,
  ColorRamp,
} from '@maptiler/weather';
import { getMapTilerKey } from '../../shared/lib/map/mapTilerKey';

/** 6종 기상 레이어 ID */
export type WeatherLayerId =
  | 'wind'
  | 'temperature'
  | 'precipitation'
  | 'pressure'
  | 'radar'
  | 'clouds';

export interface WeatherLayerMeta {
  id: WeatherLayerId;
  label: string;
  icon: string;
}

export const WEATHER_LAYERS: WeatherLayerMeta[] = [
  { id: 'wind', label: '바람', icon: '💨' },
  { id: 'temperature', label: '기온', icon: '🌡' },
  { id: 'precipitation', label: '강수', icon: '🌧' },
  { id: 'pressure', label: '기압', icon: '◎' },
  { id: 'radar', label: '레이더', icon: '📡' },
  { id: 'clouds', label: '구름', icon: '☁' },
];

const LAYER_ID_PREFIX = 'maptiler-weather-';

/** 한중일 + 남중국해 영역 [west, south, east, north] */
const TILE_BOUNDS: [number, number, number, number] = [100, 10, 150, 50];

type AnyWeatherLayer = WindLayer | TemperatureLayer | PrecipitationLayer | PressureLayer | RadarLayer;

const DEFAULT_ENABLED: Record<WeatherLayerId, boolean> = {
  wind: false,
  temperature: false,
  precipitation: false,
  pressure: false,
  radar: false,
  clouds: false,
};

/** 각 레이어별 범례 정보 */
export interface LegendInfo {
  label: string;
  unit: string;
  colorRamp: ColorRamp;
}

export const LEGEND_META: Record<WeatherLayerId, LegendInfo> = {
  wind: { label: '풍속', unit: 'm/s', colorRamp: ColorRamp.builtin.WIND_ROCKET },
  temperature: { label: '기온', unit: '°C', colorRamp: ColorRamp.builtin.TEMPERATURE_3 },
  precipitation: { label: '강수량', unit: 'mm/h', colorRamp: ColorRamp.builtin.PRECIPITATION },
  pressure: { label: '기압', unit: 'hPa', colorRamp: ColorRamp.builtin.PRESSURE_2 },
  radar: { label: '레이더', unit: 'dBZ', colorRamp: ColorRamp.builtin.RADAR },
  clouds: { label: '구름', unit: 'dBZ', colorRamp: ColorRamp.builtin.RADAR_CLOUD },
};

/**
 * 배속 옵션.
 * animateByFactor(value) → 실시간 1초당 value초 진행.
 * 3600 = 1시간/초, 7200 = 2시간/초 ...
 */
export const SPEED_OPTIONS = [
  { value: 1800, label: '30분/초' },
  { value: 3600, label: '1시간/초' },
  { value: 7200, label: '2시간/초' },
  { value: 14400, label: '4시간/초' },
];

// bounds는 TileLayerOptions에 정의되나 개별 레이어 생성자 타입에 누락되어 as any 필요
/* eslint-disable @typescript-eslint/no-explicit-any */
function createLayerInstance(layerId: WeatherLayerId, opacity: number): AnyWeatherLayer {
  const id = `${LAYER_ID_PREFIX}${layerId}`;
  const opts = { id, opacity, bounds: TILE_BOUNDS };
  switch (layerId) {
    case 'wind':
      return new WindLayer({
        ...opts,
        colorramp: ColorRamp.builtin.WIND_ROCKET,
        speed: 0.001,
        fadeFactor: 0.03,
        maxAmount: 256,
        density: 4,
        fastColor: [255, 100, 80, 230],
      } as any);
    case 'temperature':
      return new TemperatureLayer({
        ...opts,
        colorramp: ColorRamp.builtin.TEMPERATURE_3,
      } as any);
    case 'precipitation':
      return new PrecipitationLayer({
        ...opts,
        colorramp: ColorRamp.builtin.PRECIPITATION,
      } as any);
    case 'pressure':
      return new PressureLayer({
        ...opts,
        colorramp: ColorRamp.builtin.PRESSURE_2,
      } as any);
    case 'radar':
      return new RadarLayer({
        ...opts,
        colorramp: ColorRamp.builtin.RADAR,
      } as any);
    case 'clouds':
      return new RadarLayer({
        ...opts,
        colorramp: ColorRamp.builtin.RADAR_CLOUD,
      } as any);
  }
}
/* eslint-enable @typescript-eslint/no-explicit-any */

/** 타임라인 step 간격 (3시간 = 10 800초) */
const STEP_INTERVAL_SEC = 3 * 3600;

/** start~end 를 STEP_INTERVAL_SEC 단위로 나눈 epoch-초 배열 */
function buildSteps(startSec: number, endSec: number): number[] {
  const steps: number[] = [];
  for (let t = startSec; t <= endSec; t += STEP_INTERVAL_SEC) {
    steps.push(t);
  }
  // 마지막 step이 endSec 와 다르면 보정
  if (steps.length > 0 && steps[steps.length - 1] < endSec) {
    steps.push(endSec);
  }
  return steps;
}

export interface WeatherOverlayState {
  enabled: Record<WeatherLayerId, boolean>;
  activeLayerId: WeatherLayerId | null;
  opacity: number;
  isPlaying: boolean;
  animationSpeed: number;
  currentTime: Date | null;
  startTime: Date | null;
  endTime: Date | null;
  /** step epoch(초) 배열 — 타임라인 눈금 */
  steps: number[];
  isReady: boolean;
}

export interface WeatherOverlayActions {
  toggleLayer: (id: WeatherLayerId) => void;
  setOpacity: (v: number) => void;
  play: () => void;
  pause: () => void;
  setSpeed: (factor: number) => void;
  /** epoch 초 단위로 seek (SDK 내부 시간 = 초) */
  seekTo: (epochSec: number) => void;
}

/**
 * MapTiler Weather SDK 6종 오버레이 레이어를 관리하는 훅.
 * map 인스턴스가 null이면 대기, 값이 설정되면 레이어 추가/제거 활성화.
 */
export function useWeatherOverlay(
  map: maplibregl.Map | null,
): WeatherOverlayState & WeatherOverlayActions {
  const [enabled, setEnabled] = useState<Record<WeatherLayerId, boolean>>({ ...DEFAULT_ENABLED });

  const [opacity, setOpacityState] = useState(0.6);
  const [isPlaying, setIsPlaying] = useState(false);
  const [animationSpeed, setAnimationSpeed] = useState(3600);
  const [currentTime, setCurrentTime] = useState<Date | null>(null);
  const [startTime, setStartTime] = useState<Date | null>(null);
  const [endTime, setEndTime] = useState<Date | null>(null);
  const [isReady, setIsReady] = useState(false);
  const [steps, setSteps] = useState<number[]>([]);

  const layerInstancesRef = useRef<Map<WeatherLayerId, AnyWeatherLayer>>(new Map());
  const apiKeySetRef = useRef(false);
  /** SDK raw 시간 범위 (초 단위) */
  const animRangeRef = useRef<{ start: number; end: number } | null>(null);

  // 레이어 add effect 안의 async 콜백에서 최신 isPlaying/animationSpeed를 읽기 위한 ref
  const isPlayingRef = useRef(isPlaying);
  isPlayingRef.current = isPlaying;
  const animationSpeedRef = useRef(animationSpeed);
  animationSpeedRef.current = animationSpeed;

  // API key 설정 + ServiceWorker 등록
  useEffect(() => {
    if (apiKeySetRef.current) return;
    const key = getMapTilerKey();
    if (key) {
      maptilerConfig.apiKey = key;
      apiKeySetRef.current = true;
    }
    // 타일 캐시 SW 등록 (실패해도 무시 — 캐시 없이도 동작)
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw-weather-cache.js').catch(() => {});
    }
  }, []);

  // maplibre-gl Map에 MapTiler SDK 전용 메서드/프로퍼티 패치
  useEffect(() => {
    if (!map) return;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const m = map as any;
    if (typeof m.getSdkConfig === 'function') return;
    m.getSdkConfig = () => maptilerConfig;
    m.getMaptilerSessionId = () => '';
    m.isGlobeProjection = () => map.getProjection?.()?.type === 'globe';
    if (!m.telemetry) {
      m.telemetry = { registerModule: () => {} };
    }
  }, [map]);

  // enabled 변경 시 레이어 추가/제거
  useEffect(() => {
    if (!map) return;
    if (!apiKeySetRef.current) return;

    const instances = layerInstancesRef.current;

    for (const meta of WEATHER_LAYERS) {
      const isOn = enabled[meta.id];
      const existing = instances.get(meta.id);

      if (isOn && !existing) {
        const layer = createLayerInstance(meta.id, opacity);
        instances.set(meta.id, layer);

        try {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          map.addLayer(layer as any);

          // 소스가 준비되면 시간 범위 설정 + 재생 상태 적용
          layer.onSourceReadyAsync().then(() => {
            if (!instances.has(meta.id)) return;

            // SDK 내부 시간은 epoch 초 단위
            const rawStart = layer.getAnimationStart();
            const rawEnd = layer.getAnimationEnd();
            animRangeRef.current = { start: rawStart, end: rawEnd };

            setStartTime(layer.getAnimationStartDate());
            setEndTime(layer.getAnimationEndDate());
            setSteps(buildSteps(rawStart, rawEnd));

            // 시작 시간으로 초기화 (초 단위 전달)
            layer.setAnimationTime(rawStart);
            setCurrentTime(layer.getAnimationStartDate());
            setIsReady(true);

            // 재생 중이었다면 새 레이어에도 재생 적용
            if (isPlayingRef.current) {
              layer.animateByFactor(animationSpeedRef.current);
            }
          });
        } catch (e) {
          if (import.meta.env.DEV) {
            console.warn(`[WeatherOverlay] Failed to add layer ${meta.id}:`, e);
          }
          instances.delete(meta.id);
        }
      } else if (!isOn && existing) {
        try {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          if (map.getLayer((existing as any).id)) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            map.removeLayer((existing as any).id);
          }
        } catch {
          // ignore
        }
        instances.delete(meta.id);
      }
    }

    if (instances.size === 0) {
      setIsReady(false);
      setStartTime(null);
      setEndTime(null);
      setCurrentTime(null);
      setSteps([]);
      animRangeRef.current = null;
    }
  }, [enabled, map, opacity]);

  // opacity 변경 시 기존 레이어에 반영
  useEffect(() => {
    for (const layer of layerInstancesRef.current.values()) {
      layer.setOpacity(opacity);
    }
  }, [opacity]);

  // 애니메이션 상태 동기화
  useEffect(() => {
    for (const layer of layerInstancesRef.current.values()) {
      if (isPlaying) {
        layer.animateByFactor(animationSpeed);
      } else {
        layer.animateByFactor(0);
      }
    }
  }, [isPlaying, animationSpeed]);

  // 재생 중 rAF 폴링으로 currentTime 동기화 (~4fps)
  useEffect(() => {
    if (!isPlaying) return;
    const instances = layerInstancesRef.current;
    if (instances.size === 0) return;
    let rafId: number;
    let lastUpdate = 0;
    const poll = () => {
      const now = performance.now();
      if (now - lastUpdate > 250) {
        lastUpdate = now;
        const first = instances.values().next().value;
        if (first) {
          setCurrentTime(first.getAnimationTimeDate());
        }
      }
      rafId = requestAnimationFrame(poll);
    };
    rafId = requestAnimationFrame(poll);
    return () => cancelAnimationFrame(rafId);
  }, [isPlaying]);

  // cleanup on map change or unmount
  useEffect(() => {
    return () => {
      for (const [id, layer] of layerInstancesRef.current) {
        try {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          if (map?.getLayer((layer as any).id)) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            map.removeLayer((layer as any).id);
          }
        } catch {
          // ignore
        }
        void id;
      }
      layerInstancesRef.current.clear();
    };
  }, [map]);

  // 라디오 버튼 동작: 하나만 활성, 다시 누르면 전부 off
  const toggleLayer = useCallback((id: WeatherLayerId) => {
    setEnabled((prev) => {
      const next = { ...DEFAULT_ENABLED };
      if (!prev[id]) next[id] = true;
      return next;
    });
    // 레이어 전환 시 isReady 리셋 (새 소스 로딩 대기)
    setIsReady(false);
  }, []);

  const setOpacity = useCallback((v: number) => {
    setOpacityState(Math.max(0, Math.min(1, v)));
  }, []);

  const play = useCallback(() => setIsPlaying(true), []);
  const pause = useCallback(() => setIsPlaying(false), []);

  const setSpeed = useCallback((factor: number) => {
    setAnimationSpeed(factor);
  }, []);

  /** epochSec = SDK 내부 초 단위 시간 */
  const seekTo = useCallback((epochSec: number) => {
    for (const layer of layerInstancesRef.current.values()) {
      layer.setAnimationTime(epochSec);
    }
    setCurrentTime(new Date(epochSec * 1000));
    // SDK CustomLayerInterface.render() 가 호출되어야 타일이 실제 갱신됨
    // 여러 프레임에 걸쳐 repaint 트리거
    if (map) {
      let count = 0;
      const kick = () => {
        map.triggerRepaint();
        if (++count < 6) requestAnimationFrame(kick);
      };
      kick();
    }
  }, [map]);

  const activeLayerId = (Object.keys(enabled) as WeatherLayerId[]).find((k) => enabled[k]) ?? null;

  return {
    enabled,
    activeLayerId,
    opacity,
    isPlaying,
    animationSpeed,
    currentTime,
    startTime,
    endTime,
    steps,
    isReady,
    toggleLayer,
    setOpacity,
    play,
    pause,
    setSpeed,
    seekTo,
  };
}
