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,
	Containment,
}

/// <summary>
/// Singularity powers — bought with Shards, survive every Meltdown AND every
/// Singularity. This is the layer above Flux perks: where perks multiply the
/// numbers, powers change the shape of a cycle.
/// </summary>
public enum IdlePowerId
{
	Overclock,
	Cascade,
	TierSkip,
	TwinReactor,
	ChainMemory,
	FluxCatalyst,
}

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; }

	/// <summary>Has no real ceiling — the level readout drops the "/ max" half.</summary>
	public bool Uncapped { get; set; }

	/// <summary>Only offered once the reactor has reached this lifetime Flux.</summary>
	public int RevealAtFluxEarned { get; set; }

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

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

public class IdlePower
{
	public IdlePowerId 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" : "—",
		},

		// The sink. Every other perk has a ceiling, so without this one a reactor
		// that has bought the tree out has nowhere to put Flux and every further
		// Meltdown stops meaning anything. Deliberately poor value per Flux early
		// — it is what you buy when there is nothing better left, not instead of
		// the tree.
		new IdlePerk {
			Id = IdlePerkId.Containment, Name = "Containment Field", Icon = "♾️",
			Desc = "No ceiling. Every level is more income, forever — the sink for Flux you have nothing else to spend on.",
			BaseCost = 15, CostStep = 6, MaxLevel = 9999, Uncapped = true,
			RevealAtFluxEarned = 120,
			Effect = lvl => $"×{1f + 0.02f * lvl:F2} income",
		},
	};

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

	// ── Singularity powers — bought with Shards, survive everything ─────────
	public static readonly List<IdlePower> Powers = new()
	{
		new IdlePower {
			Id = IdlePowerId.Overclock, Name = "Overclock", Icon = "⚡",
			Desc = "Meltdown triggers at a far lower cycle. The whole prestige loop shortens.",
			BaseCost = 1, CostStep = 1, MaxLevel = 8,
			Effect = lvl => $"Meltdown at {IdleFormat.Short( IdleBalance.MeltdownRequirementFor( lvl ) )} Cores",
		},
		new IdlePower {
			Id = IdlePowerId.Cascade, Name = "Resonance Cascade", Icon = "🌀",
			Desc = "A resolved volley can immediately fire again on the wreckage it just made.",
			BaseCost = 2, CostStep = 2, MaxLevel = 10,
			Effect = lvl => $"{IdleFormat.Pct( IdleBalance.CascadeChance( lvl ) )} re-detonation",
		},
		new IdlePower {
			Id = IdlePowerId.TierSkip, Name = "Tier Memory", Icon = "🔓",
			Desc = "The reactor remembers exotic chests across a Meltdown — they are on the board from the first second of a cycle.",
			BaseCost = 3, CostStep = 3, MaxLevel = 4,
			Effect = lvl => lvl <= 0 ? "—" : $"{lvl} tier{(lvl == 1 ? "" : "s")} pre-unlocked",
		},
		new IdlePower {
			Id = IdlePowerId.TwinReactor, Name = "Twin Reactor", Icon = "🛰️",
			Desc = "Extra drone volleys per detonation cycle, each one landing on what the last left standing.",
			BaseCost = 4, CostStep = 5, MaxLevel = 3,
			Effect = lvl => lvl <= 0 ? "1 volley" : $"{1 + lvl} volleys per cycle",
		},
		new IdlePower {
			Id = IdlePowerId.ChainMemory, Name = "Chain Memory", Icon = "♾️",
			Desc = "Permanently steepens the chain curve — on top of Chain Theory and Resonance.",
			BaseCost = 3, CostStep = 2, MaxLevel = 8,
			Effect = lvl => $"+{0.01f * lvl:F2} chain exponent",
		},
		new IdlePower {
			Id = IdlePowerId.FluxCatalyst, Name = "Flux Catalyst", Icon = "🔷",
			Desc = "Every Meltdown pays out more Flux, so rebuilding the perk tree after a Singularity is quick.",
			BaseCost = 2, CostStep = 2, MaxLevel = 6,
			Effect = lvl => $"×{1f + 0.25f * lvl:F2} Meltdown Flux",
		},
	};

	public static IdlePower GetPower( IdlePowerId id ) => Powers.FirstOrDefault( p => p.Id == id );
}