using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Godot;
using Godot.Collections;

namespace KarrotStarterTemplate.Common.StateMachines;

public class StateInputsPackage
{
    
    /*
     * Potrzebujemy:
     *  1. Informacji jakie klawisze trzyma teraz gracz (np czy trzyma Shift, żeby przejść z Walk na Run) - done
     *  2. Globalnych flag, które sie nie resetują (np. czy gracz wcisnął C i jest w trybie kucania, wciśnięcie C ponownie wyłącza tę flagę) - done
     *  3. Informacji o tym czy w trakcie eventu gracz wcisnął dany klawisz (jeśli gracz kliknie atak w trakcie uniku to musimy zakolejkować to działanie
     *  4. Wartości w wektorach (np inputDirection z WASD) - done
     *  5. Informacji o tym, że skończyła się animacja, albo event z animnotify
     *
     *  Gdy OnCheckRelevance coś zwraca, to nie powinniśmy przechodzić w ten state, tylko kolejkować to i sprawdzać czy aktualny stejt ma flagę "canExit" na true,
     *  wtedy odpalać ostatni dostępny state?
     */
    
    private readonly Dictionary<string, Variant> _inputs = new Dictionary<string, Variant>();

    public void Add(string key, Variant value)
    {
        _inputs[key] = value;
    }
    public void Clear(string key)
    {
        _inputs.Remove(key);
    }

    public Variant Get(string key)
    {
        if (_inputs.TryGetValue(key, out Variant value))
        {
            return value;
        }
        
        return new Variant();
    }

    public Vector3 GetVector3(string key)
    {
        if (_inputs.TryGetValue(key, out var value))
        {
            return value.AsVector3();
        }
        
        return Vector3.Zero;
    }

    public string GetString(string key)
    {
        if (_inputs.TryGetValue(key, out var value))
        {
            return value.AsString();
        }

        return "";
    }

    public bool GetBool(string key)
    {
        if (_inputs.TryGetValue(key, out var value))
        {
            return value.AsBool();
        }

        return false;
    }
}

[GlobalClass]
public partial class StateMachine : Node
{
    [Export] public Node3D owner { get; set; }
    [Export] public State defaultState { get; set; }

    [Export] public bool activateOnReady { get; set; }
    
    public StateInputsPackage package { get; private set; } = new StateInputsPackage();
    public State currentState { get; private set; }
    public bool isTransitioning { get; private set; } = false;
    public bool active => currentState != null;

    public Dictionary<string, State> states { get; private set;   } = new Dictionary<string, State>();

    public CharacterBody3D ownerCharacterBody => (CharacterBody3D)owner;


    // Called when the node enters the scene tree for the first time.
    public override async void _Ready()
    {
        foreach (Node node in GetChildren())
        {
            if (node is State state)
            {
                states.Add(state.id, state);
            }
        }

        if (activateOnReady)
        {
            await Activate();
        }
    }

    public async Task Activate()
    {
        await TransitionToState(defaultState);
    }

    public async void Deactivate()
    {
        if (currentState != null)
        {
            await TransitionToState(null as State);
        }

    }

    public override void _PhysicsProcess(double delta)
    {
        currentState?.StatePhysicsProcess(delta);
        

    }

    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
        if (currentState != null)
        {
            currentState.StateProcess(delta);

            if (!isTransitioning)
            {
                string nextState = currentState.CheckRelevance();
                if (nextState != null)
                {
                    TransitionToState(nextState);
                }
            }
        }
        

    }

    private Task<bool> TransitionToState(string stateId)
    {
        // if (currentState != null && currentState.id == stateId)
        // {
        //     return Task.FromResult(false);
        // }

        return TransitionToState(states[stateId]);
    }

    private async Task<bool> TransitionToState(State state)
    {
        if (currentState == state && !state.canTransitionToSelf)
        {
            return false;
        }

        GD.Print("--");
        GD.Print($"------------------------------------------");
        GD.Print($"------Transitioning from {currentState?.id} to {state?.id}");

        isTransitioning = true;
        if (currentState != null)
        {
            await currentState.Exit();
        }
        
        if (state != null)
        {
            await state.Enter();
        }

        currentState = state;

        GD.Print($"------Transition finished to {state?.id}");
        GD.Print($"------------------------------------------");
        GD.Print("--");

        isTransitioning = false;
        return true;
    }
}