import { HexagonLayer } from '@deck.gl/aggregation-layers';
import { IconLayer, LineLayer, ScatterplotLayer, TextLayer } from '@deck.gl/layers';
import type { PickingInfo } from '@deck.gl/core';
import type { AisTarget } from '../../../entities/aisTarget/model/types';
import type { LegacyVesselInfo } from '../../../entities/legacyVessel/model/types';
import { ALARM_BADGE, type LegacyAlarmKind } from '../../../features/legacyDashboard/model/types';
import type { FleetCircle, PairLink } from '../../../features/legacyDashboard/model/types';
import type { MapToggleState } from '../../../features/mapToggles/MapToggles';
import type { DashSeg, PairRangeCircle } from '../types';
import {
  FLAT_OTHER_SHIP_SIZE,
  FLAT_TARGET_SHIP_SIZE,
  FLAT_LEGACY_HALO_RADIUS,
  FLAT_LEGACY_HALO_RADIUS_SELECTED,
  DEPTH_DISABLED_PARAMS,
  GLOBE_OVERLAY_PARAMS,
  HALO_OUTLINE_COLOR,
  HALO_OUTLINE_COLOR_SELECTED,
  PAIR_RANGE_NORMAL_DECK,
  PAIR_RANGE_WARN_DECK,
  PAIR_LINE_NORMAL_DECK,
  PAIR_LINE_WARN_DECK,
  FC_LINE_NORMAL_DECK,
  FC_LINE_SUSPICIOUS_DECK,
  FLEET_RANGE_LINE_DECK,
  FLEET_RANGE_FILL_DECK,
  PAIR_RANGE_NORMAL_DECK_HL,
  PAIR_RANGE_WARN_DECK_HL,
  PAIR_LINE_NORMAL_DECK_HL,
  PAIR_LINE_WARN_DECK_HL,
  FC_LINE_NORMAL_DECK_HL,
  FC_LINE_SUSPICIOUS_DECK_HL,
  FLEET_RANGE_LINE_DECK_HL,
  FLEET_RANGE_FILL_DECK_HL,
} from '../constants';
import { getDisplayHeading } from './shipUtils';
import {
  getShipIconSpec,
  getTargetShipIconSpec,
  getShipIconAngle,
  SPEED_THRESHOLD_KN,
} from '../../../shared/lib/map/shipKind';

/** 64×64 white ring SVG — mask:true로 getColor 틴트 적용 */
const ALARM_RING_ICON_URL = `data:image/svg+xml,${encodeURIComponent(
  '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><circle cx="32" cy="32" r="27" fill="none" stroke="white" stroke-width="5"/></svg>',
)}`;

/* ── 공통 콜백 인터페이스 ─────────────────────────────── */

interface DeckHoverCallbacks {
  touchDeckHoverState: (isHover: boolean) => void;
  setDeckHoverPairs: (next: number[]) => void;
  setDeckHoverMmsi: (next: number[]) => void;
  clearDeckHoverPairs: () => void;
  clearMapFleetHoverState: () => void;
  setMapFleetHoverState: (ownerKey: string | null, vesselMmsis: number[]) => void;
  toFleetMmsiList: (value: unknown) => number[];
}

interface DeckSelectCallbacks {
  hasAuxiliarySelectModifier: (ev?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null) => boolean;
  onSelectMmsi: (mmsi: number | null) => void;
  onToggleHighlightMmsi?: (mmsi: number) => void;
  onDeckSelectOrHighlight: (info: unknown, allowMultiSelect?: boolean) => void;
}

/* ── Mercator Deck 레이어 ─────────────────────────────── */

export interface MercatorDeckLayerContext extends DeckHoverCallbacks, DeckSelectCallbacks {
  shipLayerData: AisTarget[];
  legacyTargetsOrdered: AisTarget[];
  legacyOverlayTargets: AisTarget[];
  legacyHits: Map<number, LegacyVesselInfo> | null | undefined;
  pairLinks: PairLink[] | undefined;
  fcDashed: DashSeg[];
  fleetCircles: FleetCircle[] | undefined;
  pairRanges: PairRangeCircle[];
  pairLinksInteractive: PairLink[];
  pairRangesInteractive: PairRangeCircle[];
  fcLinesInteractive: DashSeg[];
  fleetCirclesInteractive: FleetCircle[];
  overlays: MapToggleState;
  showDensity: boolean;
  showShips: boolean;
  selectedMmsi: number | null;
  shipHighlightSet: Set<number>;
  alarmTargets?: AisTarget[];
  alarmMmsiMap?: Map<number, LegacyAlarmKind>;
  alarmPulseRadius?: number;
  alarmPulseHoverRadius?: number;
  shipPhotoTargets?: AisTarget[];
  onClickShipPhoto?: (mmsi: number) => void;
  shipLabelColors?: import('./labelColor').ShipLabelColors;
}

export function buildMercatorDeckLayers(ctx: MercatorDeckLayerContext): unknown[] {
  const layers: unknown[] = [];
  const overlayParams = DEPTH_DISABLED_PARAMS;
  const clearDeckHover = () => { ctx.touchDeckHoverState(false); };
  const isTargetShip = (mmsi: number) => (ctx.legacyHits ? ctx.legacyHits.has(mmsi) : false);

  const shipOtherData: AisTarget[] = [];
  const shipTargetData: AisTarget[] = [];
  for (const t of ctx.shipLayerData) {
    if (isTargetShip(t.mmsi)) shipTargetData.push(t);
    else shipOtherData.push(t);
  }

  /* ─ density ─ */
  if (ctx.showDensity) {
    layers.push(
      new HexagonLayer<AisTarget>({
        id: 'density',
        data: ctx.shipLayerData,
        pickable: true,
        extruded: true,
        radius: 2500,
        elevationScale: 35,
        coverage: 0.92,
        opacity: 0.35,
        getPosition: (d) => [d.lon, d.lat],
      }),
    );
  }

  /* ─ pair range ─ */
  if (ctx.overlays.pairRange && ctx.pairRanges.length > 0) {
    const validRanges = ctx.pairRanges.filter((d) => d.center && isFinite(d.center[0]) && isFinite(d.center[1]) && isFinite(d.radiusNm));
    if (validRanges.length > 0) layers.push(
      new ScatterplotLayer<PairRangeCircle>({
        id: 'pair-range',
        data: validRanges,
        pickable: true,
        billboard: false,
        parameters: overlayParams,
        filled: false,
        stroked: true,
        radiusUnits: 'meters',
        getRadius: (d) => d.radiusNm * 1852,
        radiusMinPixels: 10,
        lineWidthUnits: 'pixels',
        getLineWidth: () => 1.8,
        getLineColor: (d) => (d.warn ? PAIR_RANGE_WARN_DECK : PAIR_RANGE_NORMAL_DECK),
        getPosition: (d) => d.center,
        onHover: (info) => {
          if (!info.object) { clearDeckHover(); return; }
          ctx.touchDeckHoverState(true);
          const p = info.object as PairRangeCircle;
          ctx.setDeckHoverPairs([p.aMmsi, p.bMmsi]);
          ctx.setDeckHoverMmsi([p.aMmsi, p.bMmsi]);
          ctx.clearMapFleetHoverState();
        },
        onClick: (info) => {
          if (!info.object) { ctx.onSelectMmsi(null); return; }
          const obj = info.object as PairRangeCircle;
          const sourceEvent = (info as { srcEvent?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null }).srcEvent;
          if (sourceEvent && ctx.hasAuxiliarySelectModifier(sourceEvent)) {
            ctx.onToggleHighlightMmsi?.(obj.aMmsi);
            ctx.onToggleHighlightMmsi?.(obj.bMmsi);
            return;
          }
          ctx.onDeckSelectOrHighlight({ mmsi: obj.aMmsi });
        },
      }),
    );
  }

  /* ─ pair lines ─ */
  if (ctx.overlays.pairLines && (ctx.pairLinks?.length ?? 0) > 0) {
    layers.push(
      new LineLayer<PairLink>({
        id: 'pair-lines',
        data: ctx.pairLinks,
        pickable: true,
        parameters: overlayParams,
        getSourcePosition: (d) => d.from,
        getTargetPosition: (d) => d.to,
        getColor: (d) => (d.warn ? PAIR_LINE_WARN_DECK : PAIR_LINE_NORMAL_DECK),
        getWidth: (d) => (d.warn ? 3.5 : 2.5),
        widthUnits: 'pixels',
        onHover: (info) => {
          if (!info.object) { clearDeckHover(); return; }
          ctx.touchDeckHoverState(true);
          const obj = info.object as PairLink;
          ctx.setDeckHoverPairs([obj.aMmsi, obj.bMmsi]);
          ctx.setDeckHoverMmsi([obj.aMmsi, obj.bMmsi]);
          ctx.clearMapFleetHoverState();
        },
        onClick: (info) => {
          if (!info.object) return;
          const obj = info.object as PairLink;
          const sourceEvent = (info as { srcEvent?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null }).srcEvent;
          if (sourceEvent && ctx.hasAuxiliarySelectModifier(sourceEvent)) {
            ctx.onToggleHighlightMmsi?.(obj.aMmsi);
            ctx.onToggleHighlightMmsi?.(obj.bMmsi);
            return;
          }
          ctx.onDeckSelectOrHighlight({ mmsi: obj.aMmsi });
        },
      }),
    );
  }

  /* ─ fc lines ─ */
  if (ctx.overlays.fcLines && ctx.fcDashed.length > 0) {
    layers.push(
      new LineLayer<DashSeg>({
        id: 'fc-lines',
        data: ctx.fcDashed,
        pickable: true,
        parameters: overlayParams,
        getSourcePosition: (d) => d.from,
        getTargetPosition: (d) => d.to,
        getColor: (d) => (d.suspicious ? FC_LINE_SUSPICIOUS_DECK : FC_LINE_NORMAL_DECK),
        getWidth: () => 2.2,
        widthUnits: 'pixels',
        onHover: (info) => {
          if (!info.object) { clearDeckHover(); return; }
          ctx.touchDeckHoverState(true);
          const obj = info.object as DashSeg;
          if (obj.fromMmsi == null || obj.toMmsi == null) { clearDeckHover(); return; }
          ctx.setDeckHoverPairs([obj.fromMmsi, obj.toMmsi]);
          ctx.setDeckHoverMmsi([obj.fromMmsi, obj.toMmsi]);
          ctx.clearMapFleetHoverState();
        },
        onClick: (info) => {
          if (!info.object) return;
          const obj = info.object as DashSeg;
          if (obj.fromMmsi == null || obj.toMmsi == null) return;
          const sourceEvent = (info as { srcEvent?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null }).srcEvent;
          if (sourceEvent && ctx.hasAuxiliarySelectModifier(sourceEvent)) {
            ctx.onToggleHighlightMmsi?.(obj.fromMmsi);
            ctx.onToggleHighlightMmsi?.(obj.toMmsi);
            return;
          }
          ctx.onDeckSelectOrHighlight({ mmsi: obj.fromMmsi });
        },
      }),
    );
  }

  /* ─ fleet circles ─ */
  if (ctx.overlays.fleetCircles && (ctx.fleetCircles?.length ?? 0) > 0) {
    layers.push(
      new ScatterplotLayer<FleetCircle>({
        id: 'fleet-circles',
        data: ctx.fleetCircles,
        pickable: true,
        billboard: false,
        parameters: overlayParams,
        filled: false,
        stroked: true,
        radiusUnits: 'meters',
        getRadius: (d) => d.radiusNm * 1852,
        lineWidthUnits: 'pixels',
        getLineWidth: () => 2.0,
        getLineColor: () => FLEET_RANGE_LINE_DECK,
        getPosition: (d) => d.center,
        onHover: (info) => {
          if (!info.object) { clearDeckHover(); return; }
          ctx.touchDeckHoverState(true);
          const obj = info.object as FleetCircle;
          const list = ctx.toFleetMmsiList(obj.vesselMmsis);
          ctx.setMapFleetHoverState(obj.ownerKey || null, list);
          ctx.setDeckHoverMmsi(list);
          ctx.clearDeckHoverPairs();
        },
        onClick: (info) => {
          if (!info.object) return;
          const obj = info.object as FleetCircle;
          const list = ctx.toFleetMmsiList(obj.vesselMmsis);
          const sourceEvent = (info as { srcEvent?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null }).srcEvent;
          if (sourceEvent && ctx.hasAuxiliarySelectModifier(sourceEvent)) {
            for (const mmsi of list) ctx.onToggleHighlightMmsi?.(mmsi);
            return;
          }
          const first = list[0];
          if (first != null) ctx.onDeckSelectOrHighlight({ mmsi: first });
        },
      }),
    );
    layers.push(
      new ScatterplotLayer<FleetCircle>({
        id: 'fleet-circles-fill',
        data: ctx.fleetCircles,
        pickable: false,
        billboard: false,
        parameters: overlayParams,
        filled: true,
        stroked: false,
        radiusUnits: 'meters',
        getRadius: (d) => d.radiusNm * 1852,
        getFillColor: () => FLEET_RANGE_FILL_DECK,
        getPosition: (d) => d.center,
      }),
    );
  }

  /* ─ ships ─ */
  if (ctx.showShips) {
    const shipOnHover = (info: PickingInfo) => {
      if (!info.object) { clearDeckHover(); return; }
      ctx.touchDeckHoverState(true);
      const obj = info.object as AisTarget;
      ctx.setDeckHoverMmsi([obj.mmsi]);
      ctx.clearDeckHoverPairs();
      ctx.clearMapFleetHoverState();
    };
    const shipOnClick = (info: PickingInfo) => {
      if (!info.object) { ctx.onSelectMmsi(null); return; }
      ctx.onDeckSelectOrHighlight(
        {
          mmsi: (info.object as AisTarget).mmsi,
          srcEvent: (info as { srcEvent?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean } | null }).srcEvent,
        },
        true,
      );
    };

    if (shipOtherData.length > 0) {
      layers.push(
        new IconLayer<AisTarget>({
          id: 'ships-other',
          data: shipOtherData,
          pickable: true,
          billboard: false,
          parameters: overlayParams,
          getIcon: (d) => getShipIconSpec(d.signalKindCode ?? d.shipKindCode, d.sog),
          getPosition: (d) => [d.lon, d.lat] as [number, number],
          getAngle: (d) => getShipIconAngle(d.signalKindCode ?? d.shipKindCode, d.cog),
          sizeUnits: 'pixels',
          getSize: FLAT_OTHER_SHIP_SIZE,
          onHover: shipOnHover,
          onClick: shipOnClick,
          alphaCutoff: 0.05,
        }),
      );
    }

    if (ctx.legacyTargetsOrdered.length > 0) {
      layers.push(
        new ScatterplotLayer<AisTarget>({
          id: 'legacy-halo',
          data: ctx.legacyTargetsOrdered,
          pickable: false,
          billboard: false,
          parameters: overlayParams,
          filled: false,
          stroked: true,
          radiusUnits: 'pixels',
          getRadius: () => FLAT_LEGACY_HALO_RADIUS,
          lineWidthUnits: 'pixels',
          getLineWidth: () => 2,
          getLineColor: () => HALO_OUTLINE_COLOR,
          getPosition: (d) => [d.lon, d.lat] as [number, number],
        }),
      );
    }

    if (shipTargetData.length > 0) {
      layers.push(
        new IconLayer<AisTarget>({
          id: 'ships-target',
          data: shipTargetData,
          pickable: true,
          billboard: false,
          parameters: overlayParams,
          getIcon: (d) => getTargetShipIconSpec(ctx.legacyHits?.get(d.mmsi)?.shipCode ?? null, d.sog),
          getPosition: (d) => [d.lon, d.lat] as [number, number],
          getAngle: (d) => {
            const isMoving = Number.isFinite(d.sog) && (d.sog as number) > SPEED_THRESHOLD_KN;
            return isMoving ? -getDisplayHeading({ cog: d.cog, heading: d.heading }) : 0;
          },
          sizeUnits: 'pixels',
          getSize: FLAT_TARGET_SHIP_SIZE,
          onHover: shipOnHover,
          onClick: shipOnClick,
          alphaCutoff: 0.05,
        }),
      );
    }
  }

  /* ─ interactive overlays (only when parent overlay is enabled) ─ */
  if (ctx.overlays.pairRange && ctx.pairRangesInteractive.length > 0) {
    layers.push(new ScatterplotLayer<PairRangeCircle>({ id: 'pair-range-overlay', data: ctx.pairRangesInteractive, pickable: false, billboard: false, parameters: overlayParams, filled: false, stroked: true, radiusUnits: 'meters', getRadius: (d) => d.radiusNm * 1852, radiusMinPixels: 10, lineWidthUnits: 'pixels', getLineWidth: () => 2.8, getLineColor: (d) => (d.warn ? PAIR_RANGE_WARN_DECK_HL : PAIR_RANGE_NORMAL_DECK_HL), getPosition: (d) => d.center }));
  }
  if (ctx.overlays.pairLines && ctx.pairLinksInteractive.length > 0) {
    layers.push(new LineLayer<PairLink>({ id: 'pair-lines-overlay', data: ctx.pairLinksInteractive, pickable: false, parameters: overlayParams, getSourcePosition: (d) => d.from, getTargetPosition: (d) => d.to, getColor: (d) => (d.warn ? PAIR_LINE_WARN_DECK_HL : PAIR_LINE_NORMAL_DECK_HL), getWidth: () => 4.5, widthUnits: 'pixels' }));
  }
  if (ctx.overlays.fcLines && ctx.fcLinesInteractive.length > 0) {
    layers.push(new LineLayer<DashSeg>({ id: 'fc-lines-overlay', data: ctx.fcLinesInteractive, pickable: false, parameters: overlayParams, getSourcePosition: (d) => d.from, getTargetPosition: (d) => d.to, getColor: (d) => (d.suspicious ? FC_LINE_SUSPICIOUS_DECK_HL : FC_LINE_NORMAL_DECK_HL), getWidth: () => 3.2, widthUnits: 'pixels' }));
  }
  if (ctx.overlays.fleetCircles && ctx.fleetCirclesInteractive.length > 0) {
    layers.push(new ScatterplotLayer<FleetCircle>({ id: 'fleet-circles-overlay-fill', data: ctx.fleetCirclesInteractive, pickable: false, billboard: false, parameters: overlayParams, filled: true, stroked: false, radiusUnits: 'meters', getRadius: (d) => d.radiusNm * 1852, getFillColor: () => FLEET_RANGE_FILL_DECK_HL }));
    layers.push(new ScatterplotLayer<FleetCircle>({ id: 'fleet-circles-overlay', data: ctx.fleetCirclesInteractive, pickable: false, billboard: false, parameters: overlayParams, filled: false, stroked: true, radiusUnits: 'meters', getRadius: (d) => d.radiusNm * 1852, lineWidthUnits: 'pixels', getLineWidth: () => 3.0, getLineColor: () => FLEET_RANGE_LINE_DECK_HL, getPosition: (d) => d.center }));
  }

  /* ─ legacy overlay (highlight/selected — breathing ring, no icon enlargement) ─ */
  if (ctx.showShips && ctx.legacyOverlayTargets.length > 0) {
    layers.push(new ScatterplotLayer<AisTarget>({
      id: 'legacy-halo-overlay',
      data: ctx.legacyOverlayTargets,
      pickable: false,
      billboard: false,
      parameters: overlayParams,
      filled: false,
      stroked: true,
      radiusUnits: 'pixels',
      getRadius: (d) => (ctx.selectedMmsi && d.mmsi === ctx.selectedMmsi ? 18 : 16),
      lineWidthUnits: 'pixels',
      getLineWidth: 2.5,
      getLineColor: (d) => {
        if (ctx.selectedMmsi && d.mmsi === ctx.selectedMmsi) return [14, 234, 255, 200];
        return [245, 158, 11, 190];
      },
      getPosition: (d) => [d.lon, d.lat] as [number, number],
    }));
  }

  /* ─ ship name labels (Mercator) ─ */
  if (ctx.showShips && ctx.overlays.shipLabels) {
    const labelData: AisTarget[] = [];
    for (const t of ctx.shipLayerData) {
      const isTarget = ctx.legacyHits?.has(t.mmsi) ?? false;
      const isSelected = ctx.selectedMmsi != null && t.mmsi === ctx.selectedMmsi;
      const isHighlighted = ctx.shipHighlightSet.has(t.mmsi);
      if (isTarget || isSelected || isHighlighted) labelData.push(t);
    }
    if (labelData.length > 0) {
      layers.push(
        new TextLayer<AisTarget>({
          id: 'ship-labels',
          data: labelData,
          pickable: false,
          billboard: true,
          getText: (d) => {
            const legacy = ctx.legacyHits?.get(d.mmsi);
            const baseName = (legacy?.shipNameRoman?.toUpperCase() || legacy?.shipNameCn || d.name?.toUpperCase() || '').trim();
            if (!baseName) return '';
            const alarmKind = ctx.alarmMmsiMap?.get(d.mmsi) ?? null;
            return alarmKind ? `[${ALARM_BADGE[alarmKind].label}] ${baseName}` : baseName;
          },
          getPosition: (d) => [d.lon, d.lat] as [number, number],
          getColor: (d) => {
            if (ctx.selectedMmsi != null && d.mmsi === ctx.selectedMmsi) return [14, 234, 255, 242];
            if (ctx.shipHighlightSet.has(d.mmsi)) return [245, 158, 11, 242];
            return ctx.shipLabelColors?.deckDefault ?? [226, 232, 240, 234];
          },
          getSize: 11,
          sizeUnits: 'pixels',
          fontFamily: 'NanumSquare, Malgun Gothic, 맑은 고딕, Noto Sans KR, sans-serif',
          characterSet: 'auto',
          getPixelOffset: [0, 16],
          getTextAnchor: 'middle',
          outlineWidth: 1,
          outlineColor: ctx.shipLabelColors?.deckOutline ?? [0, 0, 0, 230],
        }),
      );
    }
  }

  /* ─ alarm pulse (IconLayer + SVG ring) + badge ─ */
  const alarmTargets = ctx.alarmTargets ?? [];
  const alarmMap = ctx.alarmMmsiMap;
  if (ctx.showShips && alarmMap && alarmMap.size > 0 && alarmTargets.length > 0) {
    const pulseSize = ctx.alarmPulseRadius ?? 40;
    const pulseHoverSize = ctx.alarmPulseHoverRadius ?? 48;
    layers.push(
      new IconLayer<AisTarget>({
        id: 'alarm-pulse',
        data: alarmTargets,
        pickable: false,
        billboard: true,
        parameters: overlayParams,
        iconAtlas: ALARM_RING_ICON_URL,
        iconMapping: { ring: { x: 0, y: 0, width: 64, height: 64, mask: true } },
        getIcon: () => 'ring',
        sizeUnits: 'pixels',
        getSize: (d) => {
          const isHover = (ctx.selectedMmsi != null && d.mmsi === ctx.selectedMmsi) || ctx.shipHighlightSet.has(d.mmsi);
          return isHover ? pulseHoverSize : pulseSize;
        },
        getColor: (d) => {
          const kind = alarmMap.get(d.mmsi);
          return kind ? ALARM_BADGE[kind].rgba : [107, 114, 128, 200] as [number, number, number, number];
        },
        getPosition: (d) => [d.lon, d.lat] as [number, number],
        updateTriggers: { getSize: [pulseSize, pulseHoverSize, ctx.selectedMmsi, ctx.shipHighlightSet] },
      }),
    );
    layers.push(
      new TextLayer<AisTarget>({
        id: 'alarm-badge',
        data: alarmTargets,
        pickable: false,
        parameters: overlayParams,
        getText: (d) => {
          const kind = alarmMap.get(d.mmsi);
          return kind ? ALARM_BADGE[kind].label : '';
        },
        getPosition: (d) => [d.lon, d.lat] as [number, number],
        getColor: [255, 255, 255, 255],
        getBackgroundColor: (d) => {
          const kind = alarmMap.get(d.mmsi);
          return kind ? ALARM_BADGE[kind].rgba : [107, 114, 128, 200] as [number, number, number, number];
        },
        background: true,
        backgroundPadding: [3, 1],
        getPixelOffset: [14, -14],
        sizeUnits: 'pixels',
        getSize: 10,
        fontFamily: 'NanumSquare, Malgun Gothic, 맑은 고딕, Noto Sans KR, sans-serif',
        characterSet: 'auto',
      }),
    );
  }

  /* ─ ship photo indicator — disabled (파란 원 아이콘 제거) ─ */

  return layers;
}

/* ── Globe Deck 오버레이 레이어 ────────────────────────── */

export interface GlobeDeckLayerContext {
  pairRanges: PairRangeCircle[];
  pairLinks: PairLink[] | undefined;
  fcDashed: DashSeg[];
  fleetCircles: FleetCircle[] | undefined;
  legacyTargetsOrdered: AisTarget[];
  legacyHits: Map<number, LegacyVesselInfo> | null | undefined;
  overlays: MapToggleState;
  showShips: boolean;
  selectedMmsi: number | null;
  isHighlightedFleet: (ownerKey: string, vesselMmsis: number[]) => boolean;
  isHighlightedPair: (aMmsi: number, bMmsi: number) => boolean;
  isHighlightedMmsi: (mmsi: number) => boolean;
  touchDeckHoverState: (isHover: boolean) => void;
  setDeckHoverPairs: (next: number[]) => void;
  setDeckHoverMmsi: (next: number[]) => void;
  clearDeckHoverPairs: () => void;
  clearDeckHoverMmsi: () => void;
  clearMapFleetHoverState: () => void;
  setMapFleetHoverState: (ownerKey: string | null, vesselMmsis: number[]) => void;
  toFleetMmsiList: (value: unknown) => number[];
}

export function buildGlobeDeckLayers(ctx: GlobeDeckLayerContext): unknown[] {
  const overlayParams = GLOBE_OVERLAY_PARAMS;
  const globeLayers: unknown[] = [];

  if (ctx.overlays.pairRange && ctx.pairRanges.length > 0) {
    globeLayers.push(new ScatterplotLayer<PairRangeCircle>({ id: 'pair-range-globe', data: ctx.pairRanges, pickable: true, billboard: false, parameters: overlayParams, filled: false, stroked: true, radiusUnits: 'meters', getRadius: (d) => d.radiusNm * 1852, radiusMinPixels: 10, lineWidthUnits: 'pixels', getLineWidth: (d) => (ctx.isHighlightedPair(d.aMmsi, d.bMmsi) ? 2.2 : 1), getLineColor: (d) => { const hl = ctx.isHighlightedPair(d.aMmsi, d.bMmsi); if (hl) return d.warn ? PAIR_RANGE_WARN_DECK_HL : PAIR_RANGE_NORMAL_DECK_HL; return d.warn ? PAIR_RANGE_WARN_DECK : PAIR_RANGE_NORMAL_DECK; }, getPosition: (d) => d.center, onHover: (info) => { if (!info.object) { ctx.clearDeckHoverPairs(); ctx.clearDeckHoverMmsi(); return; } ctx.touchDeckHoverState(true); const p = info.object as PairRangeCircle; ctx.setDeckHoverPairs([p.aMmsi, p.bMmsi]); ctx.setDeckHoverMmsi([p.aMmsi, p.bMmsi]); ctx.clearMapFleetHoverState(); } }));
  }

  if (ctx.overlays.pairLines && (ctx.pairLinks?.length ?? 0) > 0) {
    const links = ctx.pairLinks || [];
    globeLayers.push(new LineLayer<PairLink>({ id: 'pair-lines-globe', data: links, pickable: true, parameters: overlayParams, getSourcePosition: (d) => d.from, getTargetPosition: (d) => d.to, getColor: (d) => { const hl = ctx.isHighlightedPair(d.aMmsi, d.bMmsi); if (hl) return d.warn ? PAIR_LINE_WARN_DECK_HL : PAIR_LINE_NORMAL_DECK_HL; return d.warn ? PAIR_LINE_WARN_DECK : PAIR_LINE_NORMAL_DECK; }, getWidth: (d) => (ctx.isHighlightedPair(d.aMmsi, d.bMmsi) ? 2.6 : d.warn ? 2.2 : 1.4), widthUnits: 'pixels', onHover: (info) => { if (!info.object) { ctx.clearDeckHoverPairs(); ctx.clearDeckHoverMmsi(); return; } ctx.touchDeckHoverState(true); const obj = info.object as PairLink; ctx.setDeckHoverPairs([obj.aMmsi, obj.bMmsi]); ctx.setDeckHoverMmsi([obj.aMmsi, obj.bMmsi]); ctx.clearMapFleetHoverState(); } }));
  }

  if (ctx.overlays.fcLines && ctx.fcDashed.length > 0) {
    globeLayers.push(new LineLayer<DashSeg>({ id: 'fc-lines-globe', data: ctx.fcDashed, pickable: true, parameters: overlayParams, getSourcePosition: (d) => d.from, getTargetPosition: (d) => d.to, getColor: (d) => { const ih = [d.fromMmsi, d.toMmsi].some((v) => ctx.isHighlightedMmsi(v ?? -1)); if (ih) return d.suspicious ? FC_LINE_SUSPICIOUS_DECK_HL : FC_LINE_NORMAL_DECK_HL; return d.suspicious ? FC_LINE_SUSPICIOUS_DECK : FC_LINE_NORMAL_DECK; }, getWidth: (d) => { const ih = [d.fromMmsi, d.toMmsi].some((v) => ctx.isHighlightedMmsi(v ?? -1)); return ih ? 1.9 : 1.3; }, widthUnits: 'pixels', onHover: (info) => { if (!info.object) { ctx.clearDeckHoverPairs(); ctx.clearDeckHoverMmsi(); return; } ctx.touchDeckHoverState(true); const obj = info.object as DashSeg; if (obj.fromMmsi == null || obj.toMmsi == null) { ctx.clearDeckHoverPairs(); ctx.clearDeckHoverMmsi(); return; } ctx.setDeckHoverPairs([obj.fromMmsi, obj.toMmsi]); ctx.setDeckHoverMmsi([obj.fromMmsi, obj.toMmsi]); ctx.clearMapFleetHoverState(); } }));
  }

  if (ctx.overlays.fleetCircles && (ctx.fleetCircles?.length ?? 0) > 0) {
    const circles = ctx.fleetCircles || [];
    globeLayers.push(new ScatterplotLayer<FleetCircle>({ id: 'fleet-circles-globe', data: circles, pickable: true, billboard: false, parameters: overlayParams, filled: false, stroked: true, radiusUnits: 'meters', getRadius: (d) => d.radiusNm * 1852, lineWidthUnits: 'pixels', getLineWidth: (d) => (ctx.isHighlightedFleet(d.ownerKey, d.vesselMmsis) ? 1.8 : 1.1), getLineColor: (d) => (ctx.isHighlightedFleet(d.ownerKey, d.vesselMmsis) ? FLEET_RANGE_LINE_DECK_HL : FLEET_RANGE_LINE_DECK), getPosition: (d) => d.center, onHover: (info) => { if (!info.object) { ctx.clearDeckHoverPairs(); ctx.clearDeckHoverMmsi(); ctx.clearMapFleetHoverState(); return; } ctx.touchDeckHoverState(true); const obj = info.object as FleetCircle; const list = ctx.toFleetMmsiList(obj.vesselMmsis); ctx.setMapFleetHoverState(obj.ownerKey || null, list); ctx.setDeckHoverMmsi(list); ctx.clearDeckHoverPairs(); } }));
    globeLayers.push(new ScatterplotLayer<FleetCircle>({ id: 'fleet-circles-fill-globe', data: circles, pickable: false, billboard: false, parameters: overlayParams, filled: true, stroked: false, radiusUnits: 'meters', getRadius: (d) => d.radiusNm * 1852, getFillColor: (d) => (ctx.isHighlightedFleet(d.ownerKey, d.vesselMmsis) ? FLEET_RANGE_FILL_DECK_HL : FLEET_RANGE_FILL_DECK), getPosition: (d) => d.center }));
  }

  if (ctx.showShips && ctx.legacyTargetsOrdered.length > 0) {
    globeLayers.push(new ScatterplotLayer<AisTarget>({ id: 'legacy-halo-globe', data: ctx.legacyTargetsOrdered, pickable: false, billboard: false, parameters: overlayParams, filled: false, stroked: true, radiusUnits: 'pixels', getRadius: (d) => { if (ctx.selectedMmsi && d.mmsi === ctx.selectedMmsi) return FLAT_LEGACY_HALO_RADIUS_SELECTED; return FLAT_LEGACY_HALO_RADIUS; }, lineWidthUnits: 'pixels', getLineWidth: (d) => (ctx.selectedMmsi && d.mmsi === ctx.selectedMmsi ? 2.5 : 2), getLineColor: (d) => { if (ctx.selectedMmsi && d.mmsi === ctx.selectedMmsi) return HALO_OUTLINE_COLOR_SELECTED; return HALO_OUTLINE_COLOR; }, getPosition: (d) => [d.lon, d.lat] as [number, number] }));
  }

  return globeLayers;
}
