using Theriapolis.Core.Data;
using Theriapolis.Core.Items;
using Theriapolis.Core.Rules.Stats;

namespace Theriapolis.Core.Rules.Character;

/// <summary>
/// Runtime aggregate of everything that makes a creature a *character* —
/// stats, class, clade, species, background, inventory, HP, conditions, level,
/// XP. Composed onto an <see cref="Entities.Actor"/> via the
/// <c>Actor.Character</c> field; the actor handles position and rendering,
/// the character handles gameplay state.
///
/// Phase 5 M2 builds these via <see cref="CharacterBuilder"/> at character
/// creation. Saved snapshots round-trip through
/// <see cref="Persistence.PlayerCharacterState"/>.
/// </summary>
public sealed class Character
{
    public CladeDef       Clade      { get; }
    public SpeciesDef     Species    { get; }
    public ClassDef       ClassDef   { get; }
    public BackgroundDef  Background { get; }
    public AbilityScores  Abilities  { get; private set; }

    public int    Level         { get; set; } = 1;
    public int    Xp            { get; set; } = 0;
    public int    MaxHp         { get; set; }
    public int    CurrentHp     { get; set; }

    /// <summary>Skill proficiencies — class skills + background skills, deduplicated.</summary>
    public HashSet<SkillId> SkillProficiencies { get; } = new();

    public Inventory Inventory { get; } = new();

    /// <summary>Conditions currently affecting the character. Phase 5 M5 wires durations.</summary>
    public HashSet<Condition> Conditions { get; } = new();

    /// <summary>Exhaustion level 0..6, separate from <see cref="Conditions"/> (binary flags).</summary>
    public int ExhaustionLevel { get; set; } = 0;

    public bool IsAlive => CurrentHp > 0 || Conditions.Contains(Condition.Unconscious);

    public Character(
        CladeDef clade,
        SpeciesDef species,
        ClassDef classDef,
        BackgroundDef background,
        AbilityScores abilities)
    {
        Clade      = clade      ?? throw new ArgumentNullException(nameof(clade));
        Species    = species    ?? throw new ArgumentNullException(nameof(species));
        ClassDef   = classDef   ?? throw new ArgumentNullException(nameof(classDef));
        Background = background ?? throw new ArgumentNullException(nameof(background));
        Abilities  = abilities;
    }

    /// <summary>Replace ability scores wholesale (used during creation; combat doesn't mutate this).</summary>
    public void SetAbilities(AbilityScores scores)
    {
        Abilities = scores;
    }

    /// <summary>Body size category, derived from species.</summary>
    public SizeCategory Size => SizeExtensions.FromJson(Species.Size);

    /// <summary>d20 proficiency bonus for the character's current level.</summary>
    public int ProficiencyBonus => Stats.ProficiencyBonus.ForLevel(Level);

    /// <summary>
    /// Computes max HP from class hit die + CON modifier at level 1 (and
    /// avg-rounded-up + CON for each level beyond, per d20 default).
    /// </summary>
    public int ComputeMaxHpFromScratch()
    {
        int conMod = Abilities.ModFor(AbilityId.CON);
        int hp = ClassDef.HitDie + conMod;
        // Levels 2+ add avg-rounded-up + CON each. Phase 5 = level 1 only,
        // but the formula stays correct in case external code passes Level > 1.
        for (int lv = 2; lv <= Level; lv++)
        {
            int avgRoundedUp = (ClassDef.HitDie / 2) + 1;
            hp += avgRoundedUp + conMod;
        }
        return Math.Max(1, hp);
    }
}
