'use client';

import {
  useState,
  useCallback,
  useMemo,
  useRef,
  useEffect,
  useLayoutEffect,
  createContext,
  useContext,
  Children,
  isValidElement,
  ReactNode,
  MouseEvent,
  KeyboardEvent,
  CSSProperties,
} from 'react';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore — bundler resolves .ts; node16 tsc requires .js but Next.js does not
import { WORLD_MAP_DATA } from './world-map-data';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { arcPath, svgCoordsToViewportPx, latlngToSvg } from '../../lib/geo';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { CITIES, type City } from '../../lib/cities';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export type { MapTransform } from '../../lib/geo';

// =============================================================================
// SHARED TYPES
// =============================================================================

export interface Region {
  countries: string[]; // ISO 3166-1 alpha-2 codes, e.g. 'US', 'GB'
  name: string;
  color?: string;
  href?: string;
}

export interface MapView {
  x: number;
  y: number;
  scale: number;
}

/** SVG-level style overrides — values that cannot be expressed as Tailwind classes */
export interface MapStyles {
  /** Background fill of the map container. Default: '#f0f8ff' */
  mapBackground?: string;
  /** Fill for countries not assigned to any region. Default: '#e5e7eb' */
  countryDefaultFill?: string;
  /** Fill for dimmed countries when another region is hovered. Default: region color + '66' alpha or '#d1d5db' */
  countryDimmedFill?: string;
  /** Stroke drawn between countries. Default: '#ffffff' */
  countryStroke?: string;
  /** Fallback region color when none is set on the region. Default: '#3b82f6' */
  regionDefaultColor?: string;
}

/** Tailwind class overrides per named UI slot */
export interface MapClassNames {
  section?: string;
  header?: string;
  title?: string;
  content?: string;
  mapContainer?: string;
  resetButton?: string;
  zoomButton?: string;
  legend?: string;
  legendItem?: string;
  legendItemActive?: string;
  tooltip?: string;
}

export interface InteractiveRegionsMapProps {
  title: string;
  content?: string;
  regions: Region[];
  initialView?: MapView;
  mapStyles?: MapStyles;
  classNames?: MapClassNames;
  onRegionClick?: (region: Region) => void;
  onRegionHover?: (region: Region | null) => void;
  className?: string;
  children?: ReactNode;
}

// =============================================================================
// JOURNEY TYPES
// =============================================================================

export type TransportMode =
  | 'flight'
  | 'drive'
  | 'train'
  | 'ferry'
  | 'walk'
  | 'custom';

export interface Waypoint {
  /** Unique identifier */
  id: string;
  /** Display name shown in callouts and step lists */
  label: string;
  /** X coordinate in SVG viewBox space (0–1010) */
  x: number;
  /** Y coordinate in SVG viewBox space (0–666) */
  y: number;
  /** Mode of transport used to ARRIVE at this waypoint. Default: 'flight' */
  transport?: TransportMode;
  /** Override the transport icon with any ReactNode (used when transport='custom') */
  transportIcon?: ReactNode;
  /** Optional description shown in callout */
  description?: string;
  /** Travel duration to this waypoint, e.g. "2h 30m" */
  duration?: string;
  /** Any extra key/value metadata */
  meta?: Record<string, string>;
}

export interface JourneyProps {
  waypoints: Waypoint[];
  /** Controlled active step index */
  activeIndex?: number;
  /** Uncontrolled starting index. Default: 0 */
  defaultActiveIndex?: number;
  /** Called when the active step changes */
  onStepChange?: (index: number, waypoint: Waypoint) => void;
  /** Show all route lines always, or only up to the current step. Default: true */
  showFullRoute?: boolean;
  /** Automatically pan+zoom the map to centre on the active waypoint. Default: true */
  autoFocus?: boolean;
  /** Zoom level applied when auto-focusing a waypoint. Default: 3 */
  autoFocusScale?: number;
  /** Duration of the transport icon animation in ms. Default: 800 */
  animationDuration?: number;
  /** Route line + waypoint dot color. Default: '#0ea5e9' */
  pathColor?: string;
  /** Active waypoint dot color. Default: '#0ea5e9' */
  activeWaypointColor?: string;
  /** Inactive waypoint dot color. Default: '#64748b' */
  waypointColor?: string;
  /** Extra class on the SVG overlay <g> */
  className?: string;
  /** Primitives to render outside the SVG overlay (callout, controls, step list) */
  children?: ReactNode;
}

// =============================================================================
// BUILT-IN TRANSPORT ICONS
// Two forms:
//   TRANSPORT_ICONS      — ReactNode <svg> used in JourneyStepList (HTML context)
//   TRANSPORT_SVG_PATHS  — raw SVG path data used for the animated icon on the map
//                          (pure SVG elements, no foreignObject, so rotate='auto' works)
// =============================================================================

const TRANSPORT_ICONS: Record<TransportMode, ReactNode> = {
  flight: (
    <svg viewBox='0 0 24 24' fill='currentColor' width='18' height='18'>
      <path d='M21 16v-2l-8-5V3.5A1.5 1.5 0 0 0 11.5 2 1.5 1.5 0 0 0 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z' />
    </svg>
  ),
  drive: (
    <svg viewBox='0 0 24 24' fill='currentColor' width='18' height='18'>
      <path d='M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.85 7h10.29l1.08 3.11H5.77L6.85 7zM19 17H5v-5h14v5zm-2-2.5a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm-10 0a1 1 0 1 1 2 0 1 1 0 0 1-2 0z' />
    </svg>
  ),
  train: (
    <svg viewBox='0 0 24 24' fill='currentColor' width='18' height='18'>
      <path d='M12 2c-4 0-8 .5-8 4v9.5A2.5 2.5 0 0 0 6.5 18L5 19.5v.5h14v-.5L17.5 18a2.5 2.5 0 0 0 2.5-2.5V6c0-3.5-4-4-8-4zm-3 13a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm6 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM6 10V6h5v4H6zm7 0V6h5v4h-5z' />
    </svg>
  ),
  ferry: (
    <svg viewBox='0 0 24 24' fill='currentColor' width='18' height='18'>
      <path d='M20 21c-1.39 0-2.78-.47-4-1.32-2.44 1.71-5.56 1.71-8 0C6.78 20.53 5.39 21 4 21H2v2h2c1.38 0 2.74-.35 4-.99 2.52 1.29 5.48 1.29 8 0 1.26.64 2.62.99 4 .99h2v-2h-2zM3.95 19H4c1.6 0 3.02-.88 4-2 .98 1.12 2.4 2 4 2s3.02-.88 4-2c.98 1.12 2.4 2 4 2h.05l1.9-6.68c.08-.26.05-.54-.06-.78s-.29-.45-.52-.57L20 11V6c0-1.1-.9-2-2-2h-3V1H9v3H6c-1.1 0-2 .9-2 2v5l-1.37.68c-.23.12-.41.33-.52.57s-.14.52-.06.78L3.95 19zM6 6h12v4.55l-6-3-6 3V6z' />
    </svg>
  ),
  walk: (
    <svg viewBox='0 0 24 24' fill='currentColor' width='18' height='18'>
      <path d='M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z' />
    </svg>
  ),
  custom: (
    <svg viewBox='0 0 24 24' fill='currentColor' width='18' height='18'>
      <circle cx='12' cy='12' r='4' />
    </svg>
  ),
};

/**
 * Pure-SVG path data for the animated map icon (viewBox 0 0 24 24, centred at 12 12).
 * We render these as <path> inside a scaled <g> so animateMotion + rotate='auto' works
 * correctly without a foreignObject wrapper.
 */
const TRANSPORT_SVG_PATHS: Record<TransportMode, string> = {
  // Plane pointing right (east) — animateMotion will rotate it along the arc
  flight:
    'M21 16v-2l-8-5V3.5A1.5 1.5 0 0 0 11.5 2 1.5 1.5 0 0 0 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z',
  // Car
  drive:
    'M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.85 7h10.29l1.08 3.11H5.77L6.85 7zM19 17H5v-5h14v5z',
  // Train
  train:
    'M12 2c-4 0-8 .5-8 4v9.5A2.5 2.5 0 0 0 6.5 18L5 19.5v.5h14v-.5L17.5 18a2.5 2.5 0 0 0 2.5-2.5V6c0-3.5-4-4-8-4zm-3 13a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm6 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM6 10V6h5v4H6zm7 0V6h5v4h-5z',
  // Ferry / boat
  ferry:
    'M3.95 19H4c1.6 0 3.02-.88 4-2 .98 1.12 2.4 2 4 2s3.02-.88 4-2c.98 1.12 2.4 2 4 2h.05l1.9-6.68c.08-.26.05-.54-.06-.78s-.29-.45-.52-.57L20 11V6c0-1.1-.9-2-2-2h-3V1H9v3H6c-1.1 0-2 .9-2 2v5l-1.37.68c-.23.12-.41.33-.52.57s-.14.52-.06.78L3.95 19zM6 6h12v4.55l-6-3-6 3V6z',
  // Walking person — simplified to a directional arrow so rotate='auto' is meaningful
  walk: 'M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z',
  // Custom — simple circle
  custom: 'M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8z',
};

// =============================================================================
// MAP CONTEXT
// =============================================================================

interface TooltipState {
  visible: boolean;
  x: number;
  y: number;
  countryName: string;
  regionName?: string;
}

interface MapContextValue {
  regions: Region[];
  countryToRegion: Record<string, Region>;
  hoveredRegion: string | null;
  tooltip: TooltipState;
  isLoading: boolean;
  transform: MapView;
  setTransform: React.Dispatch<React.SetStateAction<MapView>>;
  isDragging: React.MutableRefObject<boolean>;
  mapStyles: MapStyles;
  classNames: MapClassNames;
  handlePointerDown: (e: React.PointerEvent<HTMLDivElement>) => void;
  handlePointerMove: (e: React.PointerEvent<HTMLDivElement>) => void;
  handlePointerUp: (e: React.PointerEvent<HTMLDivElement>) => void;
  handleCountryHover: (
    e: MouseEvent | null,
    countryId: string | null,
    countryName?: string
  ) => void;
  handleCountryMouseMove: (e: MouseEvent) => void;
  handleCountryClick: (countryId: string) => Promise<void>;
  handleKeyDown: (e: KeyboardEvent<SVGPathElement>, countryId: string) => void;
  getCountryFill: (countryId: string) => string;
  isCountryInteractive: (countryId: string) => boolean;
  resetTransform: () => void;
  zoomIn: () => void;
  zoomOut: () => void;
  mapContainerRef: React.RefObject<HTMLDivElement | null>;
  /** Called by MapJourney to publish its context value up to the root provider */
  publishJourneyCtx: (value: JourneyContextValue | null) => void;
}

const MapContext = createContext<MapContextValue | null>(null);

function useMapContext(): MapContextValue {
  const ctx = useContext(MapContext);
  if (!ctx) {
    throw new Error(
      'InteractiveRegionsMap sub-components must be used inside <InteractiveRegionsMap>.'
    );
  }
  return ctx;
}

// =============================================================================
// JOURNEY CONTEXT
// =============================================================================

interface JourneyContextValue {
  waypoints: Waypoint[];
  activeIndex: number;
  previousIndex: number | null;
  isAnimating: boolean;
  animationDuration: number;
  pathColor: string;
  waypointColor: string;
  activeWaypointColor: string;
  showFullRoute: boolean;
  goTo: (index: number) => void;
  goNext: () => void;
  goPrev: () => void;
  /** Children to be rendered outside the map canvas (controls, step list, callout) */
  extraChildren: ReactNode;
}

const JourneyContext = createContext<JourneyContextValue | null>(null);

export function useJourneyContext(): JourneyContextValue {
  const ctx = useContext(JourneyContext);
  if (!ctx) {
    throw new Error(
      'InteractiveRegionsMap.Journey sub-components must be used inside <InteractiveRegionsMap.Journey>.'
    );
  }
  return ctx;
}

// =============================================================================
// MAP SUB-COMPONENTS  (consume MapContext)
// =============================================================================

export function MapLegend({ className }: { className?: string }) {
  const { regions, hoveredRegion, mapStyles, classNames } = useMapContext();
  if (!regions.length) return null;

  return (
    <div
      className={
        className ??
        classNames.legend ??
        'mt-6 flex flex-wrap justify-center gap-4'
      }
    >
      {regions.map((region, index) => (
        <div
          key={index}
          className={
            hoveredRegion === region.name
              ? (classNames.legendItemActive ??
                'flex items-center gap-2 rounded-lg border border-gray-400 bg-gray-100 px-3 py-2 shadow-md transition-all duration-200')
              : (classNames.legendItem ??
                'flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-3 py-2 transition-all duration-200')
          }
        >
          <div
            className='h-4 w-4 rounded border border-gray-300'
            style={{
              backgroundColor:
                region.color ?? mapStyles.regionDefaultColor ?? '#3b82f6',
            }}
          />
          <span className='text-sm font-medium text-gray-700'>
            {region.name}
          </span>
        </div>
      ))}
    </div>
  );
}
MapLegend.displayName = 'InteractiveRegionsMap.Legend';

export function MapTooltip({ className }: { className?: string }) {
  const { tooltip, classNames } = useMapContext();
  if (!tooltip.visible) return null;

  return (
    <div
      className={
        className ??
        classNames.tooltip ??
        'pointer-events-none fixed z-50 rounded-md bg-gray-900 px-3 py-2 text-sm font-medium text-white shadow-md'
      }
      style={{ left: `${tooltip.x + 15}px`, top: `${tooltip.y + 15}px` }}
    >
      <div className='font-bold'>{tooltip.countryName}</div>
      {tooltip.regionName && (
        <div className='mt-0.5 text-xs text-gray-300'>{tooltip.regionName}</div>
      )}
    </div>
  );
}
MapTooltip.displayName = 'InteractiveRegionsMap.Tooltip';

export function MapResetButton({
  className,
  children,
}: {
  className?: string;
  children?: ReactNode;
}) {
  const { resetTransform, classNames } = useMapContext();

  return (
    <button
      type='button'
      onClick={resetTransform}
      onPointerDown={e => e.stopPropagation()}
      className={
        className ??
        classNames.resetButton ??
        'rounded-md bg-white px-3 py-1.5 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50'
      }
    >
      {children ?? 'Reset View'}
    </button>
  );
}
MapResetButton.displayName = 'InteractiveRegionsMap.ResetButton';

export type ZoomControlsPosition =
  | 'top-left'
  | 'top-right'
  | 'bottom-left'
  | 'bottom-right';

const ZOOM_POSITION_CLASSES: Record<ZoomControlsPosition, string> = {
  'top-left': 'absolute top-3 left-3',
  'top-right': 'absolute top-3 right-3',
  'bottom-left': 'absolute bottom-3 left-3',
  'bottom-right': 'absolute bottom-3 right-3',
};

export function MapZoomControls({
  className,
  position = 'bottom-right',
}: {
  className?: string;
  position?: ZoomControlsPosition;
}) {
  const { zoomIn, zoomOut, classNames } = useMapContext();
  const btnClass =
    classNames.zoomButton ??
    'flex h-8 w-8 items-center justify-center rounded-md bg-white text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 text-lg leading-none select-none';

  return (
    <div
      className={
        className ??
        `${ZOOM_POSITION_CLASSES[position]} z-10 flex flex-col gap-1`
      }
      onPointerDown={e => e.stopPropagation()}
    >
      <button
        type='button'
        onClick={zoomIn}
        aria-label='Zoom in'
        className={btnClass}
      >
        +
      </button>
      <button
        type='button'
        onClick={zoomOut}
        aria-label='Zoom out'
        className={btnClass}
      >
        −
      </button>
    </div>
  );
}
MapZoomControls.displayName = 'InteractiveRegionsMap.ZoomControls';

// =============================================================================
// JOURNEY SUB-COMPONENTS  (consume JourneyContext)
// =============================================================================

/**
 * Floating label callout anchored to the active waypoint.
 *
 * Rendered as a SVG <foreignObject> inside the journey overlay so it follows
 * the map transform perfectly — no JS positioning calculation needed, no drift.
 *
 * Must be placed as a child of <MapJourney>.
 */
export function JourneyCallout({ className }: { className?: string }) {
  const { waypoints, activeIndex, animationDuration } = useJourneyContext();
  const { transform } = useMapContext();

  const waypoint = waypoints[activeIndex];
  if (!waypoint) return null;

  // Width of the callout in SVG units (inversely scaled so it stays readable)
  const calloutW = 160 / transform.scale;
  const calloutH = 80 / transform.scale;
  const arrowH = 8 / transform.scale;

  return (
    <foreignObject
      key={activeIndex}
      // Position so the arrow tip is exactly at the waypoint dot
      x={waypoint.x - calloutW / 2}
      y={waypoint.y - calloutH - arrowH - 7 / transform.scale}
      width={calloutW}
      height={calloutH + arrowH}
      style={{ overflow: 'visible', pointerEvents: 'none' }}
    >
      <div
        className={
          className ?? 'pointer-events-none flex flex-col items-center'
        }
        style={{
          fontSize: `${12 / transform.scale}px`,
          transition: `opacity ${animationDuration}ms ease-out`,
        }}
      >
        {/* Callout bubble */}
        <div
          style={{
            backgroundColor: '#0ea5e9',
            color: 'white',
            borderRadius: `${6 / transform.scale}px`,
            padding: `${6 / transform.scale}px ${10 / transform.scale}px`,
            boxShadow: '0 2px 8px rgba(0,0,0,0.25)',
            whiteSpace: 'nowrap',
            display: 'inline-block',
          }}
        >
          <p style={{ fontWeight: 700, margin: 0 }}>{waypoint.label}</p>
          {waypoint.description && (
            <p
              style={{
                margin: 0,
                opacity: 0.9,
                fontSize: `${10 / transform.scale}px`,
              }}
            >
              {waypoint.description}
            </p>
          )}
          {waypoint.duration && (
            <p
              style={{
                margin: 0,
                opacity: 0.75,
                fontSize: `${10 / transform.scale}px`,
              }}
            >
              {waypoint.duration}
            </p>
          )}
        </div>
        {/* Arrow pointing down */}
        <div
          style={{
            width: 0,
            height: 0,
            borderLeft: `${8 / transform.scale}px solid transparent`,
            borderRight: `${8 / transform.scale}px solid transparent`,
            borderTop: `${8 / transform.scale}px solid #0ea5e9`,
          }}
        />
      </div>
    </foreignObject>
  );
}
JourneyCallout.displayName = 'InteractiveRegionsMap.JourneyCallout';

/**
 * Previous / Next step buttons + step counter.
 * Must be placed inside <InteractiveRegionsMap.Journey> to access JourneyContext.
 * Has no dependency on MapContext.
 */
export function JourneyControls({
  className,
  prevLabel,
  nextLabel,
  showCounter = true,
}: {
  className?: string;
  prevLabel?: ReactNode;
  nextLabel?: ReactNode;
  showCounter?: boolean;
}) {
  const { waypoints, activeIndex, goNext, goPrev, isAnimating } =
    useJourneyContext();

  return (
    <div className={className ?? 'mt-4 flex items-center justify-center gap-4'}>
      <button
        type='button'
        onClick={goPrev}
        disabled={activeIndex === 0 || isAnimating}
        className='flex h-9 w-9 items-center justify-center rounded-full bg-white text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-40'
        aria-label='Previous waypoint'
      >
        {prevLabel ?? (
          <svg
            viewBox='0 0 24 24'
            fill='none'
            stroke='currentColor'
            strokeWidth={2}
            width={16}
            height={16}
          >
            <path
              strokeLinecap='round'
              strokeLinejoin='round'
              d='M15 19l-7-7 7-7'
            />
          </svg>
        )}
      </button>

      {showCounter && (
        <span className='text-sm font-medium text-gray-600'>
          {activeIndex + 1} / {waypoints.length}
        </span>
      )}

      <button
        type='button'
        onClick={goNext}
        disabled={activeIndex === waypoints.length - 1 || isAnimating}
        className='flex h-9 w-9 items-center justify-center rounded-full bg-white text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-40'
        aria-label='Next waypoint'
      >
        {nextLabel ?? (
          <svg
            viewBox='0 0 24 24'
            fill='none'
            stroke='currentColor'
            strokeWidth={2}
            width={16}
            height={16}
          >
            <path
              strokeLinecap='round'
              strokeLinejoin='round'
              d='M9 5l7 7-7 7'
            />
          </svg>
        )}
      </button>
    </div>
  );
}
JourneyControls.displayName = 'InteractiveRegionsMap.JourneyControls';

/**
 * Ordered list of all waypoints — useful as a sidebar itinerary.
 * Highlights the active step.
 * Accepts a renderItem render-prop for full customisation.
 */
export function JourneyStepList({
  className,
  renderItem,
}: {
  className?: string;
  renderItem?: (
    waypoint: Waypoint,
    index: number,
    isActive: boolean,
    goTo: (i: number) => void
  ) => ReactNode;
}) {
  const { waypoints, activeIndex, goTo } = useJourneyContext();

  return (
    <ol className={className ?? 'mt-4 flex flex-col gap-2'}>
      {waypoints.map((wp, i) => {
        const isActive = i === activeIndex;
        if (renderItem) {
          return <li key={wp.id}>{renderItem(wp, i, isActive, goTo)}</li>;
        }

        const icon =
          wp.transportIcon ?? TRANSPORT_ICONS[wp.transport ?? 'flight'];

        return (
          <li key={wp.id}>
            <button
              type='button'
              onClick={() => goTo(i)}
              className={`flex w-full items-start gap-3 rounded-lg border px-4 py-3 text-left transition-all duration-200 ${
                isActive
                  ? 'border-sky-400 bg-sky-50 shadow-sm'
                  : 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50'
              }`}
            >
              {/* Step number */}
              <span
                className={`mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-bold ${
                  isActive
                    ? 'bg-sky-500 text-white'
                    : 'bg-gray-200 text-gray-600'
                }`}
              >
                {i + 1}
              </span>

              {/* Content */}
              <div className='min-w-0 flex-1'>
                <p
                  className={`truncate text-sm font-semibold ${
                    isActive ? 'text-sky-700' : 'text-gray-800'
                  }`}
                >
                  {wp.label}
                </p>
                {wp.description && (
                  <p className='mt-0.5 truncate text-xs text-gray-500'>
                    {wp.description}
                  </p>
                )}
              </div>

              {/* Transport + duration */}
              <div className='flex shrink-0 flex-col items-end gap-1'>
                <span
                  className={`text-xs ${isActive ? 'text-sky-500' : 'text-gray-400'}`}
                >
                  {icon}
                </span>
                {wp.duration && (
                  <span className='text-xs text-gray-400'>{wp.duration}</span>
                )}
              </div>
            </button>
          </li>
        );
      })}
    </ol>
  );
}
JourneyStepList.displayName = 'InteractiveRegionsMap.JourneyStepList';

// =============================================================================
// TRANSPORT ICON  (rAF-driven, no SMIL — eliminates the first-frame flash)
// =============================================================================

/**
 * Evaluates a quadratic bezier at parameter t ∈ [0,1].
 * Control point matches arcPath's formula: cp = midpoint offset perpendicular to chord.
 */
function evalArc(
  x1: number,
  y1: number,
  x2: number,
  y2: number,
  t: number,
  curvature = 0.25
): { x: number; y: number; angle: number } {
  const mx = (x1 + x2) / 2;
  const my = (y1 + y2) / 2;
  const dx = x2 - x1;
  const dy = y2 - y1;
  const cpx = mx - dy * curvature;
  const cpy = my + dx * curvature;
  // B(t) = (1-t)^2 P0 + 2(1-t)t CP + t^2 P1
  const mt = 1 - t;
  const x = mt * mt * x1 + 2 * mt * t * cpx + t * t * x2;
  const y = mt * mt * y1 + 2 * mt * t * cpy + t * t * y2;
  // Tangent = B'(t) = 2(1-t)(CP-P0) + 2t(P1-CP)
  const tx = 2 * mt * (cpx - x1) + 2 * t * (x2 - cpx);
  const ty = 2 * mt * (cpy - y1) + 2 * t * (y2 - cpy);
  const angle = (Math.atan2(ty, tx) * 180) / Math.PI;
  return { x, y, angle };
}

/** Easing: ease-in-out cubic */
function easeInOut(t: number): number {
  return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}

interface TransportIconProps {
  from: Waypoint;
  to: Waypoint;
  durationMs: number;
  iconSize: number;
  pathColor: string;
  svgPath: string;
}

function TransportIcon({
  from,
  to,
  durationMs,
  iconSize,
  pathColor,
  svgPath,
}: TransportIconProps) {
  const gRef = useRef<SVGGElement>(null);
  const startRef = useRef<number | null>(null);
  const rafRef = useRef<number | null>(null);

  useEffect(() => {
    // Set initial position synchronously before any paint so there's no flash at origin
    const g = gRef.current;
    if (!g) return;
    const initial = evalArc(from.x, from.y, to.x, to.y, 0);
    g.setAttribute(
      'transform',
      `translate(${initial.x},${initial.y}) rotate(${initial.angle}) scale(${iconSize / 24}) translate(-12,-12)`
    );

    const animate = (timestamp: number) => {
      if (!startRef.current) startRef.current = timestamp;
      const elapsed = timestamp - startRef.current;
      const rawT = Math.min(elapsed / durationMs, 1);
      const t = easeInOut(rawT);
      const { x, y, angle } = evalArc(from.x, from.y, to.x, to.y, t);
      if (gRef.current) {
        gRef.current.setAttribute(
          'transform',
          `translate(${x},${y}) rotate(${angle}) scale(${iconSize / 24}) translate(-12,-12)`
        );
      }
      if (rawT < 1) {
        rafRef.current = requestAnimationFrame(animate);
      }
    };

    rafRef.current = requestAnimationFrame(animate);

    return () => {
      if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
    };
  }, [from.x, from.y, to.x, to.y, durationMs, iconSize]);

  // Compute initial transform for SSR / first render (same as useEffect sets synchronously)
  const init = evalArc(from.x, from.y, to.x, to.y, 0);
  const initTransform = `translate(${init.x},${init.y}) rotate(${init.angle}) scale(${iconSize / 24}) translate(-12,-12)`;

  return (
    <g ref={gRef} transform={initTransform} fill={pathColor} opacity='0.95'>
      <path d={svgPath} />
    </g>
  );
}

// =============================================================================
// JOURNEY  (Provider + SVG overlay)
// =============================================================================

/**
 * Renders the journey route as an SVG overlay on the map canvas.
 * Also acts as the JourneyContext provider for all journey primitives.
 *
 * Must be placed as a child of <InteractiveRegionsMap>.
 * Place <InteractiveRegionsMap.JourneyCallout /> etc. as children of this component.
 */
export function MapJourney({
  waypoints,
  activeIndex: controlledIndex,
  defaultActiveIndex = 0,
  onStepChange,
  showFullRoute = true,
  autoFocus = true,
  autoFocusScale = 3,
  animationDuration = 800,
  pathColor = '#0ea5e9',
  waypointColor = '#64748b',
  activeWaypointColor = '#0ea5e9',
  className,
  children,
}: JourneyProps) {
  const {
    transform,
    setTransform,
    isDragging,
    mapContainerRef,
    publishJourneyCtx,
  } = useMapContext();

  const isControlled = controlledIndex !== undefined;
  const [internalIndex, setInternalIndex] = useState(defaultActiveIndex);
  const activeIndex = isControlled ? controlledIndex! : internalIndex;

  const [previousIndex, setPreviousIndex] = useState<number | null>(null);
  const [isAnimating, setIsAnimating] = useState(false);
  const animKeyRef = useRef(0);

  const goTo = useCallback(
    (nextIndex: number) => {
      if (nextIndex < 0 || nextIndex >= waypoints.length) return;
      if (nextIndex === activeIndex) return;

      setPreviousIndex(activeIndex);
      if (!isControlled) setInternalIndex(nextIndex);
      onStepChange?.(nextIndex, waypoints[nextIndex]!);

      // Auto-focus: pan + zoom to the new waypoint
      if (autoFocus && mapContainerRef.current) {
        const wp = waypoints[nextIndex]!;
        const container = mapContainerRef.current;
        const parts = WORLD_MAP_DATA.viewBox.split(' ').map(Number);
        const vbW = parts[2] ?? 1010;
        const vbH = parts[3] ?? 666;
        const rect = container.getBoundingClientRect();
        const scaleX = rect.width / vbW;
        const scaleY = rect.height / vbH;
        const s = autoFocusScale;
        // transformOrigin is center, so transform is: scale around center, then translate.
        // To center waypoint: tx = s * (containerW/2 - wp.x * scaleX)
        const tx = s * (rect.width / 2 - wp.x * scaleX);
        const ty = s * (rect.height / 2 - wp.y * scaleY);
        setTransform({ x: tx, y: ty, scale: s });
      }

      // Track animation
      setIsAnimating(true);
      animKeyRef.current += 1;
      const key = animKeyRef.current;
      setTimeout(() => {
        if (animKeyRef.current === key) setIsAnimating(false);
      }, animationDuration);
    },
    [
      activeIndex,
      waypoints,
      isControlled,
      onStepChange,
      autoFocus,
      autoFocusScale,
      animationDuration,
      mapContainerRef,
      setTransform,
    ]
  );

  const goNext = useCallback(() => goTo(activeIndex + 1), [goTo, activeIndex]);
  const goPrev = useCallback(() => goTo(activeIndex - 1), [goTo, activeIndex]);

  // Which legs to draw
  const visibleLegs = useMemo(() => {
    const legs: Array<{ from: Waypoint; to: Waypoint; index: number }> = [];
    for (let i = 1; i < waypoints.length; i++) {
      if (showFullRoute || i <= activeIndex) {
        const from = waypoints[i - 1];
        const to = waypoints[i];
        if (from && to) legs.push({ from, to, index: i });
      }
    }
    return legs;
  }, [waypoints, showFullRoute, activeIndex]);

  // Separate JourneyCallout children (rendered inside SVG) from DOM children (rendered outside canvas)
  const svgCallouts: ReactNode[] = [];
  const domChildren: ReactNode[] = [];
  Children.forEach(children, child => {
    if (
      isValidElement(child) &&
      (child.type as { displayName?: string })?.displayName ===
        'InteractiveRegionsMap.JourneyCallout'
    ) {
      svgCallouts.push(child);
    } else {
      domChildren.push(child);
    }
  });

  const contextValue = useMemo<JourneyContextValue>(
    () => ({
      waypoints,
      activeIndex,
      previousIndex,
      isAnimating,
      animationDuration,
      pathColor,
      waypointColor,
      activeWaypointColor,
      showFullRoute,
      goTo,
      goNext,
      goPrev,
      extraChildren: domChildren.length > 0 ? domChildren : null,
    }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [
      waypoints,
      activeIndex,
      previousIndex,
      isAnimating,
      animationDuration,
      pathColor,
      waypointColor,
      activeWaypointColor,
      showFullRoute,
      goTo,
      goNext,
      goPrev,
      children,
    ]
  );

  // The active animation leg (from previousIndex → activeIndex)
  const fromWp = previousIndex !== null ? waypoints[previousIndex] : undefined;
  const toWp = waypoints[activeIndex];
  const animLeg =
    previousIndex !== null && isAnimating && fromWp && toWp
      ? { from: fromWp, to: toWp }
      : null;

  // The transport mode for the animated leg is the ARRIVAL mode at the destination
  // (i.e., how we travel TO the active waypoint, stored on the destination waypoint)
  const animTransportMode = toWp?.transport ?? 'flight';
  // For custom transport, use the destination waypoint's custom icon path — fall back to circle
  const animSvgPath =
    animTransportMode === 'custom'
      ? TRANSPORT_SVG_PATHS.custom
      : TRANSPORT_SVG_PATHS[animTransportMode];

  // SVG overlay style — MUST share the same transition timing as the base map SVG
  // (both use 0.6s ease-out for pan/zoom) so the overlay doesn't drift relative to the map.
  // The animationDuration only governs the SMIL animateMotion icon — not this CSS transition.
  const overlayStyle: CSSProperties = {
    position: 'absolute',
    inset: 0,
    pointerEvents: 'none',
    transform: `translate(${transform.x}px, ${transform.y}px) scale(${transform.scale})`,
    transformOrigin: 'center',
    transition: isDragging.current ? 'none' : 'transform 0.6s ease-out',
  };

  // Icon size in SVG units — scales inversely so it stays visually consistent
  const iconSize = 18 / transform.scale;

  // Publish context value to the root-level JourneyContext.Provider so that
  // JourneySlot (rendered outside the canvas) can access it.
  useLayoutEffect(() => {
    publishJourneyCtx(contextValue);
    return () => publishJourneyCtx(null);
  }, [contextValue, publishJourneyCtx]);

  return (
    <>
      {/* SVG overlay — rendered inside the map canvas */}
      <svg
        viewBox={WORLD_MAP_DATA.viewBox}
        className={`pointer-events-none absolute inset-0 h-full w-full ${className ?? ''}`}
        aria-hidden='true'
        style={overlayStyle}
      >
        {/* Route lines */}
        {visibleLegs.map(({ from, to, index }) => {
          const isActiveLeg = index === activeIndex;
          const d = arcPath(from.x, from.y, to.x, to.y);
          return (
            <path
              key={`leg-${index}`}
              d={d}
              fill='none'
              stroke={isActiveLeg ? pathColor : `${pathColor}88`}
              strokeWidth={1.5 / transform.scale}
              strokeDasharray={`${6 / transform.scale} ${4 / transform.scale}`}
              strokeLinecap='round'
            />
          );
        })}

        {/* Waypoint dots */}
        {waypoints.map((wp, i) => {
          const isActive = i === activeIndex;
          const r = isActive ? 7 / transform.scale : 5 / transform.scale;
          return (
            <g key={`wp-${wp.id}`}>
              {/* Pulsing halo on active dot */}
              <circle
                cx={wp.x}
                cy={wp.y}
                r={r * 2.2}
                fill={activeWaypointColor}
                style={{
                  opacity: isActive ? 0.2 : 0,
                  transition: 'opacity 0.4s ease-out, r 0.4s ease-out',
                }}
              />
              <circle
                cx={wp.x}
                cy={wp.y}
                r={r}
                fill={isActive ? activeWaypointColor : waypointColor}
                stroke='white'
                strokeWidth={1.5 / transform.scale}
                style={{
                  transition: 'fill 0.4s ease-out',
                }}
              />
            </g>
          );
        })}

        {/* Animated transport icon — driven by a React rAF loop (no SMIL).
            This avoids the one-frame-at-origin flash that SMIL animateMotion
            causes because React sets the transform synchronously on mount. */}
        {animLeg && (
          <TransportIcon
            key={`anim-${previousIndex}-${activeIndex}`}
            from={animLeg.from}
            to={animLeg.to}
            durationMs={animationDuration}
            iconSize={iconSize}
            pathColor={pathColor}
            svgPath={animSvgPath}
          />
        )}
        {/* Arc path in defs so mpath can reference it */}
        {animLeg && (
          <defs>
            <path
              id={`journey-arc-${previousIndex}-${activeIndex}`}
              d={arcPath(
                animLeg.from.x,
                animLeg.from.y,
                animLeg.to.x,
                animLeg.to.y
              )}
            />
          </defs>
        )}

        {/* JourneyCallout children rendered inside SVG so they follow the transform */}
        {svgCallouts.length > 0 && (
          <JourneyContext.Provider value={contextValue}>
            {svgCallouts}
          </JourneyContext.Provider>
        )}
      </svg>
    </>
  );
}
MapJourney.displayName = 'InteractiveRegionsMap.Journey';

// =============================================================================
// JOURNEY SLOT  (renders journey children outside the canvas)
// =============================================================================

/**
 * Rendered by the root outside the overflow-hidden canvas.
 * Reads extraChildren from JourneyContext (if a Journey is present) and
 * renders them — this avoids any portal complexity entirely.
 */
function JourneySlot() {
  const ctx = useContext(JourneyContext);
  if (!ctx) return null;
  return <>{ctx.extraChildren}</>;
}

// =============================================================================
// DEFAULT LAYOUT
// =============================================================================

function DefaultLayout() {
  return (
    <>
      <MapTooltip />
      <MapLegend />
    </>
  );
}

// =============================================================================
// ROOT COMPONENT
// =============================================================================

function InteractiveRegionsMapRoot({
  title = 'Interactive Regions Map',
  content,
  regions = [],
  initialView,
  mapStyles = {},
  classNames = {},
  onRegionClick,
  onRegionHover,
  className = '',
  children,
}: InteractiveRegionsMapProps) {
  const [hoveredRegion, setHoveredRegion] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [journeyCtx, setJourneyCtx] = useState<JourneyContextValue | null>(
    null
  );
  const publishJourneyCtx = useCallback((value: JourneyContextValue | null) => {
    setJourneyCtx(value);
  }, []);
  const [tooltip, setTooltip] = useState<TooltipState>({
    visible: false,
    x: 0,
    y: 0,
    countryName: '',
  });

  const [transform, setTransform] = useState<MapView>(
    initialView ?? { x: 0, y: 0, scale: 1 }
  );
  const isDragging = useRef(false);
  const dragStart = useRef({ x: 0, y: 0 });
  const mapContainerRef = useRef<HTMLDivElement>(null);

  // Non-passive wheel handler to prevent page scroll while zooming
  useEffect(() => {
    const container = mapContainerRef.current;
    if (!container) return;
    const onWheel = (e: WheelEvent) => {
      e.preventDefault();
      const delta = e.deltaY * -0.002;
      setTransform(prev => ({
        ...prev,
        scale: Math.min(Math.max(1, prev.scale + delta), 10),
      }));
    };
    container.addEventListener('wheel', onWheel, { passive: false });
    return () => container.removeEventListener('wheel', onWheel);
  }, []);

  const countryToRegion = useMemo(() => {
    const mapping: Record<string, Region> = {};
    regions.forEach(region => {
      region.countries.forEach(code => {
        mapping[code.trim().toLowerCase()] = region;
      });
    });
    return mapping;
  }, [regions]);

  const handlePointerDown = useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      isDragging.current = true;
      dragStart.current = {
        x: e.clientX - transform.x,
        y: e.clientY - transform.y,
      };
      e.currentTarget.setPointerCapture(e.pointerId);
    },
    [transform.x, transform.y]
  );

  const handlePointerMove = useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      if (!isDragging.current) return;
      setTransform(prev => ({
        ...prev,
        x: e.clientX - dragStart.current.x,
        y: e.clientY - dragStart.current.y,
      }));
    },
    []
  );

  const handlePointerUp = useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      isDragging.current = false;
      e.currentTarget.releasePointerCapture(e.pointerId);
    },
    []
  );

  const resetTransform = useCallback(() => {
    setTransform(initialView ?? { x: 0, y: 0, scale: 1 });
  }, [initialView]);

  const zoomIn = useCallback(() => {
    setTransform(prev => ({ ...prev, scale: Math.min(prev.scale + 0.5, 10) }));
  }, []);

  const zoomOut = useCallback(() => {
    setTransform(prev => ({ ...prev, scale: Math.max(prev.scale - 0.5, 1) }));
  }, []);

  const handleCountryHover = useCallback(
    (e: MouseEvent | null, countryId: string | null, countryName = '') => {
      if (!countryId || !e) {
        setHoveredRegion(null);
        setTooltip(prev => ({ ...prev, visible: false }));
        onRegionHover?.(null);
        return;
      }
      const region = countryToRegion[countryId];
      setTooltip({
        visible: true,
        x: e.clientX,
        y: e.clientY,
        countryName,
        regionName: region?.name,
      });
      if (region) {
        setHoveredRegion(region.name);
        onRegionHover?.(region);
      } else {
        setHoveredRegion(null);
        onRegionHover?.(null);
      }
    },
    [countryToRegion, onRegionHover]
  );

  const handleCountryMouseMove = useCallback((e: MouseEvent) => {
    setTooltip(prev =>
      prev.visible ? { ...prev, x: e.clientX, y: e.clientY } : prev
    );
  }, []);

  const handleCountryClick = useCallback(
    async (countryId: string) => {
      const region = countryToRegion[countryId];
      if (!region) return;
      setIsLoading(true);
      try {
        if (onRegionClick) {
          onRegionClick(region);
        } else if (region.href) {
          window.location.href = region.href;
        }
      } catch (err) {
        console.error('Navigation error:', err);
      } finally {
        setIsLoading(false);
      }
    },
    [countryToRegion, onRegionClick]
  );

  const handleKeyDown = useCallback(
    (e: KeyboardEvent<SVGPathElement>, countryId: string) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        handleCountryClick(countryId);
      }
    },
    [handleCountryClick]
  );

  const getCountryFill = useCallback(
    (countryId: string): string => {
      const region = countryToRegion[countryId];
      if (!region) return mapStyles.countryDefaultFill ?? '#e5e7eb';
      if (hoveredRegion && hoveredRegion !== region.name) {
        if (mapStyles.countryDimmedFill) return mapStyles.countryDimmedFill;
        return region.color ? `${region.color}66` : '#d1d5db';
      }
      return region.color ?? mapStyles.regionDefaultColor ?? '#3b82f6';
    },
    [countryToRegion, hoveredRegion, mapStyles]
  );

  const isCountryInteractive = useCallback(
    (countryId: string) => countryId in countryToRegion,
    [countryToRegion]
  );

  const contextValue = useMemo<MapContextValue>(
    () => ({
      regions,
      countryToRegion,
      hoveredRegion,
      tooltip,
      isLoading,
      transform,
      setTransform,
      isDragging,
      mapStyles,
      classNames,
      handlePointerDown,
      handlePointerMove,
      handlePointerUp,
      handleCountryHover,
      handleCountryMouseMove,
      handleCountryClick,
      handleKeyDown,
      getCountryFill,
      isCountryInteractive,
      resetTransform,
      zoomIn,
      zoomOut,
      mapContainerRef,
      publishJourneyCtx,
    }),
    [
      regions,
      countryToRegion,
      hoveredRegion,
      tooltip,
      isLoading,
      transform,
      setTransform,
      mapStyles,
      classNames,
      handlePointerDown,
      handlePointerMove,
      handlePointerUp,
      handleCountryHover,
      handleCountryMouseMove,
      handleCountryClick,
      handleKeyDown,
      getCountryFill,
      isCountryInteractive,
      resetTransform,
      zoomIn,
      zoomOut,
      publishJourneyCtx,
    ]
  );

  return (
    <MapContext.Provider value={contextValue}>
      <JourneyContext.Provider value={journeyCtx}>
        <section
          className={`mx-auto flex max-w-6xl flex-col p-6 ${classNames.section ?? ''} ${className}`}
        >
          {/* Header */}
          <div
            className={
              classNames.header ??
              'mb-8 flex flex-col items-center justify-center text-center'
            }
          >
            <h2
              className={
                classNames.title ?? 'mb-2 text-3xl font-bold text-gray-900'
              }
            >
              {title}
            </h2>
            {content && (
              <p
                className={
                  classNames.content ?? 'max-w-2xl text-lg text-gray-600'
                }
              >
                {content}
              </p>
            )}
          </div>

          {/* Map canvas */}
          <div
            ref={mapContainerRef}
            className={
              classNames.mapContainer ??
              'relative aspect-video w-full touch-none overflow-hidden rounded-lg shadow-lg'
            }
            style={{ backgroundColor: mapStyles.mapBackground ?? '#f0f8ff' }}
            onPointerDown={handlePointerDown}
            onPointerMove={handlePointerMove}
            onPointerUp={handlePointerUp}
            onPointerLeave={handlePointerUp}
          >
            {isLoading && (
              <div className='absolute inset-0 z-10 flex items-center justify-center bg-white bg-opacity-75'>
                <div className='h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600' />
              </div>
            )}

            <svg
              viewBox={WORLD_MAP_DATA.viewBox}
              className='h-full w-full origin-center'
              aria-label='Interactive world map'
              style={{
                transform: `translate(${transform.x}px, ${transform.y}px) scale(${transform.scale})`,
                transition: isDragging.current
                  ? 'none'
                  : 'transform 0.6s ease-out',
              }}
            >
              <g>
                {WORLD_MAP_DATA.locations.map(
                  (location: { id: string; name: string; path: string }) => {
                    const interactive = isCountryInteractive(location.id);
                    return (
                      <path
                        key={location.id}
                        id={location.id}
                        name={location.name}
                        d={location.path}
                        fill={getCountryFill(location.id)}
                        stroke={mapStyles.countryStroke ?? '#ffffff'}
                        strokeWidth={0.5 / transform.scale}
                        className={`transition-colors duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 ${
                          interactive
                            ? 'cursor-pointer hover:stroke-gray-700 hover:stroke-[1.5px]'
                            : 'cursor-default'
                        }`}
                        onMouseEnter={e =>
                          handleCountryHover(e, location.id, location.name)
                        }
                        onMouseLeave={() => handleCountryHover(null, null)}
                        onMouseMove={handleCountryMouseMove}
                        onClick={() =>
                          interactive && handleCountryClick(location.id)
                        }
                        onKeyDown={e =>
                          interactive && handleKeyDown(e, location.id)
                        }
                        tabIndex={interactive ? 0 : -1}
                        role={interactive ? 'button' : 'presentation'}
                        aria-label={`${location.name}${interactive ? ` - Region: ${countryToRegion[location.id]?.name}` : ''}`}
                      >
                        <title>{location.name}</title>
                      </path>
                    );
                  }
                )}
              </g>
            </svg>

            {/* ZoomControls overlay — default only; compound mode renders its own */}
            {children ? null : <MapZoomControls />}

            {/* All compound children — MapJourney renders its SVG overlay here */}
            {children}
          </div>

          {/* Outside-map primitives: default layout or journey children slot */}
          {children ? <JourneySlot /> : <DefaultLayout />}
        </section>
      </JourneyContext.Provider>
    </MapContext.Provider>
  );
}

// =============================================================================
// COMPOUND COMPONENT ASSEMBLY
// =============================================================================

export const InteractiveRegionsMap = Object.assign(InteractiveRegionsMapRoot, {
  // Map primitives
  Legend: MapLegend,
  Tooltip: MapTooltip,
  ResetButton: MapResetButton,
  ZoomControls: MapZoomControls,
  // Journey primitives
  Journey: MapJourney,
  JourneyCallout: JourneyCallout,
  JourneyControls: JourneyControls,
  JourneyStepList: JourneyStepList,
});
