/**
 * The contract between the engine and everything else.
 *
 * The engine renders a `City` and a list of `Marker`s. It does not know what a
 * marker *is* — not that markers are companies, not that a red one means a
 * rejection. That mapping lives in an adapter, outside this package, which is
 * what lets one renderer serve a private career map, a public sector map, and
 * whatever anyone else builds, without any of them being a fork.
 *
 * See ARCHITECTURE.md §3.3.
 */

/** `[latitude, longitude]`, always in that order. */
export type LatLng = [number, number];

// ---- Geography ------------------------------------------------------------

/**
 * A hill, as a radial peak summed into the heightfield.
 *
 * `elevation` is metres above sea level at the summit. `radius` is roughly
 * where the hill meets the flats, in degrees of latitude.
 */
export interface Hill {
  name: string;
  lat: number;
  lng: number;
  elevation: number;
  radius: number;
}

/**
 * Where buildings go, how tall, and on what street grid.
 *
 * `gridAngle` is the district's street bearing in radians. It is per-district
 * rather than per-city because that is the fact on the ground in San Francisco:
 * the grid north of Market and the grid south of it are 46° out of true, and
 * reproducing that is most of what makes the city recognisable from above.
 */
export interface District {
  id: string;
  name: string;
  polygon: LatLng[];
  /** Street bearing, radians clockwise from true north. */
  gridAngle: number;
  minHeight: number;
  maxHeight: number;
  /** Chance a given lot gets a tower rather than a low-rise. */
  towerChance: number;
  /** Facade palette key; see `blocks.ts`. */
  palette: "downtown" | "residential" | "industrial";
  /** Fraction of lots that get built on at all. Defaults to 0.88. */
  coverage?: number;
}

/** A building placed by hand because the eye goes looking for it. */
export interface Landmark {
  name: string;
  lat: number;
  lng: number;
  /** Roof height in metres. */
  height: number;
  /** Half-width in degrees of longitude. */
  footprint: number;
  shape: "box" | "pyramid" | "tower" | "cylinder";
  color?: number;
  label?: boolean;
}

export interface Bridge {
  name: string;
  /** Deck centreline. Both ends should run onto land. */
  path: LatLng[];
  towers: LatLng[];
  towerHeight: number;
  deckHeight: number;
  /** Suspension sag as a fraction of tower height. */
  sag: number;
  color: number;
}

export interface Road {
  path: LatLng[];
  width: number;
  kind: "street" | "freeway";
}

/**
 * A named destination, as the interface knows it: what the legend prints and
 * what `flyTo` is keyed on.
 *
 * Split out of `Chapter` because an office has exactly the same idea — a short
 * list of places you can jump to — but positions them in metres, not in
 * latitude and longitude. Only this half of a chapter is shared; the pose is
 * not. `number` and `description` are optional here and required on `Chapter`,
 * because a city's chapters are a numbered tour with a sentence each and an
 * office's views are usually just "Reception" and "The desk bay".
 */
export interface View {
  id: string;
  label: string;
  shortLabel: string;
  number?: string;
  description?: string;
}

/** A camera destination, and a sentence about why it is on the map. */
export interface Chapter extends View {
  number: string;
  description: string;
  focus: {
    lat: number;
    lng: number;
    distance: number;
    height: number;
    rotation: number;
  };
}

/**
 * A rectangle rendered at fine terrain resolution.
 *
 * SF declares one covering the whole city and behaves as if this did not exist.
 * LA needs six — DTLA, Santa Monica, Culver, Irvine, Pasadena, downtown
 * Riverside — with the basin between them coarse, because LA/OC/Riverside is
 * roughly fourteen times SF's area and a uniform 45 m lattice over it would be
 * 4.6M points. See ARCHITECTURE.md §5.
 */
export interface FocusRegion {
  minLat: number;
  maxLat: number;
  minLng: number;
  maxLng: number;
}

/**
 * Everything the engine needs to draw a place. Pure data — a city pack must
 * contain no code, so that adding one is a contribution anybody can review.
 */
export interface City {
  id: string;
  name: string;

  /** Map centre, and the origin of scene space. */
  center: { lat: number; lng: number };
  /** Scene bounds. Everything outside this is open water or off-frame. */
  bounds: { minLat: number; maxLat: number; minLng: number; maxLng: number };

  /**
   * Degrees to scene units, for latitude. Longitude is derived as
   * `latScale * cos(center.lat)` so the place keeps its true proportions.
   */
  latScale: number;

  /**
   * How much taller than life the vertical is. Terrain and buildings share it,
   * so they stay honest relative to each other.
   */
  verticalExaggeration: number;

  /** Ground-cell size inside a focus region, in degrees. */
  cellLat: number;
  cellLng: number;
  /** Multiplier applied to cell size outside every focus region. 1 = uniform. */
  coarseFactor?: number;
  focusRegions?: FocusRegion[];

  /** Distance from open water, in degrees, over which relief ramps to zero. */
  coastFalloff: number;

  landmasses: LatLng[][];
  parks: LatLng[][];
  inlandWater: LatLng[][];
  hills: Hill[];
  districts: District[];
  landmarks: Landmark[];
  bridges: Bridge[];
  roads: Road[];
  chapters: Chapter[];

  /** Palette overrides; every field is optional. */
  palette?: Partial<ScenePalette>;
}

export interface ScenePalette {
  skyTop: number;
  skyHorizon: number;
  sea: number;
  lake: number;
  shore: number;
  sand: number;
  flats: number;
  upland: number;
  park: number;
  parkHigh: number;
}

// ---- Lighting -------------------------------------------------------------

/**
 * Everything the light rig needs, as plain numbers.
 *
 * This is a *state*, not an observation: `Environment` — `{ time, sun, weather }`
 * — is what the world is doing, and a `LightingState` is what that means for the
 * rig. `Atmosphere` owns the conversion and is the only thing allowed to make
 * one; a scene applies it and never writes back. Two modules both constructing
 * and mutating the same three lights is the failure this shape exists to
 * prevent. See CONTRACT.md §4.
 *
 * Colours are `0xrrggbb`, matching `ScenePalette` and three.js.
 */
export interface LightingState {
  sun: {
    /**
     * Unit vector from the scene toward the sun. Distance is deliberately
     * absent: how far away to place the light is a fact about the scale of the
     * scene, and the sun does not know whether it is shining on 94 m per unit
     * or on 1 m per unit.
     */
    direction: [number, number, number];
    color: number;
    intensity: number;
  };
  hemisphere: { sky: number; ground: number; intensity: number };
  ambient: { color: number; intensity: number };
  /**
   * Background gradient, or `null` to leave the background alone — which is
   * what an interior wants, since it has walls and no horizon.
   */
  sky: { top: number; horizon: number } | null;
  /** `null` for no fog at all. An office gets none. */
  fog: { color: number; near: number; far: number } | null;
}

// ---- Markers --------------------------------------------------------------

/**
 * A thing worth pointing at, minus where it is.
 *
 * `colorKey` is deliberately opaque to the engine — it indexes into a palette
 * the caller supplies. The engine will not learn what "rejected" means.
 *
 * This is the half that survives a change of coordinate system: a pin on a city
 * at 37.79 N, -122.40 E and a pin on a desk 4.2 m along the east wall are the
 * same kind of thing to everything downstream of the geometry, so an office can
 * carry its own positions and still hand a `Pin` to the same detail card.
 */
export interface Pin {
  id: string;
  label: string;
  colorKey: string;
  /** Optional href for the detail card. */
  url?: string;
  /** Optional one-liner for the detail card. */
  blurb?: string;
}

/**
 * A small, map-scale building drawn in place of a pin.
 *
 * This is deliberately a glyph rather than an architectural model. A city is
 * normally viewed from kilometres away, so loading a façade kit with one mesh
 * per window buys triangles nobody can see and gives up the one-draw-call city
 * that `blocks.ts` works hard to preserve. The glyph keeps the useful grammar
 * — a ground floor, repeated bays, a roof line and a deterministic silhouette
 * — and expresses it in a handful of procedural meshes.
 *
 * Metres are used here because these values describe a real building even
 * though the city scene does not: `World` converts horizontal metres with
 * `metresPerUnit` and vertical metres with the city's exaggeration.
 */
export interface BuildingGlyph {
  kind: "building";
  width: number;
  depth: number;
  height: number;
  /** Approximate occupied floors; used to choose the façade rhythm. */
  storeys: number;
  /** Compass bearing of local −Z, degrees clockwise from true north. */
  heading: number;
  /** The silhouette family, not a tenant or product category. */
  profile: "tower" | "hangar" | "courtyard" | "block";
  /** Stable variation for bay widths and lit panes. */
  seed?: number;
  /** Neutral shell colour. The marker palette still supplies the door/accent. */
  bodyColor?: number;
}

/** A `Pin` placed on a city, in degrees. */
export interface Marker extends Pin {
  lat: number;
  lng: number;
  /**
   * False when the position is a placeholder rather than a real address.
   * Rendered distinctly, because inventing a location on a map whose premise
   * is that it is real is worse than admitting the gap.
   */
  located?: boolean;
  /** Optional map-scale representation. Omit it for the ordinary pin. */
  glyph?: BuildingGlyph;
}

/** Caller-supplied `colorKey` -> colour. */
export type MarkerPalette = Record<string, number>;

// ---- Flights --------------------------------------------------------------

export interface Aircraft {
  id: string;
  lat: number;
  lng: number;
  /** Barometric altitude in metres. */
  altitude: number;
  /** Degrees clockwise from true north. */
  heading: number;
  callsign?: string;
}

/**
 * Where aircraft come from.
 *
 * An interface rather than a client, because this repo must not ship one for the
 * obvious source: FlightRadar24's terms do not permit scraping and do not permit
 * redistributing the data, so a client for it in an Apache-2.0 repo would be
 * publishing instructions for violating a ToS. This package ships a simulator
 * and open community sources; anything commercial is an adapter in a private
 * deployment. See ARCHITECTURE.md §4.
 */
export interface FlightSource {
  /** Current traffic. Called on a timer; must be cheap and must not throw. */
  poll(): Promise<Aircraft[]> | Aircraft[];
  /** Seconds between polls. */
  interval: number;
  dispose?(): void;
}

// ---- Satellites -----------------------------------------------------------

/**
 * Which constellation a satellite belongs to, as far as anyone looking up cares.
 *
 * A **display bucket and not a taxonomy**: no orbital regime, no operator, no
 * launch date. `engine/satellites.ts` picks a colour from it and nothing else
 * reads it, and a field that carried more would be a field that got wrong.
 *
 * `starlink` is broken out from `comms` because it is the reason the layer
 * exists — it is the constellation people can see with their eyes, in a train,
 * forty minutes after sunset. `other` is not a failure; most of the catalogue is
 * other.
 *
 * This lives here rather than in `server/wire.ts` for the same reason `Marker`
 * does: the renderer owns the vocabulary and the wire carries it, so a body off
 * the network is handed to the engine as-is with no adapter in between.
 */
export type SatelliteGroup =
  | "starlink"
  | "comms"
  | "navigation"
  | "station"
  | "weather"
  | "other";
