import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";

import {
  CSS_TOKENS,
  GLASS,
  INK,
  SPACE,
  TOUCH_TARGET_PX,
  TYPE,
  Z_LAYERS,
  Z_LAYER_NAMES,
  tokenCss,
  zIndex,
  zVarName,
} from "../../ui/tokens.ts";

const html = readFileSync(new URL("../../../index.html", import.meta.url), "utf8");

/**
 * The `:root` block, isolated.
 *
 * Several assertions below are about what is *outside* it — a raw `z-index`
 * literal anywhere else in the file is the defect the token scale exists to
 * remove — so the boundary has to be found rather than assumed.
 */
function rootBlock(): string {
  const start = html.indexOf(":root {");
  assert.ok(start > 0, "index.html must declare a :root token block");
  const end = html.indexOf("\n      }", start);
  assert.ok(end > start, ":root block must be closed");
  return html.slice(start, end);
}

describe("design tokens", () => {
  it("declares every token exactly once, in :root, with the value tokens.ts holds", () => {
    const block = rootBlock();
    for (const [name, value] of CSS_TOKENS) {
      const declaration = `${name}: ${value};`;
      assert.ok(
        block.includes(declaration),
        `index.html's :root is missing ${declaration} — regenerate it from tokenCss()`,
      );
      // Once. A token declared twice is a token whose value depends on which
      // half of the file you read.
      const occurrences = block.split(declaration).length - 1;
      assert.equal(occurrences, 1, `${name} is declared ${occurrences} times`);
    }
  });

  it("has no duplicate token names", () => {
    const names = CSS_TOKENS.map(([name]) => name);
    assert.equal(new Set(names).size, names.length);
  });

  it("orders the stacking layers strictly, lowest first", () => {
    const values = Z_LAYER_NAMES.map((layer) => Z_LAYERS[layer]);
    for (let i = 1; i < values.length; i += 1) {
      const previous = values[i - 1] ?? 0;
      const current = values[i] ?? 0;
      assert.ok(
        current > previous,
        `${Z_LAYER_NAMES[i]} (${current}) must sit above ${Z_LAYER_NAMES[i - 1]} (${previous})`,
      );
    }
  });

  it("keeps the two orderings that used to be accidents", () => {
    // The webcam "camera active" indicator over a modal, deliberately.
    assert.ok(Z_LAYERS.alert > Z_LAYERS.modal);
    // Godmode's HUD over the play HUD, deliberately.
    assert.ok(Z_LAYERS.instrument > Z_LAYERS.play);
    assert.ok(Z_LAYERS.play > Z_LAYERS.dock);
    assert.ok(Z_LAYERS.dock > Z_LAYERS.chrome);
    // And nothing above the boot card, which owns the screen until a real frame
    // is on the glass.
    assert.equal(Math.max(...Object.values(Z_LAYERS)), Z_LAYERS.boot);
  });

  it("emits a var() reference with a fallback, for the injected stylesheets", () => {
    assert.equal(zVarName("modal"), "--z-modal");
    assert.equal(zIndex("modal"), "var(--z-modal, 10)");
    // The fallback matters: a `z-index: var(--z-modal)` that resolves to nothing
    // is invalid-at-computed-value-time, which silently drops the stacking
    // context rather than visibly failing.
    for (const layer of Z_LAYER_NAMES) {
      assert.match(zIndex(layer), /^var\(--z-[a-z]+, \d+\)$/);
    }
  });

  it("keeps the systems it claims to be: one accent, four inks, five type sizes", () => {
    assert.equal(Object.keys(INK).length, 4);
    assert.equal(Object.keys(TYPE).length, 5);
    // The 4px rhythm, actually 4px.
    for (const [step, value] of Object.entries(SPACE)) {
      assert.equal(Number(value.replace("px", "")) % 4, 0, `--s${step} is off the rhythm`);
    }
    assert.equal(TOUCH_TARGET_PX, 44);
    assert.equal(TOUCH_TARGET_PX % 4, 0);
  });

  it("uses one glass recipe, with the strong weight genuinely more opaque", () => {
    const alpha = (color: string): number => Number(color.split(",").at(-1)?.replace(")", ""));
    assert.ok(alpha(GLASS.fillStrong) > alpha(GLASS.fill));
  });

  it("generates the :root body it asserts against", () => {
    const generated = tokenCss("  ");
    for (const [name, value] of CSS_TOKENS) {
      assert.ok(generated.includes(`  ${name}: ${value};`));
    }
    assert.equal(generated.split("\n").length, CSS_TOKENS.length);
  });
});
