import type { NextFunction, Request, RequestHandler, Response } from "express";

/**
 * An Express route handler whose body is `async` (returns a `Promise`).
 * Only `ResBody`/`Locals` are made generic (what this codebase actually
 * varies per-route) — params/request-body/query stay at Express's own
 * internal defaults, same as an unparameterized `Request`.
 *
 * `Locals` is constrained to `Record<string, any>`, matching Express's own
 * `Response<ResBody, LocalsObj>` exactly (see `@types/express-serve-static-
 * core`) rather than the stricter `Record<string, unknown>`: a plain
 * `interface` (e.g. `AuthLocals` in `require-auth.ts`) has no index
 * signature, so under `unknown` it fails this generic's constraint even
 * though it's assignable to `Response`'s own `Locals` param directly —
 * `any` is what lets that structural gap close.
 */
export type AsyncRequestHandler<
  ResBody = unknown,
  // biome-ignore lint/suspicious/noExplicitAny: mirrors Express's own Response<ResBody, LocalsObj extends Record<string, any>> constraint (see comment above) — `unknown` here would reject plain interfaces like AuthLocals that Response itself accepts fine.
  Locals extends Record<string, any> = Record<string, any>,
> = (req: Request, res: Response<ResBody, Locals>, next: NextFunction) => Promise<void>;

/**
 * Wraps an async Express handler so a thrown error or rejected promise is
 * forwarded to `next(err)` automatically. Without this, an unhandled
 * rejection inside an `async` route handler never reaches Express's error
 * middleware — every route ends up needing its own `try { ... } catch (err)
 * { next(err); }` boilerplate, which this removes.
 *
 * @example
 * router.post("/signup", wrapAsyncHandler(async (req, res) => {
 *   const profile = await signup(req.body);
 *   res.status(201).json(profile);
 * }));
 */
export function wrapAsyncHandler<
  ResBody = unknown,
  // biome-ignore lint/suspicious/noExplicitAny: same constraint as AsyncRequestHandler above, for the same reason.
  Locals extends Record<string, any> = Record<string, any>,
>(handler: AsyncRequestHandler<ResBody, Locals>): RequestHandler {
  return (req, res, next) => {
    handler(req, res as Response<ResBody, Locals>, next).catch(next);
  };
}
