import { useEffect, useRef } from 'react';
import { useMap } from '@vis.gl/react-maplibre';
import { API_BASE_URL } from '../../services/api';

const PROXY_PREFIX = `${API_BASE_URL}/tiles/enc`;

const ENC_SPRITE_ID = 'enc-s57';
const ENC_SOURCE_ID = 'enc-s57';
const GLOBE_SOURCE_ID = 'enc-globe';

// sprite JSON에 정의된 아이콘 이름 캐시 (프리픽스 판별용)
let spriteIconNames: Set<string> | null = null;

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

interface EncStyle {
  layers: EncStyleLayer[];
  sources: Record<string, {
    type: string;
    tiles: string[];
    minzoom?: number;
    maxzoom?: number;
  }>;
}

// style.json 캐시
let cachedStyle: EncStyle | null = null;
async function loadEncStyle(): Promise<EncStyle> {
  if (cachedStyle) return cachedStyle;
  const res = await fetch(`${PROXY_PREFIX}/style`);
  if (!res.ok) throw new Error(`ENC style fetch failed: ${res.status}`);
  cachedStyle = await res.json();
  return cachedStyle!;
}

// sprite JSON 로드 → 아이콘 이름 세트
async function loadSpriteNames(): Promise<Set<string>> {
  if (spriteIconNames) return spriteIconNames;
  const res = await fetch(`${PROXY_PREFIX}/sprite/sprite@2x.json`);
  if (!res.ok) throw new Error(`Sprite JSON fetch failed: ${res.status}`);
  const json = await res.json();
  spriteIconNames = new Set(Object.keys(json));
  return spriteIconNames;
}

// sprite 존재 여부 확인
function hasSprite(map: maplibregl.Map, id: string): boolean {
  try {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const sprites = (map.style as any)?.getSprite?.();
    return Array.isArray(sprites) && sprites.some((s: { id: string }) => s.id === id);
  } catch {
    return false;
  }
}

// ─── 아이콘/패턴 이름에 sprite 프리픽스 부착 ───
// addSprite('enc-s57', url)로 등록하면 이미지는 'enc-s57:NAME' 으로 참조해야 함
// style.json은 'NAME' 으로 참조하므로 변환 필요

const SPRITE_PREFIX = `${ENC_SPRITE_ID}:`;

function prefixIconValue(value: unknown, iconNames: Set<string>): unknown {
  if (typeof value === 'string') {
    // sprite에 정의된 아이콘 이름이면 프리픽스 부착
    if (iconNames.has(value)) return `${SPRITE_PREFIX}${value}`;
    return value;
  }
  if (Array.isArray(value)) {
    // MapLibre expression: 재귀적으로 문자열 요소 변환
    return value.map(item => prefixIconValue(item, iconNames));
  }
  return value;
}

function prefixLayerIcons(
  layer: EncStyleLayer,
  iconNames: Set<string>,
): EncStyleLayer {
  const result = { ...layer };

  // layout: icon-image
  if (result.layout?.['icon-image']) {
    result.layout = {
      ...result.layout,
      'icon-image': prefixIconValue(result.layout['icon-image'], iconNames),
    };
  }

  // paint: fill-pattern
  if (result.paint?.['fill-pattern']) {
    result.paint = {
      ...result.paint,
      'fill-pattern': prefixIconValue(result.paint['fill-pattern'], iconNames),
    };
  }

  // paint: background-pattern
  if (result.paint?.['background-pattern']) {
    result.paint = {
      ...result.paint,
      'background-pattern': prefixIconValue(result.paint['background-pattern'], iconNames),
    };
  }

  return result;
}

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

interface S57EncOverlayProps {
  visible: boolean;
}

export function S57EncOverlay({ visible }: S57EncOverlayProps) {
  const { current: mapRef } = useMap();
  const addedLayersRef = useRef<string[]>([]);
  const sourcesAddedRef = useRef(false);
  const originalGlyphsRef = useRef<string | undefined>(undefined);

  useEffect(() => {
    const map = mapRef?.getMap();
    if (!map) return;

    if (visible) {
      addEncLayers(map);
    } else {
      removeEncLayers(map);
    }

    return () => {
      if (map) removeEncLayers(map);
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [visible, mapRef]);

  async function addEncLayers(map: maplibregl.Map) {
    if (sourcesAddedRef.current) return;

    try {
      const [style, iconNames] = await Promise.all([loadEncStyle(), loadSpriteNames()]);

      // glyphs URL을 ENC 프록시로 교체 (ENC symbol 레이어용 폰트)
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const styleObj = map.style as any;
      originalGlyphsRef.current = styleObj.glyphs;
      styleObj.glyphs = `${PROXY_PREFIX}/font/{fontstack}/{range}`;

      // sprite 등록 (중복 방지)
      if (!hasSprite(map, ENC_SPRITE_ID)) {
        const spriteUrl = PROXY_PREFIX.startsWith('http')
          ? `${PROXY_PREFIX}/sprite/sprite`
          : `${window.location.origin}${PROXY_PREFIX}/sprite/sprite`;
        map.addSprite(ENC_SPRITE_ID, spriteUrl);
      }

      // sources 등록 (타일 URL을 프록시로 치환)
      if (!map.getSource(GLOBE_SOURCE_ID)) {
        const globeSrc = style.sources['globe'];
        map.addSource(GLOBE_SOURCE_ID, {
          type: 'vector',
          tiles: [`${PROXY_PREFIX}/globe/{z}/{x}/{y}`],
          minzoom: globeSrc?.minzoom ?? 0,
          maxzoom: globeSrc?.maxzoom ?? 4,
        });
      }

      if (!map.getSource(ENC_SOURCE_ID)) {
        const encSrc = style.sources['enc'];
        map.addSource(ENC_SOURCE_ID, {
          type: 'vector',
          tiles: [`${PROXY_PREFIX}/{z}/{x}/{y}`],
          minzoom: encSrc?.minzoom ?? 4,
          maxzoom: encSrc?.maxzoom ?? 17,
        });
      }

      // layers 등록 (background 포함 — ENC_EMPTY_STYLE 사용 시 배경 필요)
      const layerIds: string[] = [];
      for (const rawLayer of style.layers) {
        if (map.getLayer(rawLayer.id)) continue;

        // 아이콘/패턴 참조에 sprite 프리픽스 부착
        const layer = prefixLayerIcons(rawLayer, iconNames);

        // source 이름을 프록시 source ID로 매핑 (background 타입은 source 없음)
        const mappedSource = layer.source === 'globe' ? GLOBE_SOURCE_ID
          : layer.source === 'enc' ? ENC_SOURCE_ID
          : layer.source;

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const layerSpec: any = {
          id: layer.id,
          type: layer.type,
        };
        if (mappedSource) layerSpec.source = mappedSource;
        if (layer['source-layer']) layerSpec['source-layer'] = layer['source-layer'];
        if (layer.filter) layerSpec.filter = layer.filter;
        if (layer.layout) layerSpec.layout = layer.layout;
        if (layer.paint) layerSpec.paint = layer.paint;
        if (layer.minzoom !== undefined) layerSpec.minzoom = layer.minzoom;
        if (layer.maxzoom !== undefined) layerSpec.maxzoom = layer.maxzoom;

        map.addLayer(layerSpec as maplibregl.AddLayerObject);
        layerIds.push(layer.id);
      }

      addedLayersRef.current = layerIds;
      sourcesAddedRef.current = true;
    } catch (err) {
      console.error('[S57EncOverlay] ENC 레이어 추가 실패:', err);
    }
  }

  function removeEncLayers(map: maplibregl.Map) {
    if (!sourcesAddedRef.current) return;

    // 맵 스타일이 이미 파괴된 경우 (탭 전환 등 언마운트 시) ref만 정리
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (!(map as any).style) {
      addedLayersRef.current = [];
      sourcesAddedRef.current = false;
      return;
    }

    // layers 제거 (역순)
    for (const id of [...addedLayersRef.current].reverse()) {
      if (map.getLayer(id)) {
        map.removeLayer(id);
      }
    }
    addedLayersRef.current = [];

    // sources 제거
    if (map.getSource(ENC_SOURCE_ID)) map.removeSource(ENC_SOURCE_ID);
    if (map.getSource(GLOBE_SOURCE_ID)) map.removeSource(GLOBE_SOURCE_ID);

    // sprite 제거
    if (hasSprite(map, ENC_SPRITE_ID)) {
      map.removeSprite(ENC_SPRITE_ID);
    }

    // glyphs URL 복원
    if (originalGlyphsRef.current !== undefined) {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (map.style as any).glyphs = originalGlyphsRef.current;
    }

    sourcesAddedRef.current = false;
  }

  return null;
}
