/**
 * Error + status pages. Every page here is a self-contained HTML document —
 * inline CSS only, no external stylesheet, no JavaScript (so it renders under the
 * strict CSP, even when the app's assets are what failed). Theme-aware via
 * `prefers-color-scheme`, styled in the WrNexus design language (ink-navy,
 * azure, a faint blueprint grid + glow). Development shows the stack trace;
 * production never leaks internal paths.
 */

import { escapeHtml } from "./security.ts";

export type Mode = "development" | "production";

interface ErrorPageOptions {
  status: number;
  /** Big display code, e.g. "404" / "500". */
  code: string;
  title: string;
  message: string;
  /** Monospace eyebrow, e.g. "ERROR 404". */
  eyebrow?: string;
  /** Optional dev-only detail (error name + stack), rendered in a code panel. */
  detail?: { heading: string; body: string };
  /** Show a "Back home" action (default true). */
  home?: boolean;
}

/** Shared, self-contained, theme-aware error document. */
function errorDocument(o: ErrorPageOptions): string {
  const eyebrow = escapeHtml(o.eyebrow ?? `ERROR ${o.status}`);
  const title = escapeHtml(o.title);
  const message = escapeHtml(o.message);
  const detail = o.detail
    ? `
      <section class="detail">
        <div class="detail-head">${escapeHtml(o.detail.heading)}</div>
        <pre class="detail-body">${escapeHtml(o.detail.body)}</pre>
      </section>`
    : "";
  const home = o.home === false ? "" : `<a class="btn btn-primary" href="/">Back to home</a>`;

  return `<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="robots" content="noindex" />
    <title>${title}</title>
    <style>
      :root {
        --bg: #0a0e17; --bg2: #070a12; --text: #e7ecf5; --muted: #93a1b8;
        --brand: #6ea0ff; --brand-2: #3f7dff; --border: rgba(255,255,255,.10);
        --card: rgba(255,255,255,.03); --grid: rgba(110,160,255,.10);
      }
      @media (prefers-color-scheme: light) {
        :root {
          --bg: #f7f9fc; --bg2: #eef2f8; --text: #0f172a; --muted: #5a6b85;
          --brand: #2b62f0; --brand-2: #2b62f0; --border: rgba(15,23,42,.10);
          --card: rgba(15,23,42,.02); --grid: rgba(43,98,240,.09);
        }
      }
      * { box-sizing: border-box; }
      html, body { height: 100%; }
      body {
        margin: 0; background: var(--bg); color: var(--text);
        font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
        display: grid; place-items: center; min-height: 100%;
        padding: clamp(1.5rem, 5vw, 4rem); position: relative; overflow-x: hidden;
      }
      /* Blueprint grid + radial glow backdrop. */
      body::before {
        content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
        background-image:
          linear-gradient(to right, var(--grid) 1px, transparent 1px),
          linear-gradient(to bottom, var(--grid) 1px, transparent 1px);
        background-size: 56px 56px;
        -webkit-mask-image: radial-gradient(ellipse 75% 60% at 50% 30%, #000 10%, transparent 72%);
        mask-image: radial-gradient(ellipse 75% 60% at 50% 30%, #000 10%, transparent 72%);
      }
      body::after {
        content: ""; position: fixed; left: 50%; top: -10%; z-index: 0; pointer-events: none;
        width: min(680px, 90vw); height: 420px; transform: translateX(-50%);
        background: radial-gradient(circle at center, color-mix(in oklab, var(--brand-2) 34%, transparent), transparent 68%);
        filter: blur(8px); opacity: .55;
      }
      main {
        position: relative; z-index: 1; width: 100%; max-width: 640px; text-align: center;
        animation: rise .6s cubic-bezier(.16,1,.3,1) both;
      }
      @keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
      @media (prefers-reduced-motion: reduce) { main { animation: none; } }
      .eyebrow {
        font: 600 .72rem/1 ui-monospace, "SFMono-Regular", Menlo, monospace;
        letter-spacing: .22em; color: var(--brand); text-transform: uppercase;
      }
      .code {
        margin: .5rem 0 0; font-weight: 800; line-height: .9;
        font-size: clamp(5rem, 22vw, 11rem); letter-spacing: -.04em;
        background: linear-gradient(180deg, var(--text), color-mix(in oklab, var(--brand) 60%, var(--text)));
        -webkit-background-clip: text; background-clip: text; color: transparent;
      }
      h1 { margin: .25rem 0 0; font-size: clamp(1.4rem, 4vw, 2rem); font-weight: 700; letter-spacing: -.02em; }
      .msg { margin: .9rem auto 0; max-width: 30rem; color: var(--muted); line-height: 1.65; font-size: 1rem; }
      .actions { margin-top: 2rem; display: flex; flex-wrap: wrap; gap: .75rem; justify-content: center; }
      .btn {
        display: inline-flex; align-items: center; gap: .5rem; text-decoration: none;
        padding: .7rem 1.25rem; border-radius: 10px; font-weight: 600; font-size: .9rem;
        transition: transform .12s ease, filter .12s ease, border-color .12s ease;
      }
      .btn:active { transform: translateY(1px); }
      .btn-primary {
        color: #fff; background: linear-gradient(180deg, var(--brand), var(--brand-2));
        box-shadow: 0 8px 24px -10px color-mix(in oklab, var(--brand-2) 80%, transparent);
      }
      .btn-primary:hover { filter: brightness(1.08); }
      .detail {
        margin: 2.25rem auto 0; text-align: left; max-width: 100%;
        border: 1px solid var(--border); border-radius: 12px; background: var(--card); overflow: hidden;
      }
      .detail-head {
        padding: .7rem 1rem; font: 600 .75rem/1 ui-monospace, "SFMono-Regular", Menlo, monospace;
        color: var(--brand); border-bottom: 1px solid var(--border);
        white-space: pre-wrap; word-break: break-word;
      }
      .detail-body {
        margin: 0; padding: 1rem; max-height: 40vh; overflow: auto;
        font: .8rem/1.6 ui-monospace, "SFMono-Regular", Menlo, monospace;
        color: var(--muted); white-space: pre-wrap; word-break: break-word;
      }
    </style>
  </head>
  <body>
    <main>
      <p class="eyebrow">${eyebrow}</p>
      <div class="code" aria-hidden="true">${escapeHtml(o.code)}</div>
      <h1>${title}</h1>
      <p class="msg">${message}</p>
      <div class="actions">${home}</div>
      ${detail}
    </main>
  </body>
</html>`;
}

/** Common status → friendly copy, for a generic HTML status page. */
const STATUS_COPY: Record<number, { title: string; message: string }> = {
  400: {
    title: "Bad request",
    message: "The request couldn't be understood. Check the URL and try again.",
  },
  401: { title: "Sign in required", message: "You need to be signed in to view this page." },
  403: { title: "Access denied", message: "You don't have permission to view this page." },
  404: {
    title: "Page not found",
    message: "The page you're looking for doesn't exist or has moved.",
  },
  413: { title: "Too large", message: "The request was larger than the server allows." },
  429: {
    title: "Slow down",
    message: "You've made too many requests. Please wait a moment and try again.",
  },
  500: {
    title: "Something went wrong",
    message: "The server hit an unexpected error. Please try again in a moment.",
  },
  502: {
    title: "Bad gateway",
    message: "We couldn't reach an upstream service. Please try again shortly.",
  },
  503: {
    title: "Temporarily unavailable",
    message: "The service is down for a moment. Please try again shortly.",
  },
};

/** A beautiful, self-contained HTML page for any 4xx/5xx status. */
export function renderStatusPage(status: number): Response {
  const copy = STATUS_COPY[status] ?? {
    title: status >= 500 ? "Something went wrong" : "Something's not right",
    message: "An unexpected response was returned. Please try again.",
  };
  return new Response(
    errorDocument({ status, code: String(status), title: copy.title, message: copy.message }),
    { status, headers: { "content-type": "text/html; charset=utf-8" } },
  );
}

/** Readable, styled development error page — includes the stack trace. */
export function renderDevError(err: unknown, status = 500): Response {
  const error = err instanceof Error ? err : new Error(String(err));
  const name = error.name || "Error";
  const message = error.message || "Unknown error";
  return new Response(
    errorDocument({
      status,
      code: String(status),
      eyebrow: "DEVELOPMENT ERROR",
      title: name,
      message,
      detail: { heading: `${name}: ${message}`, body: error.stack || "(no stack available)" },
    }),
    { status, headers: { "content-type": "text/html; charset=utf-8" } },
  );
}

/** Generic production error page — no stack, no file paths. */
export function renderProdError(status = 500): Response {
  return renderStatusPage(status);
}

/** Pick the right error page for the current mode. */
export function renderError(err: unknown, mode: Mode): Response {
  return mode === "development" ? renderDevError(err) : renderProdError();
}

/** Beautiful 404 page. */
export function renderNotFound(): Response {
  return renderStatusPage(404);
}
