import { TRPCError } from "@trpc/server";
import type { TRPC_ERROR_CODE_KEY } from "@trpc/server/rpc";
import { t } from "./init";

type ErrorCtor = new (...args: never[]) => Error;

/**
 * Build a tRPC middleware that translates domain errors to TRPCError.
 *
 * Each tuple pairs a constructor with a TRPC error code. tRPC v11
 * middleware sees procedure throws as a returned `result` (not an
 * exception): `next()` resolves to `{ ok: false, error: TRPCError }`
 * where `error.cause` is the original domain error wrapped by
 * `getTRPCErrorFromUnknown`. The middleware checks `result.ok`,
 * matches `result.error.cause` against each `instanceof Ctor`, and
 * on a hit throws a fresh TRPCError with the configured code and the
 * original error preserved as `.cause`. Unmapped errors fall through
 * unchanged — tRPC's default INTERNAL_SERVER_ERROR wrapping applies
 * with the original error still reachable via `.cause`.
 *
 * Owned by features: each feature passes its own constructors in.
 * core-shared never enumerates feature-specific error classes.
 */
export function defineErrorMiddleware(
  map: ReadonlyArray<readonly [ErrorCtor, TRPC_ERROR_CODE_KEY]>,
) {
  return t.middleware(async ({ next }) => {
    const result = await next();
    if (!result.ok) {
      const cause = result.error.cause;
      if (cause instanceof Error) {
        for (const [Ctor, code] of map) {
          if (cause instanceof Ctor) {
            throw new TRPCError({ code, message: cause.message, cause });
          }
        }
      }
    }
    return result;
  });
}
