using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.ScriptHandling;

/// <summary>
/// The script class is used to make changes to how a battle executes, without requiring hardcoded
/// changes. This allows for easily defining generational differences, and add effects that the
/// developer might require.
/// </summary>
public abstract class Script : IDeepCloneable
{
    internal event Action<Script>? OnRemoveEvent;
    
    private int _suppressCount;

    public void RemoveSelf()
    {
        OnRemoveEvent?.Invoke(this);
    }

    /// <summary>
    /// The name of a script is its unique identifier.
    /// If not overridden, this will resolve the name from the <see cref="ScriptAttribute"/> of the
    /// script.
    /// </summary>
    public virtual StringKey Name => this.ResolveName();

    /// <summary>
    /// A script can be suppressed by other scripts. If a script is suppressed by at least one script
    /// we will not execute its methods. This should return the number of suppressions on the script.
    /// </summary>
    public int SuppressCount => _suppressCount;

    /// <summary>
    /// Helper function to check if there is at least one suppression on the script
    /// </summary>
    public bool IsSuppressed => _suppressCount > 0;

    /// <summary>
    /// Adds a suppression. This makes the script not run anymore. Note that adding this should also
    /// remove the suppression later.
    ///
    /// A common pattern for this is to run this in the <see cref="OnInitialize"/> and remove it in the
    /// <see cref="OnRemove"/> function.
    /// </summary>
    public void Suppress() => _suppressCount++;

    /// <summary>
    /// Removes a suppression. This allows the script to run again (provided other scripts are not
    /// suppressing it). Note that running this should only occur if <see cref="Suppress"/> was called earlier
    /// </summary>
    public void Unsuppress() => _suppressCount--;

    /// <summary>
    /// This function is ran when a volatile effect is added while that volatile effect already is
    /// in place. Instead of adding the volatile effect twice, it will execute this function instead.
    /// </summary>
    public virtual void Stack()
    {
    }

    /// <summary>
    /// This function is ran when this script stops being in effect, and is removed from its owner.
    /// </summary>
    public virtual void OnRemove()
    {
    }

    /// <summary>
    /// This function is ran when this script starts being in effect.
    /// </summary>
    public virtual void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
    {
    }
    
    /// <summary>
    /// Override to customize whether the move can be selected at all.
    /// </summary>
    public virtual void PreventMoveSelection(IMoveChoice choice, ref bool prevent)
    {
    }
    
    public virtual void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
    {
    }

    /// <summary>
    /// This function is ran just before the start of the turn. Everyone has made its choices here,
    /// and the turn is about to start. This is a great place to initialize data if you need to know
    /// something has happened during a turn.
    /// </summary>
    public virtual void OnBeforeTurnStart(ITurnChoice choice)
    {
    }

    /// <summary>
    /// This function allows you to modify the effective speed of the Pokemon. This is run before
    /// turn ordering, so overriding here will allow you to put certain Pokemon before others.
    /// </summary>
    public virtual void ChangeSpeed(ITurnChoice choice, ref uint speed)
    {
    }

    /// <summary>
    /// This function allows you to modify the effective priority of the Pokemon. This is run before
    /// turn ordering, so overriding here will allow you to put certain Pokemon before others. Note
    /// that this is only relevant on move choices, as other turn choice types do not have a priority.
    /// </summary>
    public virtual void ChangePriority(IMoveChoice choice, ref sbyte priority)
    {
    }

    /// <summary>
    /// This function allows you to change the move that is used during execution. This is useful for
    /// moves such as metronome, where the move chosen actually differs from the move used.
    /// </summary>
    public virtual void ChangeMove(IMoveChoice choice, ref StringKey moveName)
    {
    }
    
    /// <summary>
    /// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts.
    /// </summary>
    public virtual void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
    {
    }

    /// <summary>
    /// This function allows you to change a move into a multi-hit move. The number of hits set here
    /// gets used as the number of hits. If set to 0, this will behave as if the move missed on its
    /// first hit.
    /// </summary>
    public virtual void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits)
    {
    }

    /// <summary>
    /// This function allows you to prevent a move from running. If this gets set to true, the move
    /// ends execution here. No PP will be decreased in this case.
    /// </summary>
    public virtual void PreventMove(IExecutingMove move, ref bool prevent)
    {
    }

    /// <summary>
    /// This function makes the move fail. If the fail field gets set to true, the move ends execution,
    /// and fail events get triggered.
    /// </summary>
    public virtual void FailMove(IExecutingMove move, ref bool fail)
    {
    }

    /// <summary>
    /// Similar to <see cref="PreventMove"/>. This function will also stop execution of the move, but
    /// PP will still be decreased.
    /// </summary>
    public virtual void StopBeforeMove(IExecutingMove move, ref bool stop)
    {
    }

    /// <summary>
    /// This function runs just before the move starts its execution.
    /// </summary>
    public virtual void OnBeforeMove(IExecutingMove move)
    {
    }

    /// <summary>
    /// This function allows a script to prevent a move that is targeted at its owner. If set to true
    /// the move fails, and fail events get triggered.
    /// </summary>
    public virtual void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail)
    {
    }

    /// <summary>
    /// This function allows a script to make its owner invulnerable to an incoming move.
    /// </summary>
    public virtual void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
    {
    }

    /// <summary>
    /// This function occurs when a move gets missed. This runs on the scripts belonging to the executing
    /// move, which include the scripts that are attached to the owner of the script.
    /// </summary>
    public virtual void OnMoveMiss(IExecutingMove move, IPokemon target)
    {
    }

    /// <summary>
    /// This function allows the script to change the actual type that is used for the move on a target.
    /// </summary>
    public virtual void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType)
    {
    }

    /// <summary>
    /// This function allows the script to change how effective a move is on a target.
    /// </summary>
    public virtual void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
    {
    }

    /// <summary>
    /// This function allows a script to block an outgoing move from being critical.
    /// </summary>
    public virtual void BlockCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block)
    {
    }

    /// <summary>
    /// This function allows a script to block an incoming move from being critical.
    /// </summary>
    public virtual void BlockIncomingCriticalHit(IExecutingMove move, IPokemon target, byte hit, ref bool block)
    {
    }

    /// <summary>
    /// This function allows a script to modify the accuracy of a move used. This value represents
    /// the percentage accuracy, so anything above 100% will make it always hit.
    /// </summary>
    public virtual void ChangeAccuracyModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
    {
    }

    /// <summary>
    /// This function allows a script to change the critical stage of the move used.
    /// </summary>
    public virtual void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage)
    {
    }

    /// <summary>
    /// This function allows a script to change the damage modifier of a critical hit. This will only
    /// run when a hit is critical.
    /// </summary>
    public virtual void ChangeCriticalModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
    {
    }

    /// <summary>
    /// This function allows a script to change the damage modifier of a Same Type Attack Bonus, which
    /// occurs when the user has the move type as one of its own types.
    /// </summary>
    public virtual void ChangeStabModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
        ref float modifier)
    {
    }

    /// <summary>
    /// This function allows a script to change the effective base power of a move hit.
    /// </summary>
    public virtual void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
    {
    }

    /// <summary>
    /// This function allows a script to bypass defensive stat boosts for a move hit.
    /// If this is true, the damage will be calculated as if the target has no positive stat boosts. Negative
    /// stat boosts will still be applied.
    /// </summary>
    public virtual void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass)
    {
    }
    
    /// <summary>
    /// This function allows a script to bypass evasion stat boosts for a move hit.
    /// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts.
    /// </summary>
    public virtual void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass)
    {
    }

    /// <summary>
    /// This function allows a script to bypass offensive stat boosts for a move hit.
    /// If this is true, the damage will be calculated as if the user has no negative offensive stat boosts. Positive
    /// stat boosts will still be applied.
    /// </summary>
    public virtual void BypassOffensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass)
    {
    }

    /// <summary>
    /// This function allows a script to change the actual offensive stat values used when calculating damage
    /// </summary>
    public virtual void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, ref uint value)
    {
    }

    /// <summary>
    /// This function allows a script to change the actual defensive stat values used when calculating damage.
    /// </summary>
    public virtual void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, ref uint value)
    {
    }

    /// <summary>
    /// This function allows a script to change the raw modifier we retrieved from the stats of the
    /// defender and attacker. The default value is the offensive stat divided by the defensive stat.
    /// </summary>
    public virtual void ChangeDamageStatModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
    {
    }

    /// <summary>
    /// This function allows a script to apply a raw multiplier to the damage done by a move.
    /// </summary>
    public virtual void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier)
    {
    }

    /// <summary>
    /// This function allows a script to modify the outgoing damage done by a move.
    /// </summary>
    public virtual void ChangeDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
    {
    }

    /// <summary>
    /// This function allows a script to modify the incoming damage done by a move.
    /// </summary>
    public virtual void ChangeIncomingDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
    {
    }

    /// <summary>
    /// This function triggers when an incoming hit happens. This triggers after the damage is done,
    /// but before the secondary effect of the move happens.
    /// </summary>
    public virtual void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
    {
    }

    /// <summary>
    /// This function triggers when an opponent on the field faints.
    /// </summary>
    public virtual void OnOpponentFaints(IExecutingMove move, IPokemon target, byte hit)
    {
    }

    /// <summary>
    /// This function allows a script attached to a Pokemon or its parents to prevent stat boost
    /// changes on that Pokemon.
    /// </summary>
    public virtual void PreventStatBoostChange(IPokemon target, Statistic stat, sbyte amount, bool selfInflicted,
        ref bool prevent)
    {
    }

    /// <summary>
    /// This function allows a script attached to a Pokemon or its parents to modify the amount by
    /// which the stat boost will change. If the stat boost is done by the user itself, self
    /// inflicted will be true, otherwise it will be false.
    /// </summary>
    public virtual void ChangeStatBoostChange(IPokemon target, Statistic stat, bool selfInflicted, ref sbyte amount)
    {
    }

    /// <summary>
    /// This function allows a script attached to a Pokemon or its parents to prevent an incoming
    /// secondary effect. This means the move will still hit and do damage, but not trigger its
    /// secondary effect. Note that this function is not called for status moves.
    /// </summary>
    public virtual void PreventSecondaryEffect(IExecutingMove move, IPokemon target, byte hit, ref bool prevent)
    {
    }

    /// <summary>
    /// This function allows a script attached to a move or its parents to change the chance the
    /// secondary effect of a move will trigger. The chance is depicted in percentage here, so
    /// changing this to above or equal to 100 will make it always hit, while setting it to equal or
    /// below 0 will make it never hit.
    /// </summary>
    public virtual void ChangeEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance)
    {
    }

    /// <summary>
    /// This function allows a script attached to a Pokemon or its parents to change the chance the
    /// secondary effect of an incoming move will trigger. The chance is depicted in percentage here,
    /// so changing this to above or equal to 100 will make it always hit, while setting it to equal
    /// or below 0 will make it never hit.
    /// </summary>
    public virtual void ChangeIncomingEffectChance(IExecutingMove move, IPokemon target, byte hit, ref float chance)
    {
    }

    /// <summary>
    /// This function triggers when the move uses its secondary effect. Moves should implement their
    /// secondary effects here. Status moves should implement their actual functionality in this
    /// function as well, as status moves effects are defined as secondary effects for simplicity.
    /// </summary>
    public virtual void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
    {
    }

    /// <summary>
    /// This function triggers on a move or its parents when all hits on a target are finished.
    /// </summary>
    public virtual void OnAfterHits(IExecutingMove move, IPokemon target)
    {
    }

    /// <summary>
    /// This function prevents the Pokemon it is attached to from being able to switch out.
    /// </summary>
    public virtual void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent)
    {
    }

    /// <summary>
    /// This function allows the prevention of switching for any opponent.
    /// </summary>
    public virtual void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent)
    {
    }

    /// <summary>
    /// This function is called on a move and its parents when the move fails.
    /// </summary>
    public virtual void OnFail(IPokemon pokemon)
    {
    }

    /// <summary>
    /// This function is called on a script when an opponent fails.
    /// </summary>
    /// <param name="pokemon"></param>
    public virtual void OnOpponentFail(IPokemon pokemon)
    {
    }

    /// <summary>
    /// This function allows preventing the running away of the Pokemon its attached to
    /// </summary>
    public virtual void PreventSelfRunAway(IFleeChoice choice, ref bool prevent)
    {
    }

    /// <summary>
    /// This function prevents a Pokemon on another side than where its attached to from running away.
    /// </summary>
    public virtual void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent)
    {
    }

    /// <summary>
    /// This function id triggered on all scripts active in the battle after all choices have finished
    /// running. Note that choices are not active anymore here, so their scripts do not call this
    /// function.
    /// </summary>
    /// <param name="battle"></param>
    public virtual void OnEndTurn(IBattle battle)
    {
    }

    /// <summary>
    /// This function is triggered on a Pokemon and its parents when the given Pokemon takes damage.
    /// </summary>
    public virtual void OnDamage(IPokemon pokemon, DamageSource source, uint oldHealth, uint newHealth)
    {
    }

    /// <summary>
    /// This function is triggered on a Pokemon and its parents when the given Pokemon faints.
    /// </summary>
    public virtual void OnFaint(IPokemon pokemon, DamageSource source)
    {
    }

    /// <summary>
    /// This function is triggered on a Pokemon and its parents when the given Pokemon is switched into
    /// the battlefield.
    /// </summary>
    public virtual void OnSwitchIn(IPokemon pokemon)
    {
    }

    /// <summary>
    /// This function is triggered on a Pokemon and its parents when the given Pokemon consumes the
    /// held item it had.
    /// </summary>
    public virtual void OnAfterHeldItemConsume(IPokemon pokemon, IItem item)
    {
    }

    /// <summary>
    /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience,
    /// and allows for changing this amount of experience.
    /// </summary>
    public virtual void ChangeExperienceGained(IPokemon faintedPokemon, IPokemon winningPokemon, ref uint amount)
    {
    }

    /// <summary>
    /// This function is triggered on a Pokemon and its parents when the given Pokemon gains experience,
    /// and allows for making the experience be shared across multiple Pokemon.
    /// Amount is the modifier for how much experience is shared, with 1 being the default amount.
    /// </summary>
    public virtual void ShareExperience(IPokemon faintedPokemon, IPokemon winningPokemon, ref bool share,
        ref float amount)
    {
    }

    /// <summary>
    /// This function is triggered on a battle and its parents when something attempts to change the
    /// weather, and allows for blocking the weather change.
    /// </summary>
    public virtual void BlockWeatherChange(IBattle battle, ref bool block)
    {
    }

    /// <summary>
    /// This function is called when a Pokeball is thrown at a Pokemon, and allows modifying the catch
    /// rate of this attempt. Pokeball modifier effects should be implemented here, as well as for
    /// example status effects that change capture rates.
    /// </summary>
    public virtual void ChangeCatchRateBonus(IPokemon pokemon, IItem pokeball, ref byte modifier)
    {
    }

    public virtual void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
    {
    }
    
    public virtual void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
    {
    }
    
    /// <summary>
    /// Custom triggers for scripts. This allows scripts to run custom events that are not part of the
    /// standard battle flow.
    /// </summary>
    /// <param name="eventName">
    /// The name of the event that is triggered. This should be unique for each different event. Overriding scripts
    /// should validate the event name is one they should handle.
    /// </param>
    /// <param name="parameters">
    /// The parameters that are passed to the event. This can be null if no parameters are passed.
    /// </param>
    public virtual void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters)
    {
    }
}