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

/// <summary>
/// GAME 1303 Tier 8 — Several gauges drifting toward failure at once; the player runs
/// between them keeping everything in the safe band. Cephalopods in Space! (the instructor
/// demo: "attend to all the controls and watch the display").
///
/// This is the whole game in one script, which makes it a good in-class build: it's a
/// complete loop students can read in five minutes, and every field changes how it feels.
///
/// SETUP
///  1. Empty GameObject "ShipSystems" + this script.
///  2. Add a Gauge per control (Oxygen, Heat, Ink Pressure, Course). Each has a drift rate,
///     a safe band, and how much one adjustment moves it.
///  3. For each gauge, make an Interactable (Tier 6) somewhere in the cockpit and wire its
///     OnInteract -> MultiTaskStation.Adjust with the gauge's index (0, 1, 2...).
///     Eight arms, eight controls — that's the joke and the mechanic.
///  4. OnGaugeChanged(index, value) drives a UI Slider per gauge; colour it red outside
///     the safe band.
///  5. Failure: each gauge outside its band accumulates Danger. When Danger hits 1, the
///     run ends. Difficulty Ramp makes everything drift faster over time, so a run always
///     ends eventually — the score is how long you lasted.
///
/// Tuning tip: start with drift rates so slow it's boring, then speed up until you're
/// slightly panicked. That's the right value.
/// </summary>
public class MultiTaskStation : MonoBehaviour
{
    [System.Serializable]
    public class Gauge
    {
        public string label = "Oxygen";
        [Range(0f, 1f)] public float value = 0.5f;
        [Tooltip("Which way it drifts per second when ignored (negative = falls).")]
        public float driftPerSecond = -0.06f;
        [Range(0f, 1f)] public float safeMin = 0.35f;
        [Range(0f, 1f)] public float safeMax = 0.75f;
        [Tooltip("How much one interaction moves it.")]
        public float adjustAmount = 0.25f;
        [Tooltip("Adjusting pushes it the opposite way to the drift.")]
        public bool adjustOpposesDrift = true;
        public Sprite icon;

        public bool InSafeBand => value >= safeMin && value <= safeMax;
    }

    [Header("Controls")]
    public List<Gauge> gauges = new List<Gauge>();

    [Header("Failure")]
    [Tooltip("Danger gained per second per gauge outside its band.")]
    public float dangerPerSecond = 0.12f;
    [Tooltip("Danger recovered per second when everything is safe.")]
    public float dangerRecovery = 0.08f;

    [Header("Difficulty")]
    [Tooltip("Drift speeds multiply by this much every minute.")]
    public float rampPerMinute = 0.25f;

    [Header("Feedback")]
    public string adjustSfx = "click";
    public string alarmSfx = "alarm";
    public float alarmInterval = 2f;

    [Header("Events")]
    public UnityEvent<int, float> OnGaugeChanged;      // (index, value)
    public UnityEvent<int, bool> OnGaugeSafetyChanged; // (index, isSafe)
    public UnityEvent<float> OnDangerChanged;          // 0-1
    public UnityEvent<float> OnRunEnded;               // seconds survived
    public UnityEvent OnAlarm;

    public float Danger { get; private set; }
    public float SurvivedSeconds { get; private set; }
    public bool IsRunning { get; private set; } = true;

    private float alarmTimer;
    private bool[] wasSafe;

    void Start()
    {
        wasSafe = new bool[gauges.Count];
        for (int i = 0; i < gauges.Count; i++)
        {
            wasSafe[i] = gauges[i].InSafeBand;
            OnGaugeChanged?.Invoke(i, gauges[i].value);
        }
    }

    void Update()
    {
        if (!IsRunning) return;

        SurvivedSeconds += Time.deltaTime;
        float ramp = 1f + rampPerMinute * (SurvivedSeconds / 60f);

        int unsafeCount = 0;

        for (int i = 0; i < gauges.Count; i++)
        {
            var g = gauges[i];
            float before = g.value;
            g.value = Mathf.Clamp01(g.value + g.driftPerSecond * ramp * Time.deltaTime);

            if (!Mathf.Approximately(before, g.value)) OnGaugeChanged?.Invoke(i, g.value);

            bool safe = g.InSafeBand;
            if (safe != wasSafe[i])
            {
                wasSafe[i] = safe;
                OnGaugeSafetyChanged?.Invoke(i, safe);
            }
            if (!safe) unsafeCount++;
        }

        float dangerBefore = Danger;
        Danger = unsafeCount > 0
            ? Mathf.Min(1f, Danger + dangerPerSecond * unsafeCount * Time.deltaTime)
            : Mathf.Max(0f, Danger - dangerRecovery * Time.deltaTime);

        if (!Mathf.Approximately(dangerBefore, Danger)) OnDangerChanged?.Invoke(Danger);

        if (unsafeCount > 0)
        {
            alarmTimer -= Time.deltaTime;
            if (alarmTimer <= 0f)
            {
                alarmTimer = alarmInterval;
                if (!string.IsNullOrEmpty(alarmSfx)) AudioManager.Instance?.PlaySFX(alarmSfx);
                OnAlarm?.Invoke();
            }
        }
        else alarmTimer = 0f;

        if (Danger >= 1f) EndRun();
    }

    /// <summary>Wire a cockpit Interactable's OnInteract here with the gauge's index.</summary>
    public void Adjust(int index)
    {
        if (!IsRunning || index < 0 || index >= gauges.Count) return;

        var g = gauges[index];
        float direction = g.adjustOpposesDrift ? -Mathf.Sign(g.driftPerSecond) : 1f;
        g.value = Mathf.Clamp01(g.value + g.adjustAmount * direction);

        if (!string.IsNullOrEmpty(adjustSfx)) AudioManager.Instance?.PlaySFX(adjustSfx);
        OnGaugeChanged?.Invoke(index, g.value);
    }

    public void EndRun()
    {
        if (!IsRunning) return;
        IsRunning = false;
        OnRunEnded?.Invoke(SurvivedSeconds);
        GameManager.Instance?.GameOver();
    }

    public void Restart()
    {
        Danger = 0f;
        SurvivedSeconds = 0f;
        IsRunning = true;
        for (int i = 0; i < gauges.Count; i++)
        {
            gauges[i].value = (gauges[i].safeMin + gauges[i].safeMax) * 0.5f;
            OnGaugeChanged?.Invoke(i, gauges[i].value);
        }
        OnDangerChanged?.Invoke(0f);
    }
}