using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class GrandmaMovement : MonoBehaviour
{
    
    public NavMeshAgent agent;
    public float walkSpeed = 2f;

    // Caching the current path, so we can spawn objects on a position of the path, when a new path arrives
    private NavMeshPath path;
    public NavMeshPath currentPath;
    private Vector3[] pathCornersFiltered = null;
    private int pathIndex = 0;

    private Vector3 lastAxisDir = Vector3.zero;
    private Vector3 currentMovementDirection = Vector3.zero; // keeping track of the movement direction for the hazard spawning 

    [SerializeField] private FieldOfView fov;
    private Animator animator;

    private const string _horizontal = "Horizontal";
    private const string _vertical = "Vertical";
    
    // Stuck detection
    private Vector3 stuckLastPos;
    private float stuckTimer = 0f;
    private const float stuckCheckInterval = 0.5f;
    private const float stuckMoveThreshold = 0.1f;
    
    public bool IsWaiting { get; set; }
    public GrandmaAction CurrentAction { get; set; }

        
    private void Awake()
    {
        animator = GetComponent<Animator>();
    }

    
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        // required for 2D NavMesh (NavMeshPlus)
        agent.updateRotation = false;
        agent.updateUpAxis = false;
        // prevent NavMeshAgent from changing transform position directly, we move grandma ourselves
        //agent.updatePosition = false;

        // Ensure the agent is sitting on the NavMesh. If not, try to warp it to the nearest NavMesh point.
        NavMeshHit agentHit;
        if (NavMesh.SamplePosition(transform.position, out agentHit, 1f, NavMesh.AllAreas))
        {
            agent.Warp(agentHit.position);
            Debug.Log($"Agent warped to navmesh pos {agentHit.position}");
        }
        else
        {
            Debug.LogWarning("[GrandmaAI] Agent is not on the NavMesh and no sample within 1m. If movement fails, consider increasing sampling radius or rebaking NavMesh.");
        }
    }

    void Update()
    {
        // Debug draw path if path isnt null (use filtered corners if present)
        var debugCorners = pathCornersFiltered ?? (path != null ? path.corners : null);
        if (debugCorners != null && debugCorners.Length > 1)
        {
            for (int i = 0; i < debugCorners.Length - 1; i++)
                Debug.DrawLine(debugCorners[i], debugCorners[i + 1], Color.red);
        }

        if (Input.GetKeyDown(KeyCode.F))
        {
            Debug.Log("Force unstuck");
            ResolveStuckNow();
        }
    }
    
    
    public void Tick()
    {
        MoveAlongPath();
        CheckStuck();
    }
    
    public void ComputePathTo(Vector3 target)
    {
        if (!agent.isOnNavMesh)
        {
            NavMeshHit snap;
            if (NavMesh.SamplePosition(transform.position, out snap, 5f, NavMesh.AllAreas))
            {
                agent.Warp(snap.position);
                transform.position = snap.position;
                Debug.LogWarning("[GrandmaAI] Agent was off NavMesh before path calc → snapped.");
            }
            else
            {
                Debug.LogError("[GrandmaAI] Could not snap Grandma to NavMesh. Path calc aborted.");
                return;
            }
        }

        // Sample the target onto the NavMesh (try small then larger radius)
        NavMeshHit hit = new NavMeshHit();
        float[] sampleRadii = new float[] { 1f, 3f, 6f };
        bool found = false;
        for (int i = 0; i < sampleRadii.Length; i++)
        {
            if (NavMesh.SamplePosition(target, out hit, sampleRadii[i], NavMesh.AllAreas))
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            Debug.LogWarning($"[GrandmaAI] Could not sample target {target} onto NavMesh. Falling back to agent.SetDestination.");
            // fallback: let agent try to route itself (keeps compatibility)
            agent.SetDestination(target);
            path = null;
            pathCornersFiltered = null;
            return;
        }
        
        // Calculate path to the sampled nav position synchronously
        path = new NavMeshPath();
        bool calculated = agent.CalculatePath(hit.position, path);
        
        currentPath = path;
        
        // Convert the navmesh corners into Manhattan (axis-aligned) steps
        if (path != null && path.corners != null && path.corners.Length > 0)
        {
            pathCornersFiltered = ConvertToManhattanCorners(path.corners);
        }
        else
        {
            pathCornersFiltered = null;
        }

        
        pathIndex = 0;

        if (!calculated || path == null || path.corners.Length == 0 || path.status != NavMeshPathStatus.PathComplete)
        {
            Debug.LogWarning($"[GrandmaAI] Path calc issue. status={path?.status} corners={path?.corners?.Length ?? 0}");
        }
    }
    
        private void MoveAlongPath()
    {
        Vector3[] corners = pathCornersFiltered ?? (path != null ? path.corners : null);
        
        if (corners == null || corners.Length == 0)
        {
            return;
        }

        // clamp pathIndex
        if (pathIndex >= corners.Length)
        {
            // reached final corner
            return;
        }

        Vector3 target = corners[pathIndex];
        // for 2D on XY plane: keep Z unchanged 
        target.z = transform.position.z;

        Vector3 delta = target - transform.position;
        float dist = delta.magnitude;

        // if close enough, advance to next corner
        float cornerThreshold = 0.1f;
        if (dist <= cornerThreshold)
        {
            pathIndex++;
            // if we just advanced beyond last corner, clamp to last so arrival detection works
            if (pathIndex >= corners.Length)
                pathIndex = corners.Length; 
            return;
        }

        Vector3 axisDir;
        Vector3 rawAxisDir;

        if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
            axisDir = new Vector3(Mathf.Sign(delta.x), 0f, 0f);
        else
            axisDir = new Vector3(0f, Mathf.Sign(delta.y), 0f);

        // Avoid flicker
        if (lastAxisDir == Vector3.zero)
        {
            rawAxisDir = axisDir;
        }
        else
        {
            float rawX = Mathf.Abs(delta.x);
            float rawY = Mathf.Abs(delta.y);
            float ratio = Mathf.Abs(rawX - rawY);

            float threshold = 0.1f;

            if (ratio > threshold)
                rawAxisDir = axisDir;
            else
                rawAxisDir = lastAxisDir;
        }

        lastAxisDir = rawAxisDir;

        currentMovementDirection = rawAxisDir * walkSpeed; 
        
        if(animator)
        {
            if(!IsWaiting)
            {
                animator.SetFloat(_horizontal, rawAxisDir.x);
                animator.SetFloat(_vertical, rawAxisDir.y);
            }
            else
            {
                animator.SetFloat(_horizontal, 0);
                animator.SetFloat(_vertical, 0);
            }
        }

        // Apply direction to FOV
        if (rawAxisDir != Vector3.zero)
        {
            if (fov)
                fov.SetAimDirection(rawAxisDir);
        }
        
        // move by axis-aligned step
        transform.position += axisDir * walkSpeed * Time.deltaTime;
        if (!agent.isOnNavMesh)
        {
            NavMeshHit hit;
            if (NavMesh.SamplePosition(transform.position, out hit, 1f, NavMesh.AllAreas))
            {
                transform.position = hit.position;
                agent.Warp(hit.position);
                Debug.LogWarning("Grandma was off NavMesh during small movement → snapped.");
            }
        }
    }
    
        
        public bool ReachedTarget()
        {
            Vector3[] corners = pathCornersFiltered ?? (path != null ? path.corners : null);
            if (corners != null && corners.Length > 0)
            {
                Vector3 final = corners[corners.Length - 1];
                final.z = transform.position.z;
                float arriveThreshold = 0.5f; 
                if (Vector3.Distance(transform.position, final) <= arriveThreshold)
                {
                    //Debug.Log("Target reached (by final corner distance).");
                    return true;
                }
                return false;
            }

            // Otherwise fall back to agent-based check
            if (!agent.isOnNavMesh)
            {
                // snap back to navmesh
                NavMeshHit snapHit;
                //if grandma is off navmesh, snap her back to navmesh walkable area.
            
                if (NavMesh.SamplePosition(transform.position, out snapHit, 5f, NavMesh.AllAreas))
                {
                    transform.position = snapHit.position;
                    agent.Warp(snapHit.position);

                    if (CurrentAction != null)
                        ComputePathTo(CurrentAction.GetTargetPosition());

                    Debug.LogWarning("Grandma was off NavMesh → snapped + recalculated path.");
                }

                return false;
            }

            float reachThreshold = 1f;
            if (!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance + reachThreshold)
            {
                Debug.Log("Target reached (agent.remainingDistance). with Navmesh!");
                return true;
            }

            return false;
        }
        
        
    private Vector3[] ConvertToManhattanCorners(Vector3[] corners)
    {
        // oversample each long segment before Manhattanizing
        List<Vector3> dense = new List<Vector3>();

        const float sampleEvery = 1f; // adjust: smaller → more points
        dense.Add(corners[0]);

        for (int i = 1; i < corners.Length; i++)
        {
            Vector3 a = corners[i - 1];
            Vector3 b = corners[i];

            float dist = Vector3.Distance(a, b);
            int steps = Mathf.Max(1, Mathf.FloorToInt(dist / sampleEvery));

            for (int s = 1; s <= steps; s++)
            {
                float t = s / (float)steps;
                Vector3 p = Vector3.Lerp(a, b, t);
                dense.Add(p);
            }
        }


        const float minDist = 0.2f;
        List<Vector3> result = new List<Vector3>();

        Vector3 SnapSafe(Vector3 p)
        {
            NavMeshHit hit;
            if (NavMesh.SamplePosition(p, out hit, 3f, NavMesh.AllAreas))
                return hit.position;
            return p;
        }

        // Push all sampled points away first
        Vector3[] pushedCorners = new Vector3[dense.Count];
        for (int i = 0; i < dense.Count; i++)
        {
            pushedCorners[i] = PushAwayFromEdges(dense[i], 0.5f);
            pushedCorners[i] = SnapSafe(pushedCorners[i]);
        }

        result.Add(pushedCorners[0]);

        for (int i = 1; i < pushedCorners.Length; i++)
        {
            Vector3 prev = result[result.Count - 1];
            Vector3 next = pushedCorners[i];

            float dx = next.x - prev.x;
            float dy = next.y - prev.y;
            bool axisAligned = Mathf.Abs(dx) < 0.0001f || Mathf.Abs(dy) < 0.0001f;

            if (axisAligned)
            {
                if ((next - prev).sqrMagnitude >= minDist * minDist)
                    result.Add(next);
                continue;
            }

            // Manhattan split
            Vector3 xStep = SnapSafe(new Vector3(next.x, prev.y, prev.z));
            Vector3 yStep = SnapSafe(new Vector3(prev.x, next.y, prev.z));

            float distX = Vector3.Distance(xStep, next);
            float distY = Vector3.Distance(yStep, next);

            if (distX <= distY)
            {
                if ((xStep - prev).sqrMagnitude >= minDist * minDist)
                    result.Add(xStep);
                if ((next - xStep).sqrMagnitude >= minDist * minDist)
                    result.Add(next);
            }
            else
            {
                if ((yStep - prev).sqrMagnitude >= minDist * minDist)
                    result.Add(yStep);
                if ((next - yStep).sqrMagnitude >= minDist * minDist)
                    result.Add(next);
            }
        }

        return result.ToArray();
    }


    private Vector3 PushAwayFromEdges(Vector3 p, float edgeOffset)
    {
        Vector3[] dirs = new[]
        {
            Vector3.left, Vector3.right,
            Vector3.up, Vector3.down
        };

        foreach (var d in dirs)
        {
            NavMeshHit hit;
            if (NavMesh.Raycast(p, p + d * 0.4f, out hit, NavMesh.AllAreas))
            {
                p += hit.normal.normalized * edgeOffset;

                if (NavMesh.SamplePosition(p, out var snap, 0.6f, NavMesh.AllAreas))
                    p = snap.position;
            }
        }

        return p;
    }

    /// <summary>
    /// As we are not using the NavMesh velocity, we keep track of the direction manually.
    /// </summary>
    /// <returns></returns>
    public Vector3 GetMovementDirection()
    {
        return currentMovementDirection;
    }
    
    public void CheckStuck()
    {
        if (IsWaiting || agent == null) return;
        if (CurrentAction == null && (path == null || pathIndex == 0)) return;

        stuckTimer += Time.deltaTime;

        float minMove = walkSpeed * stuckCheckInterval * 0.25f;

        if (stuckTimer >= stuckCheckInterval)
        {
            float moved = Vector3.Distance(transform.position, stuckLastPos);

            if (moved < Mathf.Max(stuckMoveThreshold, minMove))
            {
                Debug.LogWarning("Grandma stuck!!");
                ResolveStuckNow();
            }

            stuckLastPos = transform.position;
            stuckTimer = 0f;
        }
    }

    public void ResolveStuckNow()
    {
        NavMeshHit hit;
        if (NavMesh.SamplePosition(transform.position, out hit, 3f, NavMesh.AllAreas))
        {
            agent.Warp(hit.position);
            transform.position = hit.position;

            // Only compute path if agent is now valid on the NavMesh
            if (agent.isOnNavMesh && CurrentAction != null)
            {
                ComputePathTo(CurrentAction.GetTargetPosition());
            }
        }
    }
    
        
}
