using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;

/// <summary>
/// GAME 1303 Tier 3 — Magnet gloves. Hold to pull metal objects toward you; let go to
/// drop them, or press Attack while holding one to throw it. The Magnet's core mechanic.
///
/// SETUP
///  1. Player: this script + an empty child "HoldPoint" in front of the chest (drag it in).
///  2. Input: add a "Magnet" Button action to the Player map (Right Mouse / gamepad
///     Left Trigger). Listens for OnMagnet. Throw uses the default "Attack" action.
///  3. Metal objects: any object with a Rigidbody2D (Dynamic) on a layer called "Metal".
///     Set Metal Layer here. Crates, enemy shields, enemy weapons, the boss's armor plate.
///  4. A thrown object should hurt enemies: add a Hitbox (Target Enemy, disabled) as a
///     child of the metal object and drag it into MetalObject.throwHitbox, OR simpler —
///     give the metal object a DamageOnContact (Target Tag "Enemy") that this script
///     enables only while it's flying. Use the MetalObject helper script for that.
///  5. Pull Force scales with distance so far objects come slowly and near ones snap in.
///     Max Objects limits how many you drag at once (1 = single-object puzzle feel).
///
/// Ideas from the pitch: pull a masked criminal's metal gun out of his hands (put the
/// gun on the Metal layer as a separate rigidbody), yank Blaster's shield plate off to
/// expose a Hurtbox weak point.
/// </summary>
public class MagnetPull : MonoBehaviour
{
    public LayerMask metalLayer;
    public float radius = 6f;
    [Tooltip("Only pull objects roughly in front of you (degrees each side of facing). 180 = all around.")]
    [Range(10f, 180f)] public float coneAngle = 60f;
    public float pullForce = 30f;
    public int maxObjects = 3;

    [Header("Hold & throw")]
    public Transform holdPoint;
    [Tooltip("Distance at which a pulled object snaps into the hold point.")]
    public float grabDistance = 0.8f;
    public float throwSpeed = 18f;
    public bool canHold = true;

    [Header("Feel")]
    public string pullSfx = "magnet";
    public string throwSfx = "throw";
    public LineRenderer beamPrefab;   // optional: one line per pulled object

    [Header("Events")]
    public UnityEvent OnMagnetOn;
    public UnityEvent OnMagnetOff;
    public UnityEvent<Rigidbody2D> OnGrabbed;
    public UnityEvent<Rigidbody2D> OnThrown;

    public bool IsActive { get; private set; }
    public Rigidbody2D HeldObject { get; private set; }

    private PlayerController2D controller;
    private readonly List<Rigidbody2D> pulling = new List<Rigidbody2D>();
    private readonly List<LineRenderer> beams = new List<LineRenderer>();
    private Vector2 Facing => controller != null ? controller.Facing : (transform.lossyScale.x >= 0 ? Vector2.right : Vector2.left);

    void Awake()
    {
        controller = GetComponent<PlayerController2D>();
        if (holdPoint == null) holdPoint = transform;
    }

    void OnMagnet(InputValue v)
    {
        if (v.isPressed) { if (!IsActive) { IsActive = true; OnMagnetOn?.Invoke(); AudioManager.Instance?.PlaySFX(pullSfx); } }
        else Deactivate();
    }

    void OnAttack(InputValue v)
    {
        if (v.isPressed && HeldObject != null) Throw();
    }

    void Deactivate()
    {
        if (!IsActive) return;
        IsActive = false;
        pulling.Clear();
        ClearBeams();
        // Drop held object gently
        if (HeldObject != null) ReleaseHeld(Vector2.zero);
        OnMagnetOff?.Invoke();
    }

    void FixedUpdate()
    {
        if (!IsActive) { return; }

        // Refresh the set of objects in range and in the cone
        pulling.Clear();
        var hits = Physics2D.OverlapCircleAll(transform.position, radius, metalLayer);
        foreach (var h in hits)
        {
            var rb = h.attachedRigidbody;
            if (rb == null || rb.bodyType != RigidbodyType2D.Dynamic || rb == HeldObject) continue;
            Vector2 to = (Vector2)rb.position - (Vector2)transform.position;
            if (Vector2.Angle(Facing, to) > coneAngle) continue;
            if (!pulling.Contains(rb)) pulling.Add(rb);
            if (pulling.Count >= maxObjects) break;
        }

        // Pull
        foreach (var rb in pulling)
        {
            Vector2 to = (Vector2)holdPoint.position - rb.position;
            float dist = to.magnitude;
            if (canHold && HeldObject == null && dist <= grabDistance) { Grab(rb); continue; }
            // Stronger when closer (inverse-ish), damped so it doesn't orbit
            float strength = pullForce * Mathf.Clamp01(1.5f - dist / radius);
            rb.AddForce(to.normalized * strength);
            rb.linearVelocity *= 0.95f;
        }

        // Keep held object at hold point
        if (HeldObject != null)
        {
            HeldObject.position = holdPoint.position;
            HeldObject.linearVelocity = Vector2.zero;
            HeldObject.angularVelocity = 0f;
        }

        UpdateBeams();
    }

    void Grab(Rigidbody2D rb)
    {
        HeldObject = rb;
        rb.bodyType = RigidbodyType2D.Kinematic;
        rb.linearVelocity = Vector2.zero;
        var mo = rb.GetComponent<MetalObject>();
        if (mo != null) mo.OnGrabbed(this);
        OnGrabbed?.Invoke(rb);
    }

    void Throw()
    {
        var rb = HeldObject;
        ReleaseHeld(Facing * throwSpeed);
        AudioManager.Instance?.PlaySFX(throwSfx);
        var mo = rb.GetComponent<MetalObject>();
        if (mo != null) mo.OnThrown();
        OnThrown?.Invoke(rb);
    }

    void ReleaseHeld(Vector2 velocity)
    {
        if (HeldObject == null) return;
        HeldObject.bodyType = RigidbodyType2D.Dynamic;
        HeldObject.linearVelocity = velocity;
        var mo = HeldObject.GetComponent<MetalObject>();
        if (mo != null && velocity == Vector2.zero) mo.OnDropped();
        HeldObject = null;
    }

    // ----- Beam visuals -----
    void UpdateBeams()
    {
        if (beamPrefab == null) return;
        while (beams.Count < pulling.Count) { var b = Instantiate(beamPrefab, transform); b.positionCount = 2; beams.Add(b); }
        for (int i = 0; i < beams.Count; i++)
        {
            bool on = i < pulling.Count;
            beams[i].enabled = on;
            if (on) { beams[i].SetPosition(0, holdPoint.position); beams[i].SetPosition(1, pulling[i].position); }
        }
    }

    void ClearBeams() { foreach (var b in beams) if (b) b.enabled = false; }

    void OnDrawGizmosSelected()
    {
        Gizmos.color = new Color(1f, 0.5f, 0f, 0.5f);
        Gizmos.DrawWireSphere(transform.position, radius);
        Vector3 f = Application.isPlaying ? (Vector3)Facing : Vector3.right;
        Gizmos.DrawLine(transform.position, transform.position + Quaternion.Euler(0, 0, coneAngle) * f * radius);
        Gizmos.DrawLine(transform.position, transform.position + Quaternion.Euler(0, 0, -coneAngle) * f * radius);
    }
}