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

/// <summary>
/// GAME 1303 Tier 3 — Grind along a power line. Zap Attack. (Also: zip lines, rails.)
///
/// SETUP — the rail
///  1. Empty GameObject "PowerLine" with an EdgeCollider2D (Is Trigger ON). Edit its points
///     to draw the wire. Put it on a layer "Rail". Add a LineRenderer or sprite to show it.
///  2. Add the Rail script (Rail.cs) so RailRide can find it.
///
/// SETUP — the player
///  3. Add RailRide to the player (PlayerController2D v1.1). Set Rail Layer.
///  4. Player must be tagged Player and have a collider that can overlap the wire's trigger.
///  5. Attach: automatic when you touch the line while falling onto it (Attach On Touch),
///     or hold Down to grab / Up to skip it. Jump to leave. The ride ends at either end.
///  6. Ride Speed: fixed, in the direction you were moving (or facing if standing still).
///     Momentum Carry: keep the speed when you jump off.
///  7. Animator: bool "IsRiding" if present.
/// </summary>
public class RailRide : MonoBehaviour
{
    public LayerMask railLayer;
    public float rideSpeed = 10f;
    public bool attachOnTouch = true;
    [Tooltip("Vertical offset so the sprite sits ON the wire rather than through it.")]
    public float rideHeightOffset = 0.5f;
    public float jumpOffForce = 14f;
    [Range(0f, 1f)] public float momentumCarry = 1f;
    public float reattachCooldown = 0.3f;

    [Header("Feel")]
    public string sfxAttach = "rail_on";
    public UnityEvent OnAttached;
    public UnityEvent OnDetached;

    public bool IsRiding { get; private set; }

    private PlayerController2D controller;
    private Rigidbody2D rb;
    private Animator anim;
    private Health health;
    private Vector2[] pts;       // world-space rail points
    private int segment;         // current segment index (pts[segment] -> pts[segment+1])
    private float t;             // 0..1 along the segment
    private int dir;             // +1 toward higher index, -1 toward lower
    private float cooldown;
    private static readonly int RideHash = Animator.StringToHash("IsRiding");
    private bool hasParam;

    void Awake()
    {
        controller = GetComponent<PlayerController2D>();
        rb = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
        health = GetComponent<Health>();
        if (anim != null) foreach (var p in anim.parameters) if (p.nameHash == RideHash) hasParam = true;
    }

    void Update() { cooldown -= Time.deltaTime; }

    void OnTriggerEnter2D(Collider2D other) => TryAttach(other);
    void OnTriggerStay2D(Collider2D other)  { if (!IsRiding) TryAttach(other); }

    void TryAttach(Collider2D other)
    {
        if (IsRiding || cooldown > 0f) return;
        if ((railLayer.value & (1 << other.gameObject.layer)) == 0) return;
        var r = other.GetComponent<Rail>();
        if (r == null) return;

        if (controller.MoveVector.y > 0.5f) return;                              // holding Up = skip the rail
        if (!attachOnTouch && controller.MoveVector.y > -0.5f) return;            // must hold Down to grab
        if (controller.IsGrounded) return;                                        // must be airborne
        Attach(r);
    }

    void Attach(Rail r)
    {
        pts = r.WorldPoints;
        if (pts.Length < 2) return;

        // Nearest segment
        float best = float.MaxValue;
        for (int i = 0; i < pts.Length - 1; i++)
        {
            float tt; float d = DistToSegment(rb.position, pts[i], pts[i + 1], out tt);
            if (d < best) { best = d; segment = i; t = tt; }
        }
        // Direction from current velocity or facing
        float vx = rb.linearVelocity.x;
        float facingX = controller.FacingRight ? 1f : -1f;
        float sign = Mathf.Abs(vx) > 0.5f ? Mathf.Sign(vx) : facingX;
        Vector2 seg = pts[segment + 1] - pts[segment];
        dir = Mathf.Sign(seg.x) == sign ? 1 : -1;

        IsRiding = true;
        controller.MovementLocked = true;
        rb.gravityScale = 0f;
        rb.linearVelocity = Vector2.zero;
        if (hasParam) anim.SetBool(RideHash, true);
        if (!string.IsNullOrEmpty(sfxAttach)) AudioManager.Instance?.PlaySFX(sfxAttach);
        OnAttached?.Invoke();
    }

    void FixedUpdate()
    {
        if (!IsRiding) return;
        if (health != null && health.IsDead) { Detach(Vector2.zero); return; }

        float remaining = rideSpeed * Time.fixedDeltaTime;
        while (remaining > 0f)
        {
            Vector2 a = pts[segment], b = pts[segment + 1];
            float segLen = Vector2.Distance(a, b);
            float posAlong = t * segLen;
            float target = posAlong + dir * remaining;

            if (target >= 0f && target <= segLen) { t = target / segLen; remaining = 0f; }
            else if (dir > 0)
            {
                remaining -= (segLen - posAlong);
                if (segment + 1 >= pts.Length - 1) { EndOfRail(); return; }
                segment++; t = 0f;
            }
            else
            {
                remaining -= posAlong;
                if (segment <= 0) { EndOfRail(); return; }
                segment--; t = 1f;
            }
        }

        Vector2 p = Vector2.Lerp(pts[segment], pts[segment + 1], t) + Vector2.up * rideHeightOffset;
        rb.MovePosition(p);
        controller.FaceDirection(dir * Mathf.Sign((pts[segment + 1] - pts[segment]).x));
    }

    void OnJump(InputValue v)
    {
        if (!v.isPressed || !IsRiding) return;
        Vector2 along = (pts[segment + 1] - pts[segment]).normalized * dir * rideSpeed * momentumCarry;
        Detach(new Vector2(along.x, jumpOffForce));
        controller.ClearJumpBuffer();
        controller.OnJumped?.Invoke();
    }

    void EndOfRail()
    {
        Vector2 along = (pts[segment + 1] - pts[segment]).normalized * dir * rideSpeed * momentumCarry;
        Detach(along);
    }

    void Detach(Vector2 exitVelocity)
    {
        IsRiding = false;
        controller.MovementLocked = false;
        rb.gravityScale = controller.BaseGravity;
        rb.linearVelocity = exitVelocity;
        cooldown = reattachCooldown;
        if (hasParam) anim.SetBool(RideHash, false);
        OnDetached?.Invoke();
    }

    static float DistToSegment(Vector2 p, Vector2 a, Vector2 b, out float t)
    {
        Vector2 ab = b - a;
        float len2 = ab.sqrMagnitude;
        t = len2 < 0.0001f ? 0f : Mathf.Clamp01(Vector2.Dot(p - a, ab) / len2);
        return Vector2.Distance(p, a + ab * t);
    }
}