#!/usr/bin/env node

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { ARENA_SOURCE_HASHES } from "../src/arena/sourceHashes.ts";

const root = fileURLToPath(new URL("../", import.meta.url));
const sharedEnvironment = [
  "src/arena/base.ts",
  "src/arena/checksum.ts",
  "src/arena/random.ts",
  "src/arena/scenarios.ts",
  "src/arena/types.ts",
];

const sourceSets = {
  "drive-101-v1": {
    environment: [...sharedEnvironment, "src/arena/drive101.ts"],
    simulator: [
      "src/transport/california.ts",
      "src/transport/types.ts",
      "src/transport/vehicleController.ts",
      "src/transport/vehicleSim.ts",
    ],
  },
  "office-nav-v1": {
    environment: [...sharedEnvironment, "src/arena/officeNav.ts"],
    simulator: [
      "src/interiors/plan.ts",
      "src/interiors/types.ts",
      "src/interiors/walker.ts",
      "src/offices/frontier-valley.ts",
    ],
  },
  "office-jobs-v1": {
    environment: [...sharedEnvironment, "src/arena/officeJobs.ts"],
    simulator: [
      "src/interiors/plan.ts",
      "src/interiors/types.ts",
      "src/interiors/walker.ts",
      "src/interiors/robotActivity.ts",
      "src/interiors/robotOperations.ts",
      "src/interiors/robotRoutes.ts",
      "src/offices/lumbridge-hq.ts",
      "src/offices/mateo-court.ts",
      "src/offices/operations/lumbridge-hq.ts",
      "src/offices/operations/mateo-court.ts",
    ],
  },
  "crow-nav-v1": {
    environment: [...sharedEnvironment, "src/arena/crowNav.ts"],
    simulator: ["src/actors/controller.ts"],
  },
  "california-flight-v1": {
    environment: [...sharedEnvironment, "src/arena/californiaFlight.ts"],
    simulator: ["src/aircraft/controller.ts"],
  },
  // `studio-ops-v1` pins the widest simulator set in the package, and every
  // entry earns its place: it is the list of files an edit to which changes
  // what a rollout does. Both office packs and both operations packs are in it
  // because the environment resolves a `Plan` of each; `devices/types.ts` is in
  // it because the capability vocabulary decides the observation's width; and
  // `engine/solar.ts` is in it because the sun is an input rather than a
  // decoration. If this list is ever shorter than what the environment imports,
  // a snapshot can survive a change it should not have survived.
  "studio-ops-v1": {
    environment: [...sharedEnvironment, "src/arena/studioOps.ts"],
    simulator: [
      "src/devices/sim.ts",
      "src/devices/types.ts",
      "src/engine/solar.ts",
      "src/interiors/plan.ts",
      "src/interiors/types.ts",
      "src/interiors/walker.ts",
      "src/interiors/robotActivity.ts",
      "src/interiors/robotOperations.ts",
      "src/interiors/robotRoutes.ts",
      "src/offices/lumbridge-hq.ts",
      "src/offices/mateo-court.ts",
      "src/offices/operations/lumbridge-hq.ts",
      "src/offices/operations/mateo-court.ts",
      "src/offices/sites.ts",
      "src/transport/vehicleTelemetry.ts",
    ],
  },
};

async function digest(paths) {
  const hash = createHash("sha256");
  for (const relative of [...paths].sort()) {
    hash.update(relative);
    hash.update("\0");
    hash.update(await readFile(new URL(`../${relative}`, import.meta.url)));
    hash.update("\0");
  }
  return `sha256:${hash.digest("hex")}`;
}

let failed = false;
for (const [envId, sets] of Object.entries(sourceSets)) {
  const actual = {
    environment: await digest(sets.environment),
    simulator: await digest(sets.simulator),
  };
  const expected = ARENA_SOURCE_HASHES[envId];
  try {
    assert.deepEqual(actual, expected);
    console.log(`arena source hashes: ${envId} ok`);
  } catch {
    failed = true;
    console.error(`arena source hashes: ${envId} mismatch`);
    console.error(JSON.stringify(actual, null, 2));
  }
}

if (failed) process.exitCode = 1;
