#!/usr/bin/env node
/**
 * A page with a nav bar and no nav — or with the WRONG one.
 *
 * `nav-component.js` only DEFINES `window.tvNav`; nothing renders until
 * `tvNav.init(type)` is called. So a page can carry the markup
 * (`<nav><div id="nav-root"></div></nav>`), load the script, and still show an
 * empty bar — which is how `db-clone.html` shipped while all 84 of its siblings
 * called init.
 *
 * Fixing that alone was not enough, and the way it failed is worth keeping:
 * `init('db-clone')` rendered the MARKETING nav, because `AUTH_TYPES` inside
 * nav-component.js is an allowlist and `db-clone` was not in it. A logged-in
 * operator got the landing-page bar — no DB Ops, no EBS Ops, no Tune, no Vault.
 * The first version of this gate passed that page happily: it asserted only
 * that init was CALLED. An assertion that cannot fail is not a test, so it now
 * checks the type is one the component actually recognises.
 *
 * AUTH_TYPES is PARSED from nav-component.js, never copied here. A second list
 * would drift from the first, and then this gate would be asserting against its
 * own stale idea of the truth.
 *
 * Exit 1 on a page that mounts the nav and never initialises it, or initialises
 * it with a type that renders the wrong nav.
 */
'use strict';

const fs = require('fs');
const path = require('path');

const ROOT = path.join(__dirname, '..', '..');
const PUBLIC_DIR = path.join(ROOT, 'public');
const NAV_SRC = path.join(PUBLIC_DIR, 'nav-component.js');

// Types that deliberately render a NON-app nav. These are real values the
// component branches on — marketing pages, the auth shell, the admin bar — not
// a dumping ground for unrecognised strings.
const PUBLIC_TYPES = new Set(['main', 'features', 'auth', 'admin']);

// A page may opt out by declaring it in its own source, the way a catalog entry
// declares `noUi`. Never inferred from here.
const EXEMPT = /<!--\s*ci:nav-exempt\b/;

function authTypes() {
  const src = fs.readFileSync(NAV_SRC, 'utf8');
  const m = src.match(/var AUTH_TYPES = \{([^}]*)\}/);
  if (!m) {
    console.error('check-nav-init: AUTH_TYPES not found in public/nav-component.js — the ' +
                  'component changed shape, or this gate is parsing the wrong thing. ' +
                  'Refusing to pass.');
    process.exit(1);
  }
  // Keys may be bare or quoted, and may contain digits (`ebs-12-2-checks`) — a
  // [a-zA-Z-] class silently dropped that one and reported a false positive.
  const found = [...m[1].matchAll(/'?([A-Za-z0-9-]+)'?\s*:\s*1/g)].map(x => x[1]);
  if (found.length < 20) {
    console.error(`check-nav-init: parsed only ${found.length} AUTH_TYPES — that is too few to ` +
                  'be right. Refusing to pass on a parse this thin.');
    process.exit(1);
  }
  return new Set(found);
}

const TYPES = authTypes();
const missingInit = [];
const wrongType = [];
let scanned = 0;

for (const file of fs.readdirSync(PUBLIC_DIR).filter(f => f.endsWith('.html')).sort()) {
  const src = fs.readFileSync(path.join(PUBLIC_DIR, file), 'utf8');
  if (!src.includes('id="nav-root"')) continue;   // page does not use the shared nav
  scanned++;
  if (EXEMPT.test(src)) continue;

  // BOTH quote styles. Matching only single quotes reported oracle-setup,
  // privacy and terms as having no nav at all — they use double quotes. A gate
  // that cries wolf gets switched off, so it must be right about the easy cases.
  const call = src.match(/tvNav\s*\.\s*init\s*\(\s*['"]([^'"]+)['"]/);
  if (!call) { missingInit.push(file); continue; }

  const type = call[1];
  if (!TYPES.has(type) && !PUBLIC_TYPES.has(type)) wrongType.push({ file, type });
}

if (!scanned) {
  // A scan that matched nothing is not a pass.
  console.error('check-nav-init: no page contains id="nav-root" — the convention changed, ' +
                'or this gate is looking for the wrong thing. Refusing to pass.');
  process.exit(1);
}

let failed = false;

if (missingInit.length) {
  failed = true;
  console.error('check-nav-init: these pages mount the shared nav but never render it.\n' +
                "Add <script>window.tvNav.init('<type>');</script> after nav-component.js:\n");
  for (const f of missingInit) console.error('  public/' + f);
  console.error('\nThe page will show an EMPTY nav bar until you do.\n');
}

if (wrongType.length) {
  failed = true;
  console.error('check-nav-init: these pages call init() with a type nav-component does not\n' +
                'recognise, so a logged-in user gets the MARKETING nav — no DB Ops, no EBS\n' +
                'Ops, no Tune, no Vault. Add the type to AUTH_TYPES in public/nav-component.js\n' +
                'if it is an app page, or use one of: ' + [...PUBLIC_TYPES].join(', ') + '\n');
  for (const w of wrongType) console.error(`  public/${w.file}  ->  '${w.type}'`);
  console.error('');
}

if (failed) process.exit(1);

console.log(`check-nav-init: ${scanned} pages mount the shared nav; all initialise it with a ` +
            `recognised type (${TYPES.size} app types known).`);
