import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from "react";
import {
  Cartesian2,
  Cartesian3,
  BingMapsImageryProvider,
  BingMapsStyle,
  BillboardGraphics,
  Color,
  Credit,
  Cesium3DTileset,
  Cesium3DTileStyle,
  CesiumTerrainProvider,
  CallbackProperty,
  CallbackPositionProperty,
  Cartographic,
  ClassificationType,
  ColorGeometryInstanceAttribute,
  ConstantPositionProperty,
  CustomDataSource,
  DefaultProxy,
  EllipsoidTerrainProvider,
  Entity,
  GeometryInstance,
  GroundPolylineGeometry,
  GroundPolylinePrimitive,
  GroundPrimitive,
  HeightReference,
  HeadingPitchRange,
  HorizontalOrigin,
  ImageryLayer,
  JulianDate,
  LabelGraphics,
  LabelStyle,
  Matrix4,
  Math as CesiumMath,
  PerInstanceColorAppearance,
  PointGraphics,
  PolygonGeometry,
  PolygonHierarchy,
  PolylineColorAppearance,
  PolylineGraphics,
  Resource,
  sampleTerrainMostDetailed,
  ScreenSpaceEventHandler,
  ScreenSpaceEventType,
  SunLight,
  VerticalOrigin,
  Viewer,
} from "cesium";
import "cesium/Build/Cesium/Widgets/widgets.css";
import { MAX_SPIRAL_RADIUS_METERS, spiralSurfaceFrame, type GeodeticRadians } from "./mapSpiralMath.js";
import { cameraSurveyPitchForViewport, cameraSurveySampleDistances, cameraSurveySpiralDistance } from "./mapCameraPresets.js";
import { mapRuntimeEntityId, type MapRuntimeBinding, type MapRuntimeFact } from "./useMapDataProductRuntime.js";
import {
  mapPresentationProfileForFact,
  mapRuntimeDisplayLabel,
  mapRuntimeFactIsVisible,
  resolveMapPresentationClass,
  resolveMapPresentationStyle,
  type MapPresentationFilters,
  type MapPresentationProfile,
} from "./mapPresentationProfile.js";
import { normalizeHGeoZoneRing } from "./hGeoZoneProjection.mjs";

const MAX_SPIRAL_SUBSTEPS_PER_FRAME = 300;
const TERRAIN_SAMPLE_TIMEOUT_MS = 12_000;
const SPIRAL_TILE_WAIT_TIMEOUT_MS = 45_000;
const MAX_HGEOZONE_INSTANCES_PER_BATCH = 256;
const elevatedTargetImageCache = new Map<string, string>();

function elevatedTargetImage(
  fillColor: Color,
  outlineColor: Color,
  outlineWidthPx: number,
  headSizePx: number,
) {
  const safeHeadSize = Math.max(1, headSizePx);
  const safeOutlineWidth = Math.max(0, outlineWidthPx);
  const imageSize = Math.max(1, Math.ceil(safeHeadSize + safeOutlineWidth * 2));
  const key = [
    fillColor.toCssColorString(),
    outlineColor.toCssColorString(),
    safeOutlineWidth,
    safeHeadSize,
    imageSize,
  ].join("|");
  const cached = elevatedTargetImageCache.get(key);
  if (cached) return { image: cached, size: imageSize };
  const center = imageSize / 2;
  const radius = Math.max(0.5, safeHeadSize / 2);
  const svg = [
    `<svg xmlns="http://www.w3.org/2000/svg" width="${imageSize}" height="${imageSize}" viewBox="0 0 ${imageSize} ${imageSize}">`,
    `<circle cx="${center}" cy="${center}" r="${radius}" fill="${fillColor.toCssColorString()}"`,
    safeOutlineWidth > 0
      ? ` stroke="${outlineColor.toCssColorString()}" stroke-width="${safeOutlineWidth}"/>`
      : "/>",
    "</svg>",
  ].join("");
  const image = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
  elevatedTargetImageCache.set(key, image);
  return { image, size: imageSize };
}

type RuntimeConfig = {
  cesiumVersion: string;
  provider: string;
  ionReady: boolean;
  gatewayReady: boolean;
  osmBuildingsReady: boolean;
  gaussianSplatsReady: boolean;
  gaussianAssetId?: string | null;
  assetEndpointBase: string;
  resourceProxyBase?: string | null;
  gatewayHealthUrl?: string | null;
};

export type MapGatewayHealth = {
  cache?: {
    mode?: string;
    writePolicy?: string;
    entries?: number;
    bytes?: number;
    maxBytes?: number;
    atCapacity?: boolean;
    persistent?: boolean;
  };
  diagnostics?: {
    cacheHits?: number;
    cacheMisses?: number;
    cacheRefreshes?: number;
    upstreamRequests?: number;
    egressRequests?: number;
    upstreamFailures?: number;
    slowUpstreamRequests?: number;
    lastFailure?: string | null;
    lastFailureAt?: string | null;
  };
  ionConfigured?: boolean;
};

type MapProviderState = "loading" | "ready" | "error" | "not-configured";

// Provider loading must be observable independently. In particular, imagery
// is optional for scene construction: a Bing metadata failure must never
// prevent Cesium World Terrain from being requested and rendered.
export type MapProviderStatus = {
  imagery: MapProviderState;
  terrain: MapProviderState;
  buildings: MapProviderState;
  errors: Partial<Record<"imagery" | "terrain" | "buildings" | "projection", string>>;
};

type IonAssetEndpoint = {
  assetId: string;
  type: "TERRAIN" | "3DTILES" | "IMAGERY";
  url?: string;
  credentialMode?: "gateway";
  externalType?: "BING";
  options?: { url?: string; mapStyle?: string };
  attributions: Array<{ html?: string; collapsible?: boolean }>;
};

export type MapPresentation = {
  imagerySource: "cesium-live";
  imageryVisible: boolean;
  cacheEnabled: boolean;
  cacheNoOverwrite: boolean;
  terrainEnabled: boolean;
  terrainExaggeration: number;
  monochrome: boolean;
  monochromeColor: string;
  imageryGamma: number;
  imageryHue: number;
  imageryAlpha: number;
  globeColor: string;
  backgroundColor: string;
  atmosphereEnabled: boolean;
  atmosphereHue: number;
  atmosphereSaturation: number;
  atmosphereBrightness: number;
  fogEnabled: boolean;
  fogDensity: number;
  sunEnabled: boolean;
  sunHour: number;
  sunIntensity: number;
  shadowsEnabled: boolean;
  buildingsVisible: boolean;
  buildingsColor: string;
  buildingsOpacity: number;
  buildingsDetail: number;
  imageryBrightness: number;
  imageryContrast: number;
  imagerySaturation: number;
  gridVisible: boolean;
  gridLodEnabled: boolean;
  gridHeightMeters: number;
  gridLod1MaxHeightKm: number;
  gridLod1StepKm: number;
  gridLod2MaxHeightKm: number;
  gridLod2StepKm: number;
  gridLod3StepKm: number;
  gridRadiusKm: number;
  gridLineWidth: number;
  gridColor: string;
  gridOpacity: number;
  gridDotsEnabled: boolean;
  gridDotsSize: number;
  gridDotsColor: string;
  gridDotsOpacity: number;
  cacheRefresh: boolean;
};

export type MapCameraView = {
  longitude: number;
  latitude: number;
  height: number;
  heading: number;
  pitch: number;
  roll: number;
};

export type CameraSpiralConfig = {
  heightAboveGroundMeters: number;
  speedMetersPerSecond: number;
  pitchMetersPerTurn: number;
  targetRadiusMeters?: number;
  viewPitchRadians?: number;
  waitForTiles?: boolean;
};

export type CameraSpiralState = {
  running: boolean;
  reason?: "stopped" | "mode_disabled" | "renderer_restarted" | "page_hidden" | "render_error" | "spiral_extent_limit" | "spiral_runtime_error" | "terrain_sampling_error" | "tile_loading_timeout" | "tile_loading_error" | "target_radius_reached";
};

export type CesiumMapRendererHandle = {
  startSpiralAnimation: (config: CameraSpiralConfig) => boolean;
  stopSpiralAnimation: (reason?: CameraSpiralState["reason"]) => void;
  getCameraView: () => MapCameraView | null;
  fitRuntimeEntities: (entityIds?: string[]) => boolean;
  focusRuntimeEntity: (entityId: string) => boolean;
};

type TerrainRouteSample = {
  distanceMeters: number;
  heightMeters: number;
};

type SpiralSession = {
  viewer: Viewer;
  origin: GeodeticRadians;
  initialHeading: number;
  pitch: number;
  roll: number;
  config: CameraSpiralConfig;
  surfaceDistanceMeters: number;
  lastTimestamp: number | null;
  pendingElapsedSeconds: number;
  tilesWaitStartedAt: number | null;
  requestId: number | null;
  previousCameraInputsEnabled: boolean;
  terrainProvider: CesiumTerrainProvider | null;
  terrainSamples: TerrainRouteSample[];
  terrainSamplePending: boolean;
  terrainSampleExhausted: boolean;
  terrainSampleFailures: number;
  terrainRequestGeneration: number;
  terrainRetryAt: number;
  terrainSampleSpacingMeters: number;
  targetSurfaceDistanceMeters: number;
  buildingsTileset: Cesium3DTileset | null;
  previousBuildingsCullRequestsWhileMoving: boolean | null;
  previousBuildingsFoveatedScreenSpaceError: boolean | null;
  previousBuildingsFoveatedTimeDelay: number | null;
};

const accent = Color.fromCssColorString("#ff2f92");
const violet = Color.fromCssColorString("#8f72dc");
const clamp = (value: number, minimum: number, maximum: number) => Math.max(minimum, Math.min(maximum, value));

function showBelowCameraHeight(viewer: Viewer, limit?: number) {
  if (!limit) return true;
  return new CallbackProperty(() => Number(viewer.camera.positionCartographic?.height || 0) <= limit, false);
}

function elevatedPinGroundHeight(viewer: Viewer, longitude: number, latitude: number, fallbackHeightMeters = 0) {
  const sampled = viewer.scene.globe.getHeight(Cartographic.fromDegrees(longitude, latitude));
  return Number.isFinite(sampled) ? Number(sampled) : fallbackHeightMeters;
}

function elevatedPinTopPosition(
  viewer: Viewer,
  longitude: number,
  latitude: number,
  stemHeightMeters: number,
  fallbackHeightMeters = 0,
) {
  return new CallbackPositionProperty(() => Cartesian3.fromDegrees(
    longitude,
    latitude,
    elevatedPinGroundHeight(viewer, longitude, latitude, fallbackHeightMeters) + stemHeightMeters,
  ), false);
}

function elevatedPinStemPositions(
  viewer: Viewer,
  longitude: number,
  latitude: number,
  stemHeightMeters: number,
  fallbackHeightMeters = 0,
) {
  return new CallbackProperty(() => {
    const groundHeight = elevatedPinGroundHeight(viewer, longitude, latitude, fallbackHeightMeters);
    return [
      Cartesian3.fromDegrees(longitude, latitude, groundHeight),
      Cartesian3.fromDegrees(longitude, latitude, groundHeight + stemHeightMeters),
    ];
  }, false);
}

function getCameraView(viewer: Viewer): MapCameraView {
  const position = viewer.camera.positionCartographic;
  return {
    longitude: CesiumMath.toDegrees(position.longitude),
    latitude: CesiumMath.toDegrees(position.latitude),
    height: position.height,
    heading: viewer.camera.heading,
    pitch: viewer.camera.pitch,
    roll: viewer.camera.roll,
  };
}

function horizontalFieldOfViewRadians(viewer: Viewer) {
  const frustum = viewer.camera.frustum as unknown as {
    fovy?: number;
    fov?: number;
    aspectRatio?: number;
  };
  const aspectRatio = Number.isFinite(frustum.aspectRatio) && Number(frustum.aspectRatio) > 0
    ? Number(frustum.aspectRatio)
    : Math.max(0.1, viewer.canvas.clientWidth / Math.max(1, viewer.canvas.clientHeight));
  if (Number.isFinite(frustum.fovy) && Number(frustum.fovy) > 0) {
    return 2 * Math.atan(Math.tan(Number(frustum.fovy) / 2) * aspectRatio);
  }
  if (Number.isFinite(frustum.fov) && Number(frustum.fov) > 0) return Number(frustum.fov);
  return Math.PI / 3;
}

function interpolateTerrainRouteHeight(samples: TerrainRouteSample[], distanceMeters: number) {
  if (samples.length === 0) return null;
  if (distanceMeters === samples[0].distanceMeters) return samples[0].heightMeters;
  for (let index = 1; index < samples.length; index += 1) {
    const upper = samples[index];
    if (distanceMeters > upper.distanceMeters) continue;
    const lower = samples[index - 1];
    const span = upper.distanceMeters - lower.distanceMeters;
    if (span <= 0) return upper.heightMeters;
    const progress = clamp((distanceMeters - lower.distanceMeters) / span, 0, 1);
    return lower.heightMeters + (upper.heightMeters - lower.heightMeters) * progress;
  }
  return null;
}

function runtimePointColor(fact: MapRuntimeFact) {
  // This is a semantic default for the generic Map entity-stream adapter,
  // not a provider style. A renderer-neutral style profile can refine it
  // later without changing a data product or its L2 workflow.
  if (fact.presentationStatus === "stale") return Color.fromCssColorString("#f5a623");
  if (["inactive", "no-position", "no_position"].includes(fact.presentationStatus)) {
    return Color.fromCssColorString("#7d8491");
  }
  return fact.semanticType === "map.moving_object" ? accent : violet;
}

type HGeoZonePickId = {
  kind: "nodedc-hgeozone";
  entityId: string;
  instanceId: string;
};

type HGeoZoneFillPart = {
  pickId: HGeoZonePickId;
  hierarchy: PolygonHierarchy;
  color: Color;
};

type HGeoZoneOutlinePart = {
  pickId: HGeoZonePickId;
  positions: Cartesian3[];
  color: Color;
};

type HGeoZonePrimitiveBatch<TPart> = {
  primitive: GroundPrimitive | GroundPolylinePrimitive;
  parts: TPart[];
};

type HGeoZoneProjectionLayer = {
  geometryKey: string;
  styleKey: string;
  hideCameraHeightMeters: number | null;
  fills: Array<HGeoZonePrimitiveBatch<HGeoZoneFillPart>>;
  outlines: Array<HGeoZonePrimitiveBatch<HGeoZoneOutlinePart>>;
};

function hGeoZoneRingPositions(ring: Array<[number, number]>) {
  return normalizeHGeoZoneRing(ring)
    .map(([longitude, latitude]) => Cartesian3.fromDegrees(longitude, latitude, 0));
}

function hGeoZoneHierarchy(polygon: Array<Array<[number, number]>>) {
  const outer = hGeoZoneRingPositions(polygon[0]);
  if (outer.length < 3) return null;
  const holes = polygon.slice(1)
    .map((ring) => hGeoZoneRingPositions(ring))
    .filter((ring) => ring.length >= 3)
    .map((ring) => new PolygonHierarchy(ring));
  return new PolygonHierarchy(outer, holes);
}

function hGeoZoneBatches<T>(items: T[]) {
  const batches: T[][] = [];
  for (let index = 0; index < items.length; index += MAX_HGEOZONE_INSTANCES_PER_BATCH) {
    batches.push(items.slice(index, index + MAX_HGEOZONE_INSTANCES_PER_BATCH));
  }
  return batches;
}

function removeHGeoZoneLayer(viewer: Viewer, layer: HGeoZoneProjectionLayer) {
  for (const batch of [...layer.fills, ...layer.outlines]) {
    viewer.scene.groundPrimitives.remove(batch.primitive);
  }
}

function hGeoZoneFaultKey(bindingId: string, geometryKey: string) {
  return `${bindingId}\u0000${geometryKey}`;
}

function quarantineHGeoZoneLayers(
  viewer: Viewer,
  layers: Map<string, HGeoZoneProjectionLayer>,
  faultedGeometryKeys: Set<string>,
) {
  if (!layers.size) return false;
  const hasPendingGeometry = [...layers.values()].some((layer) => (
    [...layer.fills, ...layer.outlines].some((batch) => !batch.primitive.ready)
  ));
  if (!hasPendingGeometry) return false;
  for (const [bindingId, layer] of layers) {
    faultedGeometryKeys.add(hGeoZoneFaultKey(bindingId, layer.geometryKey));
    removeHGeoZoneLayer(viewer, layer);
  }
  layers.clear();
  viewer.scene.requestRender();
  return true;
}

function syncHGeoZoneVisibility(viewer: Viewer, layers: Map<string, HGeoZoneProjectionLayer>) {
  const cameraHeight = Number(viewer.camera.positionCartographic?.height || 0);
  for (const layer of layers.values()) {
    const show = layer.hideCameraHeightMeters === null || cameraHeight <= layer.hideCameraHeightMeters;
    for (const batch of [...layer.fills, ...layer.outlines]) batch.primitive.show = show;
  }
  viewer.scene.requestRender();
}

function updateHGeoZoneColors<TPart extends { pickId: HGeoZonePickId; color: Color }>(
  batches: Array<HGeoZonePrimitiveBatch<TPart>>,
  nextParts: TPart[],
) {
  const nextColors = new Map(nextParts.map((part) => [part.pickId.instanceId, part.color]));
  for (const batch of batches) {
    for (const part of batch.parts) {
      const color = nextColors.get(part.pickId.instanceId);
      if (!color) continue;
      const attributes = batch.primitive.getGeometryInstanceAttributes(part.pickId);
      if (attributes) attributes.color = ColorGeometryInstanceAttribute.toValue(color);
      part.color = color;
    }
  }
}

function createHGeoZoneLayer(
  viewer: Viewer,
  geometryKey: string,
  styleKey: string,
  hideCameraHeightMeters: number | null,
  fillParts: HGeoZoneFillPart[],
  outlineParts: HGeoZoneOutlinePart[],
  outlineWidthPx: number,
) {
  const fills = hGeoZoneBatches(fillParts).map((parts) => {
    const primitive = viewer.scene.groundPrimitives.add(new GroundPrimitive({
      geometryInstances: parts.map((part) => new GeometryInstance({
        id: part.pickId,
        geometry: new PolygonGeometry({
          polygonHierarchy: part.hierarchy,
          vertexFormat: PerInstanceColorAppearance.FLAT_VERTEX_FORMAT,
        }),
        attributes: { color: ColorGeometryInstanceAttribute.fromColor(part.color) },
      })),
      appearance: new PerInstanceColorAppearance({ flat: true, translucent: true }),
      allowPicking: true,
      asynchronous: true,
      classificationType: ClassificationType.TERRAIN,
      releaseGeometryInstances: true,
    }));
    return { primitive, parts };
  });
  const outlines = outlineWidthPx <= 0 ? [] : hGeoZoneBatches(outlineParts).map((parts) => {
    const primitive = viewer.scene.groundPrimitives.add(new GroundPolylinePrimitive({
      geometryInstances: parts.map((part) => new GeometryInstance({
        id: part.pickId,
        geometry: new GroundPolylineGeometry({
          positions: part.positions,
          width: outlineWidthPx,
          loop: true,
        }),
        attributes: { color: ColorGeometryInstanceAttribute.fromColor(part.color) },
      })),
      appearance: new PolylineColorAppearance({ translucent: true }),
      allowPicking: true,
      asynchronous: true,
      classificationType: ClassificationType.TERRAIN,
      releaseGeometryInstances: true,
    }));
    return { primitive, parts };
  });
  const layer = { geometryKey, styleKey, hideCameraHeightMeters, fills, outlines };
  syncHGeoZoneVisibility(viewer, new Map([["layer", layer]]));
  return layer;
}

function syncHGeoZoneLayers(
  viewer: Viewer,
  layers: Map<string, HGeoZoneProjectionLayer>,
  bindings: MapRuntimeBinding[],
  presentationProfiles: MapPresentationProfile[],
  presentationFilters: MapPresentationFilters,
  faultedGeometryKeys: Set<string>,
) {
  const activeBindings = new Set(bindings.filter((binding) => binding.slotId === "zones").map((binding) => binding.bindingId));
  for (const [bindingId, layer] of layers) {
    if (activeBindings.has(bindingId)) continue;
    removeHGeoZoneLayer(viewer, layer);
    layers.delete(bindingId);
  }

  for (const binding of bindings) {
    if (binding.slotId !== "zones") continue;
    const fillParts: HGeoZoneFillPart[] = [];
    const outlineParts: HGeoZoneOutlinePart[] = [];
    const geometryMembers: string[] = [];
    let outlineWidthPx = 1.5;
    let hideCameraHeightMeters: number | null = null;
    let outlineColor = Color.fromCssColorString("#c9b6ff").withAlpha(0.9);
    const styleMembers: string[] = [];

    for (const fact of binding.facts) {
      const profile = mapPresentationProfileForFact(
        presentationProfiles,
        binding.presentationProfileId,
        fact.semanticType,
      );
      if (
        !fact.geometry
        || fact.geometry.type === "Point"
        || (profile && profile.target.variant !== "surface-fill")
        || (profile && !mapRuntimeFactIsVisible(fact, profile, presentationFilters, binding.bindingId))
      ) continue;
      const presentationClass = profile ? resolveMapPresentationClass(fact, profile) : undefined;
      const resolvedStyle = profile ? resolveMapPresentationStyle(profile, presentationClass) : undefined;
      const fillColor = resolvedStyle
        ? Color.fromCssColorString(resolvedStyle.color).withAlpha(resolvedStyle.opacity)
        : runtimePointColor(fact).withAlpha(0.28);
      if (profile?.target.variant === "surface-fill") {
        outlineWidthPx = profile.target.outlineWidthPx;
        hideCameraHeightMeters = profile.target.hideCameraHeightMeters;
        outlineColor = Color.fromCssColorString(profile.target.outlineColor).withAlpha(profile.target.outlineOpacity);
      }
      const polygons = fact.geometry.type === "Polygon" ? [fact.geometry.coordinates] : fact.geometry.coordinates;
      const baseEntityId = mapRuntimeEntityId(binding.bindingId, fact);
      for (const [polygonIndex, polygon] of polygons.entries()) {
        const hierarchy = hGeoZoneHierarchy(polygon);
        if (!hierarchy) continue;
        const instanceBase = `${baseEntityId}:part:${polygonIndex}`;
        const fillPickId: HGeoZonePickId = {
          kind: "nodedc-hgeozone",
          entityId: baseEntityId,
          instanceId: `${instanceBase}:fill`,
        };
        fillParts.push({ pickId: fillPickId, hierarchy, color: fillColor });
        geometryMembers.push(fillPickId.instanceId);
        styleMembers.push(`${fillPickId.instanceId}:${resolvedStyle?.id ?? "default"}:${fillColor.toCssHexString()}:${fillColor.alpha}`);
        for (const [ringIndex, ring] of polygon.entries()) {
          const positions = hGeoZoneRingPositions(ring);
          if (positions.length < 3) continue;
          const outlinePickId: HGeoZonePickId = {
            kind: "nodedc-hgeozone",
            entityId: baseEntityId,
            instanceId: `${instanceBase}:ring:${ringIndex}`,
          };
          outlineParts.push({ pickId: outlinePickId, positions, color: outlineColor });
          geometryMembers.push(outlinePickId.instanceId);
        }
      }
    }

    const geometryKey = JSON.stringify([binding.cursor, outlineWidthPx, geometryMembers]);
    const styleKey = JSON.stringify([styleMembers, outlineColor.toCssHexString(), outlineColor.alpha]);
    const current = layers.get(binding.bindingId);
    if (!fillParts.length) {
      if (current) removeHGeoZoneLayer(viewer, current);
      layers.delete(binding.bindingId);
      continue;
    }
    if (faultedGeometryKeys.has(hGeoZoneFaultKey(binding.bindingId, geometryKey))) {
      if (current) removeHGeoZoneLayer(viewer, current);
      layers.delete(binding.bindingId);
      continue;
    }
    if (current?.geometryKey === geometryKey) {
      current.hideCameraHeightMeters = hideCameraHeightMeters;
      if (current.styleKey !== styleKey) {
        const ready = [...current.fills, ...current.outlines].every((batch) => batch.primitive.ready);
        if (ready) {
          updateHGeoZoneColors(current.fills, fillParts);
          updateHGeoZoneColors(current.outlines, outlineParts);
          current.styleKey = styleKey;
          syncHGeoZoneVisibility(viewer, layers);
          continue;
        }
      } else {
        syncHGeoZoneVisibility(viewer, layers);
        continue;
      }
    }
    if (current) removeHGeoZoneLayer(viewer, current);
    layers.set(binding.bindingId, createHGeoZoneLayer(
      viewer,
      geometryKey,
      styleKey,
      hideCameraHeightMeters,
      fillParts,
      outlineParts,
      outlineWidthPx,
    ));
  }
  viewer.scene.requestRender();
}

function syncRuntimeDataSources(
  viewer: Viewer,
  dataSources: Map<string, CustomDataSource>,
  hGeoZoneLayers: Map<string, HGeoZoneProjectionLayer>,
  bindings: MapRuntimeBinding[],
  presentationProfiles: MapPresentationProfile[],
  presentationFilters: MapPresentationFilters,
  faultedHGeoZoneGeometryKeys: Set<string>,
) {
  const activeBindings = new Map(bindings
    .filter((binding) => (
      binding.slotId === "points"
      || binding.slotId === "reference-points"
      || binding.slotId === "zones"
    ))
    .map((binding) => [binding.bindingId, binding]));

  for (const [bindingId, dataSource] of dataSources) {
    if (activeBindings.has(bindingId)) continue;
    viewer.dataSources.remove(dataSource, true);
    dataSources.delete(bindingId);
  }

  for (const binding of activeBindings.values()) {
    let dataSource = dataSources.get(binding.bindingId);
    if (!dataSource) {
      dataSource = new CustomDataSource(`nodedc-map-slot:${binding.bindingId}`);
      viewer.dataSources.add(dataSource);
      dataSources.set(binding.bindingId, dataSource);
    }
    const wanted = new Set<string>();
    for (const fact of binding.facts) {
      const profile = mapPresentationProfileForFact(
        presentationProfiles,
        binding.presentationProfileId,
        fact.semanticType,
      );
      const presentationClass = profile ? resolveMapPresentationClass(fact, profile) : undefined;
      if (
        !fact.geometry
        || (profile && !mapRuntimeFactIsVisible(fact, profile, presentationFilters, binding.bindingId))
      ) continue;
      const resolvedStyle = profile ? resolveMapPresentationStyle(profile, presentationClass) : undefined;
      const color = resolvedStyle
        ? Color.fromCssColorString(resolvedStyle.color).withAlpha(resolvedStyle.opacity)
        : runtimePointColor(fact);
      const label = mapRuntimeDisplayLabel(fact, profile);
      const baseEntityId = mapRuntimeEntityId(binding.bindingId, fact);

      if (fact.geometry.type === "Point" && (binding.slotId === "points" || binding.slotId === "reference-points")) {
        if (profile && profile.target.variant !== "elevated-spike") continue;
        const entityId = baseEntityId;
        wanted.add(entityId);
        const [longitude, latitude] = fact.geometry.coordinates;
        const entity = dataSource.entities.getById(entityId) ?? dataSource.entities.add({ id: entityId });
        entity.name = label;
        entity.polygon = undefined;
        if (!profile) {
          entity.billboard = undefined;
          entity.position = new ConstantPositionProperty(Cartesian3.fromDegrees(longitude, latitude, 0));
          entity.polyline = undefined;
          entity.point = new PointGraphics({
            pixelSize: 10,
            color,
            outlineColor: Color.TRANSPARENT,
            outlineWidth: 0,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
          });
          entity.label = new LabelGraphics({
            text: label,
            font: "700 13px Arial",
            fillColor: Color.WHITE,
            outlineColor: Color.TRANSPARENT,
            outlineWidth: 0,
            style: LabelStyle.FILL,
            showBackground: true,
            backgroundColor: Color.BLACK.withAlpha(0.72),
            backgroundPadding: new Cartesian2(10, 7),
            pixelOffset: new Cartesian2(10, 0),
            horizontalOrigin: HorizontalOrigin.LEFT,
            verticalOrigin: VerticalOrigin.BOTTOM,
            heightReference: HeightReference.NONE,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
          });
          continue;
        }
        if (profile.target.variant !== "elevated-spike") continue;
        const target = profile.target;
        const fallbackHeightMeters = typeof fact.attributes.elevation_meters === "number" && Number.isFinite(fact.attributes.elevation_meters)
          ? fact.attributes.elevation_meters
          : 0;
        entity.position = elevatedPinTopPosition(
          viewer,
          longitude,
          latitude,
          target.stemHeightMeters,
          fallbackHeightMeters,
        );
        entity.polyline = new PolylineGraphics({
          positions: elevatedPinStemPositions(
            viewer,
            longitude,
            latitude,
            target.stemHeightMeters,
            fallbackHeightMeters,
          ),
          width: target.stemWidthPx,
          material: color,
          show: showBelowCameraHeight(viewer, target.hideCameraHeightMeters),
        });
        const targetImage = elevatedTargetImage(
          color,
          Color.fromCssColorString(target.outlineColor).withAlpha(target.outlineOpacity),
          target.outlineWidthPx,
          target.headSizePx,
        );
        entity.point = undefined;
        entity.billboard = new BillboardGraphics({
          image: targetImage.image,
          width: targetImage.size,
          height: targetImage.size,
          horizontalOrigin: HorizontalOrigin.CENTER,
          verticalOrigin: VerticalOrigin.CENTER,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          show: showBelowCameraHeight(viewer, target.hideCameraHeightMeters),
        });
        entity.label = new LabelGraphics({
          text: label,
          font: `${profile.label.fontWeight} ${profile.label.sizePx}px Arial`,
          fillColor: Color.fromCssColorString(profile.label.color),
          outlineColor: Color.TRANSPARENT,
          outlineWidth: 0,
          style: LabelStyle.FILL,
          showBackground: profile.label.backgroundOpacity > 0,
          backgroundColor: Color.fromCssColorString(profile.label.backgroundColor).withAlpha(profile.label.backgroundOpacity),
          backgroundPadding: new Cartesian2(profile.label.paddingX, profile.label.paddingY),
          pixelOffset: new Cartesian2(profile.label.offsetX, profile.label.offsetY),
          horizontalOrigin: HorizontalOrigin.LEFT,
          verticalOrigin: VerticalOrigin.BOTTOM,
          heightReference: HeightReference.NONE,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          show: profile.label.mode !== "none" && showBelowCameraHeight(viewer, profile.label.hideCameraHeightMeters),
        });
        continue;
      }

      if (
        binding.slotId !== "zones"
        || fact.geometry.type === "Point"
        || (profile && profile.target.variant !== "surface-fill")
      ) continue;
      const polygons = fact.geometry.type === "Polygon" ? [fact.geometry.coordinates] : fact.geometry.coordinates;
      const outerRing = normalizeHGeoZoneRing(polygons[0]?.[0] ?? []);
      if (!outerRing.length) continue;
      wanted.add(baseEntityId);
      const labelAnchor = outerRing.reduce(
        (accumulator, [longitude, latitude]) => [accumulator[0] + longitude, accumulator[1] + latitude] as [number, number],
        [0, 0] as [number, number],
      );
      const divisor = Math.max(1, outerRing.length);
      const entity = dataSource.entities.getById(baseEntityId) ?? dataSource.entities.add({ id: baseEntityId });
      entity.name = label;
      entity.position = new ConstantPositionProperty(Cartesian3.fromDegrees(labelAnchor[0] / divisor, labelAnchor[1] / divisor, 0));
      entity.billboard = undefined;
      entity.point = undefined;
      entity.polygon = undefined;
      entity.polyline = undefined;
      entity.label = new LabelGraphics({
        text: label,
        font: profile ? `${profile.label.fontWeight} ${profile.label.sizePx}px Arial` : "700 13px Arial",
        fillColor: profile ? Color.fromCssColorString(profile.label.color) : Color.WHITE,
        outlineColor: Color.TRANSPARENT,
        outlineWidth: 0,
        style: LabelStyle.FILL,
        showBackground: (profile?.label.backgroundOpacity ?? 0.72) > 0,
        backgroundColor: profile
          ? Color.fromCssColorString(profile.label.backgroundColor).withAlpha(profile.label.backgroundOpacity)
          : Color.BLACK.withAlpha(0.72),
        backgroundPadding: new Cartesian2(profile?.label.paddingX ?? 10, profile?.label.paddingY ?? 7),
        pixelOffset: new Cartesian2(profile?.label.offsetX ?? 10, profile?.label.offsetY ?? 0),
        horizontalOrigin: HorizontalOrigin.LEFT,
        verticalOrigin: VerticalOrigin.BOTTOM,
        heightReference: HeightReference.CLAMP_TO_GROUND,
        disableDepthTestDistance: Number.POSITIVE_INFINITY,
        show: (profile?.label.mode ?? "attributes") !== "none"
          && (profile ? showBelowCameraHeight(viewer, profile.label.hideCameraHeightMeters) : true),
      });
    }
    for (const entity of [...dataSource.entities.values]) {
      if (typeof entity.id === "string" && !wanted.has(entity.id)) dataSource.entities.remove(entity);
    }
  }
  syncHGeoZoneLayers(
    viewer,
    hGeoZoneLayers,
    bindings,
    presentationProfiles,
    presentationFilters,
    faultedHGeoZoneGeometryKeys,
  );
  viewer.scene.requestRender();
}

function rebuildElevatedGrid(viewer: Viewer, dataSource: CustomDataSource, presentation: MapPresentation) {
  const entities = dataSource.entities;
  entities.removeAll();
  if (!presentation.gridVisible) return;

  const cameraHeightKm = Math.max(0, Number(viewer.camera.positionCartographic?.height || 0) / 1000);
  const stepKm = !presentation.gridLodEnabled || cameraHeightKm <= presentation.gridLod1MaxHeightKm
    ? presentation.gridLod1StepKm
    : cameraHeightKm <= presentation.gridLod2MaxHeightKm
      ? presentation.gridLod2StepKm
      : presentation.gridLod3StepKm;
  const safeStepKm = clamp(stepKm, 0.25, 100);
  const safeRadiusKm = clamp(presentation.gridRadiusKm, safeStepKm, 150);
  const stepsPerSide = Math.min(32, Math.max(1, Math.floor(safeRadiusKm / safeStepKm)));
  const cameraPosition = viewer.camera.positionCartographic;
  const latitude = cameraPosition ? CesiumMath.toDegrees(cameraPosition.latitude) : 55.751244;
  const longitude = cameraPosition ? CesiumMath.toDegrees(cameraPosition.longitude) : 37.618423;
  const metersPerLatitudeDegree = 110_574;
  const metersPerLongitudeDegree = Math.max(1, 111_320 * Math.cos(CesiumMath.toRadians(latitude)));
  const stepMeters = safeStepKm * 1000;
  const radiusMeters = stepsPerSide * stepMeters;
  const deltaLatitude = stepMeters / metersPerLatitudeDegree;
  const deltaLongitude = stepMeters / metersPerLongitudeDegree;
  const radiusLatitude = radiusMeters / metersPerLatitudeDegree;
  const radiusLongitude = radiusMeters / metersPerLongitudeDegree;
  const lineColor = Color.fromCssColorString(presentation.gridColor).withAlpha(clamp(presentation.gridOpacity / 100, 0, 1));
  const dotColor = Color.fromCssColorString(presentation.gridDotsColor).withAlpha(clamp(presentation.gridDotsOpacity / 100, 0, 1));
  const elevation = Math.max(0, presentation.gridHeightMeters);

  for (let index = -stepsPerSide; index <= stepsPerSide; index += 1) {
    const nextLatitude = latitude + index * deltaLatitude;
    const nextLongitude = longitude + index * deltaLongitude;
    entities.add({
      polyline: {
        positions: [
          Cartesian3.fromDegrees(longitude - radiusLongitude, nextLatitude, elevation),
          Cartesian3.fromDegrees(longitude + radiusLongitude, nextLatitude, elevation),
        ],
        width: clamp(presentation.gridLineWidth, 1, 8),
        material: lineColor,
      },
    });
    entities.add({
      polyline: {
        positions: [
          Cartesian3.fromDegrees(nextLongitude, latitude - radiusLatitude, elevation),
          Cartesian3.fromDegrees(nextLongitude, latitude + radiusLatitude, elevation),
        ],
        width: clamp(presentation.gridLineWidth, 1, 8),
        material: lineColor,
      },
    });
  }

  if (!presentation.gridDotsEnabled) return;
  const dotStride = Math.max(1, Math.ceil((stepsPerSide * 2 + 1) / 25));
  for (let row = -stepsPerSide; row <= stepsPerSide; row += dotStride) {
    for (let column = -stepsPerSide; column <= stepsPerSide; column += dotStride) {
      entities.add({
        position: Cartesian3.fromDegrees(longitude + column * deltaLongitude, latitude + row * deltaLatitude, elevation),
        point: {
          pixelSize: clamp(presentation.gridDotsSize, 2, 28),
          color: dotColor,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
        },
      });
    }
  }
}

function applyPresentation(
  viewer: Viewer,
  imageryLayer: ImageryLayer | null,
  buildings: Cesium3DTileset | null,
  terrain: { world: CesiumTerrainProvider | null; ellipsoid: EllipsoidTerrainProvider },
  presentation: MapPresentation,
) {
  if (imageryLayer) {
    imageryLayer.show = presentation.imageryVisible && !presentation.monochrome;
    imageryLayer.brightness = presentation.imageryBrightness / 100;
    imageryLayer.contrast = presentation.imageryContrast / 100;
    imageryLayer.saturation = presentation.imagerySaturation / 100;
    imageryLayer.gamma = presentation.imageryGamma / 100;
    imageryLayer.hue = CesiumMath.toRadians(presentation.imageryHue);
    imageryLayer.alpha = presentation.imageryAlpha / 100;
  }
  if (buildings) {
    buildings.show = presentation.buildingsVisible;
    buildings.maximumScreenSpaceError = presentation.buildingsDetail;
    buildings.style = new Cesium3DTileStyle({
      color: `color('${presentation.buildingsColor}', ${presentation.buildingsOpacity})`,
    });
  }
  viewer.terrainProvider = presentation.terrainEnabled && terrain.world ? terrain.world : terrain.ellipsoid;
  viewer.scene.globe.show = true;
  viewer.scene.globe.baseColor = Color.fromCssColorString(presentation.monochrome ? presentation.monochromeColor : presentation.globeColor);
  viewer.scene.globe.enableLighting = presentation.sunEnabled;
  (viewer.scene as unknown as { verticalExaggeration?: number }).verticalExaggeration = clamp(presentation.terrainExaggeration, 0.25, 3);
  viewer.scene.backgroundColor = Color.fromCssColorString(presentation.backgroundColor);
  viewer.scene.fog.enabled = presentation.fogEnabled;
  viewer.scene.fog.density = clamp(presentation.fogDensity / 10_000, 0, 0.01);
  const atmosphere = viewer.scene.skyAtmosphere;
  if (atmosphere) {
    atmosphere.show = presentation.atmosphereEnabled;
    atmosphere.hueShift = clamp(presentation.atmosphereHue / 100, -1, 1);
    atmosphere.saturationShift = clamp(presentation.atmosphereSaturation / 100, -1, 1);
    atmosphere.brightnessShift = clamp(presentation.atmosphereBrightness / 100, -1, 1);
  }
  viewer.shadows = presentation.shadowsEnabled;
  const sunDate = JulianDate.toDate(JulianDate.now());
  sunDate.setUTCHours(clamp(Math.round(presentation.sunHour), 0, 24), 0, 0, 0);
  viewer.clock.currentTime = JulianDate.fromDate(sunDate);
  viewer.clock.shouldAnimate = false;
  const sun = new SunLight();
  (sun as unknown as { intensity?: number }).intensity = clamp(presentation.sunIntensity / 100, 0, 2);
  viewer.scene.light = sun;
  viewer.scene.requestRender();
}

export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
  onSelect?: (entityId: string) => void;
  onGatewayHealth?: (health: MapGatewayHealth | null) => void;
  onProviderStatus?: (status: MapProviderStatus) => void;
  onCameraChange?: (camera: MapCameraView) => void;
  onCacheRefreshConsumed?: () => void;
  onReadyChange?: (ready: boolean) => void;
  onSpiralStateChange?: (state: CameraSpiralState) => void;
  initialCamera?: MapCameraView;
  presentation: MapPresentation;
  runtimeBindings?: MapRuntimeBinding[];
  presentationProfiles?: MapPresentationProfile[];
  presentationFilters?: MapPresentationFilters;
}>(function CesiumMapRenderer({
  onSelect,
  onGatewayHealth,
  onProviderStatus,
  onCameraChange,
  onCacheRefreshConsumed,
  onReadyChange,
  onSpiralStateChange,
  initialCamera,
  presentation,
  runtimeBindings = [],
  presentationProfiles = [],
  presentationFilters = {},
}, ref) {
  const containerRef = useRef<HTMLDivElement>(null);
  const creditContainerRef = useRef<HTMLDivElement>(null);
  const viewerRef = useRef<Viewer | null>(null);
  const imageryLayerRef = useRef<ImageryLayer | null>(null);
  const buildingsRef = useRef<Cesium3DTileset | null>(null);
  const terrainRef = useRef<{ world: CesiumTerrainProvider | null; ellipsoid: EllipsoidTerrainProvider } | null>(null);
  const rebuildGridRef = useRef<(() => void) | null>(null);
  const runtimeDataSourcesRef = useRef(new Map<string, CustomDataSource>());
  const hGeoZoneLayersRef = useRef(new Map<string, HGeoZoneProjectionLayer>());
  const faultedHGeoZoneGeometryKeysRef = useRef(new Set<string>());
  const presentationRef = useRef(presentation);
  const runtimeBindingsRef = useRef(runtimeBindings);
  const presentationProfilesRef = useRef(presentationProfiles);
  const presentationFiltersRef = useRef(presentationFilters);
  const onSelectRef = useRef(onSelect);
  const onCameraChangeRef = useRef(onCameraChange);
  const onCacheRefreshConsumedRef = useRef(onCacheRefreshConsumed);
  const onReadyChangeRef = useRef(onReadyChange);
  const onSpiralStateChangeRef = useRef(onSpiralStateChange);
  const spiralSessionRef = useRef<SpiralSession | null>(null);

  useEffect(() => {
    onSelectRef.current = onSelect;
  }, [onSelect]);

  useEffect(() => {
    onCameraChangeRef.current = onCameraChange;
  }, [onCameraChange]);

  useEffect(() => {
    onCacheRefreshConsumedRef.current = onCacheRefreshConsumed;
  }, [onCacheRefreshConsumed]);

  useEffect(() => {
    onReadyChangeRef.current = onReadyChange;
  }, [onReadyChange]);

  useEffect(() => {
    onSpiralStateChangeRef.current = onSpiralStateChange;
  }, [onSpiralStateChange]);

  const stopSpiralAnimation = useCallback((reason: CameraSpiralState["reason"] = "stopped") => {
    const session = spiralSessionRef.current;
    if (!session) return;
    spiralSessionRef.current = null;
    if (session.requestId !== null) window.cancelAnimationFrame(session.requestId);
    if (!session.viewer.isDestroyed()) {
      if (session.buildingsTileset && !session.buildingsTileset.isDestroyed()) {
        if (session.previousBuildingsCullRequestsWhileMoving !== null) {
          session.buildingsTileset.cullRequestsWhileMoving = session.previousBuildingsCullRequestsWhileMoving;
        }
        if (session.previousBuildingsFoveatedScreenSpaceError !== null) {
          session.buildingsTileset.foveatedScreenSpaceError = session.previousBuildingsFoveatedScreenSpaceError;
        }
        if (session.previousBuildingsFoveatedTimeDelay !== null) {
          session.buildingsTileset.foveatedTimeDelay = session.previousBuildingsFoveatedTimeDelay;
        }
      }
      session.viewer.scene.screenSpaceCameraController.enableInputs = session.previousCameraInputsEnabled;
      session.viewer.scene.requestRender();
      rebuildGridRef.current?.();
      onCameraChangeRef.current?.(getCameraView(session.viewer));
    }
    onSpiralStateChangeRef.current?.({ running: false, reason });
  }, []);

  const startSpiralAnimation = useCallback((input: CameraSpiralConfig) => {
    const viewer = viewerRef.current;
    const position = viewer?.camera.positionCartographic;
    if (!viewer || viewer.isDestroyed() || !position) return false;
    if (![input.heightAboveGroundMeters, input.speedMetersPerSecond, input.pitchMetersPerTurn].every(Number.isFinite)) return false;
    if (input.targetRadiusMeters !== undefined && !Number.isFinite(input.targetRadiusMeters)) return false;
    if (input.viewPitchRadians !== undefined && !Number.isFinite(input.viewPitchRadians)) return false;
    const terrainProvider = presentationRef.current.terrainEnabled ? terrainRef.current?.world ?? null : null;
    if (presentationRef.current.terrainEnabled && !terrainProvider) return false;
    const heightAboveGroundMeters = clamp(input.heightAboveGroundMeters, 10, 100_000);
    const config: CameraSpiralConfig = {
      heightAboveGroundMeters,
      speedMetersPerSecond: clamp(input.speedMetersPerSecond, 1, 5_000),
      // Preserve overlap even in a narrow/portrait viewport. Presets express
      // an upper bound; the live frustum may reduce the actual turn pitch.
      pitchMetersPerTurn: clamp(
        cameraSurveyPitchForViewport(
          heightAboveGroundMeters,
          input.pitchMetersPerTurn,
          horizontalFieldOfViewRadians(viewer),
        ),
        1,
        100_000,
      ),
      targetRadiusMeters: clamp(input.targetRadiusMeters ?? 25_000, 100, MAX_SPIRAL_RADIUS_METERS),
      ...(input.viewPitchRadians !== undefined
        ? { viewPitchRadians: clamp(input.viewPitchRadians, -Math.PI / 2 + 0.01, -0.05) }
        : {}),
      waitForTiles: input.waitForTiles === true,
    };
    stopSpiralAnimation("renderer_restarted");
    viewer.camera.cancelFlight();
    viewer.camera.lookAtTransform(Matrix4.IDENTITY);
    const controller = viewer.scene.screenSpaceCameraController;
    const buildingsTileset = buildingsRef.current;
    const session: SpiralSession = {
      viewer,
      origin: { longitude: position.longitude, latitude: position.latitude },
      initialHeading: Number.isFinite(viewer.camera.heading) ? viewer.camera.heading : 0,
      pitch: config.viewPitchRadians
        ?? clamp(Number.isFinite(viewer.camera.pitch) ? viewer.camera.pitch : -Math.PI / 4, -Math.PI / 2 + 0.01, -0.05),
      roll: 0,
      config,
      surfaceDistanceMeters: 0,
      lastTimestamp: null,
      pendingElapsedSeconds: 0,
      tilesWaitStartedAt: null,
      requestId: null,
      previousCameraInputsEnabled: controller.enableInputs,
      terrainProvider,
      terrainSamples: [],
      terrainSamplePending: false,
      terrainSampleExhausted: false,
      terrainSampleFailures: 0,
      terrainRequestGeneration: 0,
      terrainRetryAt: 0,
      terrainSampleSpacingMeters: clamp(config.speedMetersPerSecond / 30, 10, 250),
      targetSurfaceDistanceMeters: cameraSurveySpiralDistance(
        config.targetRadiusMeters ?? 25_000,
        config.pitchMetersPerTurn,
      ),
      buildingsTileset,
      previousBuildingsCullRequestsWhileMoving: buildingsTileset?.cullRequestsWhileMoving ?? null,
      previousBuildingsFoveatedScreenSpaceError: buildingsTileset?.foveatedScreenSpaceError ?? null,
      previousBuildingsFoveatedTimeDelay: buildingsTileset?.foveatedTimeDelay ?? null,
    };
    // A survey must request the whole visible OSM Buildings footprint while
    // the camera moves. Cesium's interactive defaults deliberately defer and
    // cull edge requests; restore them when the survey stops.
    if (buildingsTileset) {
      buildingsTileset.cullRequestsWhileMoving = false;
      buildingsTileset.foveatedScreenSpaceError = false;
      buildingsTileset.foveatedTimeDelay = 0;
    }
    controller.enableInputs = false;
    spiralSessionRef.current = session;

    const groundHeightAtDistance = (distanceMeters: number) => {
      if (!session.terrainProvider) return 0;
      // Only the most-detailed sampled route is stable enough for AGL.
      // globe.getHeight() follows the currently rendered LOD and can jump as
      // tiles refine, so it must not drive the camera altitude.
      return interpolateTerrainRouteHeight(session.terrainSamples, distanceMeters);
    };

    const applyFrame = (frame: ReturnType<typeof spiralSurfaceFrame>, rawGround: number) => {
      if (spiralSessionRef.current !== session || viewer.isDestroyed()) return;
      const scene = viewer.scene as unknown as {
        verticalExaggeration?: number;
        verticalExaggerationRelativeHeight?: number;
      };
      const terrainActive = Boolean(session.terrainProvider);
      const exaggeration = terrainActive && Number.isFinite(scene.verticalExaggeration) ? Number(scene.verticalExaggeration) : 1;
      const relativeHeight = terrainActive && Number.isFinite(scene.verticalExaggerationRelativeHeight)
        ? Number(scene.verticalExaggerationRelativeHeight)
        : 0;
      const renderedGround = relativeHeight + (rawGround - relativeHeight) * exaggeration;
      viewer.camera.setView({
        destination: Cartesian3.fromRadians(
          frame.longitude,
          frame.latitude,
          renderedGround + session.config.heightAboveGroundMeters,
        ),
        orientation: {
          heading: frame.tangentHeading,
          pitch: session.pitch,
          roll: session.roll,
        },
      });
      viewer.scene.requestRender();
    };

    const requestTerrainSamples = () => {
      if (!session.terrainProvider || session.terrainSamplePending || spiralSessionRef.current !== session) return;
      if (session.terrainSampleExhausted) return;
      if (Date.now() < session.terrainRetryAt) return;
      const lastSample = session.terrainSamples.at(-1);
      // Prefetch before the camera reaches the buffer tail. At normal and
      // high speeds this leaves roughly three seconds for Gateway/VPN latency.
      if (lastSample && lastSample.distanceMeters - session.surfaceDistanceMeters > session.terrainSampleSpacingMeters * 90) return;
      const startDistance = lastSample ? lastSample.distanceMeters + session.terrainSampleSpacingMeters : 0;
      const distances: number[] = [];
      const positions: Cartographic[] = [];
      let reachedExtent = false;
      let reachedTargetDistance = false;
      const plannedDistances = cameraSurveySampleDistances(
        startDistance,
        session.terrainSampleSpacingMeters,
        session.targetSurfaceDistanceMeters,
        120,
      );
      for (const distanceMeters of plannedDistances) {
        try {
          const frame = spiralSurfaceFrame(
            session.origin,
            session.initialHeading,
            distanceMeters,
            session.config.pitchMetersPerTurn,
          );
          distances.push(distanceMeters);
          positions.push(new Cartographic(frame.longitude, frame.latitude, 0));
          if (distanceMeters >= session.targetSurfaceDistanceMeters) {
            reachedTargetDistance = true;
            break;
          }
        } catch (error) {
          if (error instanceof Error && error.message === "spiral_extent_limit") {
            reachedExtent = true;
          } else {
            stopSpiralAnimation("spiral_runtime_error");
          }
          break;
        }
      }
      if (positions.length === 0 || spiralSessionRef.current !== session) {
        if (reachedExtent && spiralSessionRef.current === session) stopSpiralAnimation("spiral_extent_limit");
        return;
      }
      session.terrainSamplePending = true;
      const requestGeneration = ++session.terrainRequestGeneration;
      let timeoutId = 0;
      const timeout = new Promise<never>((_resolve, reject) => {
        timeoutId = window.setTimeout(() => reject(new Error("terrain_sample_timeout")), TERRAIN_SAMPLE_TIMEOUT_MS);
      });
      void Promise.race([
        sampleTerrainMostDetailed(session.terrainProvider, positions),
        timeout,
      ]).then((sampledPositions) => {
        window.clearTimeout(timeoutId);
        if (
          spiralSessionRef.current !== session
          || session.terrainRequestGeneration !== requestGeneration
          || viewer.isDestroyed()
        ) return;
        const nextSamples = sampledPositions.map((sampledPosition, index) => {
          if (!Number.isFinite(sampledPosition.height)) throw new Error("terrain_height_unavailable");
          return {
            distanceMeters: distances[index],
            heightMeters: Number(sampledPosition.height),
          };
        });
        session.terrainSamples = [...session.terrainSamples, ...nextSamples]
          .filter((sample) => sample.distanceMeters >= session.surfaceDistanceMeters - session.terrainSampleSpacingMeters * 2);
        if (reachedTargetDistance || reachedExtent) session.terrainSampleExhausted = true;
        session.terrainSamplePending = false;
        session.terrainSampleFailures = 0;
        session.terrainRetryAt = 0;
        viewer.scene.requestRender();
      }).catch(() => {
        window.clearTimeout(timeoutId);
        if (spiralSessionRef.current !== session || session.terrainRequestGeneration !== requestGeneration) return;
        session.terrainSamplePending = false;
        session.terrainSampleFailures += 1;
        if (session.terrainSampleFailures >= 4) {
          stopSpiralAnimation("terrain_sampling_error");
          return;
        }
        const retryDelay = [500, 1_500, 4_000][session.terrainSampleFailures - 1] ?? 4_000;
        session.terrainRetryAt = Date.now() + retryDelay;
        viewer.scene.requestRender();
      });
    };

    const tick = (timestamp: number) => {
      if (spiralSessionRef.current !== session || viewer.isDestroyed()) return;
      if (document.hidden) {
        session.lastTimestamp = null;
        session.pendingElapsedSeconds = 0;
        session.tilesWaitStartedAt = null;
      } else if (session.lastTimestamp === null) {
        session.lastTimestamp = timestamp;
      } else {
        const currentTilesReady = !session.config.waitForTiles
          || (viewer.scene.globe.tilesLoaded && (!session.buildingsTileset || session.buildingsTileset.tilesLoaded));
        if (!currentTilesReady) {
          session.tilesWaitStartedAt ??= timestamp;
          if (timestamp - session.tilesWaitStartedAt >= SPIRAL_TILE_WAIT_TIMEOUT_MS) {
            stopSpiralAnimation("tile_loading_timeout");
            return;
          }
          // Do not accumulate route time while the current Terrain, Imagery or
          // OSM Buildings footprint is still resolving through TileCache.
          session.lastTimestamp = timestamp;
          session.pendingElapsedSeconds = 0;
          requestTerrainSamples();
          viewer.scene.requestRender();
          session.requestId = window.requestAnimationFrame(tick);
          return;
        }
        session.tilesWaitStartedAt = null;
        // Never catch up a stalled UI thread with one large camera jump. A
        // cache survey values continuous visible coverage over wall-clock
        // speed; limiting one rendered advance to 1/30 s keeps intermediate
        // viewports observable by Cesium before the next tile-settle gate.
        const elapsedSeconds = Math.min(1 / 30, Math.max(0, (timestamp - session.lastTimestamp) / 1000));
        session.lastTimestamp = timestamp;
        session.pendingElapsedSeconds += elapsedSeconds;
        try {
          let nextFrame: ReturnType<typeof spiralSurfaceFrame> | null = null;
          let nextGround: number | null = null;
          let substeps = 0;
          while (session.pendingElapsedSeconds > 0.000_001 && substeps < MAX_SPIRAL_SUBSTEPS_PER_FRAME) {
            const stepSeconds = Math.min(session.pendingElapsedSeconds, 1 / 30);
            const candidateDistance = session.surfaceDistanceMeters + session.config.speedMetersPerSecond * stepSeconds;
            const candidateFrame = spiralSurfaceFrame(
              session.origin,
              session.initialHeading,
              candidateDistance,
              session.config.pitchMetersPerTurn,
            );
            if (candidateFrame.radiusMeters >= (session.config.targetRadiusMeters ?? MAX_SPIRAL_RADIUS_METERS)) {
              stopSpiralAnimation("target_radius_reached");
              return;
            }
            const candidateGround = groundHeightAtDistance(candidateDistance);
            if (candidateGround === null) {
              const lastTerrainSample = session.terrainSamples.at(-1);
              if (session.terrainSampleExhausted && (!lastTerrainSample || candidateDistance > lastTerrainSample.distanceMeters)) {
                throw new Error("spiral_extent_limit");
              }
              // Missing terrain is a loading state, never an ellipsoid-height
              // fallback. Pause the route clock until a sampled buffer exists.
              session.pendingElapsedSeconds = 0;
              session.lastTimestamp = timestamp;
              requestTerrainSamples();
              break;
            }
            session.surfaceDistanceMeters = candidateDistance;
            session.pendingElapsedSeconds -= stepSeconds;
            nextFrame = candidateFrame;
            nextGround = candidateGround;
            substeps += 1;
          }
          if (nextFrame && nextGround !== null) applyFrame(nextFrame, nextGround);
          requestTerrainSamples();
        } catch (error) {
          stopSpiralAnimation(error instanceof Error && error.message === "spiral_extent_limit"
            ? "spiral_extent_limit"
            : "spiral_runtime_error");
          return;
        }
      }
      if (spiralSessionRef.current === session) session.requestId = window.requestAnimationFrame(tick);
    };

    try {
      requestTerrainSamples();
      const initialFrame = spiralSurfaceFrame(session.origin, session.initialHeading, 0, session.config.pitchMetersPerTurn);
      const initialGround = groundHeightAtDistance(0);
      if (initialGround !== null) applyFrame(initialFrame, initialGround);
    } catch {
      stopSpiralAnimation("spiral_runtime_error");
      return false;
    }
    session.requestId = window.requestAnimationFrame(tick);
    rebuildGridRef.current?.();
    onSpiralStateChangeRef.current?.({ running: true });
    return true;
  }, [stopSpiralAnimation]);

  const runtimeEntities = useCallback((entityIds?: string[]) => {
    const allowed = entityIds ? new Set(entityIds) : null;
    return [...runtimeDataSourcesRef.current.values()].flatMap((dataSource) => (
      [...dataSource.entities.values].filter((entity) => !allowed || allowed.has(String(entity.id)))
    ));
  }, []);

  const fitRuntimeEntities = useCallback((entityIds?: string[]) => {
    const viewer = viewerRef.current;
    if (!viewer || viewer.isDestroyed()) return false;
    const entities = runtimeEntities(entityIds);
    if (!entities.length) return false;
    void viewer.flyTo(entities, { duration: 0.55 });
    return true;
  }, [runtimeEntities]);

  const focusRuntimeEntity = useCallback((entityId: string) => {
    const viewer = viewerRef.current;
    if (!viewer || viewer.isDestroyed()) return false;
    const entity = runtimeEntities([entityId])[0];
    const position = entity?.position?.getValue(viewer.clock.currentTime);
    if (!position) return false;

    // Preserve the observer's current composition exactly as the proven
    // legacy MMAP/AIS interaction does: move the camera/viewport frame to the
    // selected point without replacing heading, pitch, roll or ground offset.
    const camera = viewer.camera;
    const canvas = viewer.scene.canvas;
    const viewportCenter = camera.pickEllipsoid(
      new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2),
      viewer.scene.globe.ellipsoid,
    );
    const cartographic = Cartographic.fromCartesian(position);
    const groundTarget = Cartesian3.fromRadians(
      cartographic.longitude,
      cartographic.latitude,
      0,
      viewer.scene.globe.ellipsoid,
    );
    const destination = viewportCenter
      ? Cartesian3.add(
        groundTarget,
        Cartesian3.subtract(camera.position, viewportCenter, new Cartesian3()),
        new Cartesian3(),
      )
      : Cartesian3.fromRadians(
        cartographic.longitude,
        cartographic.latitude,
        camera.positionCartographic.height,
        viewer.scene.globe.ellipsoid,
      );

    camera.flyTo({
      destination,
      orientation: {
        heading: camera.heading,
        pitch: camera.pitch,
        roll: camera.roll,
      },
      duration: 2.1,
    });
    return true;
  }, [runtimeEntities]);

  useImperativeHandle(ref, () => ({
    startSpiralAnimation,
    stopSpiralAnimation,
    getCameraView: () => {
      const viewer = viewerRef.current;
      return viewer && !viewer.isDestroyed() ? getCameraView(viewer) : null;
    },
    fitRuntimeEntities,
    focusRuntimeEntity,
  }), [fitRuntimeEntities, focusRuntimeEntity, startSpiralAnimation, stopSpiralAnimation]);

  useEffect(() => {
    const stopForPageLeave = () => stopSpiralAnimation("stopped");
    const resetHiddenClock = () => {
      const session = spiralSessionRef.current;
      if (!session || !document.hidden) return;
      session.lastTimestamp = null;
      session.pendingElapsedSeconds = 0;
      session.tilesWaitStartedAt = null;
    };
    window.addEventListener("pagehide", stopForPageLeave);
    document.addEventListener("visibilitychange", resetHiddenClock);
    return () => {
      window.removeEventListener("pagehide", stopForPageLeave);
      document.removeEventListener("visibilitychange", resetHiddenClock);
    };
  }, [stopSpiralAnimation]);

  useEffect(() => {
    presentationRef.current = presentation;
    const activeSpiral = spiralSessionRef.current;
    if (activeSpiral && Boolean(activeSpiral.terrainProvider) !== presentation.terrainEnabled) {
      stopSpiralAnimation("renderer_restarted");
    }
    if (viewerRef.current && terrainRef.current) applyPresentation(viewerRef.current, imageryLayerRef.current, buildingsRef.current, terrainRef.current, presentation);
    rebuildGridRef.current?.();
    const viewer = viewerRef.current;
    const terrain = terrainRef.current;
    onReadyChangeRef.current?.(Boolean(
      viewer
      && !viewer.isDestroyed()
      && terrain
      && (!presentation.terrainEnabled || terrain.world),
    ));
  }, [presentation, stopSpiralAnimation]);

  useEffect(() => {
    runtimeBindingsRef.current = runtimeBindings;
    presentationProfilesRef.current = presentationProfiles;
    presentationFiltersRef.current = presentationFilters;
    if (viewerRef.current && !viewerRef.current.isDestroyed()) {
      syncRuntimeDataSources(
        viewerRef.current,
        runtimeDataSourcesRef.current,
        hGeoZoneLayersRef.current,
        runtimeBindings,
        presentationProfiles,
        presentationFilters,
        faultedHGeoZoneGeometryKeysRef.current,
      );
    }
  }, [presentationFilters, presentationProfiles, runtimeBindings]);

  useEffect(() => {
    let viewer: Viewer | undefined;
    let handler: ScreenSpaceEventHandler | undefined;
    let resizeObserver: ResizeObserver | undefined;
    let removeGridCameraListener: (() => void) | undefined;
    let removeRefreshRenderListener: (() => void) | undefined;
    let removeRenderErrorListener: (() => void) | undefined;
    const removeProviderFailureListeners: Array<() => void> = [];
    let cancelled = false;

    const start = async () => {
      try {
        const response = await fetch("/api/map/runtime-config");
        const config = response.ok ? await response.json() as RuntimeConfig : null;
        if (config?.gatewayHealthUrl) {
          void fetch(config.gatewayHealthUrl)
            .then((healthResponse) => healthResponse.ok ? healthResponse.json() as Promise<MapGatewayHealth> : null)
            .then((health) => { if (!cancelled) onGatewayHealth?.(health); })
            .catch(() => { if (!cancelled) onGatewayHealth?.(null); });
        }
        if (!containerRef.current || cancelled) return;

        viewer = new Viewer(containerRef.current, {
          animation: false,
          baseLayer: false,
          baseLayerPicker: false,
          fullscreenButton: false,
          geocoder: false,
          homeButton: false,
          infoBox: false,
          navigationHelpButton: false,
          sceneModePicker: false,
          selectionIndicator: false,
          timeline: false,
          requestRenderMode: true,
          maximumRenderTimeChange: Number.POSITIVE_INFINITY,
          // The default Cesium panel hides the useful UI state and displays
          // `[object Object]` for several non-Error render faults. We report a
          // safe diagnostic through the Map panel and attempt one bounded
          // recovery instead of leaving a modal over a stopped renderer.
          showRenderLoopErrors: false,
          // Sandbox-only: Cesium writes credits into a dedicated, visually
          // suppressed container. Runtime attribution metadata is preserved;
          // external and commercial surfaces must provide visible credits.
          creditContainer: creditContainerRef.current ?? undefined,
        });
        const resourceProxy = config?.resourceProxyBase ? new DefaultProxy(config.resourceProxyBase) : undefined;
        const buildResource = (url: string) => {
          // Put cache intent into the upstream URL itself. Cesium providers
          // derive child resources (metadata, imagery tiles, terrain and 3D
          // tiles) from this URL; DefaultProxy then forwards the exact intent
          // to Gateway for every derived request.
          const routedUrl = new URL(url);
          // Provider bytes are cached once by Platform Map Gateway. Version
          // the browser-facing route so an old malformed HTTP response cannot
          // shadow the shared TileCache after a transport fix.
          routedUrl.searchParams.set("nodedc_client_revision", "2");
          if (!presentationRef.current.cacheEnabled) routedUrl.searchParams.set("nodedc_cache_mode", "passthrough");
          if (presentationRef.current.cacheRefresh || !presentationRef.current.cacheNoOverwrite) routedUrl.searchParams.set("nodedc_cache_refresh", "1");
          return new Resource({
            url: routedUrl.toString(),
            proxy: resourceProxy,
          });
        };
        const loadEndpoint = async (assetId: string) => {
          if (!config?.gatewayReady) throw new Error("map_gateway_not_ready");
          const endpointResponse = await fetch(`${config.assetEndpointBase}/${assetId}/endpoint`);
          if (!endpointResponse.ok) throw new Error(`Map Gateway asset ${assetId}: ${endpointResponse.status}`);
          return endpointResponse.json() as Promise<IonAssetEndpoint>;
        };
        const gridDataSource = new CustomDataSource("nodedc-map-grid");
        viewer.dataSources.add(gridDataSource);
        const terrain = { world: null as CesiumTerrainProvider | null, ellipsoid: new EllipsoidTerrainProvider() };
        viewer.terrainProvider = terrain.ellipsoid;
        viewer.scene.globe.depthTestAgainstTerrain = true;
        viewerRef.current = viewer;
        terrainRef.current = terrain;
        const rebuildGrid = () => rebuildElevatedGrid(viewer!, gridDataSource, presentationRef.current);
        rebuildGridRef.current = rebuildGrid;
        removeGridCameraListener = viewer.camera.moveEnd.addEventListener(() => {
          rebuildGrid();
          syncHGeoZoneVisibility(viewer!, hGeoZoneLayersRef.current);
          onCameraChangeRef.current?.(getCameraView(viewer!));
        });

        const providerStatus: MapProviderStatus = {
          imagery: config?.gatewayReady ? "loading" : "not-configured",
          terrain: config?.gatewayReady ? "loading" : "not-configured",
          buildings: config?.gatewayReady ? "loading" : "not-configured",
          errors: config?.gatewayReady ? {} : {
            imagery: "Platform Map Gateway недоступен",
            terrain: "Platform Map Gateway недоступен",
            buildings: "Platform Map Gateway недоступен",
          },
        };
        const providerTileFailureLatched = { imagery: false, terrain: false, buildings: false };
        const reportProvider = (provider: "imagery" | "terrain" | "buildings", state: MapProviderState, error?: unknown) => {
          if (state === "ready" && providerTileFailureLatched[provider]) return;
          providerStatus[provider] = state;
          if (state === "error") {
            providerStatus.errors[provider] = error instanceof Error && error.message ? error.message : "provider_unavailable";
          } else {
            delete providerStatus.errors[provider];
          }
          if (!cancelled) onProviderStatus?.({ ...providerStatus, errors: { ...providerStatus.errors } });
        };
        const latchProviderTileFailure = (provider: "imagery" | "terrain" | "buildings") => {
          if (cancelled) return;
          // Keep the provider failed until the renderer is recreated. Merely
          // emptying Cesium's request queues cannot turn a viewport with a
          // failed tile back into a successful cache survey.
          providerTileFailureLatched[provider] = true;
          stopSpiralAnimation("tile_loading_error");
          reportProvider(provider, "error", new Error(`${provider}_tile_failed`));
        };
        onProviderStatus?.({ ...providerStatus, errors: { ...providerStatus.errors } });
        let renderRecoveryScheduled = false;
        removeRenderErrorListener = viewer.scene.renderError.addEventListener((_scene, error) => {
          stopSpiralAnimation("render_error");
          const raw = error instanceof Error ? error.message : "cesium_render_error";
          const safe = raw.replace(/[^A-Za-z0-9_.:-]/g, "_").slice(0, 120) || "cesium_render_error";
          const projectionQuarantined = quarantineHGeoZoneLayers(
            viewer!,
            hGeoZoneLayersRef.current,
            faultedHGeoZoneGeometryKeysRef.current,
          );
          if (projectionQuarantined) {
            // A domain projection is not a live-provider or TileCache failure.
            // Quarantine the exact binding/cursor geometry and resume the base
            // scene; a new cursor or renderer restart may try a corrected set.
            providerStatus.errors.projection = `hgeozone_quarantined:${safe}`;
            if (!cancelled) onProviderStatus?.({ ...providerStatus, errors: { ...providerStatus.errors } });
          } else {
            reportProvider("imagery", "error", safe);
            reportProvider("terrain", "error", safe);
            reportProvider("buildings", "error", safe);
          }
          if (renderRecoveryScheduled) return;
          renderRecoveryScheduled = true;
          window.setTimeout(() => {
            if (cancelled || !viewer || viewer.isDestroyed()) return;
            // Cesium's default listener stops its render loop after a scene
            // error. One retry handles a transient resource/resize race but
            // remains bounded if the browser or GPU fault is persistent.
            viewer.useDefaultRenderLoop = true;
            viewer.scene.requestRender();
          }, 0);
        });
        // Projection primitives are created only after the scene-level fault
        // boundary is active. Their asynchronous Cesium workers must never be
        // able to fail before the offending domain layer can be quarantined.
        syncRuntimeDataSources(
          viewer,
          runtimeDataSourcesRef.current,
          hGeoZoneLayersRef.current,
          runtimeBindingsRef.current,
          presentationProfilesRef.current,
          presentationFiltersRef.current,
          faultedHGeoZoneGeometryKeysRef.current,
        );

        if (config?.gatewayReady) {
          // Do not serialize provider startup. A failure in Bing imagery is
          // recoverable and must not stop terrain, buildings, or the scene.
          void loadEndpoint("2").then(async (endpoint) => {
            if (endpoint.externalType !== "BING" || !endpoint.options?.url || endpoint.credentialMode !== "gateway") throw new Error("cesium_live_imagery_endpoint_invalid");
            const imageryProvider = await BingMapsImageryProvider.fromUrl(buildResource(endpoint.options.url), {
              // Cesium requires a key-shaped value to form its Bing URL. This
              // public marker is stripped by Map Gateway before upstream use.
              key: "nodedc-gateway",
              mapStyle: (endpoint.options.mapStyle || "Aerial") as BingMapsStyle,
              tileProtocol: "https",
            });
            if (cancelled || !viewer || viewer.isDestroyed()) return;
            removeProviderFailureListeners.push(imageryProvider.errorEvent.addEventListener(() => latchProviderTileFailure("imagery")));
            for (const attribution of endpoint.attributions) if (attribution.html) viewer.creditDisplay.addStaticCredit(new Credit(attribution.html, attribution.collapsible));
            imageryLayerRef.current = viewer.imageryLayers.addImageryProvider(imageryProvider);
            applyPresentation(viewer, imageryLayerRef.current, buildingsRef.current, terrain, presentationRef.current);
            reportProvider("imagery", "ready");
          }).catch((error) => reportProvider("imagery", "error", error));
          void loadEndpoint("1").then(async (terrainEndpoint) => {
            if (!terrainEndpoint.url || terrainEndpoint.credentialMode !== "gateway") throw new Error("terrain_endpoint_invalid");
            const terrainResource = buildResource(terrainEndpoint.url);
            const world = await CesiumTerrainProvider.fromUrl(terrainResource, { requestVertexNormals: true, requestWaterMask: true });
            if (cancelled || !viewer || viewer.isDestroyed()) return;
            removeProviderFailureListeners.push(world.errorEvent.addEventListener(() => latchProviderTileFailure("terrain")));
            terrain.world = world;
            for (const attribution of terrainEndpoint.attributions) if (attribution.html) viewer.creditDisplay.addStaticCredit(new Credit(attribution.html, attribution.collapsible));
            applyPresentation(viewer, imageryLayerRef.current, buildingsRef.current, terrain, presentationRef.current);
            onReadyChangeRef.current?.(!presentationRef.current.terrainEnabled || Boolean(terrain.world));
            reportProvider("terrain", "ready");
          }).catch((error) => reportProvider("terrain", "error", error));
          void loadEndpoint("96188").then(async (buildingsEndpoint) => {
            if (buildingsEndpoint.type !== "3DTILES" || !buildingsEndpoint.url || buildingsEndpoint.credentialMode !== "gateway") throw new Error("buildings_endpoint_invalid");
            const buildingsResource = buildResource(buildingsEndpoint.url);
            const buildings = await Cesium3DTileset.fromUrl(buildingsResource);
            if (cancelled || !viewer || viewer.isDestroyed()) return;
            removeProviderFailureListeners.push(buildings.tileFailed.addEventListener(() => latchProviderTileFailure("buildings")));
            viewer.scene.primitives.add(buildings);
            buildingsRef.current = buildings;
            for (const attribution of buildingsEndpoint.attributions) if (attribution.html) viewer.creditDisplay.addStaticCredit(new Credit(attribution.html, attribution.collapsible));
            applyPresentation(viewer, imageryLayerRef.current, buildings, terrain, presentationRef.current);
            reportProvider("buildings", "ready");
          }).catch((error) => reportProvider("buildings", "error", error));
          if (config.gaussianSplatsReady && config.gaussianAssetId) {
            void loadEndpoint(config.gaussianAssetId).then(async (gaussianEndpoint) => {
              if (!gaussianEndpoint.url || gaussianEndpoint.credentialMode !== "gateway") throw new Error("gaussian_endpoint_invalid");
              const gaussianResource = buildResource(gaussianEndpoint.url);
              const gaussian = await Cesium3DTileset.fromUrl(gaussianResource);
              if (cancelled || !viewer || viewer.isDestroyed()) return;
              viewer.scene.primitives.add(gaussian);
            }).catch(() => undefined);
          }
        }

        applyPresentation(viewer, imageryLayerRef.current, buildingsRef.current, terrain, presentationRef.current);

        if (initialCamera) {
          viewer.camera.setView({
            destination: Cartesian3.fromDegrees(initialCamera.longitude, initialCamera.latitude, initialCamera.height),
            orientation: {
              heading: initialCamera.heading,
              pitch: initialCamera.pitch,
              roll: initialCamera.roll,
            },
          });
        } else {
          const center = Cartesian3.fromDegrees(37.618423, 55.751244, 0);
          viewer.camera.lookAt(center, new HeadingPitchRange(
            0,
            -0.9,
            40_000,
          ));
          viewer.camera.lookAtTransform(Matrix4.IDENTITY);
        }
        rebuildGrid();
        onCameraChangeRef.current?.(getCameraView(viewer));
        onReadyChangeRef.current?.(!presentationRef.current.terrainEnabled || Boolean(terrain.world));

        if (presentationRef.current.cacheRefresh && onCacheRefreshConsumedRef.current) {
          // All root provider resources for the current view were created with
          // `nodedc_cache_refresh=1`. Reset after the first render so later
          // navigation goes back to the selected steady-state policy.
          removeRefreshRenderListener = viewer.scene.postRender.addEventListener(() => {
            removeRefreshRenderListener?.();
            removeRefreshRenderListener = undefined;
            if (!cancelled) onCacheRefreshConsumedRef.current?.();
          });
          viewer.scene.requestRender();
        }

        handler = new ScreenSpaceEventHandler(viewer.scene.canvas);
        handler.setInputAction((movement: { position: Cartesian2 }) => {
          const picked = viewer?.scene.pick(movement.position);
          const pickedId = picked?.id;
          if (pickedId instanceof Entity && pickedId.id) {
            onSelectRef.current?.(pickedId.id);
            return;
          }
          if (
            pickedId
            && typeof pickedId === "object"
            && (pickedId as Partial<HGeoZonePickId>).kind === "nodedc-hgeozone"
            && typeof (pickedId as Partial<HGeoZonePickId>).entityId === "string"
          ) onSelectRef.current?.((pickedId as HGeoZonePickId).entityId);
        }, ScreenSpaceEventType.LEFT_CLICK);
        resizeObserver = new ResizeObserver(() => {
          if (!viewer || viewer.isDestroyed()) return;
          viewer.resize();
          viewer.scene.requestRender();
        });
        resizeObserver.observe(containerRef.current);
      } catch (error) {
        // Failure before viewer creation is distinct from a provider failure;
        // surface it through the Inspector without exposing any credentials.
        if (!cancelled) onProviderStatus?.({
          imagery: "error",
          terrain: "error",
          buildings: "error",
          errors: {
            imagery: error instanceof Error ? error.message : "map_start_failed",
            terrain: error instanceof Error ? error.message : "map_start_failed",
            buildings: error instanceof Error ? error.message : "map_start_failed",
          },
        });
      }
    };

    void start();
    return () => {
      cancelled = true;
      stopSpiralAnimation("renderer_restarted");
      onReadyChangeRef.current?.(false);
      resizeObserver?.disconnect();
      removeGridCameraListener?.();
      removeRefreshRenderListener?.();
      removeRenderErrorListener?.();
      for (const removeListener of removeProviderFailureListeners) removeListener();
      handler?.destroy();
      if (viewer && !viewer.isDestroyed()) viewer.destroy();
      viewerRef.current = null;
      imageryLayerRef.current = null;
      buildingsRef.current = null;
      terrainRef.current = null;
      rebuildGridRef.current = null;
      runtimeDataSourcesRef.current.clear();
      hGeoZoneLayersRef.current.clear();
      faultedHGeoZoneGeometryKeysRef.current.clear();
    };
  }, [onGatewayHealth, onProviderStatus, stopSpiralAnimation]);

  return (
    <div className="catalog-cesium-map">
      <div ref={containerRef} className="catalog-cesium-map__canvas" />
      <div ref={creditContainerRef} className="catalog-cesium-map__credits" aria-hidden="true" />
    </div>
  );
});
