/**
 * Probeverpaarung (test mating): predict offspring of two parent genotypes.
 *
 * Pipeline: Punnett combine -> merge identical genotypes -> apply lethality
 * (remove SpSp, renormalise, collect warnings) -> annotate each surviving
 * genotype with its Farbschlag and probability. Also aggregates by Farbschlag.
 *
 * Pure & deterministic; no React, no I/O. Display texts (warning messages) are
 * resolved from CODES in the UI layer (de.ts).
 */
import { add, toNumber, toPercentString, toString, ZERO, type Fraction } from './fraction'
import { type Genotype, toDisplayString, toJSON } from './genotype'
import { combineGenotypes, mergeByGenotype } from './punnett'
import { applyLethality } from './lethality'
import { farbschlagFor } from './catalog'
import type { GeneticsWarning } from './warnings'
import type { LocusKey } from './loci'

export interface Probability {
  readonly fraction: Fraction
  /** Decimal in [0,1]. */
  readonly value: number
  /** "3/16" style. */
  readonly text: string
  /** "18.75 %" style. */
  readonly percent: string
}

function prob(f: Fraction): Probability {
  return { fraction: f, value: toNumber(f), text: toString(f), percent: toPercentString(f) }
}

export interface OffspringGenotype {
  readonly farbschlag: string
  /** Compact display string, e.g. "Aa CC Dd EE GG Pp spsp rere". */
  readonly genotype: string
  /** Structured genotype (JSON storage shape). */
  readonly genotypeJson: Record<LocusKey, [string, string]>
  readonly probability: Probability
  /** True when the Farbschlag could not be named from the catalog. */
  readonly unknownFarbschlag: boolean
}

export interface FarbschlagShare {
  readonly farbschlag: string
  readonly probability: Probability
}

export interface BreedingResult {
  /** Per-genotype offspring distribution (live births), highest probability first. */
  readonly offspring: OffspringGenotype[]
  /** Aggregated by Farbschlag (colour-morph), highest probability first. */
  readonly byFarbschlag: FarbschlagShare[]
  readonly warnings: GeneticsWarning[]
}

function byProbDesc<T extends { probability: Probability }>(a: T, b: T): number {
  return b.probability.value - a.probability.value
}

/** Predict the offspring distribution of father × mother. */
export function breed(father: Genotype, mother: Genotype): BreedingResult {
  const raw = combineGenotypes(father, mother)
  const merged = mergeByGenotype(raw)
  const { distribution, warnings } = applyLethality(merged)

  const offspring: OffspringGenotype[] = distribution
    .map((entry) => {
      const match = farbschlagFor(entry.value)
      return {
        farbschlag: match.name,
        genotype: toDisplayString(entry.value),
        genotypeJson: toJSON(entry.value),
        probability: prob(entry.probability),
        unknownFarbschlag: match.unknown,
      }
    })
    .sort(byProbDesc)

  const colorMass = new Map<string, Fraction>()
  for (const entry of distribution) {
    const name = farbschlagFor(entry.value).name
    colorMass.set(name, add(colorMass.get(name) ?? ZERO, entry.probability))
  }
  const byFarbschlag: FarbschlagShare[] = Array.from(colorMass.entries())
    .map(([farbschlag, f]) => ({ farbschlag, probability: prob(f) }))
    .sort(byProbDesc)

  return { offspring, byFarbschlag, warnings }
}
