/**
 * The simulated sky, as a plan rather than as positions.
 *
 * The engine's `SimulatedFlights` already evaluates a `SimRoute[]` in closed
 * form against wall-clock time, so the server has nothing to simulate — it hands
 * over the routes, a fixed phase origin and a seed, and every browser arrives at
 * the same answer. One cacheable request replaces a poll per second, and two
 * people on different machines see the same aircraft in the same places, which
 * a per-client simulation cannot promise and which is the point of doing it this
 * way.
 *
 * ### Where the coordinates came from
 *
 * The seven airport positions — SFO, OAK and SJC in the north, LAX, BUR, LGB and
 * SNA in the south — are published FAA airport reference points, typed in by
 * hand. They are US government facts in the public domain, and — as with every
 * other coordinate in this repo — emphatically not derived from OpenStreetMap.
 * Everything else here is a waypoint someone made up so the legs go the right
 * way. See ARCHITECTURE.md §3.2 and CONTRACT.md §8.
 *
 * The callsigns are invented, with operator prefixes that belong to nobody. A
 * repo that refuses to ship other people's logos should not ship their flight
 * numbers either.
 */

import type { WireSimRoute } from "../../../src/server/wire.ts";

const SFO: [number, number] = [37.6188, -122.375];
const OAK: [number, number] = [37.7213, -122.2207];
const SJC: [number, number] = [37.3639, -121.9289];

/**
 * Departures climb, arrivals descend, and a third of the traffic is just passing
 * through at cruise. That mix is what makes the sky read as a working airspace
 * rather than a carousel.
 */
const BAY_AREA: WireSimRoute[] = [
  // Departures — out over the Pacific, north-east over the Central Valley, and
  // south down the peninsula.
  { callsign: "LMB231", from: SFO, to: [37.3, -123.2], fromAlt: 20, toAlt: 10500, duration: 420 },
  { callsign: "LMB778", from: SFO, to: [38.4, -121.3], fromAlt: 20, toAlt: 11000, duration: 480 },
  { callsign: "PAC1082", from: SFO, to: [38.6, -122.9], fromAlt: 20, toAlt: 10000, duration: 460 },
  { callsign: "BAY412", from: OAK, to: [37.0, -121.2], fromAlt: 20, toAlt: 9500, duration: 400 },
  { callsign: "SIE1440", from: SJC, to: [36.6, -121.6], fromAlt: 20, toAlt: 9800, duration: 430 },

  // Arrivals — the long descent over Point Reyes, the south-east downwind, and
  // the two east-bay finals.
  { callsign: "GLD566", from: [38.3, -123.1], to: SFO, fromAlt: 6000, toAlt: 20, duration: 500 },
  { callsign: "LMB1889", from: [37.1, -121.6], to: SFO, fromAlt: 5500, toAlt: 20, duration: 520 },
  { callsign: "RDW915", from: [37.9, -121.3], to: OAK, fromAlt: 5200, toAlt: 20, duration: 470 },
  { callsign: "PAC331", from: [38.1, -122.3], to: SJC, fromAlt: 5800, toAlt: 20, duration: 540 },

  // Overflights, level the whole way.
  {
    callsign: "GLD55",
    from: [38.8, -122.6],
    to: [36.6, -121.4],
    fromAlt: 11000,
    toAlt: 11000,
    duration: 620,
  },
  {
    callsign: "PAC21",
    from: [37.9, -121.4],
    to: [37.2, -123.4],
    fromAlt: 10500,
    toAlt: 10500,
    duration: 700,
  },
  {
    callsign: "RDW1205",
    from: [36.5, -122.4],
    to: [38.9, -122.0],
    fromAlt: 10800,
    toAlt: 10800,
    duration: 660,
  },

  // Low and slow across the bay: general aviation is most of what is actually
  // visible from the ground, and it is the only traffic that reads as *near*.
  {
    callsign: "BAY7789",
    from: [37.45, -122.1],
    to: [38.05, -122.45],
    fromAlt: 900,
    toAlt: 900,
    duration: 520,
  },
  {
    callsign: "SIE4402",
    from: [37.95, -122.55],
    to: [37.35, -121.85],
    fromAlt: 1400,
    toAlt: 1400,
    duration: 560,
  },
];

const LAX: [number, number] = [33.9425, -118.4081];
const BUR: [number, number] = [34.2007, -118.3585];
const LGB: [number, number] = [33.8177, -118.1516];
const SNA: [number, number] = [33.6757, -117.8682];

/**
 * The Southland, which is a different shape of airspace and not a translated
 * copy of the Bay Area.
 *
 * Almost everything here runs east–west, because the terrain does: the basin is
 * walled by the San Gabriels to the north, so departures go out over the water
 * and turn, and arrivals come down the length of the valley. Four fields instead
 * of three, and the two small ones matter more than they do in the north — a
 * board four times the area at a third of the scale reads as empty very fast if
 * everything is at cruise.
 */
const SOCAL: WireSimRoute[] = [
  // Departures — the standard west-over-the-ocean climb, the north-east haul
  // over the Cajon Pass, and the two valley fields going their own way.
  { callsign: "LMB604", from: LAX, to: [33.6, -119.3], fromAlt: 20, toAlt: 10500, duration: 440 },
  { callsign: "PAC712", from: LAX, to: [34.6, -117.1], fromAlt: 20, toAlt: 11500, duration: 520 },
  { callsign: "SIE288", from: BUR, to: [34.9, -118.9], fromAlt: 20, toAlt: 9800, duration: 460 },
  { callsign: "BAY1516", from: SNA, to: [33.1, -118.6], fromAlt: 20, toAlt: 9600, duration: 420 },
  { callsign: "GLD843", from: LGB, to: [34.5, -116.9], fromAlt: 20, toAlt: 10200, duration: 500 },

  // Arrivals — the long straight-in from the east, the coastal descent from the
  // north-west, and one down the back of the mountains into Burbank.
  { callsign: "LMB1170", from: [34.1, -116.8], to: LAX, fromAlt: 6200, toAlt: 20, duration: 560 },
  { callsign: "RDW425", from: [34.7, -119.4], to: LAX, fromAlt: 6800, toAlt: 20, duration: 600 },
  { callsign: "PAC96", from: [34.8, -117.6], to: BUR, fromAlt: 5400, toAlt: 20, duration: 480 },
  { callsign: "SIE1901", from: [33.2, -117.2], to: SNA, fromAlt: 5000, toAlt: 20, duration: 450 },

  // Overflights, level the whole way: the coastal corridor and the desert one.
  {
    callsign: "GLD1330",
    from: [34.9, -119.2],
    to: [32.9, -117.0],
    fromAlt: 11200,
    toAlt: 11200,
    duration: 690,
  },
  {
    callsign: "RDW78",
    from: [33.0, -119.1],
    to: [34.9, -116.8],
    fromAlt: 10600,
    toAlt: 10600,
    duration: 720,
  },

  // Low across the basin. General aviation is most of what anybody standing in
  // Culver City actually sees, and it is the only traffic that reads as *near*.
  {
    callsign: "BAY3312",
    from: [33.78, -118.42],
    to: [34.16, -117.75],
    fromAlt: 850,
    toAlt: 850,
    duration: 540,
  },
  {
    callsign: "LMB2044",
    from: [34.22, -118.72],
    to: [33.72, -117.98],
    fromAlt: 1300,
    toAlt: 1300,
    duration: 570,
  },
];

/**
 * The airspaces this file has actually laid out, and how close a region's centre
 * has to be to get one.
 *
 * Degrees rather than kilometres because the comparison is a box, not a circle,
 * and the radii are generous: the Bay Area's is roughly downtown to the far end
 * of the bay, and the Southland's spans a basin that runs from Ventura to
 * Riverside. A region centre that lands inside one of these is close enough that
 * the real airports are the right answer; anything else gets spokes.
 */
const AIRSPACES: { centre: [number, number]; radius: number; routes: WireSimRoute[] }[] = [
  { centre: [37.7749, -122.4194], radius: 0.75, routes: BAY_AREA },
  { centre: [33.82, -118.05], radius: 1.0, routes: SOCAL },
];

/**
 * A plan for a city this file has never heard of.
 *
 * A self-hoster pointing `TERA_REGIONS` (or `TERA_ORIGIN_LAT/LNG`) at somewhere
 * that is neither of the two boards should get moving aircraft rather than an
 * empty sky, so eight legs are laid out on evenly-spaced bearings through the
 * region centre. It is not their city's real airspace and does not pretend to be
 * — it is motion in the right kind of place, which is all the map ever wanted
 * from this.
 */
function genericPlan(lat: number, lng: number): WireSimRoute[] {
  const routes: WireSimRoute[] = [];
  const span = 0.55;
  for (let i = 0; i < 8; i++) {
    const bearing = (i / 8) * Math.PI * 2;
    const dLat = Math.cos(bearing) * span;
    const dLng = (Math.sin(bearing) * span) / Math.max(0.2, Math.cos((lat * Math.PI) / 180));
    const cruise = 8000 + (i % 4) * 900;
    routes.push({
      callsign: `LMB${100 + i * 37}`,
      from: [lat - dLat, lng - dLng],
      to: [lat + dLat, lng + dLng],
      fromAlt: i % 3 === 0 ? 600 : cruise,
      toAlt: cruise,
      duration: 380 + i * 45,
    });
  }
  return routes;
}

export function planFor(centre: { lat: number; lng: number }): WireSimRoute[] {
  for (const airspace of AIRSPACES) {
    const [lat, lng] = airspace.centre;
    if (
      Math.abs(centre.lat - lat) < airspace.radius &&
      Math.abs(centre.lng - lng) < airspace.radius
    ) {
      return airspace.routes;
    }
  }
  return genericPlan(centre.lat, centre.lng);
}
