/**
 * Pure-function recompute pipeline (Pattern 5, Pitfall #8 & #9).
 *
 * Computes the new derived stats (hpMax, AC, classDC, perception, saves) for a
 * level-up. Pure: no DB writes, no I/O, no mutations of input objects.
 *
 * Pitfall #8 (boost-cap-at-18): ability boosts use applyAttributeBoost() which
 * adds +1 (not +2) when the score is already >= 18.
 * Pitfall #9 (current-HP invariant): the output `DerivedStats` shape NEVER includes
 * current/temporary HP fields — those are managed elsewhere by the level-up commit,
 * not recomputed from scratch.
 */
import { applyAttributeBoost } from './apply-attribute-boost';
import type {
  AbilityAbbreviation,
  CharacterContext,
  ClassProgressionRow,
  DerivedStats,
  Proficiency,
  WizardChoices,
} from './types';
import { PROFICIENCY_BASE_BONUS } from './types';

/** Recompute pipeline input — extends CharacterContext with armor + HP-base fields. */
export type RecomputeCharacter = CharacterContext & {
  ancestryHp: number;
  classHp: number;
  armorAc: number;
  armorProficiency: Proficiency;
  dexCap: number;
  /** Optional pre-existing rank for fortitude/reflex/will/perception/classDc — used as fallback. */
  fortitudeRank?: Proficiency;
  reflexRank?: Proficiency;
  willRank?: Proficiency;
  perceptionRank?: Proficiency;
  classDcRank?: Proficiency;
  /** Class key ability — defaults to STR for Fighter; consumers may pass explicit value. */
  classKeyAbility?: AbilityAbbreviation;
};

function abilityModifier(score: number): number {
  return Math.floor((score - 10) / 2);
}

function proficiencyBonus(rank: Proficiency, level: number): number {
  if (rank === 'UNTRAINED') return 0;
  return PROFICIENCY_BASE_BONUS[rank] + level;
}

/** Compute new ability scores by applying boosts from `choices.boostTargets`. */
function applyBoosts(
  current: Record<AbilityAbbreviation, number>,
  boostTargets: readonly AbilityAbbreviation[] | undefined,
): Record<AbilityAbbreviation, number> {
  const next: Record<AbilityAbbreviation, number> = { ...current };
  if (!boostTargets) return next;
  for (const ability of boostTargets) {
    next[ability] = applyAttributeBoost(next[ability]);
  }
  return next;
}

/**
 * Pure recompute pipeline.
 *
 * @param character - Character snapshot (current state, before commit)
 * @param choices - Wizard choices (boost targets, etc.)
 * @param progression - ClassProgression row for the new level
 * @returns DerivedStats — fresh object, never mutates input
 */
export function recomputeDerivedStats(
  character: RecomputeCharacter,
  choices: WizardChoices,
  progression: ClassProgressionRow,
): DerivedStats {
  const newLevel = progression.level;
  const newAbilities = applyBoosts(character.abilities, choices.boostTargets);

  const conMod = abilityModifier(newAbilities.CON);
  const dexMod = abilityModifier(newAbilities.DEX);
  const wisMod = abilityModifier(newAbilities.WIS);
  const keyAbility = character.classKeyAbility ?? 'STR';
  const keyMod = abilityModifier(newAbilities[keyAbility]);

  // Resolve effective ranks: progression overrides take precedence; otherwise use
  // the input character's pre-existing rank (or sensible defaults of TRAINED).
  const fortRank: Proficiency =
    progression.proficiencyChanges.fortitude ?? character.fortitudeRank ?? 'TRAINED';
  const refRank: Proficiency =
    progression.proficiencyChanges.reflex ?? character.reflexRank ?? 'TRAINED';
  const willRank: Proficiency =
    progression.proficiencyChanges.will ?? character.willRank ?? 'TRAINED';
  const percRank: Proficiency =
    progression.proficiencyChanges.perception ?? character.perceptionRank ?? 'TRAINED';
  const classDcRank: Proficiency =
    progression.proficiencyChanges.classDc ?? character.classDcRank ?? 'TRAINED';
  const armorRank: Proficiency =
    progression.proficiencyChanges.ac ?? character.armorProficiency;

  const hpMax = character.ancestryHp + (character.classHp + conMod) * newLevel;
  const ac =
    10 +
    Math.min(dexMod, character.dexCap) +
    character.armorAc +
    proficiencyBonus(armorRank, newLevel);
  const classDc = 10 + keyMod + proficiencyBonus(classDcRank, newLevel);
  const perception = wisMod + proficiencyBonus(percRank, newLevel);
  const fortitude = conMod + proficiencyBonus(fortRank, newLevel);
  const reflex = dexMod + proficiencyBonus(refRank, newLevel);
  const will = wisMod + proficiencyBonus(willRank, newLevel);

  return {
    level: newLevel,
    hpMax,
    ac,
    classDc,
    perception,
    fortitude,
    reflex,
    will,
  };
}
