// A10/B5 Quality Impact — scrap, rework, first-pass yield, cumulative yield,
// expected good output (execution-prompt §8.7) — Wertstrom P1.
//
// "Do not treat rework and scrap as equivalent" (§8.7) is the load-bearing
// rule here: scrap is a genuine YIELD loss (the part never comes out good),
// rework is a CAPACITY cost (the part eventually comes out good, but only
// after extra processing time). Concretely:
//   - `computeCumulativeYield` multiplies scrap factors ONLY — a reworked
//     unit still exits as good, so rework does not reduce yield.
//   - `computeAdditionalReworkCapacityDemand` is the rework-only capacity
//     cost (expected extra seconds per unit attempted).
//
// Inputs are the engine's own `ProcessQualityInput` rather than VsmNode
// fields directly: `scrapRatePct` mirrors the EXISTING VsmNode.scrapRate
// (0-100%, see lib/api/schemas.ts), but `reworkRatePct`/`reworkTimeSec` are
// NOT VsmNode fields today (Capability-Matrix B5: "Rate+Zeit am Prozess" —
// P1 deliberately does not add them to VsmNode, see README "Why no VsmNode
// changes"). A caller assembles this array from wherever the real data
// lives (today: only scrapRatePct can come from a real node).

import type { MetricExplain } from './types'

export interface ProcessQualityInput {
  nodeId: string
  /** [%] 0-100, mirrors VsmNode.scrapRate. */
  scrapRatePct?: number
  /** [%] 0-100 — NOT a VsmNode field today, see module doc. */
  reworkRatePct?: number
  /** Additional processing seconds per reworked unit — NOT a VsmNode field today. */
  reworkTimeSec?: number
}

export interface ProcessQualityResult {
  nodeId: string
  /** `100 − scrapRatePct − reworkRatePct`, i.e. share of units that pass
   * WITHOUT scrap or rework on the first attempt. `null` when NEITHER
   * scrap nor rework data exists for this node (no fabricated 100%). Can go
   * negative when the inputs are contradictory (scrap+rework > 100%) — left
   * un-clamped on purpose; see validation.ts "contradictory yield and scrap". */
  firstPassYieldPct: number | null
  /** Expected extra seconds of capacity consumed per unit ATTEMPTED, purely
   * from rework. `null` (not 0) when the rework rate itself is UNKNOWN, or
   * when a nonzero rework rate is given without a rework time — a silent 0
   * in either case would UNDERSTATE the true capacity need (§17.4 "missing
   * rework time where rework is significant"). Only a MEASURED
   * `reworkRatePct === 0` produces a real, computable `0`. */
  additionalReworkCapacityDemandSec: number | null
  explain: MetricExplain
}

const PROCESS_FORMULA =
  'Erstdurchlaufquote[%] = 100 − Ausschussrate[%] − Nacharbeitsrate[%] (nicht geklemmt — negative Werte sind ein Datenproblem, siehe Validierungs-Engine). Zusätzlicher Nacharbeits-Kapazitätsbedarf[s] = Nacharbeitsrate[%]/100 × Nacharbeitszeit[s].'
const PROCESS_DATA_BASIS = 'ProcessQualityInput: scrapRatePct (mirrors VsmNode.scrapRate) + reworkRatePct/reworkTimeSec (keine VsmNode-Felder heute).'

export function computeProcessQualityImpact(input: ProcessQualityInput): ProcessQualityResult {
  const exclusions: string[] = []
  let firstPassYieldPct: number | null
  if (input.scrapRatePct === undefined && input.reworkRatePct === undefined) {
    firstPassYieldPct = null
    exclusions.push('Weder Ausschuss- noch Nacharbeitsrate für diesen Prozess bekannt — Erstdurchlaufquote nicht berechenbar (kein fabriziertes 100%).')
  } else {
    if (input.scrapRatePct === undefined) exclusions.push('Ausschussrate fehlt — als 0 in die Formel eingesetzt (Annahme, keine Messung).')
    if (input.reworkRatePct === undefined) exclusions.push('Nacharbeitsrate fehlt — als 0 in die Formel eingesetzt (Annahme, keine Messung).')
    firstPassYieldPct = 100 - (input.scrapRatePct ?? 0) - (input.reworkRatePct ?? 0)
  }

  let additionalReworkCapacityDemandSec: number | null
  if (input.reworkRatePct === undefined) {
    // F3/F10: unknown is NOT the same as measured-zero — a silent 0 here
    // would understate capacity need, same doctrine and same fix pattern as
    // the reworkTimeSec-missing branch a few lines below.
    additionalReworkCapacityDemandSec = null
    exclusions.push('Nacharbeitsrate unbekannt — Kapazitätsbedarf nicht berechenbar (kein stiller 0-Ersatz, würde den Bedarf unterschätzen).')
  } else if (input.reworkRatePct === 0) {
    additionalReworkCapacityDemandSec = 0
  } else if (input.reworkTimeSec === undefined) {
    additionalReworkCapacityDemandSec = null
    exclusions.push('Nacharbeitsrate > 0, aber keine Nacharbeitszeit angegeben — Kapazitätsbedarf nicht berechenbar (kein stiller 0-Ersatz, würde den Bedarf unterschätzen).')
  } else {
    additionalReworkCapacityDemandSec = (input.reworkRatePct / 100) * input.reworkTimeSec
  }

  return {
    nodeId: input.nodeId,
    firstPassYieldPct,
    additionalReworkCapacityDemandSec,
    explain: { formula: PROCESS_FORMULA, dataBasis: PROCESS_DATA_BASIS, exclusions },
  }
}

export interface CumulativeYieldResult {
  /** Rolled-throughput yield — product of (1 − scrap_i) over all inputs with
   * a known scrap rate. `null` when NOT ONE input carries a scrap rate. */
  cumulativeYieldPct: number | null
  nodesWithKnownScrap: number
  nodesTotal: number
  explain: MetricExplain
}

const CUMULATIVE_FORMULA = 'Kumulierte Ausbeute[%] = Π (1 − Ausschussrate_i/100) über alle Prozesse mit bekannter Ausschussrate, × 100. Nacharbeit fließt NICHT ein — nachgearbeitete Teile verlassen den Prozess am Ende gut (§8.7 "Scrap ≠ Rework"), sie kosten nur zusätzliche Zeit (additionalReworkCapacityDemandSec), keine Ausbeute.'
const CUMULATIVE_DATA_BASIS = 'ProcessQualityInput[].scrapRatePct über alle Prozesse der Kette.'

export function computeCumulativeYield(inputs: ProcessQualityInput[]): CumulativeYieldResult {
  const known = inputs.filter((i) => i.scrapRatePct !== undefined)
  if (known.length === 0) {
    return {
      cumulativeYieldPct: null,
      nodesWithKnownScrap: 0,
      nodesTotal: inputs.length,
      explain: { formula: CUMULATIVE_FORMULA, dataBasis: CUMULATIVE_DATA_BASIS, exclusions: ['Kein Prozess mit bekannter Ausschussrate — nicht berechenbar (kein fabriziertes 100%).'] },
    }
  }
  const factor = known.reduce((acc, i) => acc * (1 - (i.scrapRatePct as number) / 100), 1)
  const exclusions: string[] = []
  if (known.length < inputs.length) {
    exclusions.push(`${inputs.length - known.length} von ${inputs.length} Prozessen ohne bekannte Ausschussrate — für diese wird Faktor 1.0 (kein bekannter Verlust) angenommen, keine Messung.`)
  }
  return {
    cumulativeYieldPct: factor * 100,
    nodesWithKnownScrap: known.length,
    nodesTotal: inputs.length,
    explain: { formula: CUMULATIVE_FORMULA, dataBasis: CUMULATIVE_DATA_BASIS, exclusions },
  }
}

export interface ExpectedGoodOutputResult {
  expectedGoodOutputUnits: number | null
  explain: MetricExplain
}

const EXPECTED_OUTPUT_FORMULA = 'Erwarteter Gut-Output = Geplante Ausbringungsmenge × Kumulierte Ausbeute[%] / 100.'
const EXPECTED_OUTPUT_DATA_BASIS = 'Geplante Ausbringungsmenge (Aufrufer-Eingabe) + computeCumulativeYield-Ergebnis.'

/** §8.7 "expected good output" — planned output run through the cumulative
 * (scrap-only) yield. */
export function computeExpectedGoodOutput(plannedOutputUnits: number | undefined, cumulativeYieldPct: number | null): ExpectedGoodOutputResult {
  if (plannedOutputUnits === undefined || cumulativeYieldPct === null) {
    return {
      expectedGoodOutputUnits: null,
      explain: { formula: EXPECTED_OUTPUT_FORMULA, dataBasis: EXPECTED_OUTPUT_DATA_BASIS, exclusions: ['Geplante Ausbringungsmenge oder kumulierte Ausbeute fehlt — nicht berechenbar.'] },
    }
  }
  return {
    expectedGoodOutputUnits: plannedOutputUnits * (cumulativeYieldPct / 100),
    explain: { formula: EXPECTED_OUTPUT_FORMULA, dataBasis: EXPECTED_OUTPUT_DATA_BASIS, exclusions: [] },
  }
}
