// A10 Effective Capacity (execution-prompt §8.3) — Wertstrom P1.
//
// Formula priority + fallback rules (documented per §8.3's explicit demand):
//
//   1. A directly MEASURED capacity (`node.capacityPerHour`, Stk/h) wins
//      outright and is used AS-IS — it already reflects real-world losses,
//      so no efficiency factor is applied on top of it (applying one WOULD
//      be a second, different double-counting bug).
//   2. Otherwise, capacity is DERIVED from cycle time × parallel units, then
//      derated by ONE combined efficiency factor:
//        a. If `node.oee` is set, OEE wins outright — it already contains
//           availability × performance × quality. Any separately-supplied
//           availability/performance/quality override for the SAME node is
//           ignored by this function (never multiplied in on top of OEE —
//           that is exactly the double counting §8.3 forbids). The
//           Validierungs-Engine (validation.ts) is where this situation
//           gets SURFACED to a user as a warning ("double-counted
//           availability", §17.3) — this calc function just never produces
//           the wrong number; it does not warn.
//        b. Else, availability/performance/quality overrides (any subset)
//           are multiplied together; a factor left unset is treated as 100%
//           ("kein bekannter Verlust" — a documented ASSUMPTION, not a
//           measurement, called out in `explain.exclusions`).
//        c. Else (nothing at all) — factor 1.0, documented as an assumption.

import type { VsmNode } from '@/lib/vsm-types'
import type { MetricExplain } from './types'

/** Inputs not (yet) modeled on VsmNode — kept as the engine's own override
 * object, keyed by the caller per node, rather than new VsmNode fields (see
 * README "Why no VsmNode changes"). */
export interface NodeCapacityOverrides {
  /** Standalone availability [%] — only applied when `node.oee` is NOT set
   * for the same node (double-counting priority rule above). */
  availabilityPct?: number
  performancePct?: number
  qualityPct?: number
  /** Identical resources running this process in parallel (e.g. 2 machines).
   * Defaults to 1 when omitted. Must be a positive integer-ish number. */
  numParallelUnits?: number
}

export type EfficiencySource = 'oee' | 'availability-performance-quality' | 'none'

export interface EffectiveCapacityResult {
  nodeId: string
  /** `null` when neither a measured nor a derivable capacity exists. */
  effectiveCycleTimeSec: number | null
  effectiveCapacityPerHour: number | null
  /** From `node.capacityPerHour`, verbatim, when present. */
  measuredCapacityPerHour: number | null
  /** From cycle time × parallel units × efficiency factor. */
  derivedCapacityPerHour: number | null
  /** The combined efficiency factor (0..1) actually applied to the DERIVED
   * path. Always a real number (never null) — when no OEE/availability data
   * exists this is 1.0 by documented assumption, see `efficiencySource`. */
  efficiencyFactorUsed: number
  efficiencySource: EfficiencySource
  explain: MetricExplain
}

const FORMULA =
  'Gemessene Kapazität (capacityPerHour) hat Vorrang, unverändert übernommen. Sonst: Kapazität = 3600 ÷ (Soll-Zykluszeit ÷ Teile/Zyklus) × Parallel-Einheiten × Effizienzfaktor. Effizienzfaktor = OEE/100 wenn gesetzt (OEE hat Vorrang, nie zusätzlich mit Verfügbarkeit multipliziert), sonst (Verfügbarkeit/100 × Leistung/100 × Qualität/100) für die gesetzten Anteile, sonst 1.0.'
const DATA_BASIS = 'VsmNode.cycleTimeSec/partsPerCycle/capacityPerHour/oee + optionale NodeCapacityOverrides (Verfügbarkeit/Leistung/Qualität/Parallel-Einheiten — keine VsmNode-Felder).'

function baseExclusions(source: EfficiencySource): string[] {
  const out: string[] = []
  if (source === 'none') out.push('Weder OEE noch Verfügbarkeit/Leistung/Qualität hinterlegt — Effizienzfaktor 1.0 ist eine Annahme (kein bekannter Verlust), keine Messung. Die tatsächliche Kapazität ist vermutlich niedriger.')
  if (source === 'availability-performance-quality') out.push('Mindestens einer von Verfügbarkeit/Leistung/Qualität fehlt ⇒ für den fehlenden Anteil wird 100% angenommen (kein bekannter Verlust an dieser Stelle), keine Messung.')
  return out
}

/**
 * A10 Effective Capacity per process node. Takes a `Pick<VsmNode, …>` (not
 * the full type) so any real VsmNode can be passed directly while the
 * function only reads the four fields it needs — no new VsmNode fields
 * required.
 */
export function computeEffectiveCapacity(
  node: Pick<VsmNode, 'id' | 'cycleTimeSec' | 'partsPerCycle' | 'capacityPerHour' | 'oee'>,
  overrides?: NodeCapacityOverrides,
): EffectiveCapacityResult {
  const numParallelUnits = overrides?.numParallelUnits && overrides.numParallelUnits > 0 ? overrides.numParallelUnits : 1

  let efficiencySource: EfficiencySource
  let efficiencyFactorUsed: number
  if (node.oee != null) {
    efficiencySource = 'oee'
    efficiencyFactorUsed = clampPct(node.oee) / 100
  } else if (overrides?.availabilityPct != null || overrides?.performancePct != null || overrides?.qualityPct != null) {
    efficiencySource = 'availability-performance-quality'
    efficiencyFactorUsed = (clampPct(overrides.availabilityPct ?? 100) / 100) * (clampPct(overrides.performancePct ?? 100) / 100) * (clampPct(overrides.qualityPct ?? 100) / 100)
  } else {
    efficiencySource = 'none'
    efficiencyFactorUsed = 1
  }

  const measuredCapacityPerHour = node.capacityPerHour ?? null

  let derivedCapacityPerHour: number | null = null
  const cycleTimeSec = node.cycleTimeSec
  if (cycleTimeSec != null && cycleTimeSec > 0) {
    const partsPerCycle = node.partsPerCycle && node.partsPerCycle > 0 ? node.partsPerCycle : 1
    const cycleTimeSecPerPart = cycleTimeSec / partsPerCycle
    const theoreticalCapacityPerHour = (3600 / cycleTimeSecPerPart) * numParallelUnits
    derivedCapacityPerHour = theoreticalCapacityPerHour * efficiencyFactorUsed
  }

  const effectiveCapacityPerHour = measuredCapacityPerHour ?? derivedCapacityPerHour
  const effectiveCycleTimeSec = effectiveCapacityPerHour && effectiveCapacityPerHour > 0 ? 3600 / effectiveCapacityPerHour : null

  const exclusions = baseExclusions(efficiencySource)
  if (!node.partsPerCycle || node.partsPerCycle <= 0) {
    exclusions.push('Teile/Zyklus fehlt ⇒ Annahme 1 Teil/Zyklus (Standardfall, dokumentiert — anders als eine unbekannte OEE wird hier NICHT auf "nicht berechenbar" degradiert).')
  }
  if (overrides?.numParallelUnits != null && overrides.numParallelUnits <= 0) {
    exclusions.push(`Parallel-Einheiten-Override ungültig (${overrides.numParallelUnits} ≤ 0) — auf 1 zurückgefallen (Standardfall), nicht stillschweigend verworfen.`)
  }
  if (measuredCapacityPerHour != null) {
    exclusions.push('Gemessene Kapazität (capacityPerHour) vorhanden — unverändert übernommen, KEIN zusätzlicher Effizienzfaktor angewendet (sie spiegelt reale Verluste bereits wider). Der Effizienzfaktor gilt nur für den aus der Zykluszeit abgeleiteten Wert.')
  }
  if (cycleTimeSec == null || cycleTimeSec <= 0) {
    exclusions.push('Keine (gültige) Soll-Zykluszeit vorhanden — abgeleitete Kapazität nicht berechenbar.')
  }

  return {
    nodeId: node.id,
    effectiveCycleTimeSec,
    effectiveCapacityPerHour,
    measuredCapacityPerHour,
    derivedCapacityPerHour,
    efficiencyFactorUsed,
    efficiencySource,
    explain: { formula: FORMULA, dataBasis: DATA_BASIS, exclusions },
  }
}

/** Guards a caller-supplied percent value against clearly-invalid ranges
 * without silently pretending to validate everything — negative/>100 inputs
 * are the Validierungs-Engine's job to flag; this just keeps the calc from
 * producing a negative or absurd efficiency factor. */
function clampPct(pct: number): number {
  if (!Number.isFinite(pct)) return 0
  return Math.min(100, Math.max(0, pct))
}

/**
 * A10 Utilization — effective cycle time vs. customer takt, expressed as a
 * percentage. >100% means the process cannot keep up with takt on its own.
 */
export interface UtilizationResult {
  utilizationPct: number | null
  explain: MetricExplain
}

export function computeUtilization(effectiveCycleTimeSec: number | null, taktTimeSec: number | null | undefined): UtilizationResult {
  const formula = 'Auslastung[%] = effektive Zykluszeit ÷ Kundentakt × 100.'
  const dataBasis = 'EffectiveCapacityResult.effectiveCycleTimeSec (A10) + Kundentakt (A3 oder projects.customer_takt_time_sec).'
  if (effectiveCycleTimeSec == null || taktTimeSec == null || !(taktTimeSec > 0)) {
    return { utilizationPct: null, explain: { formula, dataBasis, exclusions: ['Effektive Zykluszeit oder Kundentakt fehlt/ist 0 — nicht berechenbar.'] } }
  }
  return { utilizationPct: (effectiveCycleTimeSec / taktTimeSec) * 100, explain: { formula, dataBasis, exclusions: [] } }
}
