/**
 * SET 49 — post-build boot check.
 *
 * "#root renders" is too weak (AdminView satisfies it). This asserts the
 * CONTROL PANEL surface at boot (prompt textarea + Compile) and that
 * AdminView / install panel are NOT mounted.
 *
 * Layers:
 *   1. Source invariants on App.tsx (defaults + AdminView gated)
 *   2. Built bundle carries boot markers
 *   3. Headless Chrome dump-dom of vite-preview /app (when Chrome is available)
 */
import { spawn, type ChildProcess } from 'node:child_process';
import fs from 'node:fs';
import http from 'node:http';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
  bootSurfaceFlags,
  resolveMainSurface,
} from '../web/src/mainSurface';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const DIST = path.join(ROOT, 'web', 'dist');
const APP_SRC = path.join(ROOT, 'web', 'src', 'App.tsx');

export type BootCheckResult = { ok: boolean; detail: string };

function fail(detail: string): BootCheckResult {
  return { ok: false, detail };
}

function ok(detail: string): BootCheckResult {
  return { ok: true, detail };
}

/** Source + pure-routing invariants (no browser required). */
export function checkWebBootSource(appSrcPath: string = APP_SRC): BootCheckResult {
  if (!fs.existsSync(appSrcPath)) return fail(`missing ${appSrcPath}`);
  const src = fs.readFileSync(appSrcPath, 'utf8');

  // Boot flags must default closed.
  if (!/const \[t1Open,\s*setT1Open\]\s*=\s*useState\(false\)/.test(src)) {
    return fail('t1Open must default useState(false)');
  }
  if (!/const \[adminOpen,\s*setAdminOpen\]\s*=\s*useState\(false\)/.test(src)) {
    return fail('adminOpen must default useState(false)');
  }
  if (!/const \[installPanelOpen,\s*setInstallPanelOpen\]\s*=\s*useState\(false\)/.test(src)) {
    return fail('installPanelOpen must default useState(false)');
  }

  // Control panel markers + Compile label present in source.
  if (!src.includes('data-rha-boot="control-prompt"')) {
    return fail('missing data-rha-boot="control-prompt" on prompt textarea');
  }
  if (!src.includes('data-rha-boot="control-compile"')) {
    return fail('missing data-rha-boot="control-compile" on Compile button');
  }
  if (!/'Compile'|"Compile"|\{busy \? 'Encoding…' : anyBusy \? 'Process underway…' : 'Compile'\}/.test(src)) {
    return fail('Compile button label missing from App.tsx');
  }

  // AdminView must be gated — never the sole/unconditional main child.
  if (!/adminOpen\s*\?\s*\(\s*<AdminView/.test(src) && !/adminOpen \? \(/.test(src)) {
    // Allow multiline: adminOpen ? ( <AdminView
    if (!/adminOpen\s*\?[\s\S]{0,80}<AdminView/.test(src)) {
      return fail('AdminView must render only behind adminOpen');
    }
  }
  if (/<main[\s\S]*?<AdminView[\s\S]*?<\/main>/.test(src) && !/adminOpen/.test(src)) {
    return fail('AdminView appears in main without adminOpen gate');
  }

  const boot = resolveMainSurface(bootSurfaceFlags());
  if (boot !== 'empty') {
    return fail(`boot surface must be empty (control panel), got ${boot}`);
  }

  return ok('source invariants pass');
}

/** Built JS must retain boot markers (not tree-shaken away). */
export function checkWebBootBundle(distDir: string = DIST): BootCheckResult {
  if (!fs.existsSync(distDir)) return fail(`missing dist: ${distDir}`);
  const assets = path.join(distDir, 'assets');
  if (!fs.existsSync(assets)) return fail(`missing ${assets}`);
  const jsFiles = fs.readdirSync(assets).filter(f => f.endsWith('.js'));
  if (!jsFiles.length) return fail('no JS assets in dist');
  let blob = '';
  for (const f of jsFiles) {
    blob += fs.readFileSync(path.join(assets, f), 'utf8');
  }
  for (const marker of ['control-prompt', 'control-compile', 'data-rha-boot']) {
    if (!blob.includes(marker)) return fail(`built bundle missing marker: ${marker}`);
  }
  // Admin marker exists in bundle (component ships) but must not be the only surface.
  if (!blob.includes('Compile')) return fail('built bundle missing Compile label');
  return ok('bundle markers present');
}

function findChrome(): string | null {
  const candidates = [
    process.env.CHROME_PATH,
    '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    '/Applications/Chromium.app/Contents/MacOS/Chromium',
    'google-chrome',
    'chromium',
    'chromium-browser',
  ].filter(Boolean) as string[];
  for (const c of candidates) {
    if (c.includes('/') && fs.existsSync(c)) return c;
    // bare name — let spawn resolve PATH later
    if (!c.includes('/')) return c;
  }
  return null;
}

function sleep(ms: number): Promise<void> {
  return new Promise(r => setTimeout(r, ms));
}

async function waitHttpOk(url: string, timeoutMs: number): Promise<boolean> {
  const t0 = Date.now();
  while (Date.now() - t0 < timeoutMs) {
    try {
      const ok = await new Promise<boolean>((resolve) => {
        const req = http.get(url, res => {
          res.resume();
          resolve(!!res.statusCode && res.statusCode >= 200 && res.statusCode < 400);
        });
        req.on('error', () => resolve(false));
        req.setTimeout(1500, () => { req.destroy(); resolve(false); });
      });
      if (ok) return true;
    } catch { /* retry */ }
    await sleep(200);
  }
  return false;
}

/** Headless DOM dump: control markers present, admin/install absent at boot. */
export async function checkWebBootDom(distDir: string = DIST): Promise<BootCheckResult> {
  const chrome = findChrome();
  if (!chrome) {
    return ok('DOM check skipped (no Chrome) — source+bundle gates still apply');
  }
  if (!fs.existsSync(path.join(distDir, 'app.html')) && !fs.existsSync(path.join(distDir, 'index.html'))) {
    return fail('dist missing app.html/index.html');
  }

  const port = 4173 + Math.floor(Math.random() * 200);
  const preview: ChildProcess = spawn(
    process.platform === 'win32' ? 'npx.cmd' : 'npx',
    ['vite', 'preview', '--host', '127.0.0.1', '--port', String(port), '--strictPort'],
    { cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } },
  );
  let previewLog = '';
  preview.stdout?.on('data', (d: Buffer) => { previewLog += d.toString(); });
  preview.stderr?.on('data', (d: Buffer) => { previewLog += d.toString(); });

  try {
    const ready = await waitHttpOk(`http://127.0.0.1:${port}/app`, 20_000);
    if (!ready) {
      return fail(`vite preview not ready on :${port}: ${previewLog.slice(-400)}`);
    }

    const dump = await new Promise<string>((resolve, reject) => {
      const args = [
        '--headless=new',
        '--disable-gpu',
        '--no-sandbox',
        '--virtual-time-budget=8000',
        `--dump-dom`,
        `http://127.0.0.1:${port}/app`,
      ];
      const child = spawn(chrome, args, { stdio: ['ignore', 'pipe', 'pipe'] });
      let out = '';
      let err = '';
      child.stdout.on('data', (d: Buffer) => { out += d.toString(); });
      child.stderr.on('data', (d: Buffer) => { err += d.toString(); });
      child.on('error', reject);
      child.on('close', code => {
        if (code !== 0 && !out.includes('data-rha-boot')) {
          reject(new Error(`chrome dump-dom exit ${code}: ${err.slice(-500)}`));
        } else resolve(out);
      });
      setTimeout(() => {
        child.kill('SIGKILL');
        reject(new Error('chrome dump-dom timed out'));
      }, 25_000);
    });

    if (!dump.includes('data-rha-boot="control-prompt"')) {
      return fail('boot DOM missing control-prompt textarea');
    }
    if (!dump.includes('data-rha-boot="control-compile"')) {
      return fail('boot DOM missing control-compile button');
    }
    if (dump.includes('data-rha-boot="admin"')) {
      return fail('AdminView mounted at boot — control panel must own first paint');
    }
    if (dump.includes('data-rha-boot="install"')) {
      return fail('InstallStatsPanel mounted at boot');
    }
    return ok('headless boot DOM: control panel present, admin/install absent');
  } finally {
    preview.kill('SIGTERM');
    await sleep(300);
    try { preview.kill('SIGKILL'); } catch { /* already dead */ }
  }
}

export async function checkWebBoot(): Promise<BootCheckResult> {
  const a = checkWebBootSource();
  if (!a.ok) return a;
  const b = checkWebBootBundle();
  if (!b.ok) return b;
  const c = await checkWebBootDom();
  if (!c.ok) return c;
  return ok(`${a.detail}; ${b.detail}; ${c.detail}`);
}

const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMain) {
  checkWebBoot().then(r => {
    if (!r.ok) {
      console.error(`check-web-boot FAIL: ${r.detail}`);
      process.exit(1);
    }
    console.log(`check-web-boot OK: ${r.detail}`);
  }).catch(e => {
    console.error('check-web-boot ERROR:', e);
    process.exit(1);
  });
}
