// Headless DOT-identifier guard for the `aura graph` viewer (graph-viewer.js).
//
// Property protected: every node identifier genDot writes into the DOT it hands
// to Graphviz is a *valid Graphviz identifier*. Graphviz parses an unquoted id
// that begins with a digit but is not a pure numeral (e.g. `0__0`) as a numeral
// followed by leftover — silently collapsing distinct ids (`0__0`, `0__1`, ...)
// onto one phantom node `0`. That is the inline-expand bug: a nested composite
// whose node keys are numeric indices yields path-joined ids like `0__0`, which
// Graphviz misparses, collapsing the interior onto a single uncoloured node.
//
// The guard is Graphviz-version-INDEPENDENT: it does not render, it checks the
// DOT-identifier rule directly. An id is valid iff it is a quoted string, OR a
// pure numeral, OR matches the bare-identifier grammar `[A-Za-z_][A-Za-z0-9_]*`.
//
// It loads the *real* viewer module (so a fix or a regression there is observed
// here) and drives the exported pure `genDot` over a model with NUMERIC index
// keys containing a nested composite — exactly the shape `aura graph` emits.

import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

const require = createRequire(import.meta.url);
const here = dirname(fileURLToPath(import.meta.url));

// The triggering fixture: root node "0" is a composite reference `{comp}`, and
// the composite's own nodes are keyed "0"/"1"/"2" (numeric indices). Inline-
// expanding "0" path-joins those into `0__0`, `0__1`, `0__2`.
const model = JSON.parse(readFileSync(join(here, "fixtures", "sample-model.json"), "utf8"));

// genDot closes over module-scoped ROOT/COMP derived from window.AURA_MODEL at
// load. Stub the global BEFORE requiring the viewer (mirrors the browser, where
// render_html injects window.AURA_MODEL ahead of the script).
global.window = { AURA_MODEL: model };
const { normalizeModel, genDot } = require(join(here, "..", "assets", "graph-viewer.js"));
const ROOT = normalizeModel(model).root;

// Inline-EXPAND every expandable composite. The set of expandable ids is
// DISCOVERED from a collapsed pass (genDot returns `expandable`), never hard-
// coded — the guard must drive the real expand path regardless of how genDot
// forms its ids. (A hard-coded key like "0" would stop matching the moment the
// fix reshapes the id to "n0", silently falling back to the collapsed view and
// passing vacuously — the exact false-green this guard must not have.)
const { expandable } = genDot(ROOT, "root", new Set(), false);
if (!expandable.length) {
  console.error(
    "guard fixture exposes no expandable composite — the inline-expand path\n" +
    "(where the digit-prefixed id bug lives) is never exercised. Fix the fixture."
  );
  process.exit(1);
}
const { dot } = genDot(ROOT, "root", new Set(expandable), false);

// Collect every node identifier referenced in the DOT.
//   - node-def lines:   `<id> [label=...]`   (id is the leading token)
//   - edge endpoints:   `<a>:port:s -> <b>:port:n`  (a and b, before the colon)
// Both forms can carry the SAME digit-prefixed id, so we gather from both.
const ids = new Set();
for (const m of dot.matchAll(/^\s*([^\s\[]+)\s*\[label=/gm)) ids.add(m[1]);
for (const m of dot.matchAll(/(\S+?):[a-z]\w*:[a-z]\s*->\s*(\S+?):[a-z]\w*:[a-z]/g)) {
  ids.add(m[1]);
  ids.add(m[2]);
}

// A valid Graphviz id: quoted | pure numeral | bare identifier.
const QUOTED = /^".*"$/s;
const NUMERAL = /^-?(\.[0-9]+|[0-9]+(\.[0-9]*)?)$/;
const BARE = /^[A-Za-z_][A-Za-z0-9_]*$/;
const isValid = (id) => QUOTED.test(id) || NUMERAL.test(id) || BARE.test(id);

const bad = [...ids].filter((id) => !isValid(id));
if (bad.length) {
  console.error(
    "INVALID DOT node identifier(s) — Graphviz will misparse a digit-prefixed,\n" +
    "non-numeral id (e.g. `0__0`) and collapse the inline-expanded composite:\n  " +
    bad.map((id) => JSON.stringify(id)).join("\n  ")
  );
  process.exit(1);
}

console.log(`OK — all ${ids.size} DOT node identifiers are valid.`);
process.exit(0);
