import path from 'node:path';
import {
  CONTEXT_TIMEOUT_MS,
  PAGE_TIMEOUT_MS,
  cleanupBrowserTmp,
  openBrowser,
  safeCloseBrowser,
  safeCloseContext,
  safeClosePage,
  setupBrowserTmp,
  shouldRetryNavigation,
  withTimeout,
} from './lib/browser.mjs';
import { discoverRoutes, requireDist, startDistServer } from './lib/dist.mjs';

const browserTmp = path.resolve('.astro', 'playwright-overflow-tmp');
const MAX_NAV_RETRIES = 4;
// Common device widths: iPhone SE / Galaxy S / iPhone 14 / iPad portrait /
// iPad landscape / common laptop / full HD desktop.
const VIEWPORT_WIDTHS = [320, 390, 430, 768, 1024, 1440, 1920];
const MEASURE_TIMEOUT_MS = 25000;

await requireDist();
await setupBrowserTmp(browserTmp);

const routes = await discoverRoutes();
const { server, port } = await startDistServer();
const failures = [];

async function newMeasurementContext(browser, width) {
  const context = await browser.newContext({
    viewport: { width, height: 900 },
    javaScriptEnabled: true,
  });
  await context.route('**/*', (route) => {
    const type = route.request().resourceType();
    if (type === 'media') {
      route.abort('blockedbyclient');
    } else {
      route.continue();
    }
  });
  return context;
}

async function openMeasurementContext(browser, width) {
  return withTimeout(
    newMeasurementContext(browser, width),
    CONTEXT_TIMEOUT_MS,
    `Timed out while creating ${width}px Playwright context`
  );
}

async function measureViewport(page) {
  await page.waitForLoadState('load', { timeout: 5000 }).catch(() => {});
  return page.evaluate(() => ({
    scrollWidth: document.documentElement.scrollWidth,
    clientWidth: document.documentElement.clientWidth,
  }));
}

async function measureRoute(context, route) {
  let page;
  try {
    page = await withTimeout(
      context.newPage(),
      PAGE_TIMEOUT_MS,
      `Timed out while creating page for ${route}`
    );
    return await withTimeout(
      (async () => {
        await page.goto(`http://127.0.0.1:${port}${route}`, {
          waitUntil: 'domcontentloaded',
          timeout: 15000,
        });
        return measureViewport(page);
      })(),
      MEASURE_TIMEOUT_MS,
      `Timed out while measuring ${route}`
    );
  } finally {
    if (page) await safeClosePage(page);
  }
}

try {
  for (const width of VIEWPORT_WIDTHS) {
    let browser;
    let context;
    try {
      browser = await openBrowser(browserTmp);
      context = await openMeasurementContext(browser, width);
      for (const route of routes) {
        let result;

        for (let attempt = 0; attempt < MAX_NAV_RETRIES; attempt += 1) {
          try {
            result = await measureRoute(context, route);
            break;
          } catch (error) {
            if (!shouldRetryNavigation(error) || attempt === MAX_NAV_RETRIES - 1) {
              throw error;
            }
            await safeCloseContext(context);
            await safeCloseBrowser(browser);
            browser = await openBrowser(browserTmp);
            context = await openMeasurementContext(browser, width);
          }
        }

        if (result.scrollWidth > result.clientWidth + 1) {
          failures.push(
            `${route} overflows at ${width}px: ${result.scrollWidth}px > ${result.clientWidth}px`
          );
        }
      }
    } finally {
      if (context) await safeCloseContext(context);
      if (browser) await safeCloseBrowser(browser);
    }
  }
} finally {
  server.close();
  await cleanupBrowserTmp(browserTmp);
}

if (failures.length > 0) {
  console.error(failures.join('\n'));
  process.exit(1);
}

console.log(
  `No horizontal overflow detected at ${VIEWPORT_WIDTHS.join(', ')}px across ${routes.length} routes.`
);
