using RpgRoller.Contracts;
using RpgRoller.Domain;

namespace RpgRoller.Services;

public sealed class D6RollEngine(IDiceRoller diceRoller)
{
    public (int Total, string Breakdown, IReadOnlyList<RollDieResult> Dice) Roll(DiceExpression expression, int wildDice, bool allowFumble)
    {
        var initialDice = expression.DiceCount;
        var currentDice = initialDice;
        var pendingExplodingDice = 0;
        var pendingFumbles = 0;
        var dieResults = new List<RollDieResult>(initialDice);

        for (var i = 0; i < currentDice; i += 1)
        {
            var roll = diceRoller.Roll(expression.Sides);
            var isWild = i < wildDice;
            var isCrit = false;
            var isFumble = false;
            var isAdded = false;

            if (isWild)
            {
                if (roll == expression.Sides)
                {
                    pendingExplodingDice += 1;
                    currentDice += 1;
                    isCrit = true;
                }
                else if (allowFumble && roll == 1)
                {
                    pendingFumbles += 1;
                    isFumble = true;
                }
            }

            if (pendingExplodingDice > 0 && i >= initialDice)
            {
                pendingExplodingDice -= 1;
                isAdded = true;
                if (roll == expression.Sides)
                {
                    pendingExplodingDice += 1;
                    currentDice += 1;
                }
            }

            dieResults.Add(new(roll, isCrit, isFumble, isWild, false, isAdded));
        }

        for (var roll = expression.Sides; roll >= 1 && pendingFumbles > 0; roll -= 1)
        for (var i = currentDice - 1; i >= 0 && pendingFumbles > 0; i -= 1)
        {
            if (dieResults[i].Roll != roll)
                continue;

            dieResults[i] = dieResults[i] with
            {
                Removed = true,
                Added = false,
                Crit = false,
                Fumble = false
            };
            pendingFumbles -= 1;
        }

        var total = expression.Modifier;
        var includedDice = new List<int>(dieResults.Count);
        foreach (var die in dieResults)
        {
            if (die.Fumble)
            {
                total += 1;
                includedDice.Add(1);
            }
            else if (!die.Removed)
            {
                total += die.Roll;
                includedDice.Add(die.Roll);
            }
        }

        return (total, RollBreakdownFormatter.BuildBreakdown(includedDice, expression.Modifier, total), dieResults);
    }
}