// QAF batch orchestration (KAR-799, spec A2.1/B2.6/B2.7).
//
// Groups parsed QAF files by part number, picks a baseline per group, and runs
// the per-pair comparison.
//
// ── Warum die Sachnummer allein nicht reicht (31.07.2026) ──────────────────
// Ursprünglich war die Sachnummer der EINZIGE Gruppierungsschlüssel: "Groups
// STRICTLY by part number. Different part numbers are never compared. Files
// without a recognizable part number are bucketed separately." Die Absicht ist
// richtig und bleibt — zwei fremde Teile dürfen nie verglichen werden.
//
// Sie greift aber zu hart, wenn EINE Datei eines offensichtlich
// zusammengehörenden Paares keine Sachnummer trägt. Realfall (Kais,
// 31.07.2026): zwei QAF-8.8-Dateien desselben Umfangs, dieselbe Anfragenummer,
// derselbe Lieferant, dasselbe Teil — die "Aktuell"-Datei trug die Sachnummer,
// die "Vergabe"-Datei ließ das Feld leer. Ergebnis: zwei Gruppen mit je einer
// Datei, kein Vergleich, zweimal "Sachnummer fehlt".
//
// Deshalb jetzt eine KASKADE statt einer einzigen Regel. Die Reihenfolge ist
// nach absteigender Sicherheit gewählt, und jede Stufe ist konservativ:
//
//   1. Sachnummer            — unverändert, stärkstes Merkmal
//   2. Anfragenummer/Version — projekt- und lieferantenspezifisch vergeben,
//                              praktisch kollisionsfrei
//   3. Teil + Lieferant      — schwächer, deshalb NUR wenn beide Felder
//                              gesetzt sind und beide übereinstimmen
//
// Drei Schutzregeln, die verhindern, dass aus der Lockerung ein stiller
// Fehlvergleich wird:
//   * Stufen 2 und 3 laufen NUR über Dateien, die nach Stufe 1 allein
//     geblieben sind. Eine Datei, die bereits über ihre Sachnummer einen
//     Partner hat, wird nie neu zugeordnet.
//   * Zwei Dateien mit VERSCHIEDENEN, jeweils gesetzten Sachnummern werden
//     auch in Stufe 2/3 nie gepaart — der ursprüngliche Schutz bleibt hart.
//   * Eine Stufe paart nur bei GENAU zwei Kandidaten mit demselben Schlüssel.
//     Bei drei oder mehr wird nicht geraten.
//
// Jedes Ergebnis weist über `pairedBy` aus, worüber gepaart wurde. Ein
// Vergleich, der nicht über die Sachnummer zustande kam, muss das sagen —
// sonst wäre eine harte Regel gegen eine stille Annahme getauscht worden, und
// das wäre der schlechtere Fehler.
//
// Net-new. Pure.

import { selectBaseline, type BaselineSelection, type QafFileRef } from './baseline'
import { compareQafPair, type QafComparisonResult, type QafFileParsed } from './compare'

export interface BatchGroupResult {
  partNumber: string | null
  /** Worüber dieses Paar zustande kam. `null`, wenn kein Paar entstand.
   * Ein Wert ungleich 'partNumber' MUSS in der Oberfläche sichtbar sein —
   * der Vergleich beruht dann auf einem schwächeren Merkmal. */
  pairedBy: QafPairingBasis | null
  fileCount: number
  baseline: BaselineSelection
  comparison: QafComparisonResult | null
  needsReview: boolean
}

/** Worüber ein Paar zustande kam — im Ergebnis ausgewiesen, nie implizit. */
export type QafPairingBasis = 'partNumber' | 'requestVersion' | 'partAndSupplier'

function norm(v: string | null | undefined): string | null {
  if (typeof v !== 'string') return null
  const t = v.trim()
  return t === '' ? null : t.toLowerCase()
}

function groupKey(file: QafFileParsed): string | null {
  const pn = file.summary.partNumber.value
  return pn && pn.trim() !== '' ? pn.trim() : null
}

/** Schlüssel einer Fallback-Stufe, oder null wenn die Stufe für diese Datei
 * nicht greift (Feld leer). */
function fallbackKey(file: QafFileParsed, basis: Exclude<QafPairingBasis, 'partNumber'>): string | null {
  if (basis === 'requestVersion') return norm(file.summary.requestVersion.value)
  const part = norm(file.summary.partName.value)
  const supplier = norm(file.summary.supplier.value)
  // Beide Felder nötig: der Lieferant allein würde zwei verschiedene Teile
  // desselben Lieferanten zusammenwerfen, das Teil allein zwei Angebote
  // verschiedener Lieferanten.
  return part && supplier ? `${part}\u0000${supplier}` : null
}

export function buildBatchComparisons(files: QafFileParsed[]): BatchGroupResult[] {
  // ── Stufe 1: Sachnummer (unverändert) ────────────────────────────────────
  const groups = new Map<string | null, QafFileParsed[]>()
  for (const f of files) {
    const key = groupKey(f)
    const bucket = groups.get(key)
    if (bucket) bucket.push(f)
    else groups.set(key, [f])
  }

  // ── Stufen 2/3: nur die nach Stufe 1 allein gebliebenen Dateien ──────────
  // Eine Datei, die über ihre Sachnummer bereits einen Partner hat, wird hier
  // nie angefasst. Kandidaten sind also: die komplette "ohne Sachnummer"-
  // Gruppe plus jede Sachnummer-Gruppe mit genau einer Datei.
  const singles: QafFileParsed[] = []
  for (const [pn, groupFiles] of groups) {
    if (pn === null || groupFiles.length === 1) singles.push(...groupFiles)
  }

  /** Bereits über eine Fallback-Stufe verpaarte Dateien — je Datei-id die
   * Grundlage, damit sie in keiner weiteren Stufe erneut zugeordnet wird. */
  const rescued = new Map<string, { partner: QafFileParsed; basis: QafPairingBasis }>()

  for (const basis of ['requestVersion', 'partAndSupplier'] as const) {
    const byKey = new Map<string, QafFileParsed[]>()
    for (const f of singles) {
      if (rescued.has(f.ref.id)) continue
      const k = fallbackKey(f, basis)
      if (!k) continue
      const bucket = byKey.get(k)
      if (bucket) bucket.push(f)
      else byKey.set(k, [f])
    }
    for (const candidates of byKey.values()) {
      // Nur bei GENAU zwei Kandidaten paaren — bei drei oder mehr ist die
      // Zuordnung mehrdeutig, und Raten wäre schlimmer als kein Vergleich.
      if (candidates.length !== 2) continue
      const [a, b] = candidates
      const pnA = groupKey(a)
      const pnB = groupKey(b)
      // Der ursprüngliche Schutz bleibt hart: zwei jeweils GESETZTE, aber
      // verschiedene Sachnummern sind verschiedene Teile — niemals paaren,
      // egal wie ähnlich der Rest aussieht.
      if (pnA !== null && pnB !== null && pnA !== pnB) continue
      rescued.set(a.ref.id, { partner: b, basis })
      rescued.set(b.ref.id, { partner: a, basis })
    }
  }

  const results: BatchGroupResult[] = []
  const emittedRescued = new Set<string>()

  // ── Ergebnisse der Fallback-Paare ────────────────────────────────────────
  for (const f of singles) {
    const hit = rescued.get(f.ref.id)
    if (!hit || emittedRescued.has(f.ref.id)) continue
    emittedRescued.add(f.ref.id)
    emittedRescued.add(hit.partner.ref.id)

    const pair = [f, hit.partner]
    const baseline = selectBaseline(pair.map((x) => x.ref))
    let comparison: QafComparisonResult | null = null
    if (baseline.altId && baseline.neuId) {
      const alt = pair.find((x) => x.ref.id === baseline.altId)
      const neu = pair.find((x) => x.ref.id === baseline.neuId)
      if (alt && neu) comparison = compareQafPair(alt, neu)
    }
    results.push({
      // Die Sachnummer der Seite, die eine trägt — sonst null. So bleibt der
      // Vergleich auffindbar, auch wenn nur eine Datei sie führte.
      partNumber: groupKey(f) ?? groupKey(hit.partner),
      pairedBy: hit.basis,
      fileCount: 2,
      baseline,
      comparison,
      // Ein über ein schwächeres Merkmal zustande gekommenes Paar geht IMMER
      // in die Review — der Nutzer soll die Zuordnung bestätigen.
      needsReview: true,
    })
  }

  // ── Stufe-1-Gruppen (unverändert), abzüglich der gerade verpaarten ───────
  for (const [partNumber, groupFiles] of groups) {
    const remaining = groupFiles.filter((f) => !emittedRescued.has(f.ref.id))
    if (remaining.length === 0) continue

    if (partNumber === null) {
      results.push({
        partNumber: null,
        pairedBy: null,
        fileCount: remaining.length,
        baseline: {
          altId: null,
          neuId: null,
          status: 'insufficient',
          reason: 'Keine Sachnummer erkannt — Zuordnung prüfen (Dateiname/Inhalt).',
        },
        comparison: null,
        needsReview: true,
      })
      continue
    }

    const refs: QafFileRef[] = remaining.map((f) => f.ref)
    const baseline = selectBaseline(refs)

    let comparison: QafComparisonResult | null = null
    if (baseline.altId && baseline.neuId) {
      const alt = remaining.find((f) => f.ref.id === baseline.altId)
      const neu = remaining.find((f) => f.ref.id === baseline.neuId)
      if (alt && neu) comparison = compareQafPair(alt, neu)
    }

    results.push({
      partNumber,
      pairedBy: comparison ? 'partNumber' : null,
      fileCount: remaining.length,
      baseline,
      comparison,
      needsReview: baseline.status !== 'ok',
    })
  }

  return results
}
