/**
 * Per-request context propagated via AsyncLocalStorage.
 *
 * Every API request carries an immutable {requestId, portId, userId,
 * method, path} bag that is available from anywhere in the call stack
 * without threading it through every function signature. This is what
 * lets `logger.info(...)` deep inside a service call automatically
 * stamp the originating request id into the log line, and what lets
 * `errorResponse` know which request to attach to a persisted
 * `error_events` row.
 *
 * Why ALS over an explicit context arg: 80% of the codebase is already
 * written; threading `RequestContext` through every helper would touch
 * hundreds of files and break domain isolation. ALS is the standard
 * Node-side pattern (Express + Pino + many production services use
 * the exact same approach).
 *
 * Wiring:
 *   - The `withAuth` wrapper in `src/lib/api/helpers.ts` calls
 *     `runWithRequestContext({...}, () => handler(...))` so every code
 *     path inside the request runs inside the ALS frame.
 *   - The pino logger in `src/lib/logger.ts` mixes `getRequestContext()`
 *     into every emitted log line via the `mixin` hook.
 *   - `errorResponse(err)` reads the same context to build the user-
 *     facing error envelope and to persist a row to `error_events`.
 */

import { AsyncLocalStorage } from 'node:async_hooks';

export interface RequestContext {
  /** UUID - surfaces in `X-Request-Id` response header + every log line. */
  requestId: string;
  /** Active port for this request (empty string for super-admin pre-port). */
  portId: string;
  /** better-auth user id (empty string for unauthenticated paths). */
  userId: string;
  /** HTTP method - recorded for error_events triage. */
  method: string;
  /** Pathname (no query string) - recorded for error_events triage. */
  path: string;
  /** Wall-clock ms timestamp at request entry. Used for duration metrics. */
  startedAt: number;
}

const store = new AsyncLocalStorage<RequestContext>();

/**
 * Run `fn` inside a request-context frame. Every call within the
 * resulting callstack - including async work, queue callbacks, and
 * service-layer DB queries - sees the same context via
 * `getRequestContext()`.
 */
export function runWithRequestContext<T>(ctx: RequestContext, fn: () => T): T {
  return store.run(ctx, fn);
}

/**
 * Read the current request context, or `null` when called outside a
 * request (e.g. queue worker, scheduled job). Callers must handle the
 * null case - the logger mixin does so gracefully.
 */
export function getRequestContext(): RequestContext | null {
  return store.getStore() ?? null;
}

/** Convenience accessor for the most common field. */
export function getRequestId(): string | null {
  return store.getStore()?.requestId ?? null;
}
