Game/Idle/IdleUpgradeCatalog.cs
using System;
using System.Collections.Generic;
using System.Linq;

public enum IdleUpgradeCategory { Reactor, Chains, Systems }

public enum IdleUpgradeId
{
	// Reactor — how much material the board produces
	SpawnRate,
	SpawnCount,
	ChestValue,
	GemChance,
	BombChestChance,
	GridSize,

	// Chains — how hard the drones hit
	DroneCount,
	CycleSpeed,
	BlastSize,
	ChainPower,
	CritChance,
	CritPower,

	// Systems — offline, manual play, meta payout
	OfflineEfficiency,
	OfflineCap,
	OverloadCooldown,
	ChargeGain,
	GoldenChestRate,
	FragmentYield,
}

public enum IdlePerkId
{
	CoreEfficiency,
	Kickstart,
	DeepStorage,
	NightShift,
	FragmentCondenser,
	ChainTheory,
	AutoOverload,
	Momentum,
	PetLink,
}

public class IdleUpgrade
{
	public IdleUpgradeId       Id       { get; set; }
	public IdleUpgradeCategory Category { get; set; }
	public string Name  { get; set; }
	public string Icon  { get; set; }
	public string Desc  { get; set; }

	public double BaseCost   { get; set; }
	public double CostGrowth { get; set; }
	public int    MaxLevel   { get; set; }

	/// <summary>Cores needed to buy the level after <paramref name="level"/>.</summary>
	public double CostAt( int level ) => BaseCost * Math.Pow( CostGrowth, level );

	/// <summary>Human readable effect at a given level, e.g. "1.8 /s".</summary>
	public Func<int, string> Effect { get; set; }

	/// <summary>Cores this upgrade only becomes visible at — keeps the early list short.</summary>
	public double RevealAt { get; set; } = 0;
}

public class IdlePerk
{
	public IdlePerkId Id   { get; set; }
	public string Name     { get; set; }
	public string Icon     { get; set; }
	public string Desc     { get; set; }
	public int    BaseCost { get; set; }
	public int    CostStep { get; set; }
	public int    MaxLevel { get; set; }

	public int CostAt( int level ) => BaseCost + CostStep * level;

	public Func<int, string> Effect { get; set; }
}

public static class IdleUpgradeCatalog
{
	// ── Core upgrades — bought with Cores, wiped on Meltdown ───────────────
	public static readonly List<IdleUpgrade> All = new()
	{
		// ───────────── Reactor ─────────────
		new IdleUpgrade {
			Id = IdleUpgradeId.ChestValue, Category = IdleUpgradeCategory.Reactor,
			Name = "Dense Loot", Icon = "🎁",
			Desc = "Every chest is worth more Cores.",
			BaseCost = 30, CostGrowth = 1.175, MaxLevel = 300,
			Effect = lvl => $"×{IdleBalance.ChestValueMult( lvl ):F2} value",
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.SpawnRate, Category = IdleUpgradeCategory.Reactor,
			Name = "Feed Line", Icon = "⚙️",
			Desc = "Ambient chest drip between detonations.",
			BaseCost = 120, CostGrowth = 1.60, MaxLevel = 40,
			Effect = lvl => $"every {IdleBalance.SpawnInterval( lvl ):F2}s",
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.SpawnCount, Category = IdleUpgradeCategory.Reactor,
			Name = "Bulk Hopper", Icon = "📦",
			Desc = "Chests dumped onto the board right before every detonation. Chain length lives here.",
			BaseCost = 1000, CostGrowth = 4.00, MaxLevel = 12,
			Effect = lvl => $"{IdleBalance.SpawnCount( lvl )} per cycle",
			RevealAt = 500,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.GemChance, Category = IdleUpgradeCategory.Reactor,
			Name = "Gem Seeding", Icon = "💎",
			Desc = "Gem chests are worth 4× a normal chest.",
			BaseCost = 2400, CostGrowth = 1.70, MaxLevel = 30,
			Effect = lvl => $"{IdleFormat.Pct( IdleBalance.GemChance( lvl ) )} gems",
			RevealAt = 500,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.BombChestChance, Category = IdleUpgradeCategory.Reactor,
			Name = "Volatile Cargo", Icon = "💣",
			Desc = "Bomb chests detonate on death and extend chains.",
			BaseCost = 6000, CostGrowth = 1.75, MaxLevel = 30,
			Effect = lvl => $"{IdleFormat.Pct( IdleBalance.BombChestChance( lvl ) )} bomb chests",
			RevealAt = 1600,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.GridSize, Category = IdleUpgradeCategory.Reactor,
			Name = "Reactor Frame", Icon = "🔲",
			Desc = "Expands the reactor board. Everything you own scales with it.",
			BaseCost = 500000, CostGrowth = 120, MaxLevel = 4,
			Effect = lvl => $"{IdleBalance.GridSize( lvl )}×{IdleBalance.GridSize( lvl )} board",
			RevealAt = 40000,
		},

		// ───────────── Chains ─────────────
		new IdleUpgrade {
			Id = IdleUpgradeId.CycleSpeed, Category = IdleUpgradeCategory.Chains,
			Name = "Fuse Timing", Icon = "⏱️",
			Desc = "Drones detonate more often.",
			BaseCost = 240, CostGrowth = 1.60, MaxLevel = 40,
			Effect = lvl => $"every {IdleBalance.CycleInterval( lvl ):F2}s",
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.DroneCount, Category = IdleUpgradeCategory.Chains,
			Name = "Drone Bay", Icon = "🛰️",
			Desc = "More bombs placed per detonation cycle — widens how much of the board you cover.",
			BaseCost = 3000, CostGrowth = 8.0, MaxLevel = 9,
			Effect = lvl => $"{IdleBalance.DroneCount( lvl )} drones",
			RevealAt = 600,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.ChainPower, Category = IdleUpgradeCategory.Chains,
			Name = "Chain Theory", Icon = "⛓️",
			Desc = "Steepens the chain multiplier. This is where the real numbers come from.",
			BaseCost = 10000, CostGrowth = 3.20, MaxLevel = 25,
			Effect = lvl => $"exponent {IdleBalance.ChainExponent( lvl, 0 ):F2}",
			RevealAt = 3000,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.BlastSize, Category = IdleUpgradeCategory.Chains,
			Name = "Warhead Class", Icon = "💥",
			Desc = "Upgrades the drone payload pattern.",
			BaseCost = 50000, CostGrowth = 40, MaxLevel = 4,
			Effect = lvl => IdleBalance.BlastName( lvl ),
			RevealAt = 8000,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.CritChance, Category = IdleUpgradeCategory.Chains,
			Name = "Overpressure", Icon = "🎲",
			Desc = "Chance for a detonation to crit.",
			BaseCost = 5000, CostGrowth = 1.75, MaxLevel = 30,
			Effect = lvl => $"{IdleFormat.Pct( IdleBalance.CritChance( lvl ) )} crit",
			RevealAt = 1200,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.CritPower, Category = IdleUpgradeCategory.Chains,
			Name = "Critical Mass", Icon = "☢️",
			Desc = "Crits pay out far harder.",
			BaseCost = 10000, CostGrowth = 1.85, MaxLevel = 30,
			Effect = lvl => $"×{IdleBalance.CritPower( lvl ):F0} on crit",
			RevealAt = 2400,
		},

		// ───────────── Systems ─────────────
		new IdleUpgrade {
			Id = IdleUpgradeId.OverloadCooldown, Category = IdleUpgradeCategory.Systems,
			Name = "Manual Override", Icon = "🔴",
			Desc = "Shorter cooldown on the OVERLOAD button.",
			BaseCost = 1000, CostGrowth = 1.70, MaxLevel = 20,
			Effect = lvl => $"{IdleBalance.OverloadCooldown( lvl ):F1}s cooldown",
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.ChargeGain, Category = IdleUpgradeCategory.Systems,
			Name = "Capacitor", Icon = "🔋",
			Desc = "Overloads fill the Surge meter faster.",
			BaseCost = 4000, CostGrowth = 1.90, MaxLevel = 20,
			Effect = lvl => $"+{IdleFormat.Pct( IdleBalance.ChargeGain( lvl, 0 ) )} per overload",
			RevealAt = 1200,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.OfflineEfficiency, Category = IdleUpgradeCategory.Systems,
			Name = "Night Crew", Icon = "🌙",
			Desc = "Share of your output the reactor keeps producing while you are on another screen.",
			BaseCost = 10000, CostGrowth = 2.60, MaxLevel = 14,
			Effect = lvl => $"{IdleFormat.Pct( IdleBalance.OfflineEfficiency( lvl, 0 ) )} while unattended",
			RevealAt = 3000,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.OfflineCap, Category = IdleUpgradeCategory.Systems,
			Name = "Core Storage", Icon = "🛢️",
			Desc = "How many hours of unattended production the tanks can hold.",
			BaseCost = 16000, CostGrowth = 3.00, MaxLevel = 10,
			Effect = lvl => $"{IdleBalance.OfflineCapHours( lvl, 0 ):F0}h stored",
			RevealAt = 4000,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.GoldenChestRate, Category = IdleUpgradeCategory.Systems,
			Name = "Lucky Manifest", Icon = "🌟",
			Desc = "Golden Chests appear more often. Click them before they leave.",
			BaseCost = 12000, CostGrowth = 2.30, MaxLevel = 15,
			Effect = lvl => $"every ~{IdleBalance.GoldenInterval( lvl ):F0}s",
			RevealAt = 3000,
		},
		new IdleUpgrade {
			Id = IdleUpgradeId.FragmentYield, Category = IdleUpgradeCategory.Systems,
			Name = "Condenser Coil", Icon = "💠",
			Desc = "Cheapens every Fragment condensed today — worth several extra Fragments per level.",
			BaseCost = 50000, CostGrowth = 3.20, MaxLevel = 12,
			Effect = lvl => $"first 💠 at {IdleFormat.Short( IdleBalance.FragmentCost( lvl, 0, 0 ) )}",
			RevealAt = 8000,
		},
	};

	public static IdleUpgrade Get( IdleUpgradeId id ) => All.FirstOrDefault( u => u.Id == id );

	public static IEnumerable<IdleUpgrade> InCategory( IdleUpgradeCategory cat )
		=> All.Where( u => u.Category == cat );

	// ── Flux perks — permanent, survive every Meltdown ─────────────────────
	public static readonly List<IdlePerk> Perks = new()
	{
		new IdlePerk {
			Id = IdlePerkId.CoreEfficiency, Name = "Core Efficiency", Icon = "⚛️",
			Desc = "All Core income multiplied.",
			BaseCost = 3, CostStep = 3, MaxLevel = 12,
			Effect = lvl => $"×{1f + 0.25f * lvl:F2} income",
		},
		new IdlePerk {
			Id = IdlePerkId.ChainTheory, Name = "Resonance", Icon = "⛓️",
			Desc = "Permanently steepens the chain multiplier curve.",
			BaseCost = 10, CostStep = 8, MaxLevel = 10,
			Effect = lvl => $"+{0.02f * lvl:F2} chain exponent",
		},
		new IdlePerk {
			Id = IdlePerkId.Kickstart, Name = "Kickstart", Icon = "🚀",
			Desc = "Start every Meltdown with upgrade levels already bought.",
			BaseCost = 8, CostStep = 7, MaxLevel = 6,
			Effect = lvl => $"{lvl * 6} Dense Loot · {lvl * 4} Feed Line",
		},
		new IdlePerk {
			Id = IdlePerkId.NightShift, Name = "Night Shift", Icon = "🌙",
			Desc = "Extra unattended efficiency on top of Night Crew.",
			BaseCost = 6, CostStep = 5, MaxLevel = 8,
			Effect = lvl => $"+{IdleFormat.Pct( 0.08f * lvl )} unattended",
		},
		new IdlePerk {
			Id = IdlePerkId.DeepStorage, Name = "Deep Storage", Icon = "🛢️",
			Desc = "Extra hours of unattended storage.",
			BaseCost = 5, CostStep = 4, MaxLevel = 8,
			Effect = lvl => $"+{lvl * 2}h cap",
		},
		new IdlePerk {
			Id = IdlePerkId.Momentum, Name = "Momentum", Icon = "🔥",
			Desc = "Surge lasts longer and the meter fills faster.",
			BaseCost = 12, CostStep = 9, MaxLevel = 6,
			Effect = lvl => $"+{lvl * 4}s surge · +{IdleFormat.Pct( 0.15f * lvl )} charge",
		},
		new IdlePerk {
			Id = IdlePerkId.FragmentCondenser, Name = "Condenser Array", Icon = "💠",
			Desc = "Raises the daily Fragment ceiling and cheapens condensing.",
			BaseCost = 10, CostStep = 10, MaxLevel = 6,
			Effect = lvl => $"+{lvl * 60} 💠/day",
		},
		new IdlePerk {
			Id = IdlePerkId.AutoOverload, Name = "Autopilot", Icon = "🤖",
			Desc = "The reactor fires OVERLOAD by itself. True idle.",
			BaseCost = 25, CostStep = 25, MaxLevel = 3,
			Effect = lvl => lvl <= 0 ? "off" : $"auto every {IdleBalance.AutoOverloadInterval( lvl ):F0}s",
		},
		new IdlePerk {
			Id = IdlePerkId.PetLink, Name = "Pet Link", Icon = "🐾",
			Desc = "Your pet works the night shift too — doubles its reactor bonus.",
			BaseCost = 40, CostStep = 0, MaxLevel = 1,
			Effect = lvl => lvl > 0 ? "pet bonus ×2" : "—",
		},
	};

	public static IdlePerk GetPerk( IdlePerkId id ) => Perks.FirstOrDefault( p => p.Id == id );
}