/**
 * A random opaque identifier, in every context the app is actually served from.
 *
 * `crypto.randomUUID()` is only defined in a **secure context** — HTTPS, or a
 * `localhost` origin. `crypto.getRandomValues()` has no such restriction and is
 * available everywhere `crypto` is. That difference is easy to never notice,
 * because every way a developer normally opens this app is a secure context:
 * `vite dev` and `vite preview` both serve `localhost`, and the deployed site is
 * HTTPS.
 *
 * It is not a hypothetical gap. `main.ts` called `crypto.randomUUID()` at module
 * top level for its three wire identities, so on any other origin the call threw
 * before the scene was built and the app stopped at "Starting up" with one
 * `TypeError` in the console and no other symptom. That is:
 *
 * - the brand-capture harness, which serves `dist/` over
 *   `http://tera.lumbridgecorp.com:5210` so the app can read its own hostname and
 *   decide which door it is — this is how the bug was found, as a screenshot run
 *   that timed out waiting for a boot that was never coming;
 * - a plain `http://` static host, which `deploy/STATIC.md` explicitly invites
 *   ("Serve `dist/` from anything. A Content-Security-Policy of `default-src
 *   'self'` is sufficient") — the zero-config path this project cares about;
 * - anyone opening the dev server by LAN IP to try it on a phone.
 *
 * So the fallback is not defensive clutter. It is the difference between "runs
 * anywhere" being true and being a sentence in a document.
 *
 * The output is UUID-shaped rather than merely random because these strings go
 * on the realtime wire next to ids minted by other clients, and one shape is
 * easier to read in a log than two. Version and variant nibbles are set so it is
 * a well-formed v4 and not something that merely looks like one.
 */

/** 16 random bytes, from the best source this context actually has. */
function randomBytes(): Uint8Array {
  const bytes = new Uint8Array(16);
  const source = globalThis.crypto;
  if (source !== undefined && typeof source.getRandomValues === "function") {
    source.getRandomValues(bytes);
    return bytes;
  }
  // No `crypto` at all. Not reachable in a browser this app supports, and
  // reachable under a bare test runner, so it returns something valid rather
  // than throwing — these ids are opaque handles, never a security boundary.
  for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
  return bytes;
}

const HEX: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));

/**
 * A v4-shaped identifier: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.
 *
 * Prefers the platform's own `randomUUID` when it exists, so that in the common
 * case this is exactly what the code it replaced produced.
 */
export function randomId(): string {
  const source = globalThis.crypto;
  if (source !== undefined && typeof source.randomUUID === "function") {
    return source.randomUUID();
  }

  const b = randomBytes();
  // Version 4 in the high nibble of byte 6, RFC 4122 variant in byte 8.
  b[6] = (b[6]! & 0x0f) | 0x40;
  b[8] = (b[8]! & 0x3f) | 0x80;

  const h = (i: number): string => HEX[b[i]!]!;
  return (
    h(0) + h(1) + h(2) + h(3) + "-" +
    h(4) + h(5) + "-" +
    h(6) + h(7) + "-" +
    h(8) + h(9) + "-" +
    h(10) + h(11) + h(12) + h(13) + h(14) + h(15)
  );
}
