// Preis-Kalkulator bridge for Summary-mode comparisons (KAR-960/P3, Master-
// Prompt §22 Partial Module Population, task instruction 3a: "Preis-
// Kalkulator ... Alt/Neu-Chips auch bei Summary-Vergleichen anbieten,
// gespeist aus Hebel A+B").
//
// Before this PR, getCalculatorParams (app/qaf-differences/actions.ts) only
// ever ran for `comparison_mode: 'g60'` — a Summary comparison's Preis-
// Kalkulator page always rendered `comparisons: []`, so NO chip ever
// appeared, regardless of how much the Fertigungskosten/Zusammenfassung
// sheets actually contained (the exact §22 violation the task names).
//
// This module builds the analogous CalculatorSideParams-shaped object from
// Hebel A (a selected manufacturing step's process parameters) + Hebel B
// (the file's resolved Summary master rates) — but, UNLIKE G60's
// calculatorParamsFrom (which always returns a fully-populated object or
// null), every field here is populated INDEPENDENTLY and stays `null` when
// no source produced a value (§22: "the module must not be disabled ...
// unavailable values must be marked separately", §23: "never show empty
// 0,00 fields as real values"). A caller (getCalculatorParams) widens
// CalculatorSideParams's field types to `number | null` to carry this
// distinction all the way to the chip-rendering check in
// qaf-price-calculator.tsx (`altVal !== null`), which already treats `null`
// as "no chip" — no UI change needed there beyond the wider type.
//
// §21 (process-specific over one global master): cycleSec/partsPerCycle/
// employees/labourRatePerHour/machineRatePerHour/scrapRate all prefer the
// SELECTED manufacturing step's own per-process value; scrapRate falls back
// to the Summary master scrap rate ONLY when the step itself has none.
// sgaFkRate/sgaMatRate/profitFkRate/profitMatRate have no per-process
// equivalent in the standard Fertigungskosten sheet at all (G60-INPUT-only
// concept, see field-registry.ts cap_sum_master_* docs) — always the Summary
// master rate (same value for FK/MAT, a documented ASSUMPTION: the Summary
// sheet carries one SG&A/profit rate, not G60-INPUT's directed/sourced FK/
// MAT split).
//
// materialPerUnit and inefficiency have NO source in this PR's scope
// (MATERIAL-module wiring and G60-only "Ineffizienz" respectively — see
// manufacturing-field-candidates.ts PROCESS_PARAMETER_FIELD_KEYS doc) —
// always null, honestly, never a fabricated 0.
//
// tdd-guard:skip — covered by __tests__/calculator-bridge.test.ts.

import type { QAFRow } from '../../../qaf-parser'
import type { FieldConflictStatus } from './types'

export interface SummaryCalculatorSideParams {
  residualPerUnit: number
  cycleSec: number | null
  partsPerCycle: number | null
  employees: number | null
  labourRatePerHour: number | null
  inefficiency: number | null
  machineRatePerHour: number | null
  materialPerUnit: number | null
  scrapRate: number | null
  sgaFkRate: number | null
  sgaMatRate: number | null
  profitFkRate: number | null
  profitMatRate: number | null
  /** Quelle-Tooltip data (task instruction 3a) — the per-field source cell
   * ("Fertigungskosten!G12" / "Zusammenfassung!H20"), null when the field
   * itself is null OR the source did not carry a cell reference. */
  sources: Partial<Record<keyof Omit<SummaryCalculatorSideParams, 'sources' | 'statuses' | 'residualPerUnit'>, string | null>>
  /** KAR-960 review fix (PR #327 finding 2) — the FieldConflictStatus
   * (field-candidates.ts resolveAllFieldCandidates) an adopted Summary
   * master rate resolved to, ONLY set for the master-rate-sourced fields
   * (scrapRate/sgaFkRate/sgaMatRate/profitFkRate/profitMatRate — see
   * calculatorParamsFromSummaryStep below). Before this fix,
   * masterRatesOf (actions.ts) discarded the status
   * buildMasterRateRows/resolveAllFieldCandidates already computed, so an
   * INCONSISTENT-resolved rate (conflicting label hits on the Summary
   * sheet, priority-ranked winner picked) reached the Preis-Kalkulator chip
   * indistinguishable from an unambiguous SINGLE/AGREEMENT hit — same
   * "widersprüchliche Quellen" ambiguity the §7 Anomalien Pill already
   * flags on the same page, just silently dropped here. */
  statuses: Partial<Record<keyof Omit<SummaryCalculatorSideParams, 'sources' | 'statuses' | 'residualPerUnit'>, FieldConflictStatus>>
}

export interface SummaryMasterRates {
  scrapRate: { value: number; cell: string | null; status: FieldConflictStatus } | null
  sgaRate: { value: number; cell: string | null; status: FieldConflictStatus } | null
  profitRate: { value: number; cell: string | null; status: FieldConflictStatus } | null
}

export const EMPTY_SUMMARY_MASTER_RATES: SummaryMasterRates = { scrapRate: null, sgaRate: null, profitRate: null }

/**
 * `step` is the selected manufacturing step's raw_values (null when no step
 * is selected/matched — the Summary master rates alone still populate what
 * they can, §22). Returns null only when NEITHER the step NOR any master
 * rate contributed anything at all (nothing to show, consistent with G60's
 * calculatorParamsFrom returning null for an empty tab).
 */
export function calculatorParamsFromSummaryStep(
  step: QAFRow | null,
  masterRates: SummaryMasterRates,
): SummaryCalculatorSideParams | null {
  const scrapFromStep = step?.ausschuss !== null && step?.ausschuss !== undefined
  const scrapRate = step?.ausschuss ?? masterRates.scrapRate?.value ?? null
  const scrapCell = scrapFromStep ? (step!.sourceCells?.ausschuss ?? null) : (masterRates.scrapRate?.cell ?? null)
  // scrapRate only carries a FieldConflictStatus when it came from the
  // Summary master rate (a Fertigungskosten step value has no conflict
  // resolution concept — it is the one raw cell for that step).
  const scrapStatus = scrapFromStep ? undefined : masterRates.scrapRate?.status

  const params: SummaryCalculatorSideParams = {
    residualPerUnit: 0,
    cycleSec: step?.zykluszeit ?? null,
    partsPerCycle: step?.teileProZyklus ?? null,
    employees: step?.anzahlMA ?? null,
    labourRatePerHour: step?.lohnkosten ?? null,
    inefficiency: null,
    machineRatePerHour: step?.mss ?? null,
    materialPerUnit: null,
    scrapRate,
    sgaFkRate: masterRates.sgaRate?.value ?? null,
    sgaMatRate: masterRates.sgaRate?.value ?? null,
    profitFkRate: masterRates.profitRate?.value ?? null,
    profitMatRate: masterRates.profitRate?.value ?? null,
    sources: {
      cycleSec: step?.sourceCells?.zykluszeit ?? null,
      partsPerCycle: step?.sourceCells?.teileProZyklus ?? null,
      employees: step?.sourceCells?.anzahlMA ?? null,
      labourRatePerHour: step?.sourceCells?.lohnkosten ?? null,
      machineRatePerHour: step?.sourceCells?.mss ?? null,
      scrapRate: scrapCell,
      sgaFkRate: masterRates.sgaRate?.cell ?? null,
      sgaMatRate: masterRates.sgaRate?.cell ?? null,
      profitFkRate: masterRates.profitRate?.cell ?? null,
      profitMatRate: masterRates.profitRate?.cell ?? null,
    },
    statuses: {
      ...(scrapStatus ? { scrapRate: scrapStatus } : {}),
      ...(masterRates.sgaRate?.status ? { sgaFkRate: masterRates.sgaRate.status, sgaMatRate: masterRates.sgaRate.status } : {}),
      ...(masterRates.profitRate?.status ? { profitFkRate: masterRates.profitRate.status, profitMatRate: masterRates.profitRate.status } : {}),
    },
  }

  const anyValue = [
    params.cycleSec,
    params.partsPerCycle,
    params.employees,
    params.labourRatePerHour,
    params.machineRatePerHour,
    params.scrapRate,
    params.sgaFkRate,
    params.profitFkRate,
  ].some((v) => v !== null)
  return anyValue ? params : null
}

/** Every distinct `position_number`/process label across a side's
 * manufacturing steps — the "Kostenreiter"-equivalent picker for a Summary
 * comparison's Preis-Kalkulator (G60 picks a `qaf_g60_tab.tab_name`; Summary
 * has no tabs, so this picks a Fertigungskosten row instead). */
export function summaryCalculatorStepOptions(
  steps: readonly { position_number: string | null; process_name: string | null }[],
): string[] {
  const seen = new Set<string>()
  const options: string[] = []
  for (const s of steps) {
    const key = s.position_number?.trim() || s.process_name?.trim()
    if (key && !seen.has(key)) {
      seen.add(key)
      options.push(key)
    }
  }
  return options
}

/**
 * KAR-966 (Kais' UI feedback, item 1): speaking label per step VALUE — the
 * SAME `position_number`-preferring key `summaryCalculatorStepOptions`
 * builds above, kept in a SEPARATE function on purpose: that key is the
 * lookup value getCalculatorParams (actions.ts) matches raw_values against
 * (`position_number === tabName || process_name === tabName`) — changing it
 * to a composed "name (position)" string would break the ALT/NEU chip
 * lookup the moment a user picked such an option. This function only
 * enriches the DISPLAY text; the `<option value>` stays the raw key.
 *
 * PR #330 review fix (cleanup): builds its key set FROM
 * `summaryCalculatorStepOptions`'s output instead of re-implementing the
 * position_number/process_name key-selection rule a second time — the two
 * used to drift independently if that rule ever changed.
 *
 * Format: "<process_name> (<position_number>)" when both are present and
 * distinct, else the best available single value — mirrors g60TabLabel's
 * format (parser.ts) for the equivalent G60 dropdown.
 */
export function summaryCalculatorStepLabels(
  steps: readonly { position_number: string | null; process_name: string | null }[],
): Record<string, string> {
  const labels: Record<string, string> = {}
  for (const key of summaryCalculatorStepOptions(steps)) {
    const step = steps.find((s) => (s.position_number?.trim() || s.process_name?.trim()) === key)
    const pos = step?.position_number?.trim() || null
    const name = step?.process_name?.trim() || null
    labels[key] = name && pos && name !== pos ? `${name} (${pos})` : name || pos || key
  }
  return labels
}
