/** Render-layer contract tests that do not require a WebGL context. */

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import type { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import CALIFORNIA_TRANSPORT from "../transport/california.ts";
import { createRoadTrafficLayer } from "../engine/roadTraffic.ts";
import type { World } from "../engine/world.ts";

function fixture() {
  const world = {
    project(lat: number, lng: number): [number, number] {
      return [(lng + 121) * 20, -(lat - 36) * 20];
    },
    groundAt(): number {
      return 0;
    },
  } as unknown as World;
  const camera = new THREE.PerspectiveCamera(42, 16 / 9, 0.1, 2_000);
  const controls = {
    target: new THREE.Vector3(),
    enabled: true,
  } as unknown as OrbitControls;
  const layer = createRoadTrafficLayer(world, camera, controls, {
    pack: CALIFORNIA_TRANSPORT,
    routeId: "la-sf-us-101",
    count: 8,
    seed: 115,
  });
  return { layer, camera, controls };
}

describe("road traffic render layer", () => {
  it("draws one articulated hero and instanced background parts", () => {
    const { layer } = fixture();
    const hero = layer.group.getObjectByName("model-x-hero");
    assert.ok(hero);
    assert.equal(hero.scale.x, 0.18);
    assert.ok(layer.group.children.some((child) => child instanceof THREE.InstancedMesh));
    assert.equal(layer.hero()?.routeId, "la-sf-us-101");
    assert.ok(Math.abs(hero.position.y - 0.155) < 1e-9);
    layer.dispose();
  });

  it("stands a true-metre car on a board that has metresPerUnit", () => {
    const mpu = 1_919.3;
    const world = {
      metresPerUnit: mpu,
      project(lat: number, lng: number): [number, number] {
        return [(lng + 121) * 20, -(lat - 36) * 20];
      },
      groundAt(): number {
        return 0;
      },
    } as unknown as World;
    const camera = new THREE.PerspectiveCamera(42, 16 / 9, 0.1, 2_000);
    const controls = {
      target: new THREE.Vector3(),
      enabled: true,
    } as unknown as OrbitControls;
    const layer = createRoadTrafficLayer(world, camera, controls, {
      pack: CALIFORNIA_TRANSPORT,
      routeId: "la-sf-us-101",
      count: 4,
      seed: 115,
    });
    const hero = layer.group.getObjectByName("model-x-hero")!;
    assert.ok(Math.abs(hero.scale.x - 1 / mpu) < 1e-12);
    layer.dispose();
  });

  it("switches routes and owns the orbit-control handoff while following", () => {
    const { layer, camera, controls } = fixture();
    layer.setRoute("la-sf-i-5");
    assert.equal(layer.routeId(), "la-sf-i-5");
    assert.equal(layer.hero()?.routeId, "la-sf-i-5");

    layer.setFollowing(true);
    assert.equal(layer.following(), true);
    assert.equal(controls.enabled, false);
    layer.tick(0.1);
    assert.ok(camera.position.toArray().every(Number.isFinite));
    assert.ok(controls.target.toArray().every(Number.isFinite));

    layer.setCameraMode("driver");
    assert.equal(layer.cameraMode(), "driver");
    layer.tick(1 / 60);
    assert.ok(camera.position.y > layer.group.getObjectByName("model-x-hero")!.position.y);

    layer.setFollowing(false);
    assert.equal(controls.enabled, true);
    layer.dispose();
  });

  it("hands manual input to the deterministic hero and resumes assistance", () => {
    const { layer } = fixture();
    for (let index = 0; index < 20; index += 1) {
      layer.setVehicleActions({ throttle: 1, steering: 0.8 });
      layer.tick(1 / 30);
    }
    assert.equal(layer.hero().mode, "manual");
    assert.ok(layer.hero().lateralOffsetM > 0);

    layer.setVehicleActions({ modeRequest: "assisted" });
    layer.tick(1 / 30);
    assert.equal(layer.hero().mode, "assisted");
    layer.dispose();
  });
});
