import maplibregl, {
  type GeoJSONSourceSpecification,
  type LayerSpecification,
} from 'maplibre-gl';

export function removeLayerIfExists(map: maplibregl.Map, layerId: string | null | undefined) {
  if (!layerId) return;
  try {
    if (map.getLayer(layerId)) {
      map.removeLayer(layerId);
    }
  } catch {
    // ignore
  }
}

export function removeSourceIfExists(map: maplibregl.Map, sourceId: string) {
  try {
    if (map.getSource(sourceId)) {
      map.removeSource(sourceId);
    }
  } catch {
    // ignore
  }
}

// Ship 레이어/소스는 useGlobeShips에서 visibility 토글로 관리 (재생성 비용 회피)
const GLOBE_NATIVE_LAYER_IDS = [
  'pair-lines-ml',
  'fc-lines-ml',
  'fleet-circles-ml',
  'pair-range-ml',
  'subcables-hitarea',
  'subcables-casing',
  'subcables-line',
  'subcables-glow',
  'subcables-points',
  'subcables-label',
  'vessel-track-line',
  'vessel-track-line-hitarea',
  'vessel-track-arrow',
  'vessel-track-pts',
  'vessel-track-pts-highlight',
  'deck-globe',
];

const GLOBE_NATIVE_SOURCE_IDS = [
  'pair-lines-ml-src',
  'fc-lines-ml-src',
  'fleet-circles-ml-src',
  'pair-range-ml-src',
  'subcables-src',
  'subcables-pts-src',
  'vessel-track-line-src',
  'vessel-track-pts-src',
];

export function clearGlobeNativeLayers(map: maplibregl.Map) {
  for (const id of GLOBE_NATIVE_LAYER_IDS) {
    removeLayerIfExists(map, id);
  }
  for (const id of GLOBE_NATIVE_SOURCE_IDS) {
    removeSourceIfExists(map, id);
  }
}

export function ensureGeoJsonSource(
  map: maplibregl.Map,
  sourceId: string,
  data: GeoJSON.GeoJSON,
  options?: Partial<Omit<GeoJSONSourceSpecification, 'type' | 'data'>>,
) {
  const existing = map.getSource(sourceId);
  if (existing) {
    (existing as maplibregl.GeoJSONSource).setData(data);
  } else {
    map.addSource(sourceId, {
      type: 'geojson',
      data,
      ...options,
    } satisfies GeoJSONSourceSpecification);
  }
}

export function ensureLayer(
  map: maplibregl.Map,
  spec: LayerSpecification,
  options?: { before?: string },
) {
  if (map.getLayer(spec.id)) return;
  const before = options?.before && map.getLayer(options.before) ? options.before : undefined;
  map.addLayer(spec, before);
}

export function setLayerVisibility(map: maplibregl.Map, layerId: string, visible: boolean) {
  if (!map.getLayer(layerId)) return;
  try {
    map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');
  } catch {
    // ignore
  }
}

/**
 * setLayoutProperty('visibility') wrapper — 현재 값과 동일하면 호출 생략.
 * MapLibre는 setLayoutProperty 호출 시 항상 style._changed = true를 설정하여
 * 모든 symbol layer의 placement를 재계산시킴. text-allow-overlap:false 라벨이
 * 충돌 검사에 의해 사라지는 문제를 방지하기 위해, 값이 실제로 바뀔 때만 호출.
 */
export function guardedSetVisibility(map: maplibregl.Map, layerId: string, target: 'visible' | 'none') {
  if (!map.getLayer(layerId)) return;
  try {
    if (map.getLayoutProperty(layerId, 'visibility') === target) return;
    map.setLayoutProperty(layerId, 'visibility', target);
  } catch {
    // ignore
  }
}

export function cleanupLayers(
  map: maplibregl.Map,
  layerIds: string[],
  sourceIds: string[],
) {
  requestAnimationFrame(() => {
    for (const id of layerIds) {
      try {
        if (map.getLayer(id)) map.removeLayer(id);
      } catch {
        // ignore
      }
    }
    for (const id of sourceIds) {
      try {
        if (map.getSource(id)) map.removeSource(id);
      } catch {
        // ignore
      }
    }
  });
}
