import { useEffect, useRef, useCallback, useState } from 'react';
import { useMap } from '@vis.gl/react-maplibre';
import { API_BASE_URL } from '../../services/api';
import { useLayerTree } from '../../hooks/useLayers';
import type { Layer } from '../../services/layerService';
import { getOpacityProp, getColorProp } from './srStyles';

const SR_SOURCE_ID = 'sr';
const PROXY_PREFIX = `${API_BASE_URL}/tiles`;

// MapLibre 내부 요청은 절대 URL이 필요
const ABSOLUTE_PREFIX = API_BASE_URL.startsWith('http')
  ? PROXY_PREFIX
  : `${window.location.origin}${PROXY_PREFIX}`;

// ─── SR 스타일 JSON (Martin style/sr) ───

interface SrStyleLayer {
  id: string;
  type: string;
  source: string;
  'source-layer': string;
  paint?: Record<string, unknown>;
  layout?: Record<string, unknown>;
  filter?: unknown;
  minzoom?: number;
  maxzoom?: number;
}

interface SrStyle {
  sources: Record<string, { type: string; tiles?: string[]; url?: string }>;
  layers: SrStyleLayer[];
}

let cachedStyle: SrStyle | null = null;

async function loadSrStyle(): Promise<SrStyle> {
  if (cachedStyle) return cachedStyle;
  const res = await fetch(`${PROXY_PREFIX}/sr/style`);
  if (!res.ok) throw new Error(`SR style fetch failed: ${res.status}`);
  cachedStyle = await res.json();
  return cachedStyle!;
}

// ─── 헬퍼: wmsLayer(mpc:XXX)에서 코드 추출 ───

function extractCode(wmsLayer: string): string | null {
  // mpc:468 → '468', mpc:386_spr → '386', mpc:kcg → 'kcg', mpc:kcg_ofi → 'kcg_ofi'
  const match = wmsLayer.match(/^mpc:(.+?)(?:_(spr|sum|fal|win|apr))?$/);
  return match ? match[1] : null;
}

// ─── layerTree → SR 매핑 구축 ───

interface SrMapping {
  layerCd: string; // DB LAYER_CD (예: 'LYR001002001004005')
  code: string; // mpc: 뒤 코드 (예: '468', 'kcg', '3')
  name: string; // DB 레이어명 (예: '갯벌', '경찰청', '군산')
}

// ─── source-layer → DB layerCd 매칭 ───

function matchSourceLayer(sourceLayer: string, mappings: SrMapping[]): string[] {
  // 1차: 숫자 접두사 매칭 (468_갯벌 → code '468')
  const numMatch = sourceLayer.match(/^(\d+)/);
  if (numMatch) {
    const code = numMatch[1];
    const matched = mappings.filter((m) => m.code === code);
    if (matched.length > 0) return matched.map((m) => m.layerCd);
  }

  // 2차: 이름 정확 일치 (경찰청 = 경찰청)
  const exactMatch = mappings.filter((m) => sourceLayer === m.name);
  if (exactMatch.length > 0) return exactMatch.map((m) => m.layerCd);

  // 3차: 접미사 일치 (해경관할구역-군산 → name '군산')
  const suffixMatch = mappings.filter(
    (m) => sourceLayer.endsWith(`-${m.name}`) || sourceLayer.endsWith(`_${m.name}`),
  );
  if (suffixMatch.length > 0) return suffixMatch.map((m) => m.layerCd);

  return [];
}

function buildSrMappings(layers: Layer[]): SrMapping[] {
  const result: SrMapping[] = [];
  function traverse(nodes: Layer[]) {
    for (const node of nodes) {
      if (node.wmsLayer) {
        const code = extractCode(node.wmsLayer);
        if (code) {
          result.push({ layerCd: node.id, code, name: node.name });
        }
      }
      if (node.children) traverse(node.children);
    }
  }
  traverse(layers);
  return result;
}

// ─── 컴포넌트 ───

interface SrOverlayProps {
  enabledLayers: Set<string>;
  opacity?: number;
  layerColors?: Record<string, string>;
}

export function SrOverlay({ enabledLayers, opacity = 100, layerColors }: SrOverlayProps) {
  const { current: mapRef } = useMap();
  const { data: layerTree } = useLayerTree();
  const addedLayersRef = useRef<Set<string>>(new Set());
  const sourceAddedRef = useRef(false);
  const [style, setStyle] = useState<SrStyle | null>(cachedStyle);

  // 스타일 JSON 로드 (최초 1회)
  useEffect(() => {
    if (style) return;
    loadSrStyle()
      .then(setStyle)
      .catch((err) => console.error('[SrOverlay] SR 스타일 로드 실패:', err));
  }, [style]);

  const ensureSource = useCallback((map: maplibregl.Map) => {
    if (sourceAddedRef.current) return;
    if (map.getSource(SR_SOURCE_ID)) {
      sourceAddedRef.current = true;
      return;
    }
    map.addSource(SR_SOURCE_ID, {
      type: 'vector',
      tiles: [`${ABSOLUTE_PREFIX}/sr/{z}/{x}/{y}`],
      maxzoom: 14,
    });
    sourceAddedRef.current = true;
  }, []);

  const removeAll = useCallback((map: maplibregl.Map) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (!(map as any).style) {
      addedLayersRef.current.clear();
      sourceAddedRef.current = false;
      return;
    }
    for (const id of addedLayersRef.current) {
      if (map.getLayer(id)) map.removeLayer(id);
    }
    addedLayersRef.current.clear();
    if (map.getSource(SR_SOURCE_ID)) map.removeSource(SR_SOURCE_ID);
    sourceAddedRef.current = false;
  }, []);

  // enabledLayers 변경 시 레이어 동기화
  useEffect(() => {
    const map = mapRef?.getMap();
    if (!map || !layerTree || !style) return;

    const mappings = buildSrMappings(layerTree);

    // source-layer → DB layerCd[] 매핑
    const sourceLayerToIds = new Map<string, string[]>();
    for (const sl of style.layers) {
      const ids = matchSourceLayer(sl['source-layer'], mappings);
      if (ids.length > 0) sourceLayerToIds.set(sl['source-layer'], ids);
    }

    // 커스텀 색상 조회 (source-layer name 기반)
    const getCustomColor = (sourceLayer: string): string | undefined => {
      const ids = sourceLayerToIds.get(sourceLayer);
      if (!ids) return undefined;
      for (const id of ids) {
        const c = layerColors?.[id];
        if (c) return c;
      }
      return undefined;
    };

    // style JSON 레이어 중 활성화된 DB 레이어에 해당하는 스타일 레이어 필터
    const enabledStyleLayers = style.layers.filter((sl) => {
      const ids = sourceLayerToIds.get(sl['source-layer']);
      return ids && ids.some((id) => enabledLayers.has(id));
    });

    const syncLayers = () => {
      ensureSource(map);

      const activeLayerIds = new Set<string>();

      // 활성화된 레이어 추가 또는 visible 설정
      for (const sl of enabledStyleLayers) {
        const layerId = `sr-${sl.id}`;
        activeLayerIds.add(layerId);

        const customColor = getCustomColor(sl['source-layer']);
        const layerType = sl.type as 'fill' | 'line' | 'circle';

        if (map.getLayer(layerId)) {
          map.setLayoutProperty(layerId, 'visibility', 'visible');
          // 기존 레이어에 커스텀 색상 적용
          const colorProp = getColorProp(layerType);
          if (customColor) {
            map.setPaintProperty(layerId, colorProp, customColor);
          } else {
            const orig = sl.paint?.[colorProp];
            if (orig !== undefined) map.setPaintProperty(layerId, colorProp, orig);
          }
        } else {
          const opacityValue = opacity / 100;
          const opacityProp = getOpacityProp(layerType);
          const paint = { ...sl.paint, [opacityProp]: opacityValue };

          // 커스텀 색상 적용
          if (customColor) {
            const colorProp = getColorProp(layerType);
            paint[colorProp] = customColor;
            if (sl.type === 'fill') {
              paint['fill-outline-color'] = customColor;
            }
          }

          try {
            map.addLayer({
              id: layerId,
              type: sl.type,
              source: SR_SOURCE_ID,
              'source-layer': sl['source-layer'],
              paint,
              layout: { visibility: 'visible', ...sl.layout },
              ...(sl.filter ? { filter: sl.filter } : {}),
              ...(sl.minzoom !== undefined && { minzoom: sl.minzoom }),
              ...(sl.maxzoom !== undefined && { maxzoom: sl.maxzoom }),
            } as maplibregl.AddLayerObject);
            addedLayersRef.current.add(layerId);
          } catch (err) {
            console.warn(`[SrOverlay] 레이어 추가 실패 (${sl.id}):`, err);
          }
        }
      }

      // 비활성화된 레이어 숨김
      for (const layerId of addedLayersRef.current) {
        if (!activeLayerIds.has(layerId)) {
          if (map.getLayer(layerId)) {
            map.setLayoutProperty(layerId, 'visibility', 'none');
          }
        }
      }
    };

    if (map.isStyleLoaded()) {
      syncLayers();
    } else {
      map.once('style.load', syncLayers);
      return () => {
        map.off('style.load', syncLayers);
      };
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [enabledLayers, layerTree, style, mapRef, layerColors]);

  // opacity 변경 시 paint 업데이트
  useEffect(() => {
    const map = mapRef?.getMap();
    if (!map || !style) return;

    const opacityValue = opacity / 100;
    for (const layerId of addedLayersRef.current) {
      if (!map.getLayer(layerId)) continue;
      const originalId = layerId.replace(/^sr-/, '');
      const sl = style.layers.find((l) => l.id === originalId);
      if (sl) {
        const prop = getOpacityProp(sl.type as 'fill' | 'line' | 'circle');
        map.setPaintProperty(layerId, prop, opacityValue);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [opacity, mapRef]);

  // layerColors 변경 시 paint 업데이트
  useEffect(() => {
    const map = mapRef?.getMap();
    if (!map || !style || !layerTree) return;

    const mappings = buildSrMappings(layerTree);
    const sourceLayerToIds = new Map<string, string[]>();
    for (const sl of style.layers) {
      const ids = matchSourceLayer(sl['source-layer'], mappings);
      if (ids.length > 0) sourceLayerToIds.set(sl['source-layer'], ids);
    }

    const getCustomColor = (sourceLayer: string): string | undefined => {
      const ids = sourceLayerToIds.get(sourceLayer);
      if (!ids) return undefined;
      for (const id of ids) {
        const c = layerColors?.[id];
        if (c) return c;
      }
      return undefined;
    };

    for (const layerId of addedLayersRef.current) {
      if (!map.getLayer(layerId)) continue;
      const originalId = layerId.replace(/^sr-/, '');
      const sl = style.layers.find((l) => l.id === originalId);
      if (!sl) continue;

      const customColor = getCustomColor(sl['source-layer']);
      const layerType = sl.type as 'fill' | 'line' | 'circle';
      const colorProp = getColorProp(layerType);

      if (customColor) {
        map.setPaintProperty(layerId, colorProp, customColor);
      } else {
        const orig = sl.paint?.[colorProp];
        if (orig !== undefined) map.setPaintProperty(layerId, colorProp, orig as string);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [layerColors, mapRef, style, layerTree]);

  // cleanup on unmount
  useEffect(() => {
    const map = mapRef?.getMap();
    if (!map) return;
    return () => {
      removeAll(map);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mapRef]);

  return null;
}
