import { ABILITY_KEYS, type SystemId } from '@/lib/rules';
import type { Condition } from '@/lib/schemas/common';
import { conditionEffects, type PenaltyTarget } from './conditionEffects';
import type { AbilityKey, AdvState } from './types';

export interface MechanicalState {
  /** effective speed after conditions (0 if zeroed, halved by exhaustion 2-4, etc.) */
  speed: number;
  /** advantage state on THIS creature's attack rolls (5e) */
  attackModifier: AdvState;
  /** advantage state on attacks made AGAINST this creature (5e) */
  attacksAgainstModifier: AdvState;
  /** cannot take actions/reactions */
  incapacitated: boolean;
  /** per-ability save penalties (auto-fail dominates disadvantage) */
  saveModifiers: Partial<Record<AbilityKey, 'disadvantage' | 'auto-fail'>>;
  /** disadvantage on ability checks (5e) */
  abilityCheckDisadvantage: boolean;
  /**
   * pf2e: value-scaled status penalties, by statistic family. Penalties within a
   * family don't stack (worst value wins); different families are independent and
   * apply to different rolls, so they are tracked separately — never summed.
   */
  statusPenalties: Partial<Record<PenaltyTarget, number>>;
  /** short human-readable badges for the UI, e.g. "Speed 0", "−2 Str" */
  badges: string[];
}

/** Combine two advantage states by the 5e rule: opposing sources cancel to normal. */
function combineAdv(a: AdvState, b: AdvState): AdvState {
  if (a === b) return a;
  if (a === 'normal') return b;
  if (b === 'normal') return a;
  return 'normal'; // one advantage + one disadvantage cancel
}

const PENALTY_LABEL: Record<PenaltyTarget, string> = {
  all: 'all checks',
  str: 'Str',
  dex: 'Dex/AC',
  con: 'Fort',
  mental: 'spell/mental',
};

/**
 * Fold a creature's conditions into a single mechanical read-model. Pure and
 * synchronous — combat, the dice roller, and the player view all call this
 * (the player view ships the same table and derives effects locally from the
 * broadcast condition names, so no derived state crosses the wire).
 *
 * Unknown / homebrew condition names contribute nothing (they remain visible
 * labels), exactly as before.
 */
export function deriveState(system: SystemId, baseSpeed: number, conditions: Condition[]): MechanicalState {
  const table = conditionEffects(system);
  let speed = baseSpeed;
  let speedZero = false;
  let attackModifier: AdvState = 'normal';
  let attacksAgainstModifier: AdvState = 'normal';
  let incapacitated = false;
  let abilityCheckDisadvantage = false;
  let allSavesDisadvantage = false;
  let prone5e = false;
  const statusPenalties: Partial<Record<PenaltyTarget, number>> = {};
  const saveModifiers: Partial<Record<AbilityKey, 'disadvantage' | 'auto-fail'>> = {};
  const extraBadges: string[] = [];

  for (const cond of conditions) {
    const name = cond.name.trim().toLowerCase();

    // --- 5e Exhaustion: cumulative level-based effects (PHB). ---
    if (system === '5e' && name === 'exhaustion' && cond.value) {
      const lvl = cond.value;
      if (lvl >= 1) abilityCheckDisadvantage = true;
      if (lvl >= 2) speed = Math.min(speed, Math.floor(baseSpeed / 2)); // speed halved
      if (lvl >= 3) { attackModifier = combineAdv(attackModifier, 'disadvantage'); allSavesDisadvantage = true; }
      if (lvl >= 5) speedZero = true; // speed 0
      extraBadges.push(lvl >= 6 ? 'Exhaustion 6 (dead)' : `Exhaustion ${lvl}`);
      continue;
    }
    // --- pf2e Slowed: action loss only, no roll penalty. ---
    if (system === 'pf2e' && name === 'slowed') {
      extraBadges.push(cond.value ? `Slowed ${cond.value} (−${cond.value} actions)` : 'Slowed');
      continue;
    }
    // --- pf2e Fatigued: flat −1 to AC and saves (not value-scaled). ---
    if (system === 'pf2e' && name === 'fatigued') {
      extraBadges.push('Fatigued (−1 AC & saves)');
      continue;
    }

    const eff = table[name];
    if (!eff) continue;
    if (eff.speedZero) speedZero = true;
    if (eff.incapacitated) incapacitated = true;
    if (eff.abilityChecks) abilityCheckDisadvantage = true;
    if (eff.attackRolls) attackModifier = combineAdv(attackModifier, eff.attackRolls);
    if (eff.attacksAgainst) attacksAgainstModifier = combineAdv(attacksAgainstModifier, eff.attacksAgainst);
    if (system === '5e' && name === 'prone') prone5e = true;
    if (eff.saves) {
      for (const [ability, level] of Object.entries(eff.saves) as [AbilityKey, 'disadvantage' | 'auto-fail'][]) {
        // auto-fail dominates disadvantage
        if (level === 'auto-fail' || saveModifiers[ability] === undefined) saveModifiers[ability] = level;
      }
    }
    // pf2e: value-scaled penalty per statistic family — worst value within a family.
    if (eff.statusPenalty && cond.value !== undefined) {
      const fam = eff.statusPenalty;
      statusPenalties[fam] = Math.max(statusPenalties[fam] ?? 0, cond.value);
    }
  }

  // 5e exhaustion 3+ imposes disadvantage on all saves (unless already auto-fail).
  if (allSavesDisadvantage) {
    for (const a of ABILITY_KEYS) if (saveModifiers[a] !== 'auto-fail') saveModifiers[a] = 'disadvantage';
  }

  const badges: string[] = [];
  if (speedZero) badges.push('Speed 0');
  else if (speed !== baseSpeed) badges.push(`Speed ${speed}`);
  if (incapacitated) badges.push('Incapacitated');
  if (attackModifier === 'disadvantage') badges.push('Disadv. attacks');
  if (attackModifier === 'advantage') badges.push('Adv. attacks');
  if (attacksAgainstModifier === 'advantage') {
    // Prone is range-dependent: melee attackers have advantage, ranged have disadvantage.
    badges.push(prone5e ? 'Attacked: adv. melee / disadv. ranged' : 'Attacked w/ adv.');
  }
  for (const [fam, val] of Object.entries(statusPenalties) as [PenaltyTarget, number][]) {
    if (val > 0) badges.push(`−${val} ${PENALTY_LABEL[fam]}`);
  }
  badges.push(...extraBadges);

  return {
    speed: speedZero ? 0 : speed,
    attackModifier,
    attacksAgainstModifier,
    incapacitated,
    saveModifiers,
    abilityCheckDisadvantage,
    statusPenalties,
    badges,
  };
}

/**
 * End-of-turn auto-decay for valued conditions. In PF2e, Frightened decreases by 1
 * at the end of each of the affected creature's turns (removed at 0). Returns a new
 * conditions array; callers persist it when a combatant's turn ends.
 */
export function tickConditionsEndOfTurn(system: SystemId, conditions: Condition[]): Condition[] {
  if (system !== 'pf2e') return conditions;
  let changed = false;
  const next: Condition[] = [];
  for (const c of conditions) {
    if (c.name.trim().toLowerCase() === 'frightened' && c.value !== undefined) {
      changed = true;
      const value = c.value - 1;
      if (value > 0) next.push({ ...c, value });
      // value 0 → condition ends, drop it
    } else {
      next.push(c);
    }
  }
  return changed ? next : conditions;
}
