import type { HttpContractConfig } from "../contracts/index.js";
import {
  httpErrors,
  isAppError,
  toErrorResponseBody,
} from "../errors/index.js";
import {
  IdempotencyConflictError,
  IdempotencyInProgressError,
} from "../idempotency/index.js";
import {
  type MemoInstrumentationEvent,
  runWithMemoScope,
} from "../memo/index.js";
import type { AnyPorts } from "../ports/index.js";
import {
  AuthUnauthorizedError,
  EntitlementRequiredError,
  GateAuthorizationError,
  TenantRequiredError,
} from "../ports/index.js";
import {
  createProviderInstrumentation,
  type ProviderInstrumentationTarget,
  resolveProviderInstrumentationPort,
} from "../providers/index.js";
import {
  parseTraceparent,
  resolveTracingPort,
  runWithTracing,
  type TraceContextInput,
} from "../tracing/index.js";
import type { ContextSeed } from "./context.js";
import type {
  Handler,
  HandlerArgs,
  HttpRequestLike,
  HttpResponse,
  RequestStageTimings,
  RouteHook,
  ServerCaughtErrorHook,
  ServerHook,
  ServerUnhandledErrorMapper,
} from "./http.js";
import type { ServerInstrumentationOptions } from "./instrumentation.js";
import {
  getResponseFinalizerHook,
  type ResponseFinalizerValidationState,
} from "./internal-hooks.js";
import {
  readContextActor,
  readContextTenant,
  setActiveRequestIdentity,
} from "./request-context.js";
import {
  prepareRequestInputs,
  type RequestBodyOptions,
  requestBodyLimit,
  requestHeadersToRecord,
} from "./request-preparation.js";
import {
  defaultErrorResponse,
  errorResponse,
  finalizeResponse,
  isHttpResponseLike,
  isWebResponse,
  mergeNativeResponseHeaders,
  normalizeHttpResponse,
  normalizeResponse,
  ResponseContractViolationError,
  type ResponseOwner,
  responseForHooks,
  responseOwnerFor,
  toContractViolationResponse,
  withContractLifecycleHeaders,
  withFrameworkErrorOwnerHeader,
} from "./response-finalization.js";
import {
  type CompiledPath,
  compilePath,
  decodeMatchedParams,
  PathDecodeError,
} from "./route-matching.js";
import type { TrustedRequestInfo } from "./trusted-proxy.js";
import { InvalidRequestUrlError } from "./trusted-proxy-internal.js";
import { UseCaseRouteInputValidationError } from "./use-case-route.js";

function withoutHeadResponseBody(
  response: HttpResponse,
  requestMethod: string,
): HttpResponse {
  if (requestMethod.toUpperCase() !== "HEAD") return response;

  if (isWebResponse(response)) {
    if (response.body === null) return response;
    void response.body.cancel().catch(() => {});
    return new Response(null, {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
    });
  }

  const { body: _body, ...headResponse } = response;
  return headResponse;
}

type RequestExecutorOptions<Ctx> = {
  instrumentation?: ServerInstrumentationOptions<Ctx> | false;
  validateResponses?: boolean;
  requestBody?: RequestBodyOptions;
  onCaughtError?: ServerCaughtErrorHook<Ctx>;
  mapUnhandledError?: ServerUnhandledErrorMapper<Ctx>;
};

type ExecutionResult<Ctx> = {
  ctx?: Ctx;
  response: HttpResponse;
  error?: unknown;
  owner?: ResponseOwner;
};

type ExecutionTarget<Ctx, C extends HttpContractConfig> = {
  contract: C;
  /**
   * Compiled route pattern. Only required when the executor is invoked
   * without pre-matched params.
   */
  compiled?: CompiledPath;
  handler: Handler<Ctx, C>;
  /**
   * Success status whose response schema validation is skipped because the
   * use case bound to this route already validated its output against the
   * same schema object.
   */
  responseValidationExemptStatus?: number;
};

function copyTrustedRequestInfo(
  requestInfo: TrustedRequestInfo,
): TrustedRequestInfo;
function copyTrustedRequestInfo(requestInfo: undefined): undefined;
function copyTrustedRequestInfo(
  requestInfo: TrustedRequestInfo | undefined,
): TrustedRequestInfo | undefined;
function copyTrustedRequestInfo(
  requestInfo: TrustedRequestInfo | undefined,
): TrustedRequestInfo | undefined {
  if (!requestInfo) return undefined;
  return {
    ...requestInfo,
    url: new URL(requestInfo.url),
  };
}

/**
 * Build the per-request execution pipeline once.
 *
 * The returned executor takes the contract, compiled pattern, and user handler
 * per invocation so fallback responses (404/405) can reuse a single pipeline
 * across requests instead of rebuilding it per unmatched request.
 */
export function createRequestExecutor<
  Ctx,
  FinalPorts extends AnyPorts,
  C extends HttpContractConfig,
>(
  options: RequestExecutorOptions<Ctx>,
  finalPorts: FinalPorts,
  contextRuntime: {
    createRequestContext: (
      req: HttpRequestLike,
      contract: HttpContractConfig,
      requestInfo: TrustedRequestInfo,
    ) => Promise<Ctx>;
    finalizeContext: (seed: ContextSeed<Ctx> | Ctx) => Ctx;
    resolveRequestInfo: (req: HttpRequestLike) => TrustedRequestInfo;
  },
  hooks: ServerHook<Ctx, FinalPorts>[],
  routeHooks: readonly RouteHook<unknown, object>[] = [],
  optionsOverrides?: {
    skipRoutePreparation?: boolean;
    /**
     * Run the route through the full hook pipeline without contract
     * preparation: no query/path/header/body parsing or validation, and the
     * request body is left unconsumed for the handler.
     */
    rawRoute?: boolean;
  },
): (
  target: ExecutionTarget<Ctx, C>,
  req: HttpRequestLike,
  preMatchedParams?: Record<string, string>,
) => Promise<HttpResponse> {
  const warnedNativeReplacementHooks = new WeakSet<object>();
  const maxRequestBodyBytes = requestBodyLimit(options.requestBody);

  const executeRequest = async (
    target: ExecutionTarget<Ctx, C>,
    req: HttpRequestLike,
    preMatchedParams?: Record<string, string>,
  ) => {
    const { contract, handler: userHandler } = target;
    let requestInfo: TrustedRequestInfo | undefined;
    let baseCtx: Ctx | undefined;
    let pathValue: HandlerArgs<Ctx, C>["path"] | undefined;
    let queryValue: HandlerArgs<Ctx, C>["query"] | undefined;
    let headersValue: HandlerArgs<Ctx, C>["headers"] | undefined;
    let bodyValue: HandlerArgs<Ctx, C>["body"] | undefined;
    const startedAt = Date.now();
    // Stage timings accumulate because retries re-enter the send phase and
    // beforeHandle spans the route-hook and server-hook loops.
    const stages: RequestStageTimings = {
      onRequestMs: 0,
      parseMs: 0,
      contextMs: 0,
      beforeHandleMs: 0,
      handlerMs: 0,
      sendMs: 0,
    };
    const timeStage = async <T>(
      stage: keyof RequestStageTimings,
      run: () => Promise<T> | T,
    ): Promise<T> => {
      const stageStartedAt = performance.now();
      try {
        return await run();
      } finally {
        stages[stage] += performance.now() - stageStartedAt;
      }
    };

    const resolveErrorResult = async (
      error: unknown,
      ctx?: Ctx,
      path?: HandlerArgs<Ctx, C>["path"],
      query?: HandlerArgs<Ctx, C>["query"],
      headers?: HandlerArgs<Ctx, C>["headers"],
      body?: HandlerArgs<Ctx, C>["body"],
      resultOptions?: {
        owner?: ResponseOwner;
      },
    ): Promise<ExecutionResult<Ctx>> => {
      let currentError = error;

      const notifyCaughtError = async (caught: unknown) => {
        const args = {
          err: caught,
          req,
          requestInfo: copyTrustedRequestInfo(requestInfo),
          ctx,
          contract,
          path,
          query,
          headers,
          body,
        };
        for (const hook of hooks) {
          if (!hook.onCaughtError) continue;
          try {
            await hook.onCaughtError(args);
          } catch {
            // Observers must not change response behavior.
          }
        }
        if (options.onCaughtError) {
          try {
            await options.onCaughtError(args);
          } catch {
            // Observers must not change response behavior.
          }
        }
      };

      await notifyCaughtError(currentError);

      if (currentError instanceof ResponseContractViolationError) {
        return {
          ctx,
          response: toContractViolationResponse(currentError),
          error: currentError,
          owner: "framework",
        };
      }

      if (currentError instanceof UseCaseRouteInputValidationError) {
        return {
          ctx,
          response: errorResponse(
            500,
            currentError.code,
            currentError.message,
            {
              contractName: currentError.contractName,
              useCaseName: currentError.useCaseName,
              location: "useCaseInput",
            },
          ),
          error: currentError,
          owner: "framework",
        };
      }

      if (currentError instanceof InvalidRequestUrlError) {
        return {
          ctx,
          response: errorResponse(
            400,
            "INVALID_REQUEST_URL",
            "Malformed request URL",
          ),
          error: currentError,
          owner: "framework",
        };
      }

      if (isAppError(currentError)) {
        return {
          ctx,
          response: {
            status: currentError.status,
            ...(currentError.headers
              ? { headers: { ...currentError.headers } }
              : {}),
            body: toErrorResponseBody(currentError),
          },
          error: currentError,
          owner: resultOptions?.owner ?? "route",
        };
      }

      if (currentError instanceof AuthUnauthorizedError) {
        return {
          ctx,
          response: errorResponse(401, currentError.code, currentError.message),
          error: currentError,
          owner: "framework",
        };
      }

      if (currentError instanceof TenantRequiredError) {
        return {
          ctx,
          response: errorResponse(
            currentError.status,
            currentError.code,
            currentError.message,
          ),
          error: currentError,
          owner: "framework",
        };
      }

      if (currentError instanceof IdempotencyConflictError) {
        return {
          ctx,
          response: errorResponse(
            httpErrors.IdempotencyConflict.status,
            httpErrors.IdempotencyConflict.code,
            currentError.message,
            {
              namespace: currentError.namespace,
              key: currentError.key,
            },
          ),
          error: currentError,
          owner: "framework",
        };
      }

      if (currentError instanceof IdempotencyInProgressError) {
        return {
          ctx,
          response: errorResponse(
            httpErrors.IdempotencyInProgress.status,
            httpErrors.IdempotencyInProgress.code,
            currentError.message,
            {
              namespace: currentError.namespace,
              key: currentError.key,
            },
          ),
          error: currentError,
          owner: "framework",
        };
      }

      if (currentError instanceof GateAuthorizationError) {
        return {
          ctx,
          response: errorResponse(
            currentError.status,
            currentError.code,
            currentError.message,
            currentError.details,
          ),
          error: currentError,
          owner: "framework",
        };
      }

      if (currentError instanceof EntitlementRequiredError) {
        return {
          ctx,
          response: errorResponse(
            currentError.status,
            currentError.code,
            currentError.message,
            currentError.details,
          ),
          error: currentError,
          owner: "framework",
        };
      }

      for (const hook of hooks) {
        if (!hook.mapUnhandledError) continue;
        try {
          const handled = await hook.mapUnhandledError({
            err: currentError,
            req,
            requestInfo: copyTrustedRequestInfo(requestInfo),
            ctx,
            contract,
            path,
            query,
            headers,
            body,
          });
          if (handled) {
            const response = normalizeHttpResponse(handled);
            return {
              ctx,
              response,
              error: currentError,
              owner: responseOwnerFor(response, "framework"),
            };
          }
        } catch (hookError) {
          currentError = hookError;
          await notifyCaughtError(currentError);
        }
      }

      if (options.mapUnhandledError) {
        try {
          const handled = await options.mapUnhandledError({
            err: currentError,
            req,
            requestInfo: copyTrustedRequestInfo(requestInfo),
            ctx,
            contract,
            path,
            query,
            headers,
            body,
          });
          if (handled) {
            const response = normalizeHttpResponse(handled);
            return {
              ctx,
              response,
              error: currentError,
              owner: responseOwnerFor(response, "framework"),
            };
          }
        } catch (hookError) {
          currentError = hookError;
          await notifyCaughtError(currentError);
        }
      }

      return {
        ctx,
        response: defaultErrorResponse(currentError, ctx),
        error: currentError,
        owner: "framework",
      };
    };

    try {
      const resolvedRequestInfo = contextRuntime.resolveRequestInfo(req);
      requestInfo = resolvedRequestInfo;
      const url = new URL(req.url);

      let matchedParams: Record<string, string>;
      if (preMatchedParams) {
        matchedParams = preMatchedParams;
      } else {
        const compiled = target.compiled;
        const match = compiled ? compiled.pattern.exec(url.pathname) : null;
        const contractMethod = contract.method.toUpperCase();
        const requestMethod = req.method.toUpperCase();
        const methodMatches =
          contractMethod === requestMethod ||
          (contractMethod === "GET" && requestMethod === "HEAD");
        if (!compiled || !match || !methodMatches) {
          return errorResponse(404, "NOT_FOUND", "Not found");
        }
        try {
          matchedParams = decodeMatchedParams(compiled.keys, match);
        } catch (error) {
          if (error instanceof PathDecodeError) {
            return errorResponse(400, "INVALID_PATH", "Malformed URL path");
          }
          throw error;
        }
      }
      const rawHeaders = requestHeadersToRecord(req.headers);

      const runNativeBeforeSend = async (
        initialResult: ExecutionResult<Ctx>,
        nativeResponse: Response,
      ): Promise<Response> => {
        const originalView = responseForHooks(nativeResponse);
        const originalHeaders = originalView.headers ?? {};
        let transformed = originalView;
        for (const hook of hooks) {
          if (!hook.beforeSend) continue;
          const nextResponse = await hook.beforeSend({
            req,
            requestInfo: copyTrustedRequestInfo(resolvedRequestInfo),
            ctx: initialResult.ctx,
            contract,
            path: pathValue,
            query: queryValue,
            headers: headersValue,
            body: bodyValue,
            response: transformed,
            error: initialResult.error,
            native: true,
          });
          if (nextResponse) {
            if (
              (nextResponse.status !== nativeResponse.status ||
                nextResponse.body !== undefined) &&
              !warnedNativeReplacementHooks.has(hook) &&
              process.env.NODE_ENV !== "production"
            ) {
              warnedNativeReplacementHooks.add(hook);
              console.warn(
                `[beignet] beforeSend hook "${hook.name ?? "(anonymous)"}" returned a replacement status or body for a native Response on ${contract.method} ${contract.path}. Native responses are headers-only in beforeSend; status and body changes are ignored.`,
              );
            }
            transformed = {
              status: nativeResponse.status,
              headers: nextResponse.headers,
            };
          }
        }
        return mergeNativeResponseHeaders(
          nativeResponse,
          originalHeaders,
          transformed.headers ?? {},
        );
      };

      const applyTransformHooks = async (
        initialResult: ExecutionResult<Ctx>,
        allowRetry: boolean,
      ): Promise<ExecutionResult<Ctx>> => {
        try {
          if (isWebResponse(initialResult.response)) {
            return {
              ...initialResult,
              response: await runNativeBeforeSend(
                initialResult,
                initialResult.response,
              ),
            };
          }

          let transformed = normalizeResponse(initialResult.response);
          for (const hook of hooks) {
            if (!hook.beforeSend) continue;
            const nextResponse = await hook.beforeSend({
              req,
              requestInfo: copyTrustedRequestInfo(resolvedRequestInfo),
              ctx: initialResult.ctx,
              contract,
              path: pathValue,
              query: queryValue,
              headers: headersValue,
              body: bodyValue,
              response: transformed,
              error: initialResult.error,
            });
            if (nextResponse) {
              transformed = normalizeResponse(nextResponse);
            }
          }
          return {
            ...initialResult,
            response: transformed,
          };
        } catch (error) {
          const mapped = await resolveErrorResult(
            error,
            initialResult.ctx,
            pathValue,
            queryValue,
            headersValue,
            bodyValue,
            { owner: "framework" },
          );
          if (!allowRetry) {
            return mapped;
          }
          return applyTransformHooks(mapped, false);
        }
      };

      const applyResponseFinalizerHooks = async (
        initialResult: ExecutionResult<Ctx>,
        allowRetry: boolean,
        responseValidation: ResponseFinalizerValidationState,
      ): Promise<ExecutionResult<Ctx>> => {
        const response = normalizeHttpResponse(initialResult.response);
        const native = isWebResponse(response);
        const owner = responseOwnerFor(response, initialResult.owner);

        try {
          for (const hook of hooks) {
            const finalizer = getResponseFinalizerHook<Ctx>(hook);
            if (!finalizer) continue;
            await finalizer({
              req,
              ctx: initialResult.ctx,
              contract,
              path: pathValue,
              query: queryValue,
              headers: headersValue,
              body: bodyValue,
              response: responseForHooks(response),
              error: initialResult.error,
              native: native ? true : undefined,
              owner,
              responseValidation,
            });
          }

          return {
            ...initialResult,
            response,
          };
        } catch (error) {
          const mapped = await resolveErrorResult(
            error,
            initialResult.ctx,
            pathValue,
            queryValue,
            headersValue,
            bodyValue,
            { owner: "framework" },
          );
          if (!allowRetry) {
            return mapped;
          }
          const transformed = await applyTransformHooks(mapped, true);
          return applyResponseFinalizerHooks(
            transformed,
            false,
            "not-applicable",
          );
        }
      };

      let result: ExecutionResult<Ctx> | undefined;

      const onRequestStartedAt = performance.now();
      for (const hook of hooks) {
        if (!hook.onRequest) continue;
        try {
          const hookResult = await hook.onRequest({
            req,
            requestInfo: copyTrustedRequestInfo(resolvedRequestInfo),
            ports: finalPorts,
            contract,
            params: matchedParams,
          });
          if (hookResult) {
            const response = normalizeHttpResponse(hookResult);
            result = {
              response,
              owner: responseOwnerFor(response, "framework"),
            };
            break;
          }
        } catch (error) {
          result = await resolveErrorResult(
            error,
            undefined,
            undefined,
            undefined,
            undefined,
            undefined,
            { owner: "framework" },
          );
          break;
        }
      }
      stages.onRequestMs = performance.now() - onRequestStartedAt;

      if (!result) {
        if (optionsOverrides?.skipRoutePreparation) {
          let createdCtx!: Ctx;
          try {
            createdCtx = await timeStage("contextMs", () =>
              contextRuntime.createRequestContext(
                req,
                contract,
                resolvedRequestInfo,
              ),
            );
            baseCtx = createdCtx;
          } catch (error) {
            result = await resolveErrorResult(
              error,
              undefined,
              undefined,
              undefined,
              undefined,
              undefined,
              { owner: "framework" },
            );
          }

          if (!result) {
            try {
              result = {
                ctx: createdCtx,
                response: normalizeHttpResponse(
                  await timeStage("handlerMs", () =>
                    userHandler({
                      req,
                      ctx: createdCtx,
                      contract,
                      path: {} as HandlerArgs<Ctx, C>["path"],
                      query: {} as HandlerArgs<Ctx, C>["query"],
                      headers: rawHeaders as HandlerArgs<Ctx, C>["headers"],
                      body: undefined as HandlerArgs<Ctx, C>["body"],
                    }),
                  ),
                ),
                owner: "framework",
              };
            } catch (error) {
              result = await resolveErrorResult(
                error,
                createdCtx,
                undefined,
                undefined,
                undefined,
                undefined,
                { owner: "framework" },
              );
            }
          }
        } else {
          const parseStartedAt = performance.now();
          let path = undefined as HandlerArgs<Ctx, C>["path"] | undefined;
          let query = undefined as HandlerArgs<Ctx, C>["query"] | undefined;
          let headers = undefined as HandlerArgs<Ctx, C>["headers"] | undefined;
          let body = undefined as HandlerArgs<Ctx, C>["body"] | undefined;
          const prepared = await prepareRequestInputs({
            contract,
            req,
            url,
            rawHeaders,
            matchedParams,
            maxRequestBodyBytes,
            rawRoute: optionsOverrides?.rawRoute,
          });
          if (!prepared.ok) {
            result = {
              response: prepared.response,
              owner: "framework",
            };
          } else {
            path = prepared.inputs.path as HandlerArgs<Ctx, C>["path"];
            query = prepared.inputs.query as HandlerArgs<Ctx, C>["query"];
            headers = prepared.inputs.headers as HandlerArgs<Ctx, C>["headers"];
            body = prepared.inputs.body as HandlerArgs<Ctx, C>["body"];
          }
          stages.parseMs = performance.now() - parseStartedAt;

          if (!result) {
            pathValue = path;
            queryValue = query;
            headersValue = headers;
            bodyValue = body;

            let createdCtx!: Ctx;
            try {
              createdCtx = await timeStage("contextMs", () =>
                contextRuntime.createRequestContext(
                  req,
                  contract,
                  resolvedRequestInfo,
                ),
              );
              baseCtx = createdCtx;
            } catch (error) {
              result = await resolveErrorResult(
                error,
                undefined,
                pathValue,
                queryValue,
                headersValue,
                bodyValue,
                { owner: "framework" },
              );
            }

            if (!result) {
              const baseArgs: HandlerArgs<Ctx, C> = {
                req,
                ctx: createdCtx,
                contract,
                path: path as HandlerArgs<Ctx, C>["path"],
                query: query as HandlerArgs<Ctx, C>["query"],
                headers: headers as HandlerArgs<Ctx, C>["headers"],
                body: body as HandlerArgs<Ctx, C>["body"],
              };

              let currentCtx = createdCtx;
              const beforeHandleStartedAt = performance.now();
              for (const hook of routeHooks) {
                try {
                  const additions = await hook.resolve({
                    req,
                    ctx: currentCtx,
                    contract,
                    path,
                    query,
                    headers,
                    body,
                  });
                  if (additions && typeof additions === "object") {
                    currentCtx = contextRuntime.finalizeContext({
                      ...(currentCtx as object),
                      ...additions,
                    } as Ctx);
                  }
                } catch (error) {
                  result = await resolveErrorResult(
                    error,
                    currentCtx,
                    pathValue,
                    queryValue,
                    headersValue,
                    bodyValue,
                    { owner: "framework" },
                  );
                  break;
                }
              }

              if (!result) {
                for (const hook of hooks) {
                  if (!hook.beforeHandle) continue;
                  try {
                    const hookResult = await hook.beforeHandle({
                      req,
                      requestInfo: copyTrustedRequestInfo(resolvedRequestInfo),
                      ctx: currentCtx,
                      contract,
                      path,
                      query,
                      headers,
                      body,
                    });
                    if (isWebResponse(hookResult)) {
                      result = {
                        ctx: currentCtx,
                        response: hookResult,
                        owner: "transport",
                      };
                      break;
                    }
                    if (isHttpResponseLike(hookResult)) {
                      result = {
                        ctx: currentCtx,
                        response: normalizeResponse(hookResult),
                        owner: "framework",
                      };
                      break;
                    }
                    if (hookResult?.ctx !== undefined) {
                      currentCtx = contextRuntime.finalizeContext(
                        hookResult.ctx,
                      );
                    }
                    if (hookResult?.response) {
                      const response = normalizeHttpResponse(
                        hookResult.response,
                      );
                      result = {
                        ctx: currentCtx,
                        response,
                        owner: responseOwnerFor(response, "framework"),
                      };
                      break;
                    }
                  } catch (error) {
                    result = await resolveErrorResult(
                      error,
                      currentCtx,
                      pathValue,
                      queryValue,
                      headersValue,
                      bodyValue,
                      { owner: "framework" },
                    );
                    break;
                  }
                }
              }
              stages.beforeHandleMs = performance.now() - beforeHandleStartedAt;

              if (!result) {
                // Hooks may have elevated the actor or resolved a tenant.
                // Refresh the ambient request context so record-time
                // consumers such as createAmbientAuditLog see the finalized
                // identity.
                setActiveRequestIdentity({
                  actor: readContextActor(currentCtx),
                  tenant: readContextTenant(currentCtx),
                });
                try {
                  result = {
                    ctx: currentCtx,
                    response: normalizeHttpResponse(
                      await timeStage("handlerMs", () =>
                        userHandler({ ...baseArgs, ctx: currentCtx }),
                      ),
                    ),
                  };
                } catch (error) {
                  result = await resolveErrorResult(
                    error,
                    currentCtx,
                    pathValue,
                    queryValue,
                    headersValue,
                    bodyValue,
                  );
                }
              }
            }
          }
        }
      }

      const sendStartedAt = performance.now();
      result = await applyTransformHooks(result, true);

      let finalResponse = normalizeHttpResponse(result.response);
      let finalError = result.error;
      let finalOwner = responseOwnerFor(finalResponse, result.owner);
      let responseValidation: ResponseFinalizerValidationState =
        "not-applicable";
      if (finalOwner === "route" && !isWebResponse(finalResponse)) {
        const validateContract = options.validateResponses ?? true;
        try {
          finalResponse = await finalizeResponse(
            contract,
            finalResponse,
            target.responseValidationExemptStatus,
            { validateContract },
          );
          result = {
            ...result,
            response: finalResponse,
          };
          responseValidation = validateContract ? "validated" : "disabled";
        } catch (error) {
          if (error instanceof ResponseContractViolationError) {
            result = {
              ctx: result.ctx,
              response: toContractViolationResponse(error),
              error,
              owner: "framework",
            };
          } else {
            result = await resolveErrorResult(
              error,
              result.ctx,
              pathValue,
              queryValue,
              headersValue,
              bodyValue,
              { owner: "framework" },
            );
          }
          finalResponse = normalizeHttpResponse(result.response);
          finalError = result.error;
          finalOwner = responseOwnerFor(finalResponse, result.owner);
          result = await applyTransformHooks(result, true);
          finalResponse = normalizeHttpResponse(result.response);
          finalError = result.error;
          finalOwner = responseOwnerFor(finalResponse, result.owner);
          responseValidation = "not-applicable";
        }
      }

      result = await applyResponseFinalizerHooks(
        result,
        true,
        responseValidation,
      );
      finalResponse = normalizeHttpResponse(result.response);
      finalError = result.error;
      finalOwner = responseOwnerFor(finalResponse, result.owner);

      if (!isWebResponse(finalResponse)) {
        finalResponse = withFrameworkErrorOwnerHeader(
          finalResponse,
          finalOwner,
        );
      }
      finalResponse = withContractLifecycleHeaders(finalResponse, contract);
      finalResponse = withoutHeadResponseBody(finalResponse, req.method);
      stages.sendMs = performance.now() - sendStartedAt;

      const durationMs = Date.now() - startedAt;
      const stageTimings = roundStageTimings(stages);
      for (const hook of hooks) {
        if (!hook.afterSend) continue;
        try {
          await hook.afterSend({
            req,
            requestInfo: copyTrustedRequestInfo(resolvedRequestInfo),
            ctx: result.ctx,
            contract,
            path: pathValue,
            query: queryValue,
            headers: headersValue,
            body: bodyValue,
            response: responseForHooks(finalResponse),
            error: finalError,
            durationMs,
            stages: stageTimings,
          });
        } catch {
          // Ignore after-response hook failures; they should never change the response.
        }
      }

      return finalResponse;
    } catch (error) {
      const result = await resolveErrorResult(
        error,
        baseCtx,
        pathValue,
        queryValue,
        headersValue,
        bodyValue,
        {
          owner: "framework",
        },
      );
      const response = withoutHeadResponseBody(
        withContractLifecycleHeaders(
          normalizeHttpResponse(result.response),
          contract,
        ),
        req.method,
      );
      if (isWebResponse(response)) {
        return response;
      }
      return withFrameworkErrorOwnerHeader(
        response,
        responseOwnerFor(response, result.owner),
      );
    }
  };

  return async (
    target: ExecutionTarget<Ctx, C>,
    req: HttpRequestLike,
    preMatchedParams?: Record<string, string>,
  ) => {
    const tracing = resolveTracingPort(finalPorts);
    const instrumentationOptions =
      options.instrumentation === false ? undefined : options.instrumentation;
    const pathname = (() => {
      try {
        return new URL(req.url).pathname;
      } catch {
        return req.url;
      }
    })();
    const ignored = (
      instrumentationOptions?.ignorePaths ?? ["/api/devtools"]
    ).some((prefix) => {
      const normalized = prefix.replace(/\/+$/, "");
      return pathname === normalized || pathname.startsWith(`${normalized}/`);
    });

    if (!tracing || ignored) {
      return executeRequest(target, req, preMatchedParams);
    }

    const traceContextHeader =
      instrumentationOptions?.traceContextHeader ?? "traceparent";
    const active = tracing.current();
    const parsedTraceparent =
      traceContextHeader === false
        ? undefined
        : parseTraceparent(req.headers.get(traceContextHeader));
    const parent: TraceContextInput | undefined = active
      ? undefined
      : !parsedTraceparent
        ? undefined
        : {
            traceparent: parsedTraceparent.traceparent,
            tracestate: req.headers.get("tracestate") ?? undefined,
          };
    const traceAttributes = {
      "beignet.contract.name": target.contract.name,
      "http.request.method": req.method.toUpperCase(),
      "http.route": target.contract.path,
    } as const;

    return await runWithTracing(
      tracing,
      {
        name: `beignet.request ${target.contract.name}`,
        type: "request",
        kind: active ? "internal" : "server",
        parent,
        attributes: traceAttributes,
        metricAttributes: traceAttributes,
      },
      async (span) => {
        const response = await executeRequest(target, req, preMatchedParams);
        span?.setAttribute("http.response.status_code", response.status);
        if (response.status >= 500) span?.setStatus("error");
        return response;
      },
    );
  };
}

/**
 * Build a memo instrumentation recorder from the app ports, or undefined when
 * no instrumentation sink is wired.
 *
 * Resolved per execution rather than at route-build time because providers
 * contribute `ports.instrumentation`/`ports.devtools` during setup, after
 * routes are registered.
 */
export function createMemoScopeRecorder(
  ports: AnyPorts,
): ((event: MemoInstrumentationEvent) => void) | undefined {
  const target = ports as ProviderInstrumentationTarget;
  if (!resolveProviderInstrumentationPort(target)) return undefined;

  const instrumentation = createProviderInstrumentation(target, {
    providerName: "memo",
    watcher: "memo",
  });
  return (event) => {
    instrumentation.custom({
      name: `memo.${event.kind}`,
      label: event.kind === "hit" ? "Memo hit" : "Memo fill",
      summary: `Memo ${event.kind} for ${event.memo}`,
      details: {
        memo: event.memo,
        key: event.key,
        ...(event.durationMs !== undefined
          ? { durationMs: event.durationMs }
          : {}),
        ...(event.failed ? { failed: true } : {}),
      },
    });
  };
}

export function buildHandler<
  Ctx,
  FinalPorts extends AnyPorts,
  C extends HttpContractConfig,
>(
  options: RequestExecutorOptions<Ctx>,
  finalPorts: FinalPorts,
  contextRuntime: {
    createRequestContext: (
      req: HttpRequestLike,
      contract: HttpContractConfig,
      requestInfo: TrustedRequestInfo,
    ) => Promise<Ctx>;
    finalizeContext: (seed: ContextSeed<Ctx> | Ctx) => Ctx;
    resolveRequestInfo: (req: HttpRequestLike) => TrustedRequestInfo;
  },
  contract: C,
  userHandler: Handler<Ctx, C>,
  hooks: ServerHook<Ctx, FinalPorts>[],
  routeHooks: readonly RouteHook<unknown, object>[] = [],
  optionsOverrides?: {
    skipRoutePreparation?: boolean;
    /**
     * Run the route through the full hook pipeline without contract
     * preparation: no query/path/header/body parsing or validation, and the
     * request body is left unconsumed for the handler.
     */
    rawRoute?: boolean;
  },
  responseValidationExemptStatus?: number,
): (
  req: HttpRequestLike,
  preMatchedParams?: Record<string, string>,
) => Promise<HttpResponse> {
  const execute = createRequestExecutor<Ctx, FinalPorts, C>(
    options,
    finalPorts,
    contextRuntime,
    hooks,
    routeHooks,
    optionsOverrides,
  );
  const executionTarget: ExecutionTarget<Ctx, C> = {
    contract,
    compiled: compilePath(contract.path),
    handler: userHandler,
    responseValidationExemptStatus,
  };

  // Every HTTP execution runs inside a fresh memo scope so createMemo(...)
  // wrappers dedupe lookups for exactly one request.
  return (req, preMatchedParams) =>
    runWithMemoScope({ record: createMemoScopeRecorder(finalPorts) }, () =>
      execute(executionTarget, req, preMatchedParams),
    );
}

function roundStageTimings(stages: RequestStageTimings): RequestStageTimings {
  const round = (value: number) => Math.round(value * 100) / 100;
  return {
    onRequestMs: round(stages.onRequestMs),
    parseMs: round(stages.parseMs),
    contextMs: round(stages.contextMs),
    beforeHandleMs: round(stages.beforeHandleMs),
    handlerMs: round(stages.handlerMs),
    sendMs: round(stages.sendMs),
  };
}
