import { useEffect, useMemo, type MutableRefObject } from 'react';
import maplibregl, {
  type GeoJSONSource,
  type GeoJSONSourceSpecification,
  type LayerSpecification,
} from 'maplibre-gl';
import type { ZoneId } from '../../../entities/zone/model/meta';
import { ZONE_META } from '../../../entities/zone/model/meta';
import type { ZonesGeoJson } from '../../../entities/zone/api/useZones';
import type { MapToggleState } from '../../../features/mapToggles/MapToggles';
import type { BaseMapId, MapProjectionId } from '../types';
import { kickRepaint, onMapStyleReady } from '../lib/mapCore';
import { guardedSetVisibility } from '../lib/layerHelpers';

/** Globe tessellation에서 vertex 65535 초과를 방지하기 위해 좌표 수를 줄임.
 *  수역 폴리곤(해안선 디테일 2100+ vertex)이 globe에서 ~33x로 폭증하므로
 *  ring당 최대 maxPts개로 서브샘플링. 라벨 centroid에는 영향 없음.
 *  4수역 × 300pts × 33x ≈ 39,600 vertices (< 65535 limit). */
function simplifyZonesForGlobe(zones: ZonesGeoJson): ZonesGeoJson {
  const MAX_PTS = 300;
  const subsample = (ring: GeoJSON.Position[]): GeoJSON.Position[] => {
    if (ring.length <= MAX_PTS) return ring;
    const step = Math.ceil(ring.length / MAX_PTS);
    const out: GeoJSON.Position[] = [ring[0]];
    for (let i = step; i < ring.length - 1; i += step) out.push(ring[i]);
    out.push(ring[0]); // close ring
    return out;
  };
  return {
    ...zones,
    features: zones.features.map((f) => ({
      ...f,
      geometry: {
        ...f.geometry,
        coordinates: f.geometry.coordinates.map((polygon) =>
          polygon.map((ring) => subsample(ring)),
        ),
      },
    })),
  };
}

export function useZonesLayer(
  mapRef: MutableRefObject<maplibregl.Map | null>,
  projectionBusyRef: MutableRefObject<boolean>,
  reorderGlobeFeatureLayers: () => void,
  opts: {
    zones: ZonesGeoJson | null;
    overlays: MapToggleState;
    projection: MapProjectionId;
    baseMap: BaseMapId;
    hoveredZoneId: string | null;
    mapSyncEpoch: number;
  },
) {
  const { zones, overlays, projection, baseMap, hoveredZoneId, mapSyncEpoch } = opts;

  // globe용 간소화 데이터를 미리 캐싱 — ensure() 내 매번 재계산 방지
  const simplifiedZones = useMemo(
    () => (zones ? simplifyZonesForGlobe(zones) : null),
    [zones],
  );

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

    const srcId = 'zones-src';
    const fillId = 'zones-fill';
    const lineId = 'zones-line';
    const labelId = 'zones-label';

    const zoneColorExpr: unknown[] = ['match', ['get', 'zoneId']];
    for (const k of Object.keys(ZONE_META) as ZoneId[]) {
      zoneColorExpr.push(k, ZONE_META[k].color);
    }
    zoneColorExpr.push('#3B82F6');
    const zoneLabelExpr: unknown[] = ['match', ['to-string', ['coalesce', ['get', 'zoneId'], '']]];
    for (const k of Object.keys(ZONE_META) as ZoneId[]) {
      zoneLabelExpr.push(k, ZONE_META[k].name);
    }
    zoneLabelExpr.push(['coalesce', ['get', 'zoneName'], ['get', 'zoneLabel'], ['get', 'NAME'], '수역']);

    const ensure = () => {
      // 소스 데이터 간소화 — projectionBusy 중에도 실행해야 함
      // globe 전환 시 projectionBusy 가드 뒤에서만 실행하면 MapLibre가
      // 원본(2100+ vertex) 데이터로 globe tessellation → 73,000+ vertex → 노란 막대
      const sourceData = projection === 'globe' ? simplifiedZones : zones;
      if (sourceData) {
        try {
          const existing = map.getSource(srcId) as GeoJSONSource | undefined;
          if (existing) existing.setData(sourceData);
        } catch { /* ignore — source may not exist yet */ }
      }

      const visibility: 'visible' | 'none' = overlays.zones ? 'visible' : 'none';
      guardedSetVisibility(map, fillId, visibility);
      guardedSetVisibility(map, lineId, visibility);
      guardedSetVisibility(map, labelId, visibility);

      if (projectionBusyRef.current) return;
      if (!zones) return;
      if (!map.isStyleLoaded()) return;

      try {
        // 소스가 아직 없으면 생성 (setData는 위에서 이미 처리됨)
        if (!map.getSource(srcId)) {
          const data = projection === 'globe' ? simplifiedZones ?? zones : zones;
          map.addSource(srcId, { type: 'geojson', data: data! } as GeoJSONSourceSpecification);
        }

        const style = map.getStyle();
        const styleLayers = style && Array.isArray(style.layers) ? style.layers : [];
        const firstSymbol = styleLayers.find((l) => (l as { type?: string } | undefined)?.type === 'symbol') as
          | { id?: string }
          | undefined;
        const before = map.getLayer('deck-globe')
          ? 'deck-globe'
          : map.getLayer('ships')
            ? 'ships'
            : map.getLayer('seamark')
              ? 'seamark'
              : firstSymbol?.id;

        const zoneMatchExpr =
          hoveredZoneId !== null
            ? (['==', ['to-string', ['coalesce', ['get', 'zoneId'], '']], hoveredZoneId] as unknown[])
            : false;
        const zoneLineWidthExpr = hoveredZoneId
          ? ([
              'interpolate',
              ['linear'],
              ['zoom'],
              4,
              ['case', zoneMatchExpr, 1.6, 0.8],
              10,
              ['case', zoneMatchExpr, 2.0, 1.4],
              14,
              ['case', zoneMatchExpr, 2.8, 2.1],
            ] as unknown as never)
          : (['interpolate', ['linear'], ['zoom'], 4, 0.8, 10, 1.4, 14, 2.1] as never);

        if (map.getLayer(fillId)) {
          try {
            map.setPaintProperty(
              fillId,
              'fill-opacity',
              hoveredZoneId ? (['case', zoneMatchExpr, 0.24, 0.1] as unknown as number) : 0.12,
            );
          } catch {
            // ignore
          }
        }

        if (map.getLayer(lineId)) {
          try {
            map.setPaintProperty(
              lineId,
              'line-color',
              hoveredZoneId
                ? (['case', zoneMatchExpr, 'rgba(125,211,252,0.98)', zoneColorExpr as never] as never)
                : (zoneColorExpr as never),
            );
          } catch {
            // ignore
          }
          try {
            map.setPaintProperty(lineId, 'line-opacity', hoveredZoneId ? (['case', zoneMatchExpr, 1, 0.85] as never) : 0.85);
          } catch {
            // ignore
          }
          try {
            map.setPaintProperty(lineId, 'line-width', zoneLineWidthExpr);
          } catch {
            // ignore
          }
        }

        if (!map.getLayer(fillId)) {
          map.addLayer(
            {
              id: fillId,
              type: 'fill',
              source: srcId,
              paint: {
                'fill-color': zoneColorExpr as never,
                'fill-opacity': hoveredZoneId
                  ? ([
                      'case',
                      zoneMatchExpr,
                      0.24,
                      0.1,
                    ] as unknown as number)
                  : 0.12,
              },
              layout: { visibility },
            } as unknown as LayerSpecification,
            before,
          );
        }

        if (!map.getLayer(lineId)) {
          map.addLayer(
            {
              id: lineId,
              type: 'line',
              source: srcId,
              paint: {
                'line-color': hoveredZoneId
                  ? (['case', zoneMatchExpr, 'rgba(125,211,252,0.98)', zoneColorExpr as never] as never)
                  : (zoneColorExpr as never),
                'line-opacity': hoveredZoneId
                  ? (['case', zoneMatchExpr, 1, 0.85] as never)
                  : 0.85,
                'line-width': zoneLineWidthExpr,
              },
              layout: { visibility },
            } as unknown as LayerSpecification,
            before,
          );
        }

        if (!map.getLayer(labelId)) {
          map.addLayer(
              {
                id: labelId,
                type: 'symbol',
                source: srcId,
                layout: {
                  visibility,
                  'symbol-placement': 'point',
                  'text-field': zoneLabelExpr as never,
                  'text-size': 11,
                  'text-font': ['Noto Sans Regular'],
                'text-anchor': 'top',
                'text-offset': [0, 0.35],
                'text-allow-overlap': false,
                'text-ignore-placement': false,
              },
              paint: {
                'text-color': '#dbeafe',
                'text-halo-color': 'rgba(2,6,23,0.85)',
                'text-halo-width': 1.2,
                'text-halo-blur': 0.8,
              },
            } as unknown as LayerSpecification,
            undefined,
          );
        }
      } catch (e) {
        console.warn('Zones layer setup failed:', e);
      } finally {
        reorderGlobeFeatureLayers();
        kickRepaint(map);
      }
    };

    const stop = onMapStyleReady(map, ensure);
    return () => {
      stop();
    };
  }, [zones, simplifiedZones, overlays.zones, projection, baseMap, hoveredZoneId, mapSyncEpoch, reorderGlobeFeatureLayers]);
}
