// Loop 10 View-Spec-Adoption, Modul 5 (Zusammenfassung): Formular-Positionen,
// Kostenstruktur-Buckets, Preisbrücke, Kennzahlen-Tabelle, Treiber/Movers und
// Hochrechnungs-Facts.
//
// Muster wie overview-run.ts/negotiation-run.ts/appendix-run.ts/
// manufacturing-run.ts: ein pures Builder-Modul. Die Zusammenfassungs-
// Sektionen waren das letzte der fünf Detail-Module mit Renderer-Ableitungen
// (view-spec-adoption-analyse.md, Abschnitt 2): Facts-Zählungen per
// .filter().length in jeder Sektion (doppelt berechnet: .some + .filter),
// die Richtungs-Ableitung der Kennzahlen-Zeilen inline im JSX und die
// Explain-Attribute im Render-Loop. Genau diese Ableitungen wandern hierher
// an die Build-Zeit — die ZAHLEN kommen unverändert aus denselben
// summary-view.ts-/movers.ts-Engine-Funktionen wie bisher (Fachregeln werden
// aufgerufen, nicht neu geschrieben — Analyse, Abschnitt „Nicht-Ziele").
//
// BEWUSST KEINE neuen Chart-Spec-Typen: QafBridgeChart/QafCostStructureChart/
// QafMoversChart erwarten BridgeData/BucketDatum[]/Movers direkt — der im
// Analyse-Doc offene Punkt „Spec direkt an Recharts vs. Adapter-Schicht"
// wird hier NICHT mitentschieden; die Charts konsumieren dieselben Formen
// aus dem Run, die sie vorher als Einzel-Props bekamen. Ein Chart-Umbau
// wäre eine eigene Etappe mit eigener Golden-Betrachtung.
//
// Kennzahlen-Richtung: die Tabelle leitete die Richtung IMMER NUR aus
// deltaAbsolute ab (kein Prozentpunkte-/Prozent-Fallback wie diffDirection
// in manufacturing-run.ts) — das ist hier gepinnt statt still
// „vereinheitlicht": eine Summary-Kennzahl ohne absolutes Delta hatte nie
// einen Pfeil, und das bleibt so (Paritäts-Disziplin; Vereinheitlichung
// wäre eine sichtbare Verhaltensänderung → Produktfrage).
//
// Die Export-Rohformen (summaryLines, kpis, formLines, buckets, bridge,
// metricsTable, movers, negotiation, oneTimePayments) liefert der Run mit —
// die Schale baut ihr GlobalExportButtons-`sections`-Objekt daraus (plus
// levers aus buildLeversRun). Der frühere Rechenrest der Schale
// (leverInput/buildLevers samt Label-/Unsicherheits-Maps) ist seit den
// Hebel-Belegen (levers-run.ts) ebenfalls an die Build-Zeit gewandert —
// Review-#505-M2 damit vollständig abgeräumt.
//
// Pure Funktionen, keine I/O.

import {
  pickCurrency,
  buildSummaryKpis,
  buildBuckets,
  buildBridge,
  buildSummaryLines,
  buildNegotiation,
  buildOneTimeRows,
  buildMetricsTable,
  buildFormLines,
  type SummaryDiffRowData,
  type SummaryKpis,
  type SummaryLine,
  type FormLine,
  type BucketDatum,
  type BridgeData,
  type MetricsTableRow,
  type Negotiation,
  type OneTimeRow,
} from './summary-view'
import { buildMovers, type Movers } from './movers'
import {
  isUncertainMatch,
  type AppendixPlausibility,
  type AppendixStepMatch,
} from './appendix-run'
import {
  ruleFindingResolver,
  diffStatusLabel,
  type DiffDirection,
  type ManufacturingDiff,
  type ManufacturingStepRow,
} from './manufacturing-run'
import {
  buildSummaryExplainAttributes,
  type ExplainAttributes,
} from './explain-provenance'
import type { SummaryMetricKey } from './summary-metrics'
import { countPhrase, deltaPhrase, unchangedPhrase } from './section-titles'

// ── Run ─────────────────────────────────────────────────────────────────────

export interface MetricsRowSpec {
  row: MetricsTableRow
  /** Richtung NUR aus deltaAbsolute — gepinnte Bestands-Semantik der
   * Kennzahlen-Tabelle (siehe Modul-Kopf), bewusst nicht diffDirection. */
  dir: DiffDirection
  /** diffStatusLabel(status, dir) — "" ohne Status; der Renderer zeigt das
   * Pill nur bei vorhandenem Status, wie bisher. */
  statusLabelDe: string
  /** KAR-922/P6.2: Explain-Panel-Attribute (SUMMARY-Adapter), jetzt zur
   * Build-Zeit statt im Render-Loop. */
  explainAttrs: ExplainAttributes
}

export interface SummaryRun {
  currency: string | null
  /** Gate der Export-Buttons in der Schale — vorher `summaryDiffs.length > 0`. */
  hasSummaryData: boolean
  /** Export-Rohformen (GlobalExportButtons-`sections`) — dieselben
   * Engine-Ergebnisse, die auch die Sektionen speisen. */
  kpis: SummaryKpis | null
  summaryLines: SummaryLine[]
  negotiation: Negotiation
  oneTimeRows: OneTimeRow[]
  form: {
    lines: FormLine[]
    hasData: boolean
    factsDe: string[]
  }
  buckets: {
    data: BucketDatum[] | null
    factsDe: string[]
  }
  bridge: {
    data: BridgeData | null
    factsDe: string[]
  }
  metrics: {
    /** Original-Engine-Zeilen (Export-Shape) — rows[i].row verweist darauf. */
    table: MetricsTableRow[]
    rows: MetricsRowSpec[]
    presentCount: number
    factsDe: string[]
  }
  movers: {
    data: Movers
    /** Enthält null-Einträge wie der bisherige Renderer-Aufbau — `Section`
     * akzeptiert und filtert sie (facts: ReadonlyArray<string|null|undefined>). */
    factsDe: (string | null)[]
  }
  projection: {
    factsDe: string[]
  }
}

export function buildSummaryRun(input: {
  summaryDiffs: SummaryDiffRowData[]
  /** Für die Treiber/Movers-Zeilen (fk-Deltas je Prozessschritt) — dieselben
   * Rohzeilen, aus denen die Schale sie vorher selbst baute. */
  diffs: readonly ManufacturingDiff[]
  steps: readonly ManufacturingStepRow[]
  stepMatches: readonly AppendixStepMatch[]
  /** Regel-Befunde für die Kennzahlen-Explain-Panels — dieselbe gemergte
   * Liste (DB + live), die auch buildAppendixRun/buildManufacturingRun
   * bekommen. */
  plausibility: readonly AppendixPlausibility[]
  partName: string | null
  partNumber: string | null
  baselineFileName: string | null
  comparisonFileName: string | null
  baselineTemplateProfile: string | null
  comparisonTemplateProfile: string | null
  engineVersion: Record<string, unknown> | null
  createdAt: string | null
}): SummaryRun {
  const currency = pickCurrency(input.summaryDiffs)
  const kpis = buildSummaryKpis(input.summaryDiffs)
  const formLines = buildFormLines(input.summaryDiffs)
  const hasFormData = formLines.some((l) => l.alt !== null || l.neu !== null)
  const buckets = buildBuckets(input.summaryDiffs)
  const bridge = buildBridge(input.summaryDiffs)
  const metricsTable = buildMetricsTable(input.summaryDiffs)
  const summaryLines = buildSummaryLines(input.summaryDiffs)
  const presentCount = metricsTable.filter((r) => r.present).length
  const negotiation = buildNegotiation(input.summaryDiffs)
  const oneTimeRows = buildOneTimeRows(input.summaryDiffs)

  // Movers — der bisherige Schale-Aufbau, jetzt mit builder-lokalen Maps
  // (dieselbe Konstruktion wie manufacturing-run.ts; die frühere
  // Schale-Kopie ist mit den Hebel-Belegen entfallen, siehe Modul-Kopf).
  const stepLabelById = new Map(
    input.steps.map((s) => [
      s.id,
      s.process_name || s.position_number || s.id.slice(0, 8),
    ]),
  )
  const matchLabelById = new Map<string, string>()
  const matchUncertainById = new Map<string, boolean>()
  for (const m of input.stepMatches) {
    const label =
      (m.neu_step_id && stepLabelById.get(m.neu_step_id)) ||
      (m.alt_step_id && stepLabelById.get(m.alt_step_id)) ||
      '—'
    matchLabelById.set(m.id, label)
    matchUncertainById.set(m.id, isUncertainMatch(m))
  }
  const movers = buildMovers({
    rows: input.diffs
      .filter((d) => d.field === 'fk')
      .map((d) => ({
        stepLabel:
          (d.step_match_id ? matchLabelById.get(d.step_match_id) : null) ??
          '—',
        delta: d.delta_absolute,
        uncertain: d.step_match_id
          ? (matchUncertainById.get(d.step_match_id) ?? false)
          : true,
      })),
    fallback: {
      label: input.partName ?? input.partNumber ?? 'Vergleichsobjekt',
      delta:
        input.summaryDiffs.find((r) => r.metric_key === 'quotationPrice')
          ?.delta_absolute ?? null,
    },
    // R-20: Die Zeilen sind Fertigungskosten-Deltas je Prozessschritt und
    // müssen sich gegen die Blattzeile rekonsilieren, aus der sie stammen.
    reconcileTo: {
      label: 'Fertigungskosten',
      delta:
        input.summaryDiffs.find((r) => r.metric_key === 'manufacturingCosts')
          ?.delta_absolute ?? null,
    },
  })

  const ruleFindingFor = ruleFindingResolver(input.plausibility)
  const metricsRows: MetricsRowSpec[] = metricsTable.map((r) => {
    const dir: DiffDirection =
      r.deltaAbsolute === null
        ? 'flat'
        : r.deltaAbsolute > 0
          ? 'up'
          : r.deltaAbsolute < 0
            ? 'down'
            : 'flat'
    const explainAttrs = buildSummaryExplainAttributes({
      metricKey: r.metricKey as SummaryMetricKey,
      alt: {
        fileName: input.baselineFileName,
        cell: r.sourceAlt,
        value: r.alt,
        currency,
        templateProfile: input.baselineTemplateProfile,
      },
      neu: {
        fileName: input.comparisonFileName,
        cell: r.sourceNeu,
        value: r.neu,
        currency,
        templateProfile: input.comparisonTemplateProfile,
      },
      deltaAbsolute: r.deltaAbsolute,
      deltaPercent: r.deltaPercent,
      status: r.status,
      ruleFinding: ruleFindingFor(r.metricKey, null),
      engineVersion: input.engineVersion,
      timestamp: input.createdAt,
    })
    return {
      row: r,
      dir,
      statusLabelDe: diffStatusLabel(r.status, dir),
      explainAttrs,
    }
  })

  const changedFormCount = formLines.filter(
    (l) => l.alt !== null && l.neu !== null && l.alt !== l.neu,
  ).length
  const changedBucketCount = (buckets ?? []).filter(
    (b) => b.delta !== 0,
  ).length
  const changedMetricsCount = metricsTable.filter(
    (r) => r.present && (r.deltaAbsolute ?? 0) !== 0,
  ).length

  return {
    currency,
    hasSummaryData: input.summaryDiffs.length > 0,
    kpis,
    summaryLines,
    negotiation,
    oneTimeRows,
    form: {
      lines: formLines,
      hasData: hasFormData,
      factsDe: [
        changedFormCount > 0
          ? countPhrase({
              from: changedFormCount,
              to: changedFormCount,
              nounDe: 'Positionen verändert',
            })
          : unchangedPhrase('Alle Formularpositionen'),
      ],
    },
    buckets: {
      data: buckets,
      factsDe: buckets
        ? [
            changedBucketCount > 0
              ? countPhrase({
                  from: changedBucketCount,
                  to: changedBucketCount,
                  nounDe: 'Buckets verändert',
                })
              : unchangedPhrase('Alle Buckets'),
          ]
        : [],
    },
    bridge: {
      data: bridge,
      factsDe: bridge
        ? [
            deltaPhrase({ value: bridge.delta, unit: 'EUR_per_piece' }),
            countPhrase({
              from: bridge.steps.length,
              to: bridge.steps.length,
              nounDe: 'Kostenblöcke',
            }),
          ]
        : [],
    },
    metrics: {
      table: metricsTable,
      rows: metricsRows,
      presentCount,
      factsDe: [
        changedMetricsCount > 0
          ? countPhrase({
              from: changedMetricsCount,
              to: changedMetricsCount,
              nounDe: 'Kennzahlen verändert',
            })
          : unchangedPhrase('Alle Kennzahlen'),
      ],
    },
    movers: {
      data: movers,
      factsDe: [
        movers.rows.length > 0
          ? countPhrase({
              from: movers.rows.length,
              to: movers.rows.length,
              nounDe: 'Treiber',
            })
          : 'keine Treiber',
        movers.rows.length > 0
          ? deltaPhrase({ value: movers.totalDelta, unit: 'EUR_per_piece' })
          : null,
        // Muster 5 der Referenzdarstellung: Die Kontrollsumme gehört ins
        // Bild, nicht nur ins JSON (Begründung im bisherigen Renderer).
        movers.reconciliation.passed === false
          ? 'Kontrollsumme weicht ab'
          : movers.reconciliation.passed === true
            ? 'Kontrollsumme geht auf'
            : null,
      ],
    },
    projection: {
      factsDe: bridge
        ? [
            deltaPhrase({ value: bridge.delta, unit: 'EUR_per_piece' }),
            'je Stück, vor Volumen',
          ]
        : [],
    },
  }
}
