import type {
  AbilityKey,
  CharacterRulesInput,
  DerivedSkill,
  ProficiencyRank,
  RestOption,
  RulesSystem,
} from '../types';
import { ABILITY_KEYS } from '../types';
import {
  ABILITY_ABBR,
  ABILITY_LABELS,
  abilityModifier,
  defaultAbilityScores,
  formatDamage,
} from '../abilities';
import { DND5E_SKILLS } from './skills';

const DND5E_RESTS: readonly RestOption[] = [
  { id: 'short', label: 'Short Rest', restoresHp: false, restoresSlots: false, restoresPact: true, recovers: ['short'] },
  { id: 'long', label: 'Long Rest', restoresHp: true, restoresSlots: true, restoresPact: true, recovers: ['short', 'long'], reduceExhaustion: true },
];

/** Proficiency bonus by character level (PHB table): +2 at 1, +6 at 17. */
export function proficiencyBonus(level: number): number {
  const lvl = Math.max(1, Math.min(20, Math.floor(level) || 1));
  return 2 + Math.floor((lvl - 1) / 4);
}

/**
 * In 5e a skill/save is either proficient or not, with "expert" meaning
 * expertise (double proficiency). We map PF2e-style ranks onto that.
 */
function profMultiplier(rank: ProficiencyRank): number {
  if (rank === 'untrained') return 0;
  if (rank === 'trained') return 1;
  return 2; // expert/master/legendary => expertise
}

export const dnd5e: RulesSystem = {
  id: '5e',
  label: 'D&D 5e',
  abilities: ABILITY_KEYS,
  abilityLabels: ABILITY_LABELS,
  skills: DND5E_SKILLS,

  abilityModifier,
  defaultAbilityScores,

  proficiencyValue(level, rank) {
    return proficiencyBonus(level) * profMultiplier(rank);
  },

  initiativeModifier({ abilities }) {
    return abilityModifier(abilities.dex);
  },

  skillModifiers(input: CharacterRulesInput): DerivedSkill[] {
    const pb = proficiencyBonus(input.level);
    return DND5E_SKILLS.map((s) => {
      const rank = input.skillRanks?.[s.key] ?? 'untrained';
      return {
        key: s.key,
        label: s.label,
        ability: s.ability,
        modifier: abilityModifier(input.abilities[s.ability]) + pb * profMultiplier(rank),
      };
    });
  },

  saveModifiers(input): Record<AbilityKey, number> {
    const pb = proficiencyBonus(input.level);
    const out = {} as Record<AbilityKey, number>;
    for (const a of ABILITY_KEYS) {
      const rank = input.saveRanks?.[a] ?? 'untrained';
      out[a] = abilityModifier(input.abilities[a]) + (rank !== 'untrained' ? pb : 0);
    }
    return out;
  },

  baseArmorClass(input) {
    // Unarmored AC. Heavy armor etc. is represented as armorBonus on top.
    return 10 + abilityModifier(input.abilities.dex) + (input.armorBonus ?? 0);
  },

  spellSaveDc(input, ability) {
    return 8 + proficiencyBonus(input.level) + abilityModifier(input.abilities[ability]);
  },

  spellAttackBonus(input, ability) {
    return proficiencyBonus(input.level) + abilityModifier(input.abilities[ability]);
  },

  passivePerception(input) {
    const rank = input.skillRanks?.['perception'] ?? 'untrained';
    return 10 + abilityModifier(input.abilities.wis) + proficiencyBonus(input.level) * profMultiplier(rank);
  },

  weaponAttack(input, weapon) {
    const abilityMod = abilityModifier(input.abilities[weapon.ability]);
    const item = weapon.itemBonus ?? 0;
    const toHit = abilityMod + proficiencyBonus(input.level) * profMultiplier(weapon.rank) + item;
    const dmgMod = (weapon.addAbilityToDamage !== false ? abilityMod : 0) + item;
    return { toHit, damage: formatDamage(weapon.damageDice, dmgMod) };
  },

  carryingCapacity(input) {
    const str = input.abilities.str;
    return { encumbered: str * 5, max: str * 15, unit: 'lb' };
  },

  restOptions: DND5E_RESTS,
};

export { ABILITY_ABBR };
