// A6 Bottleneck Detection v2 (execution-prompt §8.8) — Wertstrom P1.
//
// Replaces "highest raw cycle time" with effective cycle time (parallel
// resources / availability / OEE / scrap-rework capacity impact, via
// capacity.ts) compared against customer takt. Provides primary bottleneck,
// secondary candidates, a plain-German reason (§8.8's example-sentence
// style, translated), the calculation basis, and a confidence level.
//
// `findBottleneckId` (vsm-metrics.ts) stays UNCHANGED — its formula is
// reimplemented locally below (`legacyMaxCycleTimeFallback`, not imported,
// to keep lib/vsm-engine independent of the components/ layer) as the
// degraded fallback for exactly the cases §8.8 implies it should apply: no
// takt, or not enough capacity data to run the full v2 model. The
// degradation is flagged honestly via `degraded: true`, never silently
// presented as a full v2 result.
//
// P7 fix-round Grundsatz-Entscheidung "v2 überall, wo 'Engpass' draufsteht":
// the Editor-Canvas-Badge/Tabellen-Ansicht (vsm-editor.tsx) and the
// Copilot-/XLSX-Exporte now call `computeBottleneckV2` (this function) too,
// instead of calling `findBottleneckId` directly the way they used to —
// `findBottleneckId` itself is untouched/still exported, just no longer the
// UI's own bottleneck source of truth outside of this function's internal
// degraded-fallback path.

import type { VsmNode } from '@/lib/vsm-types'
import { computeEffectiveCapacity, computeUtilization, type NodeCapacityOverrides } from './capacity'
import { formatRound1, isStationNode, type MetricExplain } from './types'

export type BottleneckConfidence = 'high' | 'medium' | 'low'

export interface BottleneckCandidate {
  nodeId: string
  nodeName: string
  effectiveCycleTimeSec: number | null
  utilizationPct: number | null
  /** (effectiveCT − takt) / takt × 100 — the "18 percent above" figure from
   * the §8.8 example sentence. */
  overTaktPct: number | null
}

export interface BottleneckResult {
  primary: BottleneckCandidate | null
  secondary: BottleneckCandidate[]
  /** §8.8 example-sentence style, in German — `null` only when `primary` is `null`. */
  reasonDe: string | null
  basisDe: string
  confidence: BottleneckConfidence
  /** `true` when this fell back to the legacy max-cycle-time heuristic
   * (no takt, or fewer than 2 process/machine nodes with a computable
   * effective cycle time) instead of running the full v2 model. */
  degraded: boolean
  explain: MetricExplain
}

/** "Approaching capacity" watch-list threshold for secondary candidates.
 * Not a number the source spec gives — §8.8 only says "possible secondary
 * bottlenecks" — a documented, named choice, not a hidden magic number. */
const SECONDARY_WATCH_THRESHOLD_PCT = 80
/** Cap on how many secondary candidates to report, to keep the result
 * genuinely useful ("secondary CANDIDATES", not a full ranked dump). */
const MAX_SECONDARY_CANDIDATES = 3

const FORMULA_V2 =
  'Auslastung[%] = effektive Zykluszeit ÷ Kundentakt × 100 (A10, siehe capacity.ts — inkl. Parallel-Einheiten/OEE/Verfügbarkeit, kein Doppelzählen). Primärer Engpass = Prozess-/Maschinen-Node mit der höchsten Auslastung. Sekundäre Kandidaten = übrige Nodes mit Auslastung ≥ 80% (dokumentierte Beobachtungs-Schwelle, max. 3).'
const FORMULA_DEGRADED = 'Fallback: höchste rohe Soll-Zykluszeit (identische Logik zu vsm-metrics.ts findBottleneckId) — kein Kundentakt oder zu wenig Kapazitätsdaten für Engpass v2.'
const DATA_BASIS = 'VsmNode[] (type=process|machine, cycleTimeSec/partsPerCycle/capacityPerHour/oee) + Kundentakt + optionale NodeCapacityOverrides je Node.'

function legacyMaxCycleTimeFallback(stationNodes: VsmNode[]): VsmNode | null {
  const withCt = stationNodes.filter((n) => (n.cycleTimeSec ?? 0) > 0)
  if (withCt.length < 2) return null
  return withCt.reduce((a, b) => ((a.cycleTimeSec ?? 0) >= (b.cycleTimeSec ?? 0) ? a : b))
}

/**
 * A6 Bottleneck Detection v2. `overridesByNodeId` supplies the
 * availability/performance/quality/parallel-unit inputs capacity.ts needs
 * that don't exist on VsmNode yet — omit entirely to rely on
 * cycleTimeSec/partsPerCycle/capacityPerHour/oee alone.
 */
export function computeBottleneckV2(nodes: VsmNode[], taktTimeSec: number | null | undefined, overridesByNodeId?: Record<string, NodeCapacityOverrides>): BottleneckResult {
  const stationNodes = nodes.filter(isStationNode)

  const capacityByNode = new Map(stationNodes.map((n) => [n.id, computeEffectiveCapacity(n, overridesByNodeId?.[n.id])]))
  const usableNodes = stationNodes.filter((n) => capacityByNode.get(n.id)?.effectiveCycleTimeSec != null)

  const taktUsable = taktTimeSec != null && taktTimeSec > 0

  if (!taktUsable || usableNodes.length < 2) {
    const fallbackNode = legacyMaxCycleTimeFallback(stationNodes)
    const reasons: string[] = []
    if (!taktUsable) reasons.push('kein Kundentakt übergeben')
    if (usableNodes.length < 2) reasons.push('weniger als 2 Prozess-/Maschinen-Nodes mit berechenbarer effektiver Zykluszeit')
    return {
      primary: fallbackNode
        ? { nodeId: fallbackNode.id, nodeName: fallbackNode.name, effectiveCycleTimeSec: fallbackNode.cycleTimeSec ?? null, utilizationPct: null, overTaktPct: null }
        : null,
      secondary: [],
      reasonDe: fallbackNode
        ? `${fallbackNode.name} ist der vorläufige Engpass (höchste Soll-Zykluszeit: ${fallbackNode.cycleTimeSec}s) — vereinfachte Heuristik, weil ${reasons.join(' und ')}.`
        : null,
      basisDe: FORMULA_DEGRADED,
      confidence: 'low',
      degraded: true,
      explain: { formula: FORMULA_DEGRADED, dataBasis: DATA_BASIS, exclusions: [`Degradiert: ${reasons.join(', ')}.`, 'Keine Berücksichtigung von Parallelität/Verfügbarkeit/OEE/Scrap/Rework in diesem Fallback (identisch zur alten Max-CT-Heuristik).'] },
    }
  }

  const candidates: BottleneckCandidate[] = usableNodes.map((n) => {
    const ec = capacityByNode.get(n.id)!
    const util = computeUtilization(ec.effectiveCycleTimeSec, taktTimeSec)
    const overTaktPct = ec.effectiveCycleTimeSec != null && taktTimeSec ? ((ec.effectiveCycleTimeSec - taktTimeSec) / taktTimeSec) * 100 : null
    return { nodeId: n.id, nodeName: n.name, effectiveCycleTimeSec: ec.effectiveCycleTimeSec, utilizationPct: util.utilizationPct, overTaktPct }
  })
  candidates.sort((a, b) => (b.utilizationPct ?? -Infinity) - (a.utilizationPct ?? -Infinity))

  const primary = candidates[0]
  const secondary = candidates.slice(1).filter((c) => (c.utilizationPct ?? 0) >= SECONDARY_WATCH_THRESHOLD_PCT).slice(0, MAX_SECONDARY_CANDIDATES)

  const reasonDe =
    primary.overTaktPct != null && primary.overTaktPct > 0
      ? `${primary.nodeName} ist der primäre Engpass: die effektive Zykluszeit (${formatRound1(primary.effectiveCycleTimeSec)}s) liegt ${formatRound1(primary.overTaktPct)}% über dem Kundentakt (${formatRound1(taktTimeSec)}s).`
      : `Kein Prozess- oder Maschinen-Node überschreitet den Kundentakt (${formatRound1(taktTimeSec)}s). ${primary.nodeName} hat mit ${formatRound1(primary.utilizationPct)}% die höchste Auslastung und ist damit der relative Engpass.`

  // F5: capacityPerHour is capacity.ts's own top-priority, MEASURED source —
  // it never needs cycleTimeSec (see capacity.ts's documented priority
  // rule). Requiring cycleTimeSec here too used to wrongly degrade
  // confidence to "medium" for a node measured purely via capacityPerHour.
  // "high" additionally requires every station node to actually BE in the
  // analysis (computable effective cycle time): a station that silently
  // dropped out of the candidate set must never coexist with a
  // high-confidence claim — same invisibility class as the F1 finding.
  const unusableStations = stationNodes.filter((n) => capacityByNode.get(n.id)?.effectiveCycleTimeSec == null)
  const allWellMeasured =
    unusableStations.length === 0 && stationNodes.every((n) => n.oee != null || n.capacityPerHour != null)
  const confidence: BottleneckConfidence = allWellMeasured ? 'high' : 'medium'

  return {
    primary,
    secondary,
    reasonDe,
    basisDe: FORMULA_V2,
    confidence,
    degraded: false,
    explain: {
      formula: FORMULA_V2,
      dataBasis: DATA_BASIS,
      exclusions: [
        'Berücksichtigt werden type="process"- UND type="machine"-Nodes (die einzigen Stations-Typen mit Zykluszeit-/OEE-Eingabe im Editor) — Bestand/Transport/Kunde/Lieferant/Zeitwert sind keine Engpass-Kandidaten.',
        confidence === 'medium' ? 'Nicht bei jedem Prozess-/Maschinen-Node liegt OEE oder eine gemessene Kapazität vor — die effektive Zykluszeit nutzt für diese den 1.0-Annahme-Pfad (siehe capacity.ts), Konfidenz entsprechend "medium".' : 'Jeder Prozess-/Maschinen-Node hat OEE oder eine gemessene Kapazität hinterlegt.',
        ...(unusableStations.length > 0
          ? [
              `${unusableStations.length} Stations-Node(s) ohne berechenbare effektive Zykluszeit sind NICHT in der Engpass-Analyse enthalten: ${unusableStations.map((n) => `"${n.name}"`).join(', ')} — keine Aussage über diese Nodes möglich.`,
            ]
          : []),
        `Sekundäre Kandidaten: Auslastungs-Schwelle ${SECONDARY_WATCH_THRESHOLD_PCT}% ist eine dokumentierte, nicht spezifizierte Wahl (siehe Konstante SECONDARY_WATCH_THRESHOLD_PCT).`,
      ],
    },
  }
}
