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

/// <summary>
/// GAME 1303 Tier 7 — The money. Cash on hand, a day counter, ordering stock, and the
/// running totals. Store Simulator.
///
/// SETUP
///  1. One empty GameObject "StoreEconomy" with this script.
///  2. Starting Cash. Ordering a box costs the ProductType's Wholesale Price × Units Per
///     Box; a sale earns the Retail Price. That gap is the whole game.
///  3. Day cycle: Day Length Seconds, then EndDay() runs automatically (or call it from a
///     "close up" button). Rent is deducted each night — that's the pressure that stops
///     the game being a sandbox.
///  4. Wire OnCashChanged to a HUD text, OnDayEnded to a summary panel showing the day's
///     sales, and OnWentBroke to a game-over.
///  5. Delivery: OrderBox spawns the product's box prefab at the Delivery Point after a
///     short delay, so the player has to go fetch it.
///
/// A note for Sanad: the reference games all work because money is TIGHT at the start.
/// Set Starting Cash low enough that the first delivery is a real decision.
/// </summary>
public class StoreEconomy : MonoBehaviour
{
    public static StoreEconomy Instance { get; private set; }

    [Header("Money")]
    public float startingCash = 200f;
    public float dailyRent = 50f;

    [Header("Day cycle")]
    public bool useDayCycle = true;
    public float dayLengthSeconds = 300f;

    [Header("Delivery")]
    public Transform deliveryPoint;
    public float deliveryDelay = 5f;

    [Header("Events")]
    public UnityEvent<float> OnCashChanged;
    public UnityEvent<int> OnDayChanged;
    public UnityEvent<float> OnDayTimeChanged;      // 0-1 through the day
    public UnityEvent<float, float> OnDayEnded;     // (revenue, expenses) for the day
    public UnityEvent OnWentBroke;
    public UnityEvent<ProductType> OnDelivered;

    public float Cash { get; private set; }
    public int Day { get; private set; } = 1;
    public float DayRevenue { get; private set; }
    public float DayExpenses { get; private set; }
    public float TotalRevenue { get; private set; }

    private float dayTimer;

    void Awake()
    {
        if (Instance != null && Instance != this) { Destroy(gameObject); return; }
        Instance = this;
        Cash = startingCash;
    }

    void Start()
    {
        dayTimer = dayLengthSeconds;
        OnCashChanged?.Invoke(Cash);
        OnDayChanged?.Invoke(Day);
    }

    void Update()
    {
        if (!useDayCycle || dayLengthSeconds <= 0f) return;
        dayTimer -= Time.deltaTime;
        OnDayTimeChanged?.Invoke(1f - Mathf.Clamp01(dayTimer / dayLengthSeconds));
        if (dayTimer <= 0f) EndDay();
    }

    // ---- Money ----
    public void Earn(float amount)
    {
        Cash += amount;
        DayRevenue += amount;
        TotalRevenue += amount;
        OnCashChanged?.Invoke(Cash);
    }

    public bool Spend(float amount)
    {
        if (amount > Cash) return false;
        Cash -= amount;
        DayExpenses += amount;
        OnCashChanged?.Invoke(Cash);
        return true;
    }

    // ---- Ordering stock ----
    public float BoxCost(ProductType product) =>
        product == null ? 0f : product.wholesalePrice * product.unitsPerBox;

    /// <summary>Buy a box. Wire a shop UI button here.</summary>
    public bool OrderBox(ProductType product)
    {
        if (product == null || product.worldPrefab == null) return false;
        if (!Spend(BoxCost(product))) return false;
        StartCoroutine(DeliverAfterDelay(product));
        return true;
    }

    System.Collections.IEnumerator DeliverAfterDelay(ProductType product)
    {
        yield return new WaitForSeconds(deliveryDelay);

        Vector3 where = deliveryPoint != null ? deliveryPoint.position : transform.position;
        var box = Instantiate(product.worldPrefab, where + Vector3.up * 0.5f, Quaternion.identity);

        // Make sure the delivered object is actually carryable and holds the stock
        var carryable = box.GetComponent<Carryable>();
        if (carryable == null) carryable = box.AddComponent<Carryable>();
        carryable.product = product;
        carryable.units = product.unitsPerBox;

        OnDelivered?.Invoke(product);
    }

    // ---- Days ----
    public void EndDay()
    {
        Cash -= dailyRent;
        DayExpenses += dailyRent;
        OnCashChanged?.Invoke(Cash);
        OnDayEnded?.Invoke(DayRevenue, DayExpenses);

        if (Cash < 0f) { OnWentBroke?.Invoke(); return; }

        Day++;
        DayRevenue = 0f;
        DayExpenses = 0f;
        dayTimer = dayLengthSeconds;
        OnDayChanged?.Invoke(Day);
    }
}