// Capability-Kern shared types (KAR-959/P2, Master-Prompt §11-§17 + §31-§32).
//
// Architectural fundament for the QAF-Corpus-Generalization-Programme
// (Epic KAR-957): replaces the 17 blocking template-gates catalogued in
// gate-audit.md (B1-B17) with capability-based logic ("if
// capabilities.hasManufacturingDetail" instead of "if
// !isG60DetailQaf") — Master-Prompt §32. This PR ships the API the gates
// migrate ONTO; the gates themselves stay untouched (P3/P4 follow-up,
// gate-audit.md §4 replacement order).
//
// Builds on KAR-958/P1's Empty-Field-Reason-Taxonomie (../types.ts
// EmptyFieldReason/FacetDegradation) rather than re-inventing it, and on
// KAR-892/P1.1's CanonicalField registry (../canonical-fields.ts,
// ../canonical-model.ts) rather than a parallel field dictionary — see
// field-registry.ts module header for how.
//
// tdd-guard:skip — type declarations only, same category as ../types.ts.

import type { QafModule } from '../canonical-fields.types'
import type { EmptyFieldReason } from '../types'

export type { QafModule }

// ── Scope-aware value type (Master-Prompt §17) ──────────────────────────────
//
// The full §17 scope taxonomy lists 8+ distinct scopes (workbook/sheet/
// shared-by-all-variants/shared-by-subset/variant/process/material-position/
// cost-block/derived/reconstructed/weighted/conflicting). KAR-959/P2
// deliberately implements the 3-way slice the task instruction specifies —
// global | variant:<id> | process:<id> — not the full taxonomy: a
// material-position-specific or SBM-row-specific value is modeled as
// `process:<id>` here (ASSUMPTION, see field-registry.ts's
// deriveFieldScope doc for the exact mapping and why). `ScopedValue<T>` is a
// plain string-literal-keyed type, not a closed enum — a future PR can widen
// the FieldScope union (e.g. add `material:<id>` or `cost_block:<id>`)
// without changing this type's shape or breaking existing callers.
export type FieldScope = 'global' | `variant:${string}` | `process:${string}`

/** A value tagged with the scope it applies at (Master-Prompt §17: "Do not
 * duplicate a shared value into all variants without preserving its
 * original scope."). */
export interface ScopedValue<T> {
  scope: FieldScope
  value: T
}

// ── Semantic sheet roles (Master-Prompt §15) ────────────────────────────────
//
// Master-Prompt §15 lists 17 candidate canonical roles (SUMMARY,
// MASTER_DATA, VARIANT_DEFINITION, MANUFACTURING_COST, MATERIAL_BOM,
// SETUP_COST, SCRAP_COST, OVERHEAD, TOOLING, SEKOF, SBM, LOGISTICS,
// RAW_MATERIAL, RAW_MATERIAL_RISK, COMPARISON_HELPER, ASSUMPTIONS,
// CALCULATION_HELPER, UNKNOWN_RELEVANT, UNKNOWN_AUXILIARY). Task instruction
// narrows this to the 11 roles actually needed to drive the 8-module
// capability matrix this PR computes (the task's own role list) — a role
// name per already-existing/soon-existing MODULE plus `setup`/`input`
// (G60's rate-card sheet, structurally distinct from any of the 8 modules)
// plus `unknown` (Master-Prompt §15: "No sheet may disappear silently" —
// every sheet gets a role, `unknown` included, never omitted from the
// result). Widening towards the full 17-role §15 taxonomy is future work
// (P3+, once VARIANT_DEFINITION/TOOLING/SEKOF/OVERHEAD get their own
// dedicated capability treatment) — this type is additive-safe for that
// (plain string union).
export type SheetRole =
  | 'summary'
  | 'material'
  | 'manufacturing'
  | 'sbm'
  | 'rmr'
  | 'logistics'
  | 'lccn'
  | 'co2e'
  | 'setup'
  | 'input'
  // KAR-962/P5 §27 label-synonym-discovery finding: a "Prämissenblatt"/
  // "Assumptions sheet" tab (DuckDB dev-split scan, Master-Prompt §27 —
  // 172+166 resp. 45+43 dev-split sheet hits, see sheet-resolver.ts
  // EXTRA_ROLE_ALIASES comment for the exact counts) carries workbook-wide
  // master rates/assumptions (Lohnsatz/SGA/Gewinn/Scrap among them — the
  // KAR-962 task's own P3-follow-up target, see premise-field-candidates.ts)
  // — a distinct semantic role from every existing one (not a cost-DETAIL
  // sheet like `manufacturing`/`material`, not the workbook-level `summary`
  // totals sheet). Master-Prompt §15's own role list names "ASSUMPTIONS" —
  // this is that role, added only now that corpus evidence names a concrete
  // consumer for it (§27: "Do not build the pattern library speculatively").
  | 'premise'
  | 'unknown'

/** One sheet's role classification, with confidence + the evidence that drove
 * it (Master-Prompt §15: "Each capability must contain ... Detection rule
 * ... Supporting evidence."). */
export interface SheetRoleAssignment {
  sheetName: string
  role: SheetRole
  /** 0..1. 0 for `role: 'unknown'` (Master-Prompt §15 point 2: "Classified
   * with low confidence" is a distinct outcome from a genuine unknown — this
   * registry-only, name-signal-only classifier (P2 scope, see
   * sheet-resolver.ts module header) does not yet produce that intermediate
   * case; every non-`unknown` role comes from an alias-table hit and is
   * reported at that alias's fixed confidence). */
  confidence: number
  /** Which evidence signal produced this classification — a short,
   * greppable pointer (e.g. "module-sheet-name-registry:MANUFACTURING",
   * "corpus-alias:lv-detail-eu"), not free text — Master-Prompt §15
   * "Detection rule". */
  signal: string
  /** Human-readable justification, always non-empty (Master-Prompt §15
   * "Supporting evidence"). */
  reasoning: string
}

// ── Workbook capability matrix (Master-Prompt §11) ──────────────────────────

/** Master-Prompt §11 defines an 8-value status vocabulary
 * (AVAILABLE/PARTIALLY_AVAILABLE/DERIVABLE/AMBIGUOUS/NOT_AVAILABLE/
 * PARSER_NOT_IMPLEMENTED/SOURCE_ERROR + "must never be represented as the
 * same state" for the PARSER_NOT_IMPLEMENTED/NOT_AVAILABLE pair). The task
 * instruction narrows this to the 5-value slice this PR's detector can
 * actually distinguish today (reusing KAN-958/P1's EmptyFieldReason as the
 * WHY-carrier instead of growing the status enum itself further):
 *   - AVAILABLE   — module data extracted, core fields found, high confidence.
 *   - PARTIAL     — module data extracted but incomplete (§11 PARTIALLY_AVAILABLE).
 *   - DERIVABLE   — a plausible source sheet was identified (sheet-resolver
 *                   role signal) but no parser extracted data from it yet —
 *                   §11's PARSER_NOT_IMPLEMENTED, kept distinct from MISSING
 *                   per that section's explicit "must never be represented
 *                   as the same state" rule.
 *   - MISSING     — no plausible source found after the resolver's search
 *                   (§11 NOT_AVAILABLE).
 *   - PARSE_FAILED — a source was found and an extraction was attempted, but
 *                   it failed/degraded below the parser's own minimum-signal
 *                   floor (§11 SOURCE_ERROR).
 * §11's AMBIGUOUS is not modeled as a separate status in this PR — an
 * ambiguous case (e.g. a genuine value conflict between two candidate
 * sources) is instead surfaced through field-candidates.ts's own
 * `INCONSISTENT` FieldConflictStatus, one layer down from the
 * module-level matrix (documented ASSUMPTION/scope limitation).
 */
export type CapabilityStatus = 'AVAILABLE' | 'PARTIAL' | 'DERIVABLE' | 'MISSING' | 'PARSE_FAILED'

export interface CapabilitySource {
  sheet: string | null
  region?: string | null
}

/** One module's capability finding (Master-Prompt §11 per-capability
 * required fields, narrowed to what this PR's detector actually produces —
 * confidence + source sheet/region + a WHY via EmptyFieldReason, not the
 * full 12-field §11 list (applicable variants/processes/material positions,
 * data-quality state, known limitations are P3+ territory once
 * VariantStructureDetector/DataQualityAnalyzer exist, Master-Prompt §31). */
export interface ModuleCapability {
  module: QafModule
  status: CapabilityStatus
  /** 0..1. */
  confidence: number
  source: CapabilitySource | null
  /** Set whenever status is MISSING/PARSE_FAILED/DERIVABLE (reuses KAR-958/P1's
   * taxonomy — see ../types.ts EmptyFieldReason for the exact meaning of each
   * value). Undefined for AVAILABLE, and for PARTIAL unless the parser's own
   * FacetDegradation happened to also apply at PARTIAL severity (e.g.
   * INCONSISTENT). */
  reason?: EmptyFieldReason
  message: string
}

/** The 8 QAF-9.1 modules this PR's capability detector covers — task
 * instruction's own module list ("initial die Felder der 8 Module"). G60 is
 * a structurally distinct fixed-coordinate pipeline (internal/g60/, its own
 * parser/detector) and WAF/LAF/LEK are foreign-form REJECTIONS, not modules
 * that ever get ingested as a QAF at all (foreign-form-detection.ts) — both
 * excluded from this matrix by design, not an oversight; widening to G60 is
 * explicitly named future work (P3/P4) in the PR body. */
export const CAPABILITY_MODULES: readonly QafModule[] = [
  'SUMMARY',
  'MANUFACTURING',
  'MATERIAL',
  'SBM',
  'LOGISTICS',
  'RMR',
  'LC_CN',
  'CO2E',
] as const

export interface WorkbookCapabilityMatrix {
  sheetRoles: SheetRoleAssignment[]
  modules: ModuleCapability[]
  /** ASSUMPTIONS made by this computation (Master-Prompt §15: "ASSUMPTIONS
   * explizit") — e.g. the fixed sheet-role priority order used to break a
   * MATERIAL/RMR name collision, or the AVAILABLE-vs-PARTIAL confidence
   * threshold. Always non-empty (there is always at least one documented
   * scope-limitation assumption for a P2 foundation module). */
  assumptions: string[]
  /** Versioned, same "computed once, read thereafter" discipline every other
   * g60_meta-embedded artifact in this codebase follows (templateFingerprint,
   * multiQafContainer, ...) — bump when the detection algorithm changes so a
   * later re-tuning does not retroactively reinterpret an already-persisted
   * matrix. */
  modelVersion: string
}

export const CAPABILITY_MODEL_VERSION = '1.0.0'

// ── Field candidates (Master-Prompt §14) ────────────────────────────────────

/** One candidate SOURCE for a canonical field's value — before conflict
 * resolution (Master-Prompt §14: "Preserve all candidates. ... Never
 * silently discard a conflicting candidate."). */
export interface FieldCandidate<T = unknown> {
  fieldId: string
  module: QafModule
  value: T
  scope: FieldScope
  source: {
    sheet: string | null
    cell?: string | null
  }
  /** 0..1 — the source's own extraction confidence (parser-reported), used
   * as the PRIMARY priority-rule signal by field-candidates.ts's resolver. */
  confidence: number
}

export type FieldConflictStatus = 'NONE' | 'SINGLE' | 'AGREEMENT' | 'INCONSISTENT'

/** The resolved outcome for one canonical field, retaining every candidate
 * (Master-Prompt §14 point 6: "Retain complete source lineage."). */
export interface FieldResolution<T = unknown> {
  fieldId: string
  status: FieldConflictStatus
  /** The priority-preferred candidate. Populated even when `status` is
   * INCONSISTENT (§14 point 3: "Select a preferred source" — a conflict
   * still yields a best-effort selection, it is FLAGGED, not silently
   * resolved and not left unresolved). Null only when status is NONE. */
  selected: FieldCandidate<T> | null
  /** Every candidate that was collected for this field, in priority order
   * (highest-priority first). */
  candidates: ReadonlyArray<FieldCandidate<T>>
  /** Human-readable account of the priority rule applied (§14 point 7:
   * "Explain the selection."). */
  explanation: string
}
