#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';

function parseArgs(argv) {
  const args = {
    url: '',
    label: 'baseline',
    runs: 3,
    formFactor: 'mobile',
    outDir: 'flutter/lighthouse/results',
  };

  for (let i = 2; i < argv.length; i += 1) {
    const current = argv[i];
    const next = argv[i + 1];
    if (current === '--url' && next) {
      args.url = next;
      i += 1;
    } else if (current === '--label' && next) {
      args.label = next;
      i += 1;
    } else if (current === '--runs' && next) {
      args.runs = Number.parseInt(next, 10);
      i += 1;
    } else if (current === '--form-factor' && next) {
      args.formFactor = next;
      i += 1;
    } else if (current === '--out-dir' && next) {
      args.outDir = next;
      i += 1;
    }
  }

  if (!args.url) {
    throw new Error('Missing --url, example: node flutter/lighthouse/collect.mjs --url http://localhost:5000');
  }
  if (!Number.isFinite(args.runs) || args.runs < 1) {
    throw new Error('--runs must be >= 1');
  }

  return args;
}

function median(values) {
  if (values.length === 0) return null;
  const ordered = [...values].sort((a, b) => a - b);
  const center = Math.floor(ordered.length / 2);
  if (ordered.length % 2 === 0) {
    return (ordered[center - 1] + ordered[center]) / 2;
  }
  return ordered[center];
}

function toMs(value) {
  if (typeof value !== 'number') return null;
  return Math.round(value);
}

function readMetric(lhr, id) {
  const audit = lhr.audits[id];
  return audit && typeof audit.numericValue === 'number' ? audit.numericValue : null;
}

function readResourceSummary(lhr) {
  const details = lhr.audits['resource-summary']?.details;
  const items = details?.items;
  if (!Array.isArray(items)) return { transferKb: null, requests: null };

  const total = items.find((item) => item.resourceType === 'total');
  if (!total) return { transferKb: null, requests: null };

  return {
    transferKb: typeof total.transferSize === 'number' ? Math.round(total.transferSize / 1024) : null,
    requests: typeof total.requestCount === 'number' ? total.requestCount : null,
  };
}

function formatNullable(value) {
  return value == null ? 'n/a' : String(value);
}

function main() {
  const args = parseArgs(process.argv);
  const outputRoot = resolve(args.outDir);
  mkdirSync(outputRoot, { recursive: true });

  const runRows = [];

  for (let runIndex = 1; runIndex <= args.runs; runIndex += 1) {
    const outputPath = join(outputRoot, `${args.label}-run-${runIndex}.json`);
    const flags = [
      'lighthouse',
      args.url,
      '--only-categories=performance,accessibility,seo',
      '--throttling-method=simulate',
      `--form-factor=${args.formFactor}`,
      '--screenEmulation.mobile=true',
      '--output=json',
      `--output-path=${outputPath}`,
      '--chrome-flags=--headless=new --no-sandbox',
      '--quiet',
    ];

    execFileSync('npx', flags, { stdio: 'inherit' });

    const lhr = JSON.parse(readFileSync(outputPath, 'utf8'));
    const summary = readResourceSummary(lhr);

    runRows.push({
      run: runIndex,
      performance: Math.round((lhr.categories.performance.score ?? 0) * 100),
      accessibility: Math.round((lhr.categories.accessibility.score ?? 0) * 100),
      seo: Math.round((lhr.categories.seo.score ?? 0) * 100),
      lcpMs: toMs(readMetric(lhr, 'largest-contentful-paint')),
      tbtMs: toMs(readMetric(lhr, 'total-blocking-time')),
      inpMs: toMs(readMetric(lhr, 'interaction-to-next-paint')),
      transferKb: summary.transferKb,
      requests: summary.requests,
      fetchTime: lhr.fetchTime,
      finalUrl: lhr.finalUrl,
    });
  }

  const medians = {
    performance: median(runRows.map((r) => r.performance)),
    accessibility: median(runRows.map((r) => r.accessibility)),
    seo: median(runRows.map((r) => r.seo)),
    lcpMs: median(runRows.filter((r) => r.lcpMs != null).map((r) => r.lcpMs)),
    tbtMs: median(runRows.filter((r) => r.tbtMs != null).map((r) => r.tbtMs)),
    inpMs: median(runRows.filter((r) => r.inpMs != null).map((r) => r.inpMs)),
    transferKb: median(runRows.filter((r) => r.transferKb != null).map((r) => r.transferKb)),
    requests: median(runRows.filter((r) => r.requests != null).map((r) => r.requests)),
  };

  const summaryPath = join(outputRoot, `${args.label}-summary.json`);
  writeFileSync(
    summaryPath,
    JSON.stringify(
      {
        url: args.url,
        label: args.label,
        runs: args.runs,
        formFactor: args.formFactor,
        medians,
        results: runRows,
      },
      null,
      2,
    ),
  );

  const markdownPath = join(outputRoot, `${args.label}-summary.md`);
  const markdown = [
    `# Lighthouse summary: ${args.label}`,
    '',
    `- URL: ${args.url}`,
    `- Runs: ${args.runs}`,
    `- Form factor: ${args.formFactor}`,
    '',
    '| Metric | Median |',
    '| --- | ---: |',
    `| Performance | ${formatNullable(medians.performance)} |`,
    `| Accessibility | ${formatNullable(medians.accessibility)} |`,
    `| SEO | ${formatNullable(medians.seo)} |`,
    `| LCP (ms) | ${formatNullable(medians.lcpMs)} |`,
    `| TBT (ms) | ${formatNullable(medians.tbtMs)} |`,
    `| INP (ms) | ${formatNullable(medians.inpMs)} |`,
    `| Transfer (KB) | ${formatNullable(medians.transferKb)} |`,
    `| Requests | ${formatNullable(medians.requests)} |`,
    '',
    '## Runs',
    '',
    '| Run | Perf | A11y | SEO | LCP | TBT | INP | KB | Req |',
    '| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |',
    ...runRows.map(
      (row) =>
        `| ${row.run} | ${row.performance} | ${row.accessibility} | ${row.seo} | ${formatNullable(row.lcpMs)} | ${formatNullable(row.tbtMs)} | ${formatNullable(row.inpMs)} | ${formatNullable(row.transferKb)} | ${formatNullable(row.requests)} |`,
    ),
    '',
  ].join('\n');

  writeFileSync(markdownPath, markdown);
  process.stdout.write(`Wrote ${summaryPath}\n`);
  process.stdout.write(`Wrote ${markdownPath}\n`);
}

main();
