// UI/UX Score Loop — pass 1 driver.
// Loads each route at three sizes in Chrome Canary, captures screenshots,
// logs console errors, runs a small interaction probe per flow, and
// writes a JSON report. Does not modify any source files.

import puppeteer from "puppeteer-core";
import { mkdir, writeFile } from "node:fs/promises";

const BASE = "http://127.0.0.1:5173";
const SHOTS = "/tmp/cyclone-uiux/shots";
const REPORT = "/tmp/cyclone-uiux/report.json";

const SIZES = [
  { name: "desktop", w: 1440, h: 900 },
  { name: "tablet", w: 768, h: 1024 },
  { name: "mobile", w: 375, h: 812 },
];

// Routes to load. path = the route; name = the flow label; ready = a
// selector we wait for to consider the page "rendered".
const FLOWS = [
  { name: "dashboard",     path: "/",                ready: "aside nav, h1, h2" },
  { name: "upload",        path: "/upload",          ready: "section[aria-label='File upload']" },
  { name: "inbox",         path: "/inbox",           ready: "main, section[aria-label='Queue summary']" },
  { name: "claims",        path: "/claims",          ready: "table, [data-testid='claims-page-body']" },
  { name: "claims-denied", path: "/claims?status=denied", ready: "table, [data-testid='claims-page-body']" },
  { name: "remittances",   path: "/remittances",     ready: "main, table" },
  { name: "providers",     path: "/providers",       ready: "main, table" },
  { name: "reconciliation",path: "/reconciliation",  ready: "main" },
  { name: "acks",          path: "/acks",            ready: "main, table" },
  { name: "batches",       path: "/batches",         ready: "main, table" },
  { name: "batch-diff",    path: "/batch-diff",      ready: "main" },
  { name: "activity",      path: "/activity",        ready: "main" },
  { name: "404",           path: "/does-not-exist",  ready: "main" },
];

async function setupViewports(browser) {
  const pages = [];
  for (const size of SIZES) {
    const page = await browser.newPage();
    await page.setViewport({ width: size.w, height: size.h, deviceScaleFactor: 1 });
    pages.push({ page, size });
  }
  return pages;
}

async function probeFlow(page, flow) {
  const consoleErrors = [];
  const pageErrors = [];
  const failedRequests = [];

  const onConsole = (msg) => {
    if (msg.type() === "error") consoleErrors.push(msg.text());
  };
  const onPageError = (err) => pageErrors.push(err.message);
  const onRequestFailed = (req) => failedRequests.push(`${req.method()} ${req.url()} :: ${req.failure()?.errorText}`);

  page.on("console", onConsole);
  page.on("pageerror", onPageError);
  page.on("requestfailed", onRequestFailed);

  const t0 = Date.now();
  let rendered = false;
  let readyError = null;
  try {
    await page.goto(`${BASE}${flow.path}`, { waitUntil: "networkidle2", timeout: 15000 });
    if (flow.ready) {
      try {
        await page.waitForSelector(flow.ready, { timeout: 5000 });
        rendered = true;
      } catch (e) {
        readyError = e.message;
      }
    } else {
      rendered = true;
    }
  } catch (e) {
    readyError = e.message;
  }
  const loadMs = Date.now() - t0;

  page.off("console", onConsole);
  page.off("pageerror", onPageError);
  page.off("requestfailed", onRequestFailed);

  return { rendered, loadMs, readyError, consoleErrors, pageErrors, failedRequests };
}

async function probeInteractions(page, flow) {
  const findings = [];
  // Generic a11y / structural probes per flow.
  try {
    // Sidebar visible? (md+ shows it; < md hides it)
    const aside = await page.$("aside");
    findings.push({ check: "sidebar-present", pass: !!aside });
  } catch (e) {
    findings.push({ check: "sidebar-present", pass: false, err: e.message });
  }
  try {
    // Top bar present?
    const main = await page.$("main#main-content");
    findings.push({ check: "main-present", pass: !!main });
  } catch (e) {
    findings.push({ check: "main-present", pass: false, err: e.message });
  }
  try {
    // H1 or page heading?
    const heading = await page.evaluate(() => {
      const h = document.querySelector("h1, h2");
      return h ? h.textContent?.trim().slice(0, 60) : null;
    });
    findings.push({ check: "heading-present", pass: !!heading, value: heading });
  } catch (e) {
    findings.push({ check: "heading-present", pass: false, err: e.message });
  }

  // Flow-specific probes.
  if (flow.name === "claims" || flow.name === "claims-denied") {
    try {
      const chips = await page.$$("[role='radio'], button[role='radio']");
      findings.push({ check: "status-chips", pass: chips.length >= 1, count: chips.length });
    } catch (e) {
      findings.push({ check: "status-chips", pass: false, err: e.message });
    }
    try {
      const search = await page.$("input[placeholder*='Search']");
      findings.push({ check: "search-input", pass: !!search });
    } catch (e) {
      findings.push({ check: "search-input", pass: false, err: e.message });
    }
  }
  if (flow.name === "upload") {
    try {
      const dropzone = await page.$("section[aria-label='File upload']");
      findings.push({ check: "dropzone-present", pass: !!dropzone });
      const selects = await page.$$("button[role='combobox']");
      findings.push({ check: "payer-kind-selects", pass: selects.length >= 2, count: selects.length });
    } catch (e) {
      findings.push({ check: "upload-elements", pass: false, err: e.message });
    }
  }
  if (flow.name === "inbox") {
    try {
      const lanes = await page.$$("main > div > div");
      findings.push({ check: "lane-cards", pass: lanes.length >= 1, count: lanes.length });
    } catch (e) {
      findings.push({ check: "lane-cards", pass: false, err: e.message });
    }
  }
  if (flow.name === "404") {
    try {
      const text = await page.evaluate(() => document.body.innerText);
      findings.push({ check: "404-text", pass: text.includes("404") || text.toLowerCase().includes("doesn't exist") });
    } catch (e) {
      findings.push({ check: "404-text", pass: false, err: e.message });
    }
  }

  return findings;
}

async function main() {
  await mkdir(SHOTS, { recursive: true });
  const browser = await puppeteer.launch({
    executablePath: "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
    headless: "new",
    args: ["--no-sandbox", "--disable-dev-shm-usage"],
  });
  const startedAt = new Date().toISOString();
  const results = [];

  for (const size of SIZES) {
    const page = await browser.newPage();
    await page.setViewport({ width: size.w, height: size.h, deviceScaleFactor: 1 });

    for (const flow of FLOWS) {
      const probe = await probeFlow(page, flow);
      const interactions = await probeInteractions(page, flow);
      const shot = `${SHOTS}/${flow.name}--${size.name}.png`;
      try {
        await page.screenshot({ path: shot, fullPage: false });
      } catch (e) {
        // ignore — recording in result
      }
      results.push({
        flow: flow.name,
        path: flow.path,
        size: size.name,
        viewport: { w: size.w, h: size.h },
        probe,
        interactions,
        shot,
      });
      console.log(
        `${size.name.padEnd(7)} ${flow.name.padEnd(20)} ` +
        `render=${probe.rendered} load=${probe.loadMs}ms ` +
        `consoleErr=${probe.consoleErrors.length} pageErr=${probe.pageErrors.length}`
      );
    }

    await page.close();
  }

  await browser.close();

  // Aggregate.
  const summary = {
    startedAt,
    endedAt: new Date().toISOString(),
    flows: results.length,
    sizes: SIZES.map((s) => s.name),
    renderedOk: results.filter((r) => r.probe.rendered).length,
    withConsoleErrors: results.filter((r) => r.probe.consoleErrors.length > 0).length,
    withPageErrors: results.filter((r) => r.probe.pageErrors.length > 0).length,
    withFailedRequests: results.filter((r) => r.probe.failedRequests.length > 0).length,
    results,
  };
  await writeFile(REPORT, JSON.stringify(summary, null, 2));
  console.log("\nSummary:", JSON.stringify({
    flows: summary.flows,
    renderedOk: summary.renderedOk,
    withConsoleErrors: summary.withConsoleErrors,
    withPageErrors: summary.withPageErrors,
    withFailedRequests: summary.withFailedRequests,
  }, null, 2));
  console.log("\nReport:", REPORT);
  console.log("Shots:", SHOTS);
}

main().catch((e) => {
  console.error("FATAL", e);
  process.exit(1);
});
