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

/// <summary>
/// Persistent state for Reactor (Idle) mode.
/// Serialized as a nested object inside PlayerSave — see PlayerSave.Idle.
/// Everything here survives a Meltdown except what ResetForMeltdown() clears.
/// </summary>
public class IdleSave
{
	// ── Unlock / onboarding ───────────────────────────────────────
	public bool Unlocked      { get; set; } = false;  // granted after the first finished run
	public bool IntroShown    { get; set; } = false;  // first-entry explainer
	public bool MeltdownSeen  { get; set; } = false;  // has the prestige panel ever been opened

	// ── Currencies ────────────────────────────────────────────────
	public double Cores         { get; set; } = 0;   // spendable, wiped by Meltdown
	public double CycleCores    { get; set; } = 0;   // earned since last Meltdown — drives Flux payout
	public double AllTimeCores  { get; set; } = 0;   // never resets
	public int    Flux          { get; set; } = 0;   // unspent prestige currency
	public int    FluxEarned    { get; set; } = 0;   // lifetime, for milestones
	public int    Meltdowns     { get; set; } = 0;

	// ── Upgrade levels ────────────────────────────────────────────
	// Keys are IdleUpgradeId.ToString(); missing key == level 0.
	public Dictionary<string, int> Upgrades { get; set; } = new();
	public Dictionary<string, int> Perks    { get; set; } = new();

	// ── Offline bookkeeping ───────────────────────────────────────
	public long   LastSeenUnix   { get; set; } = 0;
	public double MeasuredCps    { get; set; } = 0;   // smoothed cores/sec, used for offline payout
	public double BestCps        { get; set; } = 0;

	// Pending offline haul, staged so the welcome-back panel can show it
	public double PendingOfflineCores   { get; set; } = 0;
	public double PendingOfflineSeconds { get; set; } = 0;
	public int    PendingOfflineFrags   { get; set; } = 0;

	// ── Daily loop ────────────────────────────────────────────────
	public string LastDailyDate { get; set; } = "";
	public int    DailyStreak   { get; set; } = 0;
	public bool   DailyClaimed  { get; set; } = false;

	// ── Fragment drip (own bucket — does not touch the pet farm cap) ──
	public int    TodayReactorFragments { get; set; } = 0;
	public string LastFragDate          { get; set; } = "";
	public double FragProgress          { get; set; } = 0; // cores banked toward the next fragment

	// ── Records / milestones ──────────────────────────────────────
	public int    TotalDetonations { get; set; } = 0;
	public long   TotalChestsBlown { get; set; } = 0;
	public int    BestChain        { get; set; } = 0;
	public double BestSingleBlast  { get; set; } = 0;
	public int    GoldenGrabbed    { get; set; } = 0;
	public double SecondsRun       { get; set; } = 0;
	public List<string> Achievements { get; set; } = new();

	// ── Upgrade helpers ───────────────────────────────────────────
	public int Level( IdleUpgradeId id )
		=> Upgrades.TryGetValue( id.ToString(), out int v ) ? v : 0;

	public void SetLevel( IdleUpgradeId id, int level )
		=> Upgrades[id.ToString()] = Math.Max( 0, level );

	public int PerkLevel( IdlePerkId id )
		=> Perks.TryGetValue( id.ToString(), out int v ) ? v : 0;

	public void SetPerkLevel( IdlePerkId id, int level )
		=> Perks[id.ToString()] = Math.Max( 0, level );

	public bool HasAchievement( string id ) => Achievements.Contains( id );

	// ── Meltdown (prestige) ───────────────────────────────────────
	/// <summary>Wipes the run-scoped economy. Flux, perks, records and achievements stay.</summary>
	public void ResetForMeltdown()
	{
		Cores      = 0;
		CycleCores = 0;
		Upgrades.Clear();
		Meltdowns++;

		// Kickstart perk hands back a head start so the early grind shrinks each time
		int kick = PerkLevel( IdlePerkId.Kickstart );
		if ( kick > 0 )
		{
			SetLevel( IdleUpgradeId.ChestValue, kick * 6 );
			SetLevel( IdleUpgradeId.SpawnRate,  kick * 4 );
			SetLevel( IdleUpgradeId.CycleSpeed, kick * 4 );
			SetLevel( IdleUpgradeId.DroneCount, Math.Min( kick, 3 ) );
		}
	}

	// ── Day rollover for the fragment bucket ──────────────────────
	public void EnsureFragDay()
	{
		string today = DateTime.UtcNow.ToString( "yyyy-MM-dd" );
		if ( LastFragDate == today ) return;
		LastFragDate = today;
		TodayReactorFragments = 0;
	}

	public int FragmentsToday
	{
		get { EnsureFragDay(); return TodayReactorFragments; }
	}
}