using System;

namespace PKHeX.Core;

public static class EvolutionReversal
{
    public static EvoCriteria[] Reverse(this IEvolutionLookup lineage, ushort species, byte form, PKM pk, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks, int stopSpecies)
    {
        var lvl = levelMax;
        var first = new EvoCriteria { Species = species, Form = form, LevelMax = lvl };

        const int maxEvolutions = 3;
        Span<EvoCriteria> evos = stackalloc EvoCriteria[maxEvolutions];
        evos[0] = first;

        switch (species)
        {
            case (int)Species.Silvally:
                form = 0;
                break;
        }

        // There aren't any circular evolution paths, and all lineages have at most 3 evolutions total.
        // There aren't any convergent evolution paths, so only yield the first connection.
        int ctr = 1;
        while (species != stopSpecies)
        {
            var key = EvolutionTree.GetLookupKey(species, form);
            var node = lineage[key];

            bool oneValid = false;
            for (int i = 0; i < 2; i++)
            {
                ref var link = ref i == 0 ? ref node.First : ref node.Second;
                if (link.IsEmpty)
                    break;

                if (link.IsEvolutionBanned(pk) && !skipChecks)
                    continue;

                var evo = link.Method;
                if (!evo.Valid(pk, lvl, skipChecks))
                    continue;

                if (evo.RequiresLevelUp && levelMin >= lvl)
                    break; // impossible evolution

                UpdateMinValues(evos[..ctr], evo, levelMin);

                species = link.Species;
                form = link.Form;
                evos[ctr++] = evo.GetEvoCriteria(species, form, lvl);
                if (evo.RequiresLevelUp)
                    lvl--;

                oneValid = true;
                break;
            }
            if (!oneValid)
                break;
        }

        // Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc
        ref var last = ref evos[ctr - 1];
        if (last.Species > maxSpeciesID)
        {
            for (int i = 0; i < ctr; i++)
            {
                if (evos[i].Species > maxSpeciesID)
                    continue;
                ctr--;
                break;
            }
        }

        // Last species is the wild/hatched species, the minimum level is because it has not evolved from previous species
        var result = evos[..ctr];
        last = ref result[^1];
        last = last with { LevelMin = levelMin, LevelUpRequired = 0 };

        // Rectify minimum levels
        RectifyMinimumLevels(result);

        return result.ToArray();
    }

    private static void RectifyMinimumLevels(Span<EvoCriteria> result)
    {
        for (int i = result.Length - 2; i >= 0; i--)
        {
            ref var evo = ref result[i];
            var prev = result[i + 1];
            var min = (byte)Math.Max(prev.LevelMin + evo.LevelUpRequired, evo.LevelMin);
            evo = evo with { LevelMin = min };
        }
    }

    private static void UpdateMinValues(Span<EvoCriteria> evos, EvolutionMethod evo, byte minLevel)
    {
        ref var last = ref evos[^1];
        if (!evo.RequiresLevelUp)
        {
            // Evolutions like elemental stones, trade, etc
            last = last with { LevelMin = minLevel };
            return;
        }
        if (evo.Level == 0)
        {
            // Friendship based Level Up Evolutions, Pichu -> Pikachu, Eevee -> Umbreon, etc
            last = last with { LevelMin = (byte)(minLevel + 1) };

            // Raichu from Pikachu would have a minimum level of 1; accounting for Pichu (level up required) results in a minimum level of 2
            if (evos.Length > 1)
            {
                ref var first = ref evos[0];
                if (!first.RequiresLvlUp)
                    first = first with { LevelMin = (byte)(minLevel + 1) };
            }
        }
        else // level up evolutions
        {
            last = last with { LevelMin = evo.Level };

            if (evos.Length > 1)
            {
                ref var first = ref evos[0];
                if (first.RequiresLvlUp)
                {
                    // Pokemon like Crobat, its minimum level is Golbat minimum level + 1
                    if (first.LevelMin <= evo.Level)
                        first = first with { LevelMin = (byte)(evo.Level + 1) };
                }
                else
                {
                    // Pokemon like Nidoqueen who evolve with an evolution stone, minimum level is prior evolution minimum level
                    if (first.LevelMin < evo.Level)
                        first = first with { LevelMin = evo.Level };
                }
            }
        }
        last = last with { LevelUpRequired = evo.RequiresLevelUp ? (byte)1 : (byte)0 };
    }
}