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

export type ExecutionKind =
  "http" | "api" | "action" | "loader" | "middleware" | "realtime" | "queue" | "cron" | "webhook";
export interface ResponseContext {
  status: number;
  headers: Headers;
  setStatus(status: number): void;
}
export interface ExecutionContext {
  kind: ExecutionKind;
  id: string;
  request: Request;
  response: ResponseContext;
  user: unknown | null;
  session: unknown | null;
  tenant: Tenant | null;
  locale: string;
  timezone: string;
  db?: unknown;
  cache?: unknown;
  logger?: unknown;
  trace?: Tracer;
  signal: AbortSignal;
  deadline: Date | null;
  metadata: Record<string, unknown>;
  authorize(permission: string): void | Promise<void>;
}
export interface ExecutionContextInput extends Partial<
  Omit<
    ExecutionContext,
    "kind" | "id" | "request" | "response" | "signal" | "deadline" | "metadata" | "authorize"
  >
> {
  kind: ExecutionKind;
  id?: string;
  request?: Request;
  response?: Partial<Pick<ResponseContext, "status">> & { headers?: HeadersInit };
  signal?: AbortSignal;
  deadline?: Date | number | null;
  timeoutMs?: number;
  metadata?: Record<string, unknown>;
  authorize?: (permission: string) => void | Promise<void>;
}
export function createExecutionContext(input: ExecutionContextInput): ExecutionContext {
  const controller = new AbortController();
  const source = input.signal;
  if (source?.aborted) controller.abort(source.reason);
  else source?.addEventListener("abort", () => controller.abort(source.reason), { once: true });
  const deadline =
    input.deadline instanceof Date
      ? input.deadline
      : typeof input.deadline === "number"
        ? new Date(input.deadline)
        : input.timeoutMs !== undefined
          ? new Date(Date.now() + input.timeoutMs)
          : null;
  let timer: ReturnType<typeof setTimeout> | undefined;
  if (deadline) {
    const delay = deadline.getTime() - Date.now();
    if (delay <= 0)
      controller.abort(new DOMException("Execution deadline exceeded", "TimeoutError"));
    else {
      timer = setTimeout(
        () => controller.abort(new DOMException("Execution deadline exceeded", "TimeoutError")),
        delay,
      );
      timer.unref?.();
    }
  }
  controller.signal.addEventListener(
    "abort",
    () => {
      if (timer) clearTimeout(timer);
    },
    { once: true },
  );
  const response: ResponseContext = {
    status: input.response?.status ?? 200,
    headers: new Headers(input.response?.headers),
    setStatus(status) {
      if (!Number.isInteger(status) || status < 100 || status > 599)
        throw new RangeError("response status must be an HTTP status code");
      this.status = status;
    },
  };
  return {
    kind: input.kind,
    id: input.id ?? crypto.randomUUID(),
    request: input.request ?? new Request(`https://execution.wrnexus.invalid/${input.kind}`),
    response,
    user: input.user ?? null,
    session: input.session ?? null,
    tenant: input.tenant ?? null,
    locale: input.locale ?? "en",
    timezone: input.timezone ?? "UTC",
    db: input.db,
    cache: input.cache,
    logger: input.logger,
    trace: input.trace,
    signal: controller.signal,
    deadline,
    metadata: { ...(input.metadata ?? {}) },
    authorize:
      input.authorize ??
      (() => {
        throw new Error("WRN-AUTHORIZATION-NOT-CONFIGURED");
      }),
  };
}
export function executionContextFromHttp(
  context: Context,
  kind: Extract<
    ExecutionKind,
    "http" | "api" | "action" | "loader" | "middleware" | "webhook"
  > = "http",
  input: Omit<
    ExecutionContextInput,
    "kind" | "request" | "user" | "tenant" | "locale" | "trace"
  > = {},
): ExecutionContext {
  return createExecutionContext({
    ...input,
    kind,
    request: context.req,
    user: context.user ?? null,
    session: context.session,
    tenant: context.tenant ?? null,
    locale: context.lang || "en",
    trace: context.tracer,
    signal: input.signal ?? context.req.signal,
    db: input.db ?? context.locals.db,
    cache: input.cache ?? context.locals.cache,
    logger: input.logger ?? context.locals.logger,
    metadata: { ...context.locals, ...(input.metadata ?? {}) },
  });
}
