/**
 * Pure KPI derivations for the project overview page (KAR-983).
 * All functions are side-effect-free and unit-testable.
 *
 * Convention: every compute function returns `null` when the underlying
 * module has no entity at all for the project (no assessment, no comparison,
 * no value-stream map) — the caller renders a generic empty tile in that
 * case. A non-null result may still contain zero/null fields inside (e.g.
 * an assessment with no answered questions yet) — that is real state, not
 * "no data", and is rendered as such.
 */

import type { VsmNode } from '@/lib/vsm-types'
import { computeVaClassBreakdown, findBottleneckId } from '@/components/wertstrom/vsm-metrics'
import { buildSummaryKpis, type SummaryDiffRowData, type SummaryKpis } from '@/lib/qaf-differences'
import { calcLscMeasureStatus } from './aggregations'

// ─── Fabrikanalyse ─────────────────────────────────────────────────────────

export interface FactoryAnalysisKpi {
  answeredCount: number
  /** Average of `selected_rating` (1–4) across answered, relevant responses. */
  scoreAvg: number | null
  /** rating 3 or 4 */
  fulfilledCount: number
  /** rating 2 */
  partialCount: number
  /** rating 1 */
  notFulfilledCount: number
}

/**
 * Aggregates every response across every assessment of the project.
 * `hasAssessments` distinguishes "project has no assessment at all" (→ null,
 * empty tile) from "assessment exists, nothing answered yet" (→ a real
 * result with zero counts and `scoreAvg: null`).
 */
export function computeFactoryAnalysisKpi(
  hasAssessments: boolean,
  responses: ReadonlyArray<{ is_relevant: boolean; selected_rating: number | null }>,
): FactoryAnalysisKpi | null {
  if (!hasAssessments) return null

  const relevant = responses.filter((r) => r.is_relevant !== false)
  const answered = relevant.filter((r) => r.selected_rating !== null && r.selected_rating !== undefined)

  let sum = 0
  let fulfilledCount = 0
  let partialCount = 0
  let notFulfilledCount = 0
  for (const r of answered) {
    const rating = r.selected_rating as number
    sum += rating
    if (rating === 1) notFulfilledCount++
    else if (rating === 2) partialCount++
    else if (rating === 3 || rating === 4) fulfilledCount++
  }

  return {
    answeredCount: answered.length,
    scoreAvg: answered.length > 0 ? sum / answered.length : null,
    fulfilledCount,
    partialCount,
    notFulfilledCount,
  }
}

// ─── QAF ────────────────────────────────────────────────────────────────────

export interface QafKpiData {
  comparisonTitle: string | null
  partNumber: string | null
  /** null when the newest comparison has no persisted summary-diff metrics
   * (e.g. a Multi-QAF comparison, which never writes qaf_summary_diff). */
  kpis: SummaryKpis | null
}

/**
 * Wraps the existing, tested `buildSummaryKpis` — no re-computation, only
 * reads the persisted diff rows of the newest comparison.
 */
export function computeQafKpi(
  comparison: { title: string | null; part_number: string | null } | null,
  summaryDiffRows: ReadonlyArray<SummaryDiffRowData>,
): QafKpiData | null {
  if (!comparison) return null
  return {
    comparisonTitle: comparison.title,
    partNumber: comparison.part_number,
    kpis: buildSummaryKpis([...summaryDiffRows]),
  }
}

// ─── Wertstrom ──────────────────────────────────────────────────────────────

export interface ValueStreamKpi {
  title: string
  /** null when no node carries timing data (never a misleading "0 s"). */
  leadTimeSec: number | null
  vaRatioPct: number | null
  bottleneckName: string | null
}

/** Reuses `computeVaClassBreakdown`/`findBottleneckId` (components/wertstrom/vsm-metrics) — no bespoke recomputation. */
export function computeValueStreamKpi(
  vsm: { title: string; nodes: VsmNode[] } | null,
): ValueStreamKpi | null {
  if (!vsm) return null

  const breakdown = computeVaClassBreakdown(vsm.nodes)
  const bottleneckId = findBottleneckId(vsm.nodes)
  const bottleneckName = bottleneckId
    ? (vsm.nodes.find((n) => n.id === bottleneckId)?.name ?? null)
    : null

  return {
    title: vsm.title,
    leadTimeSec: breakdown.totalSeconds > 0 ? breakdown.totalSeconds : null,
    vaRatioPct: breakdown.totalSeconds > 0 ? breakdown.vaRatioPct : null,
    bottleneckName,
  }
}

// ─── Maßnahmen ──────────────────────────────────────────────────────────────

export interface MeasuresKpi {
  /** Sum of the three visible buckets (open + inProgress + done) — rejected
   * workshop_actions are deliberately not part of the stock count, so the
   * total always equals what the tile displays (PR #347 review fix). */
  total: number
  open: number
  inProgress: number
  done: number
}

/**
 * Combines `lsc_measures` (status: offen/in_arbeit/erledigt) and
 * `workshop_actions` (status: open/in_progress/done/rejected) into one
 * counter. Reuses `calcLscMeasureStatus` for the lsc_measures half.
 */
export function computeMeasuresKpi(
  lscMeasures: ReadonlyArray<{ status: string }>,
  workshopActions: ReadonlyArray<{ status: string }>,
): MeasuresKpi | null {
  if (lscMeasures.length === 0 && workshopActions.length === 0) return null

  // calcLscMeasureStatus maps BOTH vocabularies (German lsc_measures codes and
  // English workshop_actions codes) into open/in_progress/done and silently
  // drops anything else — for workshop_actions that is exactly 'rejected'.
  const lsc = calcLscMeasureStatus([...lscMeasures])
  const workshop = calcLscMeasureStatus([...workshopActions])

  const open = lsc.open + workshop.open
  const inProgress = lsc.in_progress + workshop.in_progress
  const done = lsc.done + workshop.done
  const total = open + inProgress + done
  // Only-rejected projects have no active measure stock to show.
  if (total === 0) return null

  return { total, open, inProgress, done }
}
