import type { Container } from "inversify";
import type { FeatureManifest } from "./define-feature";
import { isInstrumented, isCaptured, isAudited } from "./brand-runtime";
import { ConformanceError } from "./conformance-error";
import type { BindContext } from "../di/bind-context";

/**
 * Runtime check that every manifest-declared use case is bound through the
 * brand-attaching wrappers (`withSpan` → `__instrumented`,
 * `withCapture` → `__captured`, `withAudit` → `__audited` when required).
 *
 * Called at the tail of each feature's `bindProductionX(ctx)` so:
 *   - `pnpm dev` refuses to boot on drift (synchronous throw)
 *   - The check runs once per feature, not per request
 *   - Future apps that wire `bindProductionX` inherit the check for free
 *
 * The `symbols` map is declared inline by the feature's binder; the manifest
 * holds the contract, the binder holds the container symbols. This keeps
 * the manifest free of DI-coupling while letting each feature declare its
 * own wiring keys.
 */
export function assertFeatureConformance(
  container: Container,
  manifest: FeatureManifest,
  symbols: Record<string, symbol>,
  _ctx: BindContext,
): void {
  void _ctx; // future: also check ctx.bus / ctx.auditLog presence vs requiredCores
  for (const [name, useCase] of Object.entries(manifest.useCases)) {
    const sym = symbols[name];
    if (!sym) {
      throw new ConformanceError(
        `${manifest.name}.${name}: no symbol provided in symbols map`,
      );
    }
    let bound: unknown;
    try {
      bound = container.get(sym);
    } catch (cause) {
      throw new ConformanceError(
        `${manifest.name}.${name}: container could not resolve symbol (${String(cause)})`,
      );
    }
    if (!isInstrumented(bound)) {
      throw new ConformanceError(
        `${manifest.name}.${name}: missing __instrumented brand — was withSpan applied at bind time?`,
      );
    }
    if (!isCaptured(bound)) {
      throw new ConformanceError(
        `${manifest.name}.${name}: missing __captured brand — was withCapture applied at bind time?`,
      );
    }
    if (useCase.mutates && useCase.audits.length > 0) {
      if (!isAudited(bound)) {
        throw new ConformanceError(
          `${manifest.name}.${name}: declares audits but binding is missing __audited brand — was withAudit applied at bind time?`,
        );
      }
    }
  }
}
