import type { ILogger } from "./logger.interface";
import type { Captured } from "../conformance/brands";
import { attachBrand } from "../conformance/brand-runtime";
import { isReported, markReported } from "./reported-flag";

/**
 * Higher-order wrapper applied at DI bind time. Mirrors `withSpan`: takes a
 * factory result `(args) => Promise<R>` and returns the same shape, but any
 * thrown error is captured via `logger.captureException(err, { tags })` before
 * being re-thrown.
 *
 * Skips capture if the error already carries the `__sentryReported` flag —
 * this is what prevents double-capture when the same error bubbles through
 * a wrapped repo → use case → controller chain (the repo's catch site
 * captures first; outer wrappers see the flag and bail).
 *
 * Usage at bind time:
 *
 *   const captured = withCapture(logger, { feature: "blog", layer: "use-case", name: "blog.getArticles" }, factory(deps));
 *   const wrapped  = withSpan(tracer, opts, captured);
 *
 * Span wraps capture: the span timing reflects the captured-and-rethrown
 * failure (errored span gets a duration), and the capture has accurate
 * tags by the time it fires.
 */
export function withCapture<Args extends unknown[], R>(
  logger: ILogger,
  tags: Record<string, string>,
  fn: (...args: Args) => Promise<R>,
): Captured<(...args: Args) => Promise<R>> {
  const PROPAGATED_BRANDS = [
    "__instrumented",
    "__audited",
    "__analyzed",
    "__consentChecked",
    "__rateLimited",
  ] as const;

  const wrapped: (...args: Args) => Promise<R> = async (...args) => {
    try {
      return await fn(...args);
    } catch (err) {
      if (!isReported(err)) {
        logger.captureException(err, { tags });
        markReported(err);
      }
      throw err;
    }
  };
  // Propagate brands from the inner function (e.g. __audited from withAudit,
  // __instrumented if already spanned) so the outermost binding carries all brands.
  // __captured is omitted here because it is attached explicitly below.
  for (const brand of PROPAGATED_BRANDS) {
    if ((fn as unknown as Record<string, unknown>)[brand] === true) {
      attachBrand(wrapped, brand);
    }
  }
  attachBrand(wrapped, "__captured");
  return wrapped as Captured<(...args: Args) => Promise<R>>;
}
