/**
 * Coverage primitives — types, defaults, and the vitest-threshold helper.
 *
 * This file is intentionally **self-contained** (no relative imports) so it
 * can be loaded at vitest config time, where the Node ESM resolver doesn't
 * auto-resolve `.ts` extensions on relative imports. Other files in
 * `conformance/` (including `define-feature.ts`) import their coverage types
 * from here, not the reverse. See ADR-020.
 */

/**
 * A coverage threshold band for a specific path or layer. All four percentages
 * are required; partial bands are not supported (matches vitest's v8 coverage
 * threshold shape exactly).
 */
export type CoverageBand = {
  readonly statements: number;
  readonly branches: number;
  readonly functions: number;
  readonly lines: number;
};

/**
 * The named coverage bands a feature declares. Keys are layer aliases
 * resolved to glob patterns under `src/`; `baseline` is the fallthrough
 * applied to everything not matched by a layer band.
 *
 * Layer aliases (relative to `packages/<feature>/src/`):
 *   - "entities"    -> entities/**
 *   - "use-cases"   -> application/use-cases/**
 *   - "controllers" -> interface-adapters/controllers/**
 *
 * `baseline` is REQUIRED. Layer bands are OPTIONAL.
 */
export type CoverageBands = {
  readonly baseline: CoverageBand;
  readonly entities?: CoverageBand;
  readonly "use-cases"?: CoverageBand;
  readonly controllers?: CoverageBand;
};

/**
 * The coverage section of a feature manifest.
 */
export type CoverageManifest = {
  readonly bands: CoverageBands;
  readonly mutationTargets?: readonly ("entities" | "use-cases")[];
  readonly mutationScore?: number;
};

/**
 * The defaults applied when a manifest omits the `coverage` section.
 * Matches the historical per-feature vitest.config.ts thresholds documented
 * in ADR-011 (TDD foundation).
 */
export const DEFAULT_COVERAGE_BANDS: CoverageBands = {
  baseline: { statements: 80, branches: 75, functions: 80, lines: 80 },
  entities: { statements: 100, branches: 100, functions: 100, lines: 100 },
  "use-cases": { statements: 100, branches: 95, functions: 100, lines: 100 },
  controllers: { statements: 100, branches: 95, functions: 100, lines: 100 },
};

export const DEFAULT_MUTATION_SCORE = 80;
export const DEFAULT_MUTATION_TARGETS = ["entities", "use-cases"] as const;

/**
 * Maps a CoverageBands layer alias to the glob pattern vitest matches files
 * against. Per ADR-020.
 */
const LAYER_GLOBS = {
  entities: "src/entities/**",
  "use-cases": "src/application/use-cases/**",
  controllers: "src/interface-adapters/controllers/**",
} as const;

type ThresholdNumbers = {
  statements: number;
  branches: number;
  functions: number;
  lines: number;
};

/**
 * The shape vitest's `coverage.thresholds` config accepts: a baseline plus
 * per-glob overrides.
 */
export type VitestThresholds = ThresholdNumbers & {
  [glob: string]: number | ThresholdNumbers;
};

/**
 * The minimum manifest shape this file consumes. Generic constraint so
 * callers can pass any object satisfying the structural minimum (e.g. a
 * full `FeatureManifest` with extra properties).
 */
type ManifestWithCoverage = {
  readonly coverage?: CoverageManifest;
};

/**
 * Resolve a manifest's effective coverage bands, filling in DEFAULT_*  for
 * missing layers. Always go through this helper rather than reading
 * `manifest.coverage` directly.
 */
export function getCoverageBands<M extends ManifestWithCoverage>(
  manifest: M,
): CoverageBands {
  const declared = manifest.coverage?.bands;
  if (!declared) return DEFAULT_COVERAGE_BANDS;
  return {
    baseline: declared.baseline,
    entities: declared.entities ?? DEFAULT_COVERAGE_BANDS.entities,
    "use-cases": declared["use-cases"] ?? DEFAULT_COVERAGE_BANDS["use-cases"],
    controllers: declared.controllers ?? DEFAULT_COVERAGE_BANDS.controllers,
  };
}

/**
 * Resolve a manifest's mutation configuration, falling back to defaults.
 */
export function getMutationConfig<M extends ManifestWithCoverage>(
  manifest: M,
): {
  targets: readonly ("entities" | "use-cases")[];
  score: number;
} {
  return {
    targets: manifest.coverage?.mutationTargets ?? DEFAULT_MUTATION_TARGETS,
    score: manifest.coverage?.mutationScore ?? DEFAULT_MUTATION_SCORE,
  };
}

/**
 * Convert resolved CoverageBands into the shape vitest expects under
 * `coverage.thresholds`. Layer aliases become glob keys; baseline becomes
 * the top-level keys.
 */
export function vitestThresholdsFromBands(
  bands: CoverageBands,
): VitestThresholds {
  const result: VitestThresholds = {
    statements: bands.baseline.statements,
    branches: bands.baseline.branches,
    functions: bands.baseline.functions,
    lines: bands.baseline.lines,
  };
  for (const [alias, glob] of Object.entries(LAYER_GLOBS) as Array<
    [keyof typeof LAYER_GLOBS, string]
  >) {
    const band = bands[alias];
    if (band) {
      result[glob] = {
        statements: band.statements,
        branches: band.branches,
        functions: band.functions,
        lines: band.lines,
      };
    }
  }
  return result;
}

/**
 * Convenience wrapper: resolve a manifest's effective bands then emit the
 * vitest shape. Use this from a feature's `vitest.config.ts`.
 */
export function vitestThresholdsFromManifest<M extends ManifestWithCoverage>(
  manifest: M,
): VitestThresholds {
  return vitestThresholdsFromBands(getCoverageBands(manifest));
}
