import type {
  HttpContractConfig,
  InferHeaderSchemaOutput,
  InferOutput,
  StandardSchema,
} from "../contracts/index.js";
import type { AnyPorts } from "../ports/index.js";
import type {
  TrustedProxyConfig,
  TrustedRequestInfo,
} from "./trusted-proxy.js";

/**
 * Framework-neutral request shape consumed by Beignet server adapters.
 *
 * Platform adapters should convert their native request into this shape before
 * passing it to `server.api(...)` or a single route handler.
 */
export interface HttpRequestLike {
  /**
   * HTTP method as received from the platform.
   */
  method: string;
  /**
   * Absolute request URL.
   */
  url: string;
  /**
   * Request headers.
   */
  headers: Headers;
  /**
   * The platform request when an adapter has one available.
   *
   * Use this as an escape hatch for platform-specific APIs. Prefer the
   * framework-agnostic methods below when possible.
   */
  raw?: Request;
  /**
   * Parse the request body as JSON.
   */
  json(): Promise<unknown>;
  /**
   * Parse the request body as text.
   */
  text(): Promise<string>;
  /**
   * Parse the request body as an array buffer when the platform supports it.
   */
  arrayBuffer?(): Promise<ArrayBuffer>;
  /**
   * Parse the request body as a Blob when the platform supports it.
   */
  blob?(): Promise<Blob>;
  /**
   * Parse the request body as form data when the platform supports it.
   */
  formData?(): Promise<FormData>;
  /**
   * Clone the request when the platform supports replaying the body.
   */
  clone?(): HttpRequestLike;
}

/**
 * Framework-neutral response object returned by route handlers and hooks.
 */
export interface HttpResponseLike {
  /**
   * HTTP status code.
   */
  status: number;
  /**
   * Response headers.
   */
  headers?: Record<string, string>;
  /**
   * JSON-serializable body or an adapter-specific body value.
   */
  body?: unknown;
}

/**
 * Response accepted by Beignet handlers.
 *
 * Use `HttpResponseLike` for framework-neutral responses. Return a native
 * `Response` only when the current adapter can pass it through unchanged.
 */
export type HttpResponse = HttpResponseLike | Response;

/**
 * Framework-neutral Beignet API handler consumed by HTTP adapters.
 */
export type HttpAdapterApiHandler = (
  req: HttpRequestLike,
) => Promise<HttpResponse>;

/**
 * Native handler shape produced by an HTTP adapter.
 */
export type HttpAdapterHandler<NativeRequest, NativeResponse> = (
  req: NativeRequest,
) => Promise<NativeResponse>;

/**
 * Contract implemented by packages that adapt Beignet's framework-neutral
 * server runtime to a platform HTTP API.
 *
 * Core owns request parsing, hooks, route matching, validation, error mapping,
 * response ownership, and provider lifecycle. Adapters own only the conversion
 * between the platform request/response types and Beignet's `HttpRequestLike`
 * / `HttpResponse` boundary.
 */
export interface HttpAdapter<NativeRequest, NativeResponse> {
  /**
   * Human-readable adapter name for diagnostics and documentation.
   */
  name: string;
  /**
   * Convert a platform request into Beignet's framework-neutral request shape.
   */
  toRequestLike(req: NativeRequest): HttpRequestLike;
  /**
   * Convert a Beignet response into the platform response type.
   */
  toNativeResponse(res: HttpResponse): NativeResponse | Promise<NativeResponse>;
  /**
   * Wrap a Beignet API handler in the platform's native handler shape.
   */
  createHandler(
    handler: HttpAdapterApiHandler,
  ): HttpAdapterHandler<NativeRequest, NativeResponse>;
}

type InferSchemaOrFallback<
  T extends StandardSchema | null,
  Fallback,
> = T extends StandardSchema ? InferOutput<T> : Fallback;

/**
 * Infer the handler path parameter type for a contract.
 */
export type InferPath<C extends HttpContractConfig> = InferSchemaOrFallback<
  C["pathParams"],
  Record<string, string>
>;

/**
 * Infer the handler query parameter type for a contract.
 */
export type InferQuery<C extends HttpContractConfig> = InferSchemaOrFallback<
  C["query"],
  Record<string, string | string[]>
>;

/**
 * Infer the handler request body type for a contract.
 */
export type InferBody<C extends HttpContractConfig> = InferSchemaOrFallback<
  C["body"],
  unknown
>;

/**
 * Infer the merged request header type for a contract.
 */
export type InferHeaders<C extends HttpContractConfig> =
  InferHeaderSchemaOutput<Exclude<C["headers"], undefined>> extends undefined
    ? Record<string, string>
    : InferHeaderSchemaOutput<Exclude<C["headers"], undefined>>;

/**
 * Arguments passed to a route handler after request parsing and validation.
 */
export interface HandlerArgs<Ctx, C extends HttpContractConfig> {
  /**
   * Framework-neutral request.
   */
  req: HttpRequestLike;
  /**
   * Application context assembled by the server context blueprint.
   */
  ctx: Ctx;
  /**
   * Matched contract config.
   */
  contract: C;

  /**
   * Parsed path parameters.
   */
  path: InferPath<C>;
  /**
   * Parsed query parameters.
   */
  query: InferQuery<C>;
  /**
   * Parsed request headers.
   */
  headers: InferHeaders<C>;
  /**
   * Parsed request body.
   */
  body: InferBody<C>;
}

/**
 * Route handler function for a contract.
 */
export type Handler<Ctx, C extends HttpContractConfig> = (
  args: HandlerArgs<Ctx, C>,
) => Promise<HttpResponse> | HttpResponse;

/**
 * Value or promise of that value.
 */
export type MaybePromise<T> = T | Promise<T>;

/**
 * Arguments passed to a route-scoped hook after request parsing and context
 * creation.
 */
export type RouteHookArgs<
  Ctx,
  C extends HttpContractConfig = HttpContractConfig,
> = HandlerArgs<Ctx, C>;

/**
 * Hook that runs only for the route or route group where it is attached.
 *
 * Route hooks are for scoped policy and context enrichment such as
 * authentication, tenant resolution, feature gates, and idempotency. They add
 * fields to the handler context instead of replacing the app context.
 *
 * Hook additions must not include `gate`: the server re-attaches the gate
 * declared by the context blueprint after every hook, so identity changes are
 * picked up automatically.
 */
export interface RouteHook<
  Ctx,
  AddedCtx extends object & { gate?: never } = Record<string, never>,
> {
  /**
   * Optional name used in diagnostics and devtools.
   */
  name?: string;
  /**
   * Resolve additional context for this route or throw to stop handling.
   */
  resolve: (args: RouteHookArgs<Ctx>) => MaybePromise<AddedCtx | undefined>;
}

type AddedCtxFromHook<Hook> =
  Hook extends RouteHook<infer _Ctx, infer AddedCtx> ? AddedCtx : unknown;

type UnionToIntersection<Union> = (
  Union extends unknown
    ? (value: Union) => void
    : never
) extends (value: infer Intersection) => void
  ? Intersection
  : never;

/**
 * Intersection of the context fields added by a route hook list.
 */
export type AddedCtxFromHooks<Hooks extends readonly unknown[]> =
  Hooks extends readonly []
    ? unknown
    : UnionToIntersection<AddedCtxFromHook<Hooks[number]>>;

/**
 * Hook that runs after a route is matched but before request parsing and
 * context creation.
 *
 * Returning a response short-circuits the rest of the request pipeline.
 */
export type OnRequestHook<
  Ports extends AnyPorts = AnyPorts,
  C extends HttpContractConfig = HttpContractConfig,
> = (args: {
  req: HttpRequestLike;
  requestInfo: TrustedRequestInfo;
  ports: Ports;
  contract: C;
  params: Record<string, string>;
}) => MaybePromise<HttpResponse | undefined>;

/**
 * Result from a `beforeHandle` hook.
 *
 * Returning a plain response short-circuits the handler. Returning an object can
 * replace the context, short-circuit with a response, or do both.
 */
export type BeforeHandleResult<Ctx> =
  | undefined
  | HttpResponse
  | {
      ctx?: Ctx;
      response?: HttpResponse;
    };

/**
 * Hook that runs after request parsing/context creation and before the handler.
 */
export type BeforeHandleHook<
  Ctx,
  C extends HttpContractConfig = HttpContractConfig,
> = (args: {
  req: HttpRequestLike;
  requestInfo: TrustedRequestInfo;
  ctx: Ctx;
  contract: C;
  path: InferPath<C>;
  query: InferQuery<C>;
  headers: InferHeaders<C>;
  body: InferBody<C>;
}) => MaybePromise<BeforeHandleResult<Ctx>>;

/**
 * Hook that runs before the response is returned.
 *
 * Return a response to replace or decorate the outgoing response. Hooks run in
 * declaration order. For native web `Response` results the hook receives a
 * headers-only view and only header changes are applied.
 */
export type BeforeSendHook<
  Ctx,
  C extends HttpContractConfig = HttpContractConfig,
> = (args: {
  req: HttpRequestLike;
  requestInfo: TrustedRequestInfo;
  ctx?: Ctx;
  contract: C;
  path?: InferPath<C>;
  query?: InferQuery<C>;
  headers?: InferHeaders<C>;
  body?: InferBody<C>;
  response: HttpResponseLike;
  error?: unknown;
  /**
   * True when the route returned a native web Response. The response argument
   * is a headers-only view ({ status, headers }); the body is not readable and
   * returned body/status changes are ignored. Header changes are merged onto
   * the native Response.
   */
  native?: boolean;
}) => MaybePromise<HttpResponseLike | undefined>;

/**
 * Per-stage timing breakdown of one request, in milliseconds.
 *
 * Stages are measured around the pipeline phases in execution order:
 * `onRequest` hooks, request parsing/validation, context creation, route
 * hooks plus `beforeHandle` hooks, the route handler, and response
 * preparation (`beforeSend`, response validation, and finalizers). Stages
 * that did not run for a request — parsing on raw routes, the handler after
 * a hook short-circuit — report `0`. The stages do not sum exactly to the
 * request `durationMs`; routing and bookkeeping live in the gaps.
 */
export type RequestStageTimings = {
  onRequestMs: number;
  parseMs: number;
  contextMs: number;
  beforeHandleMs: number;
  handlerMs: number;
  sendMs: number;
};

/**
 * Hook that runs after the response has been prepared.
 *
 * This is for logging and observability. Errors thrown by `afterSend` hooks are
 * ignored by the server pipeline.
 */
export type AfterSendHook<
  Ctx,
  C extends HttpContractConfig = HttpContractConfig,
> = (args: {
  req: HttpRequestLike;
  requestInfo: TrustedRequestInfo;
  ctx?: Ctx;
  contract: C;
  path?: InferPath<C>;
  query?: InferQuery<C>;
  headers?: InferHeaders<C>;
  body?: InferBody<C>;
  response: HttpResponseLike;
  error?: unknown;
  durationMs: number;
  /**
   * Per-stage timing breakdown of this request.
   */
  stages: RequestStageTimings;
}) => MaybePromise<void>;

/**
 * Hook notified when the framework catches an error while handling a request.
 */
export type ServerCaughtErrorHook<
  Ctx,
  C extends HttpContractConfig = HttpContractConfig,
> = (args: {
  err: unknown;
  req: HttpRequestLike;
  requestInfo?: TrustedRequestInfo;
  ctx?: Ctx;
  contract: C;
  path?: InferPath<C>;
  query?: InferQuery<C>;
  headers?: InferHeaders<C>;
  body?: InferBody<C>;
}) => MaybePromise<void>;

/**
 * Hook that may map an unexpected error to a custom response.
 *
 * Return `undefined` to let the server's default unhandled-error mapper create
 * the response.
 */
export type ServerUnhandledErrorMapper<
  Ctx,
  C extends HttpContractConfig = HttpContractConfig,
> = (args: {
  err: unknown;
  req: HttpRequestLike;
  requestInfo?: TrustedRequestInfo;
  ctx?: Ctx;
  contract: C;
  path?: InferPath<C>;
  query?: InferQuery<C>;
  headers?: InferHeaders<C>;
  body?: InferBody<C>;
}) => MaybePromise<HttpResponse | undefined>;

/**
 * Server lifecycle hook collection.
 *
 * Hooks run in the order they are registered within each server-hook phase.
 * `onRequest` can short-circuit before context creation; route hooks resolve
 * route-scoped context before server `beforeHandle`; `beforeHandle` can replace
 * context or short-circuit; `beforeSend` can replace the outgoing response, and
 * route-owned replacements are contract-validated before send; `afterSend`
 * observes the final response.
 */
export interface ServerHook<Ctx, Ports extends AnyPorts = AnyPorts> {
  /**
   * Optional name used in diagnostics and devtools.
   */
  name?: string;
  /**
   * Validates the hook configuration against the registered contracts.
   *
   * Invoked once at startup, right after `createServer(...)` collects the
   * contracts from the `routes` option and before provider setup. Throw to
   * fail startup instead of degrading silently at request time. Contracts
   * registered later through `server.route(...)` are not seen here, so hooks
   * that need full coverage should keep a runtime check as a backstop.
   */
  validate?: (args: {
    contracts: readonly HttpContractConfig[];
    /**
     * Server-level trusted-proxy policy. Request phases receive its resolved
     * value as `requestInfo`.
     */
    trustedProxy?: TrustedProxyConfig;
  }) => void;
  /**
   * Runs after route matching and before body/query/header parsing.
   */
  onRequest?: OnRequestHook<Ports>;
  /**
   * Runs after request parsing, context creation, and route hook resolution.
   */
  beforeHandle?: BeforeHandleHook<Ctx>;
  /**
   * Runs before the response is returned. Native web `Response` results get a
   * headers-only view with `native: true`.
   */
  beforeSend?: BeforeSendHook<Ctx>;
  /**
   * Observes the final response after send preparation.
   */
  afterSend?: AfterSendHook<Ctx>;
  /**
   * Observes framework-caught errors.
   */
  onCaughtError?: ServerCaughtErrorHook<Ctx>;
  /**
   * Maps unexpected errors to responses.
   */
  mapUnhandledError?: ServerUnhandledErrorMapper<Ctx>;
}

/**
 * Compiled route entry used by server internals and adapter helpers.
 */
export type ResolvedRoute<_Ctx, C extends HttpContractConfig> = {
  /**
   * Contract config for the route.
   */
  contract: C;
  /**
   * Handler that receives the raw request and optional path params.
   */
  handler: (
    req: HttpRequestLike,
    params?: Record<string, string>,
  ) => Promise<HttpResponse>;
  /**
   * Test whether the route matches an incoming method and pathname.
   */
  match: (
    method: string,
    pathname: string,
  ) => { matched: true } | { matched: false };
};
