// CanonicalFieldRegistry — capability-scoped view (KAR-959/P2, Master-Prompt
// §13).
//
// Master-Prompt §13 asks for "a complete inventory of every field found
// anywhere in the corpus" with (among other things) id/module/DE+EN
// labels/unit/scope. KAR-892/P1.1 already shipped exactly that inventory —
// ../canonical-fields.ts's CANONICAL_FIELDS, ~250 fields across the 12 QAF
// modules, DE+EN labels, unit, classification, requirement, level — as a
// reviewed, evidence-cited, versioned TS data structure. Duplicating that
// registry here would be the "kein Hardcode-Wildwuchs" anti-pattern the task
// instruction explicitly warns against. This module is instead a thin,
// ADDITIVE layer on top of it:
//   1. `deriveFieldScope` — the one dimension canonical-fields.ts does not
//      carry (global|variant|process, Master-Prompt §17) — computed
//      deterministically from the existing `level` field, not hand-tagged.
//   2. `EXTRA_CAPABILITY_FIELDS` — a small number of fields the corpus
//      analysis / task instruction names that are NOT yet in
//      canonical-fields.ts (verified absent by grep before adding, see each
//      entry's own comment) — kept in a SEPARATE array, never spliced into
//      canonical-fields.ts's own CANONICAL_FIELDS export, so P1's already-
//      reviewed registry file stays untouched by this PR.
//   3. Thin lookup helpers (byModule/byScope/allFields) that merge both
//      sources — the single surface capability-detector.ts and
//      field-candidates.ts consume.
//
// tdd-guard:skip for the EXTRA_CAPABILITY_FIELDS data block itself (pure
// data, same category as canonical-fields.ts); the derive/lookup functions
// below are covered by __tests__/field-registry.test.ts.

import { CANONICAL_FIELDS } from '../canonical-fields'
import { byModule as canonicalByModule, findByAlias as canonicalFindByAlias } from '../canonical-model'
import type { CanonicalField } from '../canonical-fields.types'
import type { QafModule } from '../canonical-fields.types'
import type { SheetRole } from './types'

// ── Scope derivation (Master-Prompt §17) ────────────────────────────────────

export type CapabilityFieldScopeKind = 'global' | 'process'

/**
 * Derive a field's scope kind from its existing `level` (canonical-fields.ts
 * §13.x tables already classify every field 'row' | 'summary').
 *
 * ASSUMPTION (documented per Master-Prompt §15/§17 discipline — this PR's
 * ScopedValue<T> only has 3 scope kinds: global | variant:<id> |
 * process:<id>): `level: 'summary'` (once per file/summary block) maps to
 * `global`; `level: 'row'` (once per detail row — a manufacturing step, a
 * material position, an SBM device, an RMR block row, ...) maps to
 * `process`. Master-Prompt §17 itself distinguishes finer scopes
 * ("Process-specific values" vs. "Material-position-specific values" vs.
 * "Cost-block-specific values" are three SEPARATE bullet points) — this
 * registry deliberately does not split them yet; `process:<id>` is used as
 * the umbrella "per-structural-row" scope for every row-level module, not
 * literally only for MANUFACTURING steps. A future PR can introduce
 * additional FieldScope literal variants (e.g. `material:<id>`) without
 * breaking this function's callers, since FieldScope is an open template
 * literal type, not a closed enum.
 *
 * `variant` scope is NOT produced by this function at all — variant
 * identity is owned by the multi-qaf/ module (VariantDefinition,
 * identity.ts), which this P2 foundation does not wire into. A
 * variant-bound ScopedValue is something a FUTURE multi-qaf integration
 * constructs by re-scoping a `process`/`global` candidate once it knows
 * which VirtualQafVariant produced it — out of scope here (documented
 * limitation, see PR body).
 */
export function deriveFieldScope(field: Pick<CanonicalField, 'level'>): CapabilityFieldScopeKind {
  return field.level === 'summary' ? 'global' : 'process'
}

// ── Extra capability-only fields (additive, NOT in canonical-fields.ts) ────
//
// Both entries below were verified ABSENT from canonical-fields.ts (grep for
// their labels/ids across the file, KAR-959 research) before being added
// here — this is not a duplicate of anything P1 already registered.

const EXTRA_CAPABILITY_FIELDS: readonly CanonicalField[] = [
  // Master-Prompt process-parameter list (task instruction: "Zykluszeit,
  // Teile/Zyklus, MA, MSS, Ineffizienz") — the first four already exist in
  // canonical-fields.ts (mfg_cycle_time, mfg_parts_per_cycle,
  // mfg_direct_employee_count, mfg_machine_hour_rate). "Ineffizienz" does
  // not: it is a real, shipped G60 concept (internal/g60/calculator.ts
  // `inefficiency`, internal/g60/structure-guard.ts:178 "ineff
  // (Ineffizienz-Faktor) per-row", internal/g60/export.ts "Ineffizienz
  // ALT"/"Ineffizienz NEU" columns) that never got a canonical-fields.ts
  // entry because P1.1's registry pass covered the Leitfaden-documented
  // modules, not G60's own internal calculator inputs. Modeled here as a
  // MANUFACTURING-module, row-level (process-scoped) field — G60's own
  // per-tab "ineff" factor is the manufacturing-step-level analogue of the
  // standard QAF's mfg_social_overhead_rate (SGK), just not yet
  // Leitfaden-documented outside G60.
  {
    id: 'cap_mfg_inefficiency_factor',
    module: 'MANUFACTURING',
    section: 'Capability-Kern extension (KAR-959/P2)',
    labelDe: 'Ineffizienz-Faktor',
    labelEn: 'Inefficiency factor',
    aliases: ['ineff', 'Ineffizienz-Aufschlag'],
    dataType: 'percent',
    unit: '%',
    classification: 'input',
    requirement: 'optional',
    level: 'row',
    templateVersions: ['G60'],
    evidence: [
      { source: 'code:g60/structure-guard.ts:178', note: 'row-41-area "ineff" per-row label anchor' },
      { source: 'code:g60/calculator.ts', note: 'UnitPriceInput.inefficiency, applied to labourPerUnit' },
      { source: 'code:g60/export.ts', note: '"Ineffizienz ALT"/"Ineffizienz NEU" export columns' },
    ],
    notes: 'Not yet parsed outside G60 — KAR-959/P2 extension field, not part of the KAR-892/P1.1 canonical-fields.ts registry.',
  },
  // Master-Prompt process-parameter/master-rate list (task instruction:
  // "Master-Sätze (Lohnsatz, SGA, Gewinn, Scrap)") — Lohnsatz maps to the
  // existing mfg_direct_labor_cost, Scrap to mfg_scrap_rate/mfg_scrap_cost/
  // sum_scrap_material/sum_scrap_manufacturing (all already registered).
  // SG&A + profit as a MASTER RATE (applied once per file/summary, not per
  // process step) has no canonical-fields.ts SUMMARY-level entry — only
  // G60's own per-column g60_overhead_*/g60_profit_* fields exist (direct/
  // sourced variants, INPUT!B22-B29 rate card). This is exactly the "B
  // (Master-Sätze) systematic weak spot" capability-matrix.md's real-corpus
  // scan independently found (14-22% VORHANDEN vs. 100% "sheet located") —
  // registering the field here (status likely EMPTY_IN_SOURCE/
  // NOT_YET_SUPPORTED on the standard-QAF SUMMARY sheet for most files) is
  // what makes that gap VISIBLE in a WorkbookCapabilityMatrix instead of
  // silently absent from the registry entirely.
  {
    id: 'cap_sum_sga_profit_rate',
    module: 'SUMMARY',
    section: 'Capability-Kern extension (KAR-959/P2)',
    labelDe: 'SGK-/Gewinnsatz (Zusammenfassung)',
    labelEn: 'SG&A/profit rate (Summary)',
    aliases: ['SGA-Satz', 'Gewinnsatz', 'SG&A rate', 'profit rate'],
    dataType: 'percent',
    unit: '%',
    classification: 'input',
    requirement: 'optional',
    level: 'summary',
    templateVersions: ['QAF_LEGACY_DE_SUMMARY', 'QAF_V9_SUMMARY'],
    evidence: [
      { source: 'brain:qaf-corpus/reports/gate-audit.md#B12', note: 'summary-metrics.ts LEGACY template fallback gap for a sibling metric' },
      { source: 'brain:qaf-corpus/capability-matrix.md', note: 'Master-Sätze (B) group: 14-22% VORHANDEN across both major families despite 100% sheet-located (F)' },
    ],
    notes: 'Not yet parsed by any Kadi-v2 SUMMARY parser today (no SummaryMetricKey/QafSummary slot exists) — KAR-959/P2 extension field, registered so the corpus-documented gap is a visible capability finding rather than a silent absence.',
  },
  // ── KAR-960/P3 Hebel B extension fields (Master-Prompt §21/§22) ───────────
  //
  // capability-matrix.md's Hebel 2 names 4 DISTINCT Zusammenfassung/Summary
  // master rates ("Lohnsatz, SG&A %, Gewinn %, Scrap %") — cap_sum_sga_profit_rate
  // above (P2) already registered ONE combined field for the "SGK-/Gewinnsatz"
  // concept; these 4 are added SEPARATELY (additive, cap_sum_sga_profit_rate
  // is left untouched/unused by the P3 producer below) so each of the 4
  // task-named rates gets its own fieldId/FieldCandidate lineage rather than
  // conflating two economically different rates (social-overhead-on-labor vs.
  // company profit margin) into one resolved value. Labels are DE/EN
  // BMW-QAF-vocabulary candidates (Fertigungskosten-row wording for the // allow-customer-string
  // DE/EN pair, no Leitfaden-documented SUMMARY-sheet cell for any of the 4 —
  // see summary-field-candidates.ts module header for the conservative,
  // label-scan-only extraction this feeds). `level: 'summary'` (global scope,
  // §21: a workbook-wide master rate, distinct from the already-registered
  // ROW-level mfg_direct_labor_cost/mfg_social_overhead_rate/mfg_scrap_rate,
  // which apply per manufacturing step).
  {
    id: 'cap_sum_master_labor_rate',
    module: 'SUMMARY',
    section: 'Capability-Kern extension (KAR-960/P3)',
    labelDe: 'Lohnsatz (Zusammenfassung)',
    labelEn: 'Labor rate (Summary)',
    aliases: ['Kalkulationslohnsatz', 'Stundenlohnsatz', 'Calculation labor rate', 'Hourly labor rate'],
    dataType: 'number',
    unit: 'BW/h',
    classification: 'input',
    requirement: 'optional',
    level: 'summary',
    templateVersions: ['QAF_LEGACY_DE_SUMMARY', 'QAF_V9_SUMMARY'],
    evidence: [
      { source: 'brain:qaf-corpus/capability-matrix.md', note: 'Hebel 2 — "Master-Sätze (Lohnsatz, SG&A %, Gewinn %, Scrap %)" im Zusammenfassung/Summary-Blatt' },
    ],
    notes: 'Not parsed by Kadi-v2 before KAR-960/P3 — see summary-field-candidates.ts summaryMasterRateFieldCandidates.',
  },
  {
    id: 'cap_sum_master_sga_rate',
    module: 'SUMMARY',
    section: 'Capability-Kern extension (KAR-960/P3)',
    labelDe: 'SGK-Satz (Zusammenfassung)',
    labelEn: 'SG&A rate (Summary)',
    aliases: ['SG&A-Satz', 'Sozialgemeinkostensatz', 'SGA rate'],
    dataType: 'percent',
    unit: '%',
    classification: 'input',
    requirement: 'optional',
    level: 'summary',
    templateVersions: ['QAF_LEGACY_DE_SUMMARY', 'QAF_V9_SUMMARY'],
    evidence: [
      { source: 'brain:qaf-corpus/capability-matrix.md', note: 'Hebel 2 — "Master-Sätze (Lohnsatz, SG&A %, Gewinn %, Scrap %)" im Zusammenfassung/Summary-Blatt' },
    ],
    notes: 'Not parsed by Kadi-v2 before KAR-960/P3 — distinct from cap_sum_sga_profit_rate (P2, combined SGA+profit) and mfg_social_overhead_rate (row-level SGK per process step).',
  },
  {
    id: 'cap_sum_master_profit_rate',
    module: 'SUMMARY',
    section: 'Capability-Kern extension (KAR-960/P3)',
    labelDe: 'Gewinnsatz (Zusammenfassung)',
    labelEn: 'Profit rate (Summary)',
    aliases: ['Gewinnaufschlag', 'Margensatz', 'Profit margin rate', 'Margin rate'],
    dataType: 'percent',
    unit: '%',
    classification: 'input',
    requirement: 'optional',
    level: 'summary',
    templateVersions: ['QAF_LEGACY_DE_SUMMARY', 'QAF_V9_SUMMARY'],
    evidence: [
      { source: 'brain:qaf-corpus/capability-matrix.md', note: 'Hebel 2 — "Master-Sätze (Lohnsatz, SG&A %, Gewinn %, Scrap %)" im Zusammenfassung/Summary-Blatt' },
    ],
    notes: 'Not parsed by Kadi-v2 before KAR-960/P3.',
  },
  {
    id: 'cap_sum_master_scrap_rate',
    module: 'SUMMARY',
    section: 'Capability-Kern extension (KAR-960/P3)',
    labelDe: 'Ausschusssatz (Zusammenfassung)',
    labelEn: 'Scrap rate (Summary)',
    aliases: ['Schrottsatz', 'Ausschussquote', 'Scrap quota'],
    dataType: 'percent',
    unit: '%',
    classification: 'input',
    requirement: 'optional',
    level: 'summary',
    templateVersions: ['QAF_LEGACY_DE_SUMMARY', 'QAF_V9_SUMMARY'],
    evidence: [
      { source: 'brain:qaf-corpus/capability-matrix.md', note: 'Hebel 2 — "Master-Sätze (Lohnsatz, SG&A %, Gewinn %, Scrap %)" im Zusammenfassung/Summary-Blatt' },
    ],
    notes: 'Not parsed by Kadi-v2 before KAR-960/P3 — distinct from mfg_scrap_rate (row-level "Ausschuss pro Prozessschritt" per manufacturing step) and sum_scrap_material/sum_scrap_manufacturing (already-parsed absolute Summen, not a rate).',
  },
]

// ── Merged registry access ──────────────────────────────────────────────────

/** Every canonical field this capability module knows about — KAR-892/P1.1's
 * CANONICAL_FIELDS plus this module's own EXTRA_CAPABILITY_FIELDS. Declaration
 * order preserved (P1 fields first) so `byCanonicalId`/`findByAlias`-style
 * lookups stay stable. */
export const CAPABILITY_FIELDS: readonly CanonicalField[] = [...CANONICAL_FIELDS, ...EXTRA_CAPABILITY_FIELDS]

export interface CapabilityFieldRegistryEntry extends CanonicalField {
  scope: CapabilityFieldScopeKind
}

function withScope(field: CanonicalField): CapabilityFieldRegistryEntry {
  return { ...field, scope: deriveFieldScope(field) }
}

/** All fields for one QAF module, scope-annotated, in registry order. Thin
 * wrapper over ../canonical-model.ts's byModule — reuses P1's own lookup
 * rather than re-implementing filtering. */
export function fieldsForModule(module: QafModule): CapabilityFieldRegistryEntry[] {
  return canonicalByModule(module, CAPABILITY_FIELDS).map(withScope)
}

/** All fields at a given scope kind, across every module. */
export function fieldsByScope(scope: CapabilityFieldScopeKind, registry: readonly CanonicalField[] = CAPABILITY_FIELDS): CapabilityFieldRegistryEntry[] {
  return registry.map(withScope).filter((f) => f.scope === scope)
}

/** Alias lookup (label -> field), scope-annotated — thin wrapper over
 * ../canonical-model.ts's findByAlias against CAPABILITY_FIELDS instead of
 * the bare CANONICAL_FIELDS, so a lookup also finds EXTRA_CAPABILITY_FIELDS. */
export function findFieldByAlias(label: string, lang?: 'de' | 'en'): CapabilityFieldRegistryEntry[] {
  return canonicalFindByAlias(label, lang, CAPABILITY_FIELDS).map(withScope)
}

/** The 8 capability-module QAF modules mapped onto the SheetRole they are
 * carried by (Master-Prompt §31's SemanticSheetResolver <-> CanonicalFieldRegistry
 * pairing) — null for a module with no 1:1 sheet-role mapping in this PR's
 * scope (there is none today; kept as a function, not a bare Record, so a
 * future module without one — e.g. a derived/virtual module — degrades
 * gracefully instead of requiring every Record key to be populated). */
export function moduleSheetRole(module: QafModule): Exclude<SheetRole, 'unknown'> | null {
  switch (module) {
    case 'SUMMARY':
      return 'summary'
    case 'MANUFACTURING':
      return 'manufacturing'
    case 'MATERIAL':
      return 'material'
    case 'SBM':
      return 'sbm'
    case 'LOGISTICS':
      return 'logistics'
    case 'RMR':
      return 'rmr'
    case 'LC_CN':
      return 'lccn'
    case 'CO2E':
      return 'co2e'
    default:
      return null
  }
}
