using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public abstract class UtilityAI : MonoBehaviour
{
    public List<Need> needs = new List<Need>();

    [Header("Debug Info (Read Only)")]
    [SerializeField] protected List<ActionScoreEntry> actionScores = new List<ActionScoreEntry>();

    protected GrandmaAction currentAction;
    protected GrandmaAction completedAction; // this is needed to cache the old action
    protected bool isWaiting = false;

    protected Queue<GrandmaAction> actionQueue = new Queue<GrandmaAction>(5);

    protected GrandmaMovement movement;

    protected ActionAdvertiser currentActionAdvertiser;

    private float barTime = 1f;

    public bool showActionProgress;
    
    protected virtual void Awake()
    {
        movement = GetComponent<GrandmaMovement>();
    }

    protected virtual void Update()
    {
        movement.IsWaiting = isWaiting;
        movement.CurrentAction = currentAction;
        movement.Tick();

        // Decay all needs over time
        UpdateNeeds();

        if (isWaiting) return;
        

        // If we have no current action, pick or consume one from the queue
        if (currentAction == null)
        {
            SelectAndStartAction();
            return;
        }
        // If we reached the target and are not waiting, trigger action execution
        if (movement.ReachedTarget() && !isWaiting)
        {
            OnReachedTarget();
        }
        // Extra per-frame logic (specific to derived classes)
        OnUpdateExtra();
    }

    protected virtual void UpdateNeeds()
    {
        foreach (var need in needs)
        {
            need.UpdateNeed(Time.deltaTime);
        }
    }

    protected virtual void SelectAndStartAction()
    {
        // If queue is empty, select the best new action
        if (actionQueue.Count == 0)
        {
            var newAction = SelectBestAction();
            if (newAction != null && actionQueue.Count < 2)
            {
                actionQueue.Enqueue(newAction);
                currentActionAdvertiser = newAction.advertiser;
            }
        }
        // Consume next valid action
        if (actionQueue.Count > 0)
        {
            // Remove invalid or destroyed advertisers from the queue
            PruneDestroyedAdvertisersFromQueue();

            // Dequeue until a valid action is found
            while (actionQueue.Count > 0)
            {
                var next = actionQueue.Dequeue();
                if (next.advertiser != null || next.fixedTargetPosition.HasValue)
                {
                    currentAction = next;
                    break;
                }
            }

            if (currentAction != null)
                movement.ComputePathTo(currentAction.GetTargetPosition());
        }
        
        Debug.Log($"(UtilityAI.SelectAndStartAction)currentAction ==null: {currentAction==null}");

        
    }

    protected virtual void OnReachedTarget()
    {
        // Start waiting and fulfill the current action
        int duration = currentActionAdvertiser.progressDuration;
        StartCoroutine(WaitAndFulfill(duration));
    }

    protected virtual IEnumerator WaitAndFulfill(int barCount)
    {
        // Block movement and decision-making during action execution
        isWaiting = true;

        OnActionStarted(currentAction);

        if (showActionProgress && currentActionAdvertiser != null)
        {
            // Setup ActionProgress before starting the bars
            ActionProgress.Instance.SetMaxProgressAmount(currentActionAdvertiser.progressDuration);
            ActionProgress.Instance.SetInProgress(true);
        }

        // Wait for each progress bar step
        for (int i = 0; i < barCount + 1; i++)
        {
            yield return new WaitForSeconds(barTime);
            if (showActionProgress) ActionProgress.Instance.IncreaseProgressAmount();
        }   
        
        // Fulfill need and cache completed action
        if (currentAction != null)
        {
            completedAction = currentAction;
            if (CanFulfillNeed(currentAction))
            {
                FulfillNeed(currentAction);
            }

            OnActionCompleted(completedAction);
        }
        
        // Reset action state after completion
        currentAction = null;
        isWaiting = false;
    }

    protected virtual void FulfillNeed(GrandmaAction action)
    {
        // Increase the corresponding need value based on the advertiser's effect
        Need need = needs.FirstOrDefault(n => n.name == action.advertiser.needName);
        if (need != null)
            need.value = Mathf.Clamp(need.value + action.advertiser.effectAmount, 0f, 100f);
    }

    protected virtual bool CanFulfillNeed(GrandmaAction action) => true;


    protected virtual GrandmaAction SelectBestAction()
    {
        ActionAdvertiser[] advertisers =
            FindObjectsByType<ActionAdvertiser>(FindObjectsSortMode.None);

        List<GrandmaAction> actions = new List<GrandmaAction>();
        actionScores.Clear();

        foreach (var adv in advertisers)
        {
            Need need = needs.FirstOrDefault(n => n.name == adv.needName);
            if (need == null) continue;

            float score = need.GetUtility();
            actions.Add(new GrandmaAction(adv, score));

            actionScores.Add(new ActionScoreEntry
            {
                advertiserName = adv.name,
                needName = adv.needName,
                score = score
            });
        }

#if UNITY_EDITOR
        UnityEditor.EditorUtility.SetDirty(this);
#endif

        float maxScore = actions.Max(a => a.score);

        const float eps = 0.1f; // tolerance for "same" score
        var bestActions = actions
            .Where(a => Mathf.Abs(a.score - maxScore) < eps)
            .ToList();
        // Return a random action among the best-scoring ones
        return bestActions[Random.Range(0, bestActions.Count)];
    }

    protected void PruneDestroyedAdvertisersFromQueue()
    {
        if (actionQueue.Count == 0) return;
        // Interactable advertisers survive even if the advertiser gameobject is destroyed
        actionQueue = new Queue<GrandmaAction>(
            actionQueue.Where(a =>
                a != null &&
                (
                    a.fixedTargetPosition.HasValue ||
                    a.advertiser != null
                )
            )
        );
    }

    public virtual void ClearActions()
    {
        actionQueue.Clear();
        currentAction = null;
    }

    /* Specific Logic */

    protected virtual void OnActionCompleted(GrandmaAction action) { }
    protected virtual void OnActionStarted(GrandmaAction action) { }

    protected virtual void OnUpdateExtra() { }
}


