/**
 * Geometric-error bands observed in the current Cesium OSM Buildings 96188
 * hierarchy. They are ordered from the global root towards maximum detail.
 * The provider may publish a newer hierarchy later, so these values are a
 * measured runtime profile rather than a permanent Cesium API contract.
 */
export const OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS = [
  77_067.33978,
  38_533.66989,
  19_266.834945,
  9_633.417472,
  4_816.708736,
  2_408.354368,
  1_204.177184,
  602.088592,
  301.044296,
  150.522148,
  75.261074,
  37.630537,
  18.815269,
  9.407634,
  4.703817,
  2.351909,
] as const;

export const CAMERA_SURVEY_CROSS_TRACK_FRACTION = 0.69;

export type CameraSurveyPreset = {
  id: string;
  profile: number;
  label: string;
  heightAboveGroundMeters: number;
  pitchMetersPerTurn: number;
  speedMetersPerSecond: number;
  targetRadiusMeters: number;
};

/**
 * Ten operational scale profiles cover a regional survey from street to
 * macro scale. They deliberately do not claim a one-to-one provider LOD:
 * Cesium selects imagery, terrain and 3D Tiles through different SSE rules,
 * viewport geometry, pixel ratio, availability and tile bounding volumes.
 *
 * Height and turn pitch double together. With a 60 degree horizontal field
 * of view and a near-nadir camera, pitch = 0.8 * height keeps about 31%
 * cross-track overlap. Runtime additionally clamps pitch to the live viewport,
 * so a narrow inspector cannot open gaps between turns.
 *
 * Speed is a ceiling, not a promise: the renderer pauses whenever current
 * Imagery, Terrain or OSM Buildings requests are unsettled. A 25 km target
 * radius makes every profile a finite city-scale pass instead of silently
 * continuing to the 250 km geodesic safety boundary.
 */
export const CAMERA_SURVEY_PRESETS = [
  { id: "osm-detail-1", profile: 1, label: "Профиль 1 · 80 м", heightAboveGroundMeters: 80, pitchMetersPerTurn: 64, speedMetersPerSecond: 256, targetRadiusMeters: 25_000 },
  { id: "osm-detail-2", profile: 2, label: "Профиль 2 · 160 м", heightAboveGroundMeters: 160, pitchMetersPerTurn: 128, speedMetersPerSecond: 512, targetRadiusMeters: 25_000 },
  { id: "osm-detail-3", profile: 3, label: "Профиль 3 · 320 м", heightAboveGroundMeters: 320, pitchMetersPerTurn: 256, speedMetersPerSecond: 1_024, targetRadiusMeters: 25_000 },
  { id: "osm-detail-4", profile: 4, label: "Профиль 4 · 640 м", heightAboveGroundMeters: 640, pitchMetersPerTurn: 512, speedMetersPerSecond: 2_048, targetRadiusMeters: 25_000 },
  { id: "osm-detail-5", profile: 5, label: "Профиль 5 · 1,28 км", heightAboveGroundMeters: 1_280, pitchMetersPerTurn: 1_024, speedMetersPerSecond: 4_096, targetRadiusMeters: 25_000 },
  { id: "osm-detail-6", profile: 6, label: "Профиль 6 · 2,56 км", heightAboveGroundMeters: 2_560, pitchMetersPerTurn: 2_048, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
  { id: "osm-detail-7", profile: 7, label: "Профиль 7 · 5,12 км", heightAboveGroundMeters: 5_120, pitchMetersPerTurn: 4_096, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
  { id: "osm-detail-8", profile: 8, label: "Профиль 8 · 10,24 км", heightAboveGroundMeters: 10_240, pitchMetersPerTurn: 8_192, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
  { id: "osm-detail-9", profile: 9, label: "Профиль 9 · 20,48 км", heightAboveGroundMeters: 20_480, pitchMetersPerTurn: 16_384, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
  { id: "osm-detail-10", profile: 10, label: "Профиль 10 · 40,96 км", heightAboveGroundMeters: 40_960, pitchMetersPerTurn: 32_768, speedMetersPerSecond: 5_000, targetRadiusMeters: 25_000 },
] as const satisfies readonly CameraSurveyPreset[];

export type CameraSurveyPresetId = (typeof CAMERA_SURVEY_PRESETS)[number]["id"];
export type CameraSurveySelection = CameraSurveyPresetId | "custom";

export const DEFAULT_CAMERA_SURVEY_PRESET = CAMERA_SURVEY_PRESETS[0];
export const OSM_BUILDINGS_OBSERVED_BAND_COUNT = OSM_BUILDINGS_GEOMETRIC_ERROR_LEVELS.length;

export function findCameraSurveyPreset(id: CameraSurveyPresetId) {
  return CAMERA_SURVEY_PRESETS.find((preset) => preset.id === id);
}

export function cameraSurveyCrossTrackWidth(heightAboveGroundMeters: number, horizontalFieldOfViewDegrees = 60) {
  return 2 * heightAboveGroundMeters * Math.tan(horizontalFieldOfViewDegrees * Math.PI / 360);
}

export function cameraSurveyTurnOverlap(preset: CameraSurveyPreset, horizontalFieldOfViewDegrees = 60) {
  const width = cameraSurveyCrossTrackWidth(preset.heightAboveGroundMeters, horizontalFieldOfViewDegrees);
  return 1 - preset.pitchMetersPerTurn / width;
}

export function cameraSurveyPitchForViewport(
  heightAboveGroundMeters: number,
  requestedPitchMeters: number,
  horizontalFieldOfViewRadians: number,
) {
  const crossTrackWidthMeters = 2 * heightAboveGroundMeters * Math.tan(horizontalFieldOfViewRadians / 2);
  return Math.min(requestedPitchMeters, crossTrackWidthMeters * CAMERA_SURVEY_CROSS_TRACK_FRACTION);
}

export function cameraSurveySpiralDistance(targetRadiusMeters: number, pitchMetersPerTurn: number) {
  const angle = (Math.PI * 2 * targetRadiusMeters) / pitchMetersPerTurn;
  const radialGrowth = pitchMetersPerTurn / (Math.PI * 2);
  return (radialGrowth / 2) * (
    angle * Math.sqrt(1 + angle * angle)
    + Math.asinh(angle)
  );
}

export function cameraSurveySampleDistances(
  startDistanceMeters: number,
  spacingMeters: number,
  targetDistanceMeters: number,
  maximumSamples: number,
) {
  if (startDistanceMeters > targetDistanceMeters || maximumSamples <= 0) return [];
  const distances: number[] = [];
  for (let index = 0; index < maximumSamples; index += 1) {
    const distanceMeters = Math.min(startDistanceMeters + index * spacingMeters, targetDistanceMeters);
    distances.push(distanceMeters);
    if (distanceMeters >= targetDistanceMeters) break;
  }
  return distances;
}

export function cameraSurveyTravelSeconds(preset: CameraSurveyPreset) {
  return cameraSurveySpiralDistance(preset.targetRadiusMeters, preset.pitchMetersPerTurn)
    / preset.speedMetersPerSecond;
}
