/**
 * Core request context and middleware contracts.
 *
 * The `Context` object is the single value that flows through middleware,
 * pages and API routes. It is intentionally small and framework-agnostic so
 * it can later be reused by the `.wrn` compiler output.
 */

import type { Tenant } from "./tenant.ts";
import type { Tracer } from "./observability.ts";

import {
  applyCookieHeaders,
  createCookieStore,
  createLocalStorageSnapshot,
  createSessionStore,
  type CookieStore,
  type LocalStorageSnapshot,
  type SessionStore,
} from "./storage.ts";

/** Translate a key for the active language, interpolating `{param}` placeholders. */
export type TFunction = (key: string, params?: Record<string, string | number>) => string;

export type Context = {
  /** The raw incoming web-standard Request. */
  req: Request;
  /** Parsed URL of the request (pathname, query, etc.). */
  url: URL;
  /** Active language for this request (resolved by the runtime); "" if i18n is unused. */
  lang: string;
  /** Translate a key for the active language (identity until the runtime sets it). */
  t: TFunction;
  /** Dynamic route params, e.g. `/users/[id]` -> `{ id: "42" }`. */
  params: Record<string, string>;
  /**
   * Per-request scratch space. Middleware can attach values here
   * (e.g. the authenticated user) and downstream handlers can read them.
   */
  locals: Record<string, unknown>;
  /**
   * The authenticated user for this request, or null when anonymous. Populated
   * by the `sessionAuth` middleware (or `logIn`); read via `getUser(ctx)`.
   */
  user?: unknown;
  /** Active tenant/workspace resolved by tenant middleware. */
  tenant?: Tenant;
  /** Request tracer installed by observability middleware. */
  tracer?: Tracer;
  /**
   * The direct socket peer IP, set by the server from `server.requestIP`. This
   * is NOT spoofable by request headers — prefer it over `x-forwarded-for` for
   * rate limiting unless you run behind a trusted proxy.
   */
  ip?: string;
  /** Read/write HTTP cookies for the current response. */
  cookies: CookieStore;
  /** In-memory cookie-backed session store. */
  session: SessionStore;
  /** Read-only localStorage snapshot sent by the browser for CSR data bindings. */
  localStorage: LocalStorageSnapshot;
};

/** Calls the next middleware in the chain (or the final route handler). */
export type Next = () => Promise<Response> | Response;

/**
 * Middleware runs before pages and API routes. It can:
 *  - inspect/modify `ctx`
 *  - short-circuit by returning a `Response` without calling `next()`
 *  - continue by returning `await next()`
 */
export type Middleware = (ctx: Context, next: Next) => Promise<Response> | Response;

/** SEO metadata rendered into the document `<head>`. */
export type SeoConfig = {
  /** BCP 47 document language used on `<html lang>` (default: `en`). */
  lang?: string;
  title?: string;
  titleTemplate?: string;
  description?: string;
  canonical?: string;
  canonicalBase?: string;
  robots?: string;
  keywords?: string | string[];
  image?: string;
  siteName?: string;
  type?: string;
  locale?: string;
  twitterCard?: string;
  twitterSite?: string;
  themeColor?: string;
};

/** Page metadata rendered into the document `<head>`. */
export type PageMeta = SeoConfig;

/** A page module's default export. Returns an HTML string for the body. */
export type PageComponent = (ctx: Context) => string | Promise<string>;

/** Create a fresh context for an incoming request. */
export function createContext(req: Request, url: URL): Context {
  const cookies = createCookieStore(req);
  return {
    req,
    url,
    params: {},
    locals: {},
    lang: "",
    t: (key) => key,
    cookies,
    // `url` already reflects X-Forwarded-Proto when trustProxy is on, so session
    // cookies get `Secure` behind a TLS-terminating proxy (matches CSRF cookies).
    session: createSessionStore(cookies, req, undefined, url.protocol === "https:"),
    localStorage: createLocalStorageSnapshot(req),
  };
}

/** Apply headers accumulated on the context, such as Set-Cookie. */
export function withContextHeaders(ctx: Context, res: Response): Response {
  const headers = new Headers(res.headers);
  applyCookieHeaders(ctx, headers);
  return new Response(res.body, {
    status: res.status,
    statusText: res.statusText,
    headers,
  });
}
