/**
 * The one place the fire wire and the fire renderer meet, checked from both
 * sides.
 *
 * `src/server/fires.ts` owns `promote()` and the shape it returns.
 * `src/engine/scene.ts` owns `SceneHandle.setFires` and declares the *minimum*
 * a renderer needs — deliberately its own copy, so the engine never imports a
 * wire module and neither file has to exist for the other to compile. The cost
 * of that decision is exactly one thing: nothing stops the two drifting.
 *
 * This is that one thing. The assignment below is the whole test — if
 * `FirePromotion` stops satisfying `FireView`, `tsc` fails here rather than in
 * a render loop three weeks later — and the runtime assertions state the same
 * contract for a reader who is not running the compiler.
 *
 * The risk it guards is named in the round's own notes: cloud-1's `fires.py`
 * was modified the day this landed and nothing in this repo's test suite covers
 * it, so a renamed column has to fail loudly somewhere. This is the somewhere on
 * the client side.
 */

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { emptyPromotion, promote, type FirePromotion } from "../server/fires.ts";
import type { FireView } from "../engine/scene.ts";

/**
 * The compile-time half. A `FirePromotion` must be usable wherever the scene
 * asks for a `FireView`, with no mapping step in between — because there is no
 * mapping step in `main.ts` either.
 */
const _assignable: (p: FirePromotion) => FireView = (p) => p;

const SOCAL = { minLat: 32.5, maxLat: 34.9, minLng: -119.5, maxLng: -117.2 };

describe("the fire seam", () => {
  it("hands promote() output straight to the scene with no adapter", () => {
    const view: FireView = _assignable(emptyPromotion());
    assert.deepEqual(view.drawn, []);
    assert.deepEqual(view.detections, []);
    // Epoch zero rather than "now": nothing has answered, and saying so with a
    // real timestamp would be the lie this field exists to prevent.
    assert.equal(view.fetchedAt, new Date(0).toISOString());
    assert.equal(view.ageMs, null);
  });

  it("survives a body it did not build, rather than throwing into a render loop", () => {
    for (const body of [null, undefined, {} as never]) {
      const view: FireView = _assignable(promote(body, SOCAL, 1_000));
      assert.deepEqual(view.drawn, []);
      assert.deepEqual(view.detections, []);
    }
  });

  it("keeps the field names the renderer reads", () => {
    // Named explicitly rather than inferred, so a rename on either side lands
    // here as a failing assertion with the old name printed in it.
    const view: FireView = _assignable(emptyPromotion());
    for (const key of ["drawn", "detections", "fetchedAt", "ageMs"]) {
      assert.ok(key in view, `FireView lost ${key}`);
    }
  });
});
