/**
 * Pure static-file resolver for the browser HTTPS server (D1). Serves the built relay-web bundle
 * SAME-ORIGIN as the browser WSS so the page's Origin and CSP stay aligned with the WS endpoint.
 *
 * Deterministic given (root, urlPath): it resolves the request path under `root`, reads the file,
 * and returns its bytes + Content-Type on a hit — or `null` when the path is malformed, escapes
 * `root` (traversal), or names no readable regular file. The caller maps `null` to a 404.
 *
 * Security (STRICT traversal guard): the resolved absolute path MUST be `root` itself or a
 * descendant of it. `../`, percent-encoded `..` (`%2e%2e`), and NUL bytes all reject to `null` —
 * this function never reads a byte outside `root`.
 */
import { readFileSync, statSync } from 'node:fs'
import { resolve, sep, extname } from 'node:path'

export interface StaticFile {
  readonly status: number
  readonly headers: Record<string, string>
  readonly body: Buffer
}

/** Served for a bare `/` request. */
const DEFAULT_DOC = 'index.html'
const OCTET_STREAM = 'application/octet-stream'

/** Extension → Content-Type. Covers what the relay-web bundle ships plus common static assets. */
const MIME_BY_EXT: Readonly<Record<string, string>> = {
  '.html': 'text/html; charset=utf-8',
  '.js': 'text/javascript; charset=utf-8',
  '.mjs': 'text/javascript; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.map': 'application/json; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.svg': 'image/svg+xml',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.ico': 'image/x-icon',
  '.webmanifest': 'application/manifest+json',
  '.woff': 'font/woff',
  '.woff2': 'font/woff2',
  '.txt': 'text/plain; charset=utf-8',
}

function contentTypeFor(filePath: string): string {
  return MIME_BY_EXT[extname(filePath).toLowerCase()] ?? OCTET_STREAM
}

/**
 * Strip query/hash, percent-decode, and reject malformed / NUL-byte paths. Returns the path made
 * relative to `root` (leading slashes stripped, `/` → `index.html`), or `null` to reject.
 */
function normalizeUrlPath(urlPath: string): string | null {
  const noQuery = urlPath.split('?')[0].split('#')[0]
  let decoded: string
  try {
    decoded = decodeURIComponent(noQuery)
  } catch {
    return null
  }
  if (decoded.includes('\0')) return null
  const rel = decoded.replace(/^\/+/, '')
  return rel.length === 0 ? DEFAULT_DOC : rel
}

/**
 * Resolve `urlPath` to a file under `root`. Returns the file bytes + MIME on a hit, or `null` when
 * the path is malformed, escapes `root`, or names no readable regular file.
 */
export function serveStatic(root: string, urlPath: string): StaticFile | null {
  const rel = normalizeUrlPath(urlPath)
  if (rel === null) return null

  const rootAbs = resolve(root)
  const resolved = resolve(rootAbs, rel)

  // STRICT traversal guard: resolved must be the root itself or a descendant of it.
  if (resolved !== rootAbs && !resolved.startsWith(rootAbs + sep)) return null

  try {
    if (!statSync(resolved).isFile()) return null
    const body = readFileSync(resolved)
    return {
      status: 200,
      headers: {
        'content-type': contentTypeFor(resolved),
        'content-length': String(body.length),
      },
      body,
    }
  } catch {
    // ENOENT / EACCES / etc. — treat as "no servable file", caller 404s.
    return null
  }
}
