import { createServer } from 'node:http';
import { readdir, readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { chromium } from 'playwright';

const dist = path.resolve('dist');
const widths = [320, 390, 430, 768, 1024, 1440, 1920];

const MIME = {
  '.html': 'text/html; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.js': 'text/javascript; charset=utf-8',
  '.svg': 'image/svg+xml',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.webp': 'image/webp',
  '.avif': 'image/avif',
  '.ico': 'image/x-icon',
  '.woff': 'font/woff',
  '.woff2': 'font/woff2',
  '.mp4': 'video/mp4',
  '.webm': 'video/webm',
  '.pdf': 'application/pdf',
};

function contentType(file) {
  const ext = path.extname(file).toLowerCase();
  return MIME[ext] ?? 'application/octet-stream';
}

async function walk(dir) {
  const entries = await readdir(dir, { withFileTypes: true });
  const files = [];
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      files.push(...(await walk(fullPath)));
    } else {
      files.push(fullPath);
    }
  }
  return files;
}

async function discoverRoutes() {
  const files = await walk(dist);
  const routes = new Set();
  for (const file of files) {
    if (!file.endsWith('.html')) continue;
    const rel = path.relative(dist, file).replaceAll(path.sep, '/');
    if (rel === '404.html') continue;
    if (rel.endsWith('/index.html')) {
      routes.add('/' + rel.slice(0, -'index.html'.length));
    } else if (rel === 'index.html') {
      routes.add('/');
    } else {
      routes.add('/' + rel.replace(/\.html$/, '/'));
    }
  }
  return [...routes].sort();
}

async function resolveFile(url) {
  const parsed = new URL(url, 'http://localhost');
  const safePath = path
    .normalize(decodeURIComponent(parsed.pathname))
    .replace(/^\/+/, '')
    .replace(/^(\.\.(\/|\\|$))+/, '');
  const candidate = path.join(dist, safePath);
  const candidates = [
    candidate,
    path.join(candidate, 'index.html'),
    path.join(dist, `${safePath}.html`),
  ];

  for (const file of candidates) {
    try {
      const fileStat = await stat(file);
      if (fileStat.isFile()) return file;
    } catch {
      // Try the next candidate.
    }
  }

  return path.join(dist, '404.html');
}

try {
  await stat(dist);
} catch {
  throw new Error('dist/ does not exist. Run npm run build first.');
}

const routes = await discoverRoutes();

const server = createServer(async (req, res) => {
  try {
    const file = await resolveFile(req.url ?? '/');
    const body = await readFile(file);
    res.writeHead(200, { 'content-type': contentType(file) });
    res.end(body);
  } catch (error) {
    res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' });
    res.end(String(error));
  }
});

await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const { port } = server.address();
const browser = await chromium.launch({ headless: true });
const failures = [];

async function measureViewport(page) {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    try {
      await page.waitForLoadState('load');
      return await page.evaluate(() => ({
        scrollWidth: document.documentElement.scrollWidth,
        clientWidth: document.documentElement.clientWidth,
      }));
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      if (attempt === 2 || !/Execution context was destroyed|navigation/i.test(message)) {
        throw error;
      }
      await page.waitForLoadState('load').catch(() => {});
    }
  }
}

try {
  for (const width of widths) {
    const page = await browser.newPage({
      viewport: { width, height: 900 },
      javaScriptEnabled: false,
    });

    for (const route of routes) {
      await page.goto(`http://127.0.0.1:${port}${route}`, { waitUntil: 'load' });
      if (route.startsWith('/writing/')) {
        await page
          .waitForURL((url) => url.pathname.startsWith('/articles/'), { timeout: 1000 })
          .catch(() => {});
      }
      const result = await measureViewport(page);

      if (result.scrollWidth > result.clientWidth + 1) {
        failures.push(
          `${route} overflows at ${width}px: ${result.scrollWidth}px > ${result.clientWidth}px`
        );
      }
    }

    await page.close();
  }
} finally {
  await browser.close();
  server.close();
}

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

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