import { createServer } from 'node:http';
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { walk } from './walk.mjs';

export const dist = path.resolve('dist');

const INDEX_FILE = 'index.html';

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

// Derives the site's route list from the HTML files in dist/.
export 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 === INDEX_FILE) {
      routes.add('/');
    } else if (rel.endsWith(`/${INDEX_FILE}`)) {
      routes.add('/' + rel.slice(0, -INDEX_FILE.length));
    } else {
      routes.add('/' + rel.replace(/\.html$/, '/'));
    }
  }

  return [...routes].sort();
}

// Maps a request URL (or pathname) to a file in dist/, mirroring static-host
// semantics: the path itself, its directory index.html, or the extensionless
// .html variant. Returns null when nothing matches.
export 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 null;
}

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',
};

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

// Serves dist/ on an ephemeral localhost port, falling back to 404.html.
// Returns the server (callers own closing it) and the assigned port.
export async function startDistServer() {
  const server = createServer(async (req, res) => {
    try {
      const file = (await resolveFile(req.url ?? '/')) ?? path.join(dist, '404.html');
      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));
  return { server, port: server.address().port };
}
