using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;

/// <summary>
/// GAME 1303 Tier 5 — Free 8-direction movement with no gravity. The top-down equivalent
/// of PlayerController2D. Last Delivery, Apep's Trial (top-down mode), Cozy Critters' pet.
///
/// SETUP
///  1. Character: Sprite + Rigidbody2D (Gravity Scale 0, Freeze Rotation Z,
///     Collision Detection Continuous) + Collider2D + this script + PlayerInput
///     (Send Messages). Tag "Player" if it's the player.
///  2. Walls need solid (non-trigger) colliders — the Rigidbody handles the sliding.
///  3. Facing:
///       FlipSprite     – flips on X like a side-scroller (simplest).
///       FourDirection  – sets Animator ints/floats so you can have up/down/left/right
///                        animation sets: MoveX, MoveY (floats, last non-zero direction)
///                        plus Speed. This is the standard Zelda-style setup.
///       RotateSprite   – points the sprite along movement (a delivery robot, a ship).
///  4. AI Controlled: tick to ignore player input; drive it with SetMoveInput() from a
///     wander brain (Cozy Critters' PetBrain in Tier 7 does this).
///
/// This deliberately mirrors PlayerController2D's API — InputEnabled, MovementLocked,
/// SpeedMultiplier, ResetMotion — so Tier 1's RespawnManager and Tier 3 modules that
/// only need those still work.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class TopDownMover : MonoBehaviour
{
    public enum FacingMode { FlipSprite, FourDirection, RotateSprite, None }

    [Header("Movement")]
    public float moveSpeed = 5f;
    public float acceleration = 50f;
    public float deceleration = 60f;
    [Tooltip("Normalize diagonals so you don't move 40% faster on the diagonal.")]
    public bool normalizeDiagonal = true;

    [Header("Facing")]
    public FacingMode facing = FacingMode.FlipSprite;
    public bool artFacesRight = true;
    [Tooltip("RotateSprite mode: degrees to add if your art points up instead of right.")]
    public float rotationOffset = 0f;

    [Header("Control")]
    public bool aiControlled = false;

    [Header("Events")]
    public UnityEvent OnStartedMoving;
    public UnityEvent OnStoppedMoving;

    // Mirrors PlayerController2D so shared systems work
    public bool InputEnabled { get; set; } = true;
    public bool MovementLocked { get; set; }
    public float SpeedMultiplier { get; set; } = 1f;
    public Vector2 Velocity => rb.linearVelocity;
    public Vector2 MoveVector => moveInput;
    public Vector2 Facing2D => lastDirection;
    public bool IsMoving { get; private set; }

    private Rigidbody2D rb;
    private Animator anim;
    private Vector2 moveInput;
    private Vector2 lastDirection = Vector2.down;
    private bool facingRight = true;

    static readonly int SpeedHash = Animator.StringToHash("Speed");
    static readonly int MoveXHash = Animator.StringToHash("MoveX");
    static readonly int MoveYHash = Animator.StringToHash("MoveY");
    private bool hasSpeed, hasMoveX, hasMoveY;

    void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        rb.gravityScale = 0f;
        anim = GetComponent<Animator>();
        if (anim != null)
            foreach (var p in anim.parameters)
            {
                if (p.nameHash == SpeedHash) hasSpeed = true;
                if (p.nameHash == MoveXHash) hasMoveX = true;
                if (p.nameHash == MoveYHash) hasMoveY = true;
            }
        facingRight = artFacesRight ? transform.localScale.x > 0f : transform.localScale.x < 0f;
    }

    void OnMove(InputValue value)
    {
        if (aiControlled) return;
        moveInput = InputEnabled ? value.Get<Vector2>() : Vector2.zero;
    }

    /// <summary>For AI: feed a direction (any length up to 1). Cozy Critters, enemies.</summary>
    public void SetMoveInput(Vector2 input) => moveInput = Vector2.ClampMagnitude(input, 1f);

    void FixedUpdate()
    {
        if (MovementLocked) return;

        Vector2 input = moveInput;
        if (normalizeDiagonal && input.sqrMagnitude > 1f) input = input.normalized;

        Vector2 target = input * moveSpeed * SpeedMultiplier;
        float rate = target.sqrMagnitude > 0.01f ? acceleration : deceleration;
        rb.linearVelocity = Vector2.MoveTowards(rb.linearVelocity, target, rate * Time.fixedDeltaTime);

        bool movingNow = input.sqrMagnitude > 0.01f;
        if (movingNow)
        {
            lastDirection = input.normalized;
            ApplyFacing();
        }
        if (movingNow != IsMoving)
        {
            IsMoving = movingNow;
            if (movingNow) OnStartedMoving?.Invoke(); else OnStoppedMoving?.Invoke();
        }

        UpdateAnimator();
    }

    void ApplyFacing()
    {
        switch (facing)
        {
            case FacingMode.FlipSprite:
                if (Mathf.Abs(lastDirection.x) < 0.01f) break;
                bool wantRight = lastDirection.x > 0f;
                if (wantRight != facingRight)
                {
                    facingRight = wantRight;
                    Vector3 s = transform.localScale; s.x *= -1f; transform.localScale = s;
                }
                break;

            case FacingMode.RotateSprite:
                float angle = Mathf.Atan2(lastDirection.y, lastDirection.x) * Mathf.Rad2Deg + rotationOffset;
                transform.rotation = Quaternion.Euler(0f, 0f, angle);
                break;
        }
        // FourDirection is handled by the animator parameters below.
    }

    void UpdateAnimator()
    {
        if (anim == null) return;
        if (hasSpeed) anim.SetFloat(SpeedHash, rb.linearVelocity.magnitude);
        if (facing == FacingMode.FourDirection)
        {
            if (hasMoveX) anim.SetFloat(MoveXHash, lastDirection.x);
            if (hasMoveY) anim.SetFloat(MoveYHash, lastDirection.y);
        }
    }

    public void ResetMotion()
    {
        rb.linearVelocity = Vector2.zero;
        moveInput = Vector2.zero;
        MovementLocked = false;
        SpeedMultiplier = 1f;
        IsMoving = false;
    }
}