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

/// <summary>
/// GAME 1303 Tier 3 — RUN REALLY FAST AND THROW YOURSELF AT PEOPLE. Missile Man's core.
///
/// How it plays: while running, a charge meter fills. Press Attack with enough charge
/// and Missile Man launches himself in his facing direction, invulnerable, with a
/// hitbox on. Damage scales with charge. Hitting a wall ends the launch (and can hurt
/// him a little, if you like).
///
/// SETUP
///  1. Player: PlayerController2D v1.1 + Health + Knockback + this script + a DISABLED
///     child "LaunchHitbox" with Hitbox (Target Enemy, big knockback). Drag it in.
///  2. Listens to "Attack" (default action). If you also use MeleeAttack, set that to
///     Player Controlled OFF — Missile Man doesn't punch anyway.
///  3. Charge Rate: seconds of running at near-full speed to reach full charge.
///     Min Charge To Launch: 0.3 means you can launch weakly after a short run.
///  4. OnChargeChanged(0-1) -> a HUD bar. OnLaunched / OnLaunchEnded for SFX, trails.
///  5. Optional Wall Layer: the launch stops when the front raycast hits it.
///
/// Zap Attack can use this too as a "charged dash-through".
/// </summary>
public class MomentumLauncher : MonoBehaviour
{
    [Header("Charge")]
    public float chargeRate = 1.5f;
    [Tooltip("Fraction of moveSpeed you must be moving at for charge to build.")]
    [Range(0f, 1f)] public float speedFractionToCharge = 0.8f;
    public float chargeDecayRate = 1f;   // per second when not running
    [Range(0f, 1f)] public float minChargeToLaunch = 0.3f;

    [Header("Launch")]
    public float launchSpeedMin = 15f;
    public float launchSpeedMax = 30f;
    public float launchDuration = 0.5f;
    public bool invulnerableWhileLaunching = true;
    [Tooltip("Hitbox damage at full charge (min charge does 1).")]
    public int maxDamage = 5;
    public Hitbox launchHitbox;
    public LayerMask wallLayer;
    public float wallCheckDistance = 0.5f;
    public int wallImpactSelfDamage = 0;

    [Header("Feel")]
    public string animatorTrigger = "Launch";
    public string sfxName = "launch";

    [Header("Events")]
    public UnityEvent<float> OnChargeChanged;
    public UnityEvent OnLaunched;
    public UnityEvent OnLaunchEnded;

    public float Charge { get; private set; }
    public bool IsLaunching { get; private set; }

    private PlayerController2D controller;
    private Rigidbody2D rb;
    private Health health;
    private Animator anim;
    private Collider2D col;

    void Awake()
    {
        controller = GetComponent<PlayerController2D>();
        rb = GetComponent<Rigidbody2D>();
        health = GetComponent<Health>();
        anim = GetComponent<Animator>();
        col = GetComponent<Collider2D>();
        if (launchHitbox != null) launchHitbox.gameObject.SetActive(false);
    }

    void Update()
    {
        if (IsLaunching) return;

        float threshold = controller.moveSpeed * controller.SpeedMultiplier * speedFractionToCharge;
        bool running = controller.IsGrounded && Mathf.Abs(controller.Velocity.x) >= threshold;

        float before = Charge;
        Charge = running
            ? Mathf.Min(1f, Charge + Time.deltaTime / chargeRate)
            : Mathf.Max(0f, Charge - Time.deltaTime * chargeDecayRate);
        if (!Mathf.Approximately(before, Charge)) OnChargeChanged?.Invoke(Charge);
    }

    void OnAttack(InputValue v)
    {
        if (!v.isPressed) return;
        TryLaunch();
    }

    public bool TryLaunch()
    {
        if (IsLaunching || Charge < minChargeToLaunch || !controller.InputEnabled || controller.MovementLocked) return false;
        StartCoroutine(LaunchRoutine());
        return true;
    }

    IEnumerator LaunchRoutine()
    {
        IsLaunching = true;
        float power = Charge;                      // 0.3 .. 1
        Charge = 0f;
        OnChargeChanged?.Invoke(0f);

        float speed = Mathf.Lerp(launchSpeedMin, launchSpeedMax, power);
        Vector2 dir = controller.Facing;

        controller.MovementLocked = true;
        rb.gravityScale = 0f;
        if (invulnerableWhileLaunching && health != null) health.SetInvulnerable(launchDuration);
        if (launchHitbox != null)
        {
            launchHitbox.damage = Mathf.Max(1, Mathf.RoundToInt(maxDamage * power));
            launchHitbox.gameObject.SetActive(true);
        }
        if (anim != null && !string.IsNullOrEmpty(animatorTrigger)) anim.SetTrigger(animatorTrigger);
        if (!string.IsNullOrEmpty(sfxName)) AudioManager.Instance?.PlaySFX(sfxName);
        OnLaunched?.Invoke();

        float t = 0f;
        while (t < launchDuration)
        {
            rb.linearVelocity = dir * speed;
            t += Time.fixedDeltaTime;

            // Wall in the way?
            float half = col != null ? col.bounds.extents.x : 0.5f;
            if (Physics2D.Raycast(rb.position, dir, half + wallCheckDistance, wallLayer))
            {
                if (wallImpactSelfDamage > 0 && health != null)
                {
                    health.ClearInvulnerability();
                    health.TakeDamage(wallImpactSelfDamage);
                }
                break;
            }
            yield return new WaitForFixedUpdate();
        }

        if (launchHitbox != null) launchHitbox.gameObject.SetActive(false);
        rb.linearVelocity = new Vector2(rb.linearVelocity.x * 0.3f, rb.linearVelocity.y);
        rb.gravityScale = controller.BaseGravity;
        controller.MovementLocked = false;
        IsLaunching = false;
        OnLaunchEnded?.Invoke();
    }
}