import { useEffect, useMemo, useRef, type MutableRefObject } from 'react';
import maplibregl from 'maplibre-gl';
import type { SubcableGeoJson } from '../../../entities/subcable/model/types';
import type { MapToggleState } from '../../../features/mapToggles/MapToggles';
import type { MapProjectionId } from '../types';
import { kickRepaint } from '../lib/mapCore';
import { useNativeMapLayers, type NativeLayerSpec } from './useNativeMapLayers';

/* ── Layer / Source IDs ─────────────────────────────────────────────── */
const SRC_ID = 'subcables-src';
const POINTS_SRC_ID = 'subcables-pts-src';

const HITAREA_ID = 'subcables-hitarea';
const CASING_ID = 'subcables-casing';
const LINE_ID = 'subcables-line';
const GLOW_ID = 'subcables-glow';
const POINTS_ID = 'subcables-points';
const LABEL_ID = 'subcables-label';
const HOVER_LABEL_ID = 'subcables-hover-label';

/* ── Paint defaults (used for layer creation + hover reset) ──────── */
const LINE_OPACITY_DEFAULT = ['interpolate', ['linear'], ['zoom'], 2, 0.7, 6, 0.82, 10, 0.92];
const LINE_WIDTH_DEFAULT = ['interpolate', ['linear'], ['zoom'], 2, 1.6, 6, 2.5, 10, 4.0];
const CASING_OPACITY_DEFAULT = ['interpolate', ['linear'], ['zoom'], 2, 0.3, 5, 0.5, 8, 0.65];
const CASING_WIDTH_DEFAULT = ['interpolate', ['linear'], ['zoom'], 2, 2.4, 6, 3.6, 10, 5.5];
const POINTS_OPACITY_DEFAULT = ['interpolate', ['linear'], ['zoom'], 2, 0.5, 5, 0.7, 8, 0.85];
const POINTS_RADIUS_DEFAULT = ['interpolate', ['linear'], ['zoom'], 2, 1.5, 6, 2.5, 10, 4];

/* ── Layer specifications ────────────────────────────────────────── */
const LAYER_SPECS: NativeLayerSpec[] = [
  {
    id: HITAREA_ID,
    type: 'line',
    sourceId: SRC_ID,
    paint: { 'line-color': 'rgba(0,0,0,0)', 'line-width': 14, 'line-opacity': 0 },
    layout: { 'line-cap': 'round', 'line-join': 'round' },
    minzoom: 3,
  },
  {
    id: CASING_ID,
    type: 'line',
    sourceId: SRC_ID,
    paint: {
      'line-color': 'rgba(0,0,0,0.55)',
      'line-width': CASING_WIDTH_DEFAULT,
      'line-opacity': CASING_OPACITY_DEFAULT,
    },
    layout: { 'line-cap': 'round', 'line-join': 'round' },
    minzoom: 3,
  },
  {
    id: LINE_ID,
    type: 'line',
    sourceId: SRC_ID,
    paint: {
      'line-color': ['get', 'color'],
      'line-opacity': LINE_OPACITY_DEFAULT,
      'line-width': LINE_WIDTH_DEFAULT,
    },
    layout: { 'line-cap': 'round', 'line-join': 'round' },
  },
  {
    id: GLOW_ID,
    type: 'line',
    sourceId: SRC_ID,
    paint: {
      'line-color': '#ffffff',
      'line-opacity': 0,
      'line-width': ['interpolate', ['linear'], ['zoom'], 2, 10, 6, 16, 10, 24],
      'line-blur': ['interpolate', ['linear'], ['zoom'], 2, 4, 6, 6, 10, 8],
    },
    filter: ['==', ['get', 'id'], ''],
    layout: { 'line-cap': 'round', 'line-join': 'round' },
    minzoom: 3,
  },
  {
    id: POINTS_ID,
    type: 'circle',
    sourceId: POINTS_SRC_ID,
    paint: {
      'circle-radius': POINTS_RADIUS_DEFAULT,
      'circle-color': ['get', 'color'],
      'circle-opacity': POINTS_OPACITY_DEFAULT,
      'circle-stroke-color': 'rgba(0,0,0,0.5)',
      'circle-stroke-width': 0.5,
    },
    minzoom: 3,
  },
  {
    id: LABEL_ID,
    type: 'symbol',
    sourceId: SRC_ID,
    paint: {
      'text-color': 'rgba(220,232,245,0.82)',
      'text-halo-color': 'rgba(2,6,23,0.9)',
      'text-halo-width': 1.2,
      'text-halo-blur': 0.5,
      'text-opacity': ['interpolate', ['linear'], ['zoom'], 4, 0, 5, 0.7, 8, 0.88],
    },
    layout: {
      'symbol-placement': 'line',
      'text-field': ['get', 'name'],
      'text-size': ['interpolate', ['linear'], ['zoom'], 4, 9, 8, 11, 12, 13],
      'text-font': ['Noto Sans Regular'],
      'text-allow-overlap': false,
      'text-padding': 8,
      'text-rotation-alignment': 'map',
    },
    minzoom: 4,
  },
  {
    id: HOVER_LABEL_ID,
    type: 'symbol',
    sourceId: SRC_ID,
    paint: {
      'text-color': '#ffffff',
      'text-halo-color': 'rgba(0,0,0,0.85)',
      'text-halo-width': 2,
      'text-halo-blur': 0.5,
      'text-opacity': 0,
    },
    layout: {
      'symbol-placement': 'line',
      'text-field': ['get', 'name'],
      'text-size': ['interpolate', ['linear'], ['zoom'], 2, 14, 6, 17, 10, 20],
      'text-font': ['Noto Sans Bold'],
      'text-allow-overlap': true,
      'text-padding': 2,
      'text-rotation-alignment': 'map',
    },
    filter: ['==', ['get', 'id'], ''],
    minzoom: 2,
  },
];

export function useSubcablesLayer(
  mapRef: MutableRefObject<maplibregl.Map | null>,
  projectionBusyRef: MutableRefObject<boolean>,
  reorderGlobeFeatureLayers: () => void,
  opts: {
    subcableGeo: SubcableGeoJson | null;
    overlays: MapToggleState;
    projection: MapProjectionId;
    mapSyncEpoch: number;
    hoveredCableId: string | null;
    onHoverCable: (cableId: string | null) => void;
    onClickCable: (cableId: string | null) => void;
  },
) {
  const { subcableGeo, overlays, projection, mapSyncEpoch, hoveredCableId, onHoverCable, onClickCable } = opts;

  const onHoverRef = useRef(onHoverCable);
  const onClickRef = useRef(onClickCable);
  const hoveredCableIdRef = useRef(hoveredCableId);
  useEffect(() => {
    onHoverRef.current = onHoverCable;
    onClickRef.current = onClickCable;
    hoveredCableIdRef.current = hoveredCableId;
  });

  /* ── Derived point features ──────────────────────────────────────── */
  const pointsGeoJson = useMemo<GeoJSON.FeatureCollection>(() => {
    if (!subcableGeo) return { type: 'FeatureCollection', features: [] };
    const features: GeoJSON.Feature<GeoJSON.Point>[] = [];
    for (const f of subcableGeo.features) {
      const coords = f.properties.coordinates;
      if (!coords || coords.length < 2) continue;
      features.push({
        type: 'Feature',
        properties: { id: f.properties.id, color: f.properties.color },
        geometry: { type: 'Point', coordinates: coords },
      });
    }
    return { type: 'FeatureCollection', features };
  }, [subcableGeo]);

  /* ================================================================
   * Effect 1: Layer creation & data update (via useNativeMapLayers)
   * ================================================================ */
  useNativeMapLayers(
    mapRef,
    projectionBusyRef,
    reorderGlobeFeatureLayers,
    {
      sources: [
        { id: SRC_ID, data: subcableGeo, options: { tolerance: 1, buffer: 64 } },
        { id: POINTS_SRC_ID, data: pointsGeoJson },
      ],
      layers: LAYER_SPECS,
      visible: overlays.subcables,
      beforeLayer: ['zones-fill', 'deck-globe'],
      onAfterSetup: (map) => applyHoverHighlight(map, hoveredCableIdRef.current),
    },
    [subcableGeo, pointsGeoJson, overlays.subcables, projection, mapSyncEpoch, reorderGlobeFeatureLayers],
  );

  /* ================================================================
   * Effect 2: Hover highlight (paint-only, no layer creation)
   * ================================================================ */
  useEffect(() => {
    const map = mapRef.current;
    if (!map || !map.isStyleLoaded()) return;
    if (projectionBusyRef.current) return;
    if (!map.getLayer(LINE_ID)) return;

    applyHoverHighlight(map, hoveredCableId);
    kickRepaint(map);
  }, [hoveredCableId]);

  /* ================================================================
   * Effect 3: Mouse events (bind to hit-area for easy hovering)
   * ================================================================ */
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (!overlays.subcables) return;

    let cancelled = false;
    let retryTimer: ReturnType<typeof setTimeout> | null = null;

    const onMouseMove = (e: maplibregl.MapMouseEvent & { features?: maplibregl.MapGeoJSONFeature[] }) => {
      const cableId = e.features?.[0]?.properties?.id;
      if (typeof cableId === 'string' && cableId) {
        map.getCanvas().style.cursor = 'pointer';
        onHoverRef.current(cableId);
      }
    };

    const onMouseLeave = () => {
      map.getCanvas().style.cursor = '';
      onHoverRef.current(null);
    };

    const onClick = (e: maplibregl.MapMouseEvent & { features?: maplibregl.MapGeoJSONFeature[] }) => {
      const cableId = e.features?.[0]?.properties?.id;
      if (typeof cableId === 'string' && cableId) {
        onClickRef.current(cableId);
      }
    };

    const bindEvents = () => {
      if (cancelled) return;
      const targetLayer = map.getLayer(HITAREA_ID) ? HITAREA_ID : map.getLayer(LINE_ID) ? LINE_ID : null;
      if (!targetLayer) {
        retryTimer = setTimeout(bindEvents, 200);
        return;
      }
      map.on('mousemove', targetLayer, onMouseMove);
      map.on('mouseleave', targetLayer, onMouseLeave);
      map.on('click', targetLayer, onClick);
    };

    if (map.isStyleLoaded()) {
      bindEvents();
    } else {
      map.once('idle', bindEvents);
    }

    return () => {
      cancelled = true;
      if (retryTimer) clearTimeout(retryTimer);
      try {
        map.off('mousemove', HITAREA_ID, onMouseMove);
        map.off('mouseleave', HITAREA_ID, onMouseLeave);
        map.off('click', HITAREA_ID, onClick);
        map.off('mousemove', LINE_ID, onMouseMove);
        map.off('mouseleave', LINE_ID, onMouseLeave);
        map.off('click', LINE_ID, onClick);
      } catch {
        // ignore
      }
    };
  }, [overlays.subcables, mapSyncEpoch]);
}

/* ── Hover highlight helper (paint-only mutations) ────────────────── */
// 기본 레이어는 항상 기본값 유지, glow 레이어(filter 기반)로만 호버 강조
function applyHoverHighlight(map: maplibregl.Map, hoveredId: string | null) {
  const noMatch = ['==', ['get', 'id'], ''] as never;
  if (hoveredId) {
    const matchExpr = ['==', ['get', 'id'], hoveredId];
    if (map.getLayer(GLOW_ID)) {
      map.setFilter(GLOW_ID, matchExpr as never);
      map.setPaintProperty(GLOW_ID, 'line-opacity', 0.55);
    }
    if (map.getLayer(HOVER_LABEL_ID)) {
      map.setFilter(HOVER_LABEL_ID, matchExpr as never);
      map.setPaintProperty(HOVER_LABEL_ID, 'text-opacity', 1.0);
    }
  } else {
    if (map.getLayer(GLOW_ID)) {
      map.setFilter(GLOW_ID, noMatch);
      map.setPaintProperty(GLOW_ID, 'line-opacity', 0);
    }
    if (map.getLayer(HOVER_LABEL_ID)) {
      map.setFilter(HOVER_LABEL_ID, noMatch);
      map.setPaintProperty(HOVER_LABEL_ID, 'text-opacity', 0);
    }
  }
}
