using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// GAME 1303 Tier 6 — A crate you can shove around to reach a ledge or hold a pressure
/// plate. Anabasis, Apep's Trial.
///
/// SETUP
///  1. Crate: Sprite + Rigidbody2D + BoxCollider2D (solid) + this script.
///  2. Physics Material: make a 2D Physics Material with Friction 0.4 and drop it on the
///     collider, or the crate slides forever.
///  3. Mode:
///       Physics  – the player just walks into it and it slides (Rigidbody Dynamic, high
///                  Linear Damping, high Mass). Simple and forgiving.
///       Grid     – snaps one cell per push (Rigidbody Kinematic). Sokoban-precise; set
///                  Cell Size and Blocking Layers.
///  4. Only Push Horizontally stops the player shoving crates off ledges by jumping on them.
///  5. Reset On Respawn: the crate returns to its start position when the player dies,
///     so a puzzle can't be made unsolvable. Wire RespawnManager.OnRespawned ->
///     PushBlock.ResetPosition. (Strongly recommended — students forget this and then
///     wonder why testers get stuck.)
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class PushBlock : MonoBehaviour
{
    public enum Mode { Physics, Grid }

    public Mode mode = Mode.Physics;
    public bool onlyPushHorizontally = true;
    public string pushSfx = "push";

    [Header("Physics mode")]
    [Tooltip("How hard the player has to push. Higher mass = heavier feel.")]
    public float mass = 4f;
    public float linearDamping = 5f;

    [Header("Grid mode")]
    public float cellSize = 1f;
    public float stepTime = 0.15f;
    public LayerMask blockingLayers;

    [Header("Events")]
    public UnityEvent OnPushed;
    public UnityEvent OnLanded;

    private Rigidbody2D rb;
    private Vector3 startPosition;
    private bool stepping;
    private Vector3 stepFrom, stepTo;
    private float stepProgress;

    void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        startPosition = transform.position;

        if (mode == Mode.Physics)
        {
            rb.bodyType = RigidbodyType2D.Dynamic;
            rb.mass = mass;
            rb.linearDamping = linearDamping;
            rb.freezeRotation = true;
        }
        else
        {
            rb.bodyType = RigidbodyType2D.Kinematic;
            transform.position = Snap(transform.position);
        }
    }

    void Update()
    {
        if (mode != Mode.Grid || !stepping) return;

        stepProgress += Time.deltaTime / Mathf.Max(0.01f, stepTime);
        if (stepProgress >= 1f)
        {
            transform.position = stepTo;
            stepping = false;
            OnLanded?.Invoke();
        }
        else transform.position = Vector3.Lerp(stepFrom, stepTo, stepProgress);
    }

    void OnCollisionStay2D(Collision2D col)
    {
        if (mode != Mode.Grid || stepping) return;
        if (!col.collider.CompareTag("Player")) return;

        // Which way is the player pushing? Use the contact normal.
        Vector2 n = col.GetContact(0).normal;
        Vector2Int dir = Mathf.Abs(n.x) > Mathf.Abs(n.y)
            ? new Vector2Int(n.x > 0 ? -1 : 1, 0)
            : new Vector2Int(0, n.y > 0 ? -1 : 1);

        if (onlyPushHorizontally && dir.y != 0) return;
        TryStep(dir);
    }

    public bool TryStep(Vector2Int dir)
    {
        if (stepping) return false;

        Vector3 from = Snap(transform.position);
        Vector3 to = from + new Vector3(dir.x, dir.y, 0f) * cellSize;
        if (Physics2D.OverlapBox(to, Vector2.one * cellSize * 0.8f, 0f, blockingLayers) != null) return false;

        stepFrom = from;
        stepTo = to;
        stepProgress = 0f;
        stepping = true;
        if (!string.IsNullOrEmpty(pushSfx)) AudioManager.Instance?.PlaySFX(pushSfx);
        OnPushed?.Invoke();
        return true;
    }

    /// <summary>Wire RespawnManager.OnRespawned here so puzzles can't be softlocked.</summary>
    public void ResetPosition()
    {
        transform.position = startPosition;
        if (rb != null) { rb.linearVelocity = Vector2.zero; rb.angularVelocity = 0f; }
        stepping = false;
    }

    Vector3 Snap(Vector3 p) => new Vector3(
        Mathf.Round(p.x / cellSize) * cellSize,
        Mathf.Round(p.y / cellSize) * cellSize,
        p.z);

    void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.green;
        Gizmos.DrawWireCube(Application.isPlaying ? startPosition : transform.position, Vector3.one * 0.9f);
    }
}