Game/Menu/ReactorMenu.razor
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Services;
@using System;
@using System.Linq;
@using System.Collections.Generic;
@inherits PanelComponent
@namespace Sandbox

@*
	Landing screen for Chain Reaction.

	Same s&box text rule as IdleHud: an element holds either pure literal text
	or a single @expression, never a mix — Razor turns non-ASCII literals into
	AddMarkupContent, which the panel renderer hoists ahead of AddContent.
*@

<root>
@if ( !GameRunning )
{
	<div class="rm-root">

		<div class="rm-bg"></div>
		<div class="rm-bg-glow"></div>

		@* Slow drifting reactor cells behind everything.
		   LatticeCols x LatticeRows has to overfill the widest plausible design
		   space — the panel scales to a 1080-tall canvas, so an ultrawide window
		   asks for well over 2000 design pixels of width. Too few cells and the
		   grid simply stops partway down the screen. *@
		<div class="rm-grid-bg">
			@for ( int i = 0; i < LatticeCols * LatticeRows; i++ )
			{
				int col = i % LatticeCols;
				int row = i / LatticeCols;
				float dist = MathF.Sqrt( (col - 9f) * (col - 9f) + (row - 4f) * (row - 4f) );
				string cls = (i * 7 % 23 == 0) ? "rm-gc-hot"
						   : (i * 5 % 17 == 0) ? "rm-gc-lit"
						   :                     "rm-gc";
				float delay = (dist * 0.30f) % 5f;
				<div class="@cls" style="animation-delay:[email protected]("F2")s;"></div>
			}
		</div>

		@for ( int i = 0; i < 22; i++ )
		{
			float pLeft = (i * 4.7f) % 100f;
			float pDur  = 9f + (i * 0.7f) % 8f;
			float pDel  = (i * 1.1f) % 11f;
			int   pSz   = (i % 4 == 0) ? 4 : 2;
			string pCol = (i % 3 == 0) ? "rgba(94,234,212,0.45)" : "rgba(96,165,250,0.28)";
			<div class="rm-particle"
				 style="left:@pLeft.ToString("F1")%;width:@(pSz)px;height:@(pSz)px;background-color:@pCol;animation-duration:@pDur.ToString("F1")s;animation-delay:@pDel.ToString("F1")s;"></div>
		}

		<div class="rm-ui">

			@* ═════════════════════ LEFT — identity ═════════════════════ *@
			<div class="rm-left">
				<div class="rm-eyebrow">IDLE CHAIN REACTOR</div>
				<div class="rm-title">CHAIN</div>
				<div class="rm-title accent">REACTION</div>
				<div class="rm-tagline">Drop · Detonate · Compound</div>

				<div class="rm-stats">
					<div class="rm-stat-row">
						<span class="rm-stat-icon">⚛️</span>
						<span class="rm-stat-val">@CoresLabel()</span>
						<span class="rm-stat-lbl">cores banked</span>
					</div>
					<div class="rm-stat-row">
						<span class="rm-stat-icon">📈</span>
						<span class="rm-stat-val">@RateLabel()</span>
						<span class="rm-stat-lbl">per second</span>
					</div>
					<div class="rm-stat-row">
						<span class="rm-stat-icon">🔷</span>
						<span class="rm-stat-val">@FluxLabel()</span>
						<span class="rm-stat-lbl">flux</span>
					</div>
					<div class="rm-stat-row">
						<span class="rm-stat-icon">💠</span>
						<span class="rm-stat-val">@FragLabel()</span>
						<span class="rm-stat-lbl">fragments</span>
					</div>
					<div class="rm-stat-row">
						<span class="rm-stat-icon">⛓️</span>
						<span class="rm-stat-val">@BestChainLabel()</span>
						<span class="rm-stat-lbl">best chain</span>
					</div>
				</div>

				<div class="rm-ver">Chain Reaction · Early Access</div>
			</div>

			@* ═════════════════════ CENTER — panels ═════════════════════ *@
			<div class="rm-center">

				@* ── Reactor status ── *@
				@if ( _tab == "reactor" )
				{
					<div class="rm-card">
						<div class="rm-card-hd">THE REACTOR</div>

						<div class="rm-blurb">A board that plays itself. Chests drop, drones bomb them, chain reactions compound into Cores. Spend them, then melt the whole cycle down for permanent Flux.</div>

						<div class="rm-tank">
							<div class="rm-tank-top">
								<span class="rm-tank-lbl">UNATTENDED STORAGE</span>
								<span class="rm-tank-val">@TankLabel()</span>
							</div>
							<div class="rm-tank-track">
								<div class="rm-tank-fill @TankClass()" style="width:@TankPct()%;"></div>
							</div>
							<div class="rm-tank-note">@TankNote()</div>
						</div>

						@if ( DailyReady )
						{
							<div class="rm-daily">@DailyLabel()</div>
						}

						<div class="rm-grid-stats">
							<div class="rm-gs">
								<span class="rm-gs-val">@MeltdownsLabel()</span>
								<span class="rm-gs-lbl">meltdowns</span>
							</div>
							<div class="rm-gs">
								<span class="rm-gs-val">@LogLabel()</span>
								<span class="rm-gs-lbl">reactor log</span>
							</div>
							<div class="rm-gs">
								<span class="rm-gs-val">@PetBonusLabel()</span>
								<span class="rm-gs-lbl">pet output</span>
							</div>
						</div>
					</div>
				}

				@* ── Pet ── *@
				@if ( _tab == "pet" )
				{
					<div class="rm-card">
						<div class="rm-card-hd">YOUR COMPANION</div>

						<div class="rm-pet-hero">
							@if ( PetImage != null )
							{
								<image src=@PetImage class="rm-pet-img"/>
							}
							@if ( PetImage == null )
							{
								<div class="rm-pet-emoji">@PetEmojiLabel()</div>
							}
						</div>

						<div class="rm-pet-name">@PetNameLabel()</div>
						<div class="rm-pet-level">@PetLevelLabel()</div>
						<div class="rm-pet-blurb">@PetBlurbLabel()</div>

						<div class="rm-pet-bonus">
							<span class="rm-pet-bonus-val">@PetBonusLabel()</span>
							<span class="rm-pet-bonus-lbl">reactor output, forever</span>
						</div>

						@if ( !PetMaxed )
						{
							<div class="rm-pet-next">@PetNextLabel()</div>
							<div class="rm-tank-track">
								<div class="rm-tank-fill" style="width:@PetProgressPct()%;"></div>
							</div>
							<button class="rm-pet-buy @PetBuyClass()" onclick=@OnUpgradePet>@PetBuyLabel()</button>
						}
						@if ( PetMaxed )
						{
							<div class="rm-pet-next">Fully grown. Nothing left to feed it.</div>
						}

						<div class="rm-pet-foot">Fragments condense out of the reactor on a daily ceiling. The pet is what they are for.</div>
					</div>
				}

				@* ── Leaderboard ── *@
				@if ( _tab == "lb" )
				{
					<div class="rm-card">
						<div class="rm-card-hd">LEADERBOARD</div>
						<div class="rm-blurb">Ranked on total Flux earned — how deep your reactor has ever gone.</div>

						@if ( _loadingLb )
						{
							<div class="rm-empty">Loading…</div>
						}
						@if ( !_loadingLb && NoBoard )
						{
							<div class="rm-empty">No reactors online yet — be the first.</div>
						}
						@if ( !_loadingLb && !NoBoard )
						{
							@for ( int i = 0; i < _board.Entries.Length; i++ )
							{
								var entry = _board.Entries[i];
								int rank = i + 1;
								<div class="rm-lb-row @(IsMe( entry ) ? "me" : "")">
									<span class="rm-lb-rank">@RankLabel( rank )</span>
									<div class="rm-lb-name-col">
										<span class="rm-lb-name">@GetName( entry )</span>
										<span class="rm-lb-title">@ReactorTitle( (long)entry.Value )</span>
									</div>
									<span class="rm-lb-score">@FluxEntryLabel( entry )</span>
								</div>
							}
						}
					</div>
				}

				@* ── Community ── *@
				@if ( _tab == "community" )
				{
					<div class="rm-card">
						<div class="rm-card-hd">FROM THE DEV</div>
						<div class="rm-comm-greeting">Hey! 👋</div>
						<div class="rm-blurb">You are playing during Early Access — you are one of the first, and your feedback genuinely shapes what gets built next. Thanks for that.</div>

						<div class="rm-devnote">
							<span class="rm-devnote-lbl">THIS WEEK</span>
							<span class="rm-devnote-txt">Chain Reaction is now a pure idle reactor. Balancing the upgrade curve and the Meltdown loop — tell me where it drags.</span>
						</div>

						<div class="rm-section-title">What's next</div>
						@foreach ( var ev in _events )
						{
							var e = ev;
							<div class="rm-event-row">
								<span class="rm-event-badge @e.Color">@e.Badge</span>
								<span class="rm-event-text">@e.Text</span>
							</div>
						}

						<div class="rm-blurb">Got ideas, bugs, or a screenshot of an absurd chain? Come say hi — it is just me on the other side.</div>
						<button class="rm-discord @(_discordCopied ? "copied" : "")" onclick=@OnDiscordCopy>@DiscordLabel()</button>
					</div>
				}

				@* ── Credits ── *@
				@if ( _tab == "credits" )
				{
					<div class="rm-card">
						<div class="rm-card-hd">CREDITS</div>

						<div class="rm-credit">
							<div class="rm-credit-role">Game design, code and art</div>
							<div class="rm-credit-name">Elysia</div>
						</div>
						<div class="rm-credit">
							<div class="rm-credit-role">Sound</div>
							<div class="rm-credit-name">Freesound · Pixabay</div>
						</div>
						<div class="rm-credit">
							<div class="rm-credit-role">Special thanks</div>
							<div class="rm-credit-name">Everyone who sent feedback</div>
						</div>

						<div class="rm-blurb">Built solo, in the open, one chain reaction at a time. Thanks for playing.</div>
					</div>
				}
			</div>

			@* ═════════════════════ RIGHT — actions ═════════════════════ *@
			<div class="rm-right">

				<button class="rm-play" onclick=@OnEnterReactor>
					<div class="rm-play-shine"></div>
					<span class="rm-play-icon">⚛️</span>
					<span class="rm-play-label">ENTER THE REACTOR</span>
					@if ( SomethingWaiting )
					{
						<span class="rm-play-dot"></span>
					}
				</button>

				<div class="rm-tabs">
					<button class="rm-tab @TabClass("reactor")" onclick=@(() => SetTab("reactor"))>⚛️ Reactor</button>
					<button class="rm-tab @TabClass("pet")" onclick=@(() => SetTab("pet"))>
						🐾 Companion
						@if ( CanUpgradePet )
						{
							<span class="rm-tab-pill">!</span>
						}
					</button>
					<button class="rm-tab @TabClass("lb")" onclick=@(() => SetTab("lb"))>🏅 Leaderboard</button>
					<button class="rm-tab @TabClass("community")" onclick=@(() => SetTab("community"))>💬 Community</button>
					<button class="rm-tab @TabClass("credits")" onclick=@(() => SetTab("credits"))>🎖️ Credits</button>
				</div>
			</div>
		</div>

		@* ══════════════════ EVOLUTION CINEMATIC ══════════════════ *@
		@if ( _showEvo )
		{
			<div class="rm-evo-bg @_evoPhase">
				@if ( !string.IsNullOrEmpty( _evoText ) )
				{
					<div class="rm-evo-text">@_evoText</div>
				}

				<div class="rm-evo-pet @_evoPhase">
					@if ( EvoImage != null )
					{
						<image src=@EvoImage/>
					}
				</div>

				@if ( _evoPhase == "evo-reveal" )
				{
					<div class="rm-evo-name">@PetNameLabel()</div>
					<div class="rm-evo-level">@PetLevelLabel()</div>
					<button class="rm-evo-btn" onclick=@OnCloseEvo>Amazing</button>
				}
			</div>
		}
	</div>
}
</root>

@code {
	public static ReactorMenu Instance { get; private set; }

	ChainReactionGame Game => ChainReactionGame.Instance;

	PlayerSave _save;
	IdleSave   Idle => _save?.Idle;

	bool GameRunning => Game?.GameStarted == true;

	string _tab = "reactor";

	// 20x12 at a 150px cell + 6px gap covers 3120x1872 design units, which is
	// past even a 32:9 window on the 1080-tall canvas.
	const int LatticeCols = 20;
	const int LatticeRows = 12;

	// Leaderboard
	Leaderboards.Board2 _board;
	bool _loadingLb;
	const string GameIdent = "playnplay.10_xplosion";
	bool NoBoard => _board?.Entries == null || _board.Entries.Length == 0;

	// Community
	class Ev { public string Badge; public string Text; public string Color; }
	readonly List<Ev> _events = new()
	{
		new Ev { Badge = "NEW",     Text = "The Reactor — the whole game is now one idle board", Color = "green"  },
		new Ev { Badge = "NEW",     Text = "Meltdown prestige, Flux perks and the Reactor Log",   Color = "green"  },
		new Ev { Badge = "SOON",    Text = "More chest tiers past Reactor Cores",                  Color = "purple" },
		new Ev { Badge = "PLANNED", Text = "Offline production while the game is closed",          Color = "gray"   },
	};
	bool _discordCopied;

	// Pet animation + evolution cinematic
	bool  _petFrame;
	float _petFrameTimer;
	bool   _showEvo;
	string _evoPhase = "";
	string _evoText  = "";
	int    _evoFromLevel = 1;
	bool   _evoShowNew;

	bool _wasRunning;

	// ══════════════════════════════════════════════════════════════
	// Lifecycle
	// ══════════════════════════════════════════════════════════════

	protected override void OnStart()
	{
		Instance = this;
		Reload();
		_ = LoadLeaderboard();
	}

	public void Reload()
	{
		_save = PlayerSave.Load();
		StateHasChanged();
	}

	protected override void OnUpdate()
	{
		bool running = GameRunning;
		if ( running != _wasRunning )
		{
			_wasRunning = running;
			// Coming back from the reactor — pick up whatever it saved
			if ( !running ) Reload();
			StateHasChanged();
		}

		if ( running ) return;

		_petFrameTimer -= Time.Delta;
		if ( _petFrameTimer <= 0f )
		{
			_petFrameTimer = 0.4f;
			_petFrame = !_petFrame;
			StateHasChanged();
		}
	}

	protected override int BuildHash()
	{
		if ( GameRunning ) return 0;

		var hc = new HashCode();
		hc.Add( _tab );
		hc.Add( _petFrame );
		hc.Add( _showEvo );
		hc.Add( _evoPhase );
		hc.Add( _save?.TotalFragments ?? 0 );
		hc.Add( _save?.PetLevel ?? 0 );
		hc.Add( (long)(Idle?.Cores ?? 0) );
		hc.Add( _loadingLb );
		hc.Add( _board?.Entries?.Length ?? 0 );
		hc.Add( _discordCopied );
		return hc.ToHashCode();
	}

	// ══════════════════════════════════════════════════════════════
	// Actions
	// ══════════════════════════════════════════════════════════════

	void SetTab( string tab )
	{
		_tab = tab;
		Sound.Play( "UI_Button" );
		if ( tab == "lb" ) _ = LoadLeaderboard();
		StateHasChanged();
	}

	void OnEnterReactor()
	{
		Sound.Play( "PlayButton" );
		Game?.StartGame( GameMode.Idle, null );
	}

	void OnDiscordCopy()
	{
		_discordCopied = true;
		Sound.Play( "UI_Button" );
		StateHasChanged();
	}

	void OnUpgradePet()
	{
		if ( _save == null || !_save.CanUpgradePet ) { Sound.Play( "Pop_Button_1" ); return; }

		_evoFromLevel = _save.PetLevel;
		_save.TryUpgradePet();
		_ = PlayEvolution();
	}

	async System.Threading.Tasks.Task PlayEvolution()
	{
		_showEvo    = true;
		_evoShowNew = false;
		_evoPhase   = "evo-shake";
		_evoText    = _evoFromLevel == 1 ? "The egg is trembling…" : "Something is changing…";
		Sound.Play( "Evolution_Pet" );
		StateHasChanged();

		await GameTask.DelaySeconds( 2.0f );

		_evoPhase = "evo-flash";
		_evoText  = "";
		Sound.Play( "pearl_stars" );
		StateHasChanged();

		await GameTask.DelaySeconds( 0.7f );

		_evoShowNew = true;
		_evoPhase   = "evo-reveal";
		Sound.Play( "fanfare_100" );
		StateHasChanged();
	}

	void OnCloseEvo()
	{
		_showEvo = false;
		_evoPhase = "";
		if ( _save != null )
		{
			_save.PendingEvolution = false;
			_save.Save();
		}
		Sound.Play( "UI_Button" );
		StateHasChanged();
	}

	async System.Threading.Tasks.Task LoadLeaderboard()
	{
		_loadingLb = true;
		StateHasChanged();

		try
		{
			var b = Leaderboards.GetFromStat( GameIdent, "chain_reaction_reactor" );
			b.SetAggregationMax();
			b.SetSortDescending();
			b.MaxEntries = 100;
			await b.Refresh();
			_board = b;
		}
		catch ( Exception e )
		{
			Log.Warning( $"Reactor leaderboard: {e.Message}" );
		}

		_loadingLb = false;
		StateHasChanged();
	}

	// ══════════════════════════════════════════════════════════════
	// Derived state
	// ══════════════════════════════════════════════════════════════

	bool CanUpgradePet => _save?.CanUpgradePet ?? false;
	bool PetMaxed      => (_save?.PetLevel ?? 1) >= IdlePet.MaxLevel;
	bool DailyReady    => IdleReactor.DailyAvailable( Idle );

	bool SomethingWaiting
		=> DailyReady || IdleReactor.PreviewOfflineCores( Idle ) > 0 || CanUpgradePet;

	string PetImage => PetVisuals.GetImagePath( _save?.PetLevel ?? 1, true, _petFrame );

	string EvoImage => _evoShowNew
		? PetVisuals.GetEvolutionImage( _save?.PetLevel ?? 1, _petFrame )
		: PetVisuals.GetEvolutionImage( _evoFromLevel, _petFrame );

	// ══════════════════════════════════════════════════════════════
	// Labels — one composed string per element
	// ══════════════════════════════════════════════════════════════

	string CoresLabel()     => IdleFormat.Short( Idle?.Cores ?? 0 );
	string RateLabel()      => IdleFormat.Short( Idle?.MeasuredCps ?? 0 );
	string FluxLabel()      => (Idle?.Flux ?? 0).ToString();
	string FragLabel()      => (_save?.TotalFragments ?? 0).ToString();
	string BestChainLabel() => (Idle?.BestChain ?? 0).ToString();
	string MeltdownsLabel() => (Idle?.Meltdowns ?? 0).ToString();

	string LogLabel()
		=> Idle == null ? "0 / 0" : $"{IdleAchievements.CompletedCount( Idle )} / {IdleAchievements.All.Count}";

	string TabClass( string t ) => _tab == t ? "on" : "";

	// ── Storage tank ──
	string TankPct() => (IdleReactor.PreviewStorageFraction( Idle ) * 100f).ToString( "F1" );
	string TankClass() => IdleReactor.PreviewStorageFraction( Idle ) >= 1f ? "full" : "";

	string TankLabel()
	{
		if ( Idle == null ) return "0s";
		float capH = IdleBalance.OfflineCapHours(
			Idle.Level( IdleUpgradeId.OfflineCap ),
			Idle.PerkLevel( IdlePerkId.DeepStorage ) );
		return $"{IdleFormat.Duration( IdleReactor.PreviewOfflineSeconds( Idle ) )} / {capH:F0}h";
	}

	string TankNote()
	{
		double pending = IdleReactor.PreviewOfflineCores( Idle );
		if ( pending <= 0 ) return "The reactor banks output while you sit on this screen.";
		return $"{IdleFormat.Short( pending )} Cores waiting to be collected";
	}

	string DailyLabel()
	{
		int next = Math.Min( 7, (Idle?.DailyStreak ?? 0) + 1 );
		return $"Daily bonus ready — Day {next}";
	}

	// ── Pet ──
	string PetEmojiLabel() => IdlePet.Emoji( _save?.PetLevel ?? 1 );
	string PetNameLabel()  => IdlePet.Name( _save?.PetLevel ?? 1 );
	string PetBlurbLabel() => IdlePet.Blurb( _save?.PetLevel ?? 1 );
	string PetLevelLabel() => $"Level {_save?.PetLevel ?? 1} / {IdlePet.MaxLevel}";

	string PetBonusLabel()
	{
		double bonus = IdlePet.OutputBonus( _save?.PetLevel ?? 1 );
		if ( (Idle?.PerkLevel( IdlePerkId.PetLink ) ?? 0) > 0 ) bonus *= 2.0;
		return $"+{bonus * 100.0:F0}%";
	}

	string PetNextLabel()
	{
		int lvl = _save?.PetLevel ?? 1;
		return $"Next: {IdlePet.Name( lvl + 1 )} — +{IdlePet.OutputBonus( lvl + 1 ) * 100.0:F0}% output";
	}

	string PetProgressPct()
	{
		int cost = _save?.PetUpgradeCost ?? 0;
		if ( cost <= 0 ) return "100";
		float f = Math.Clamp( (_save?.TotalFragments ?? 0) / (float)cost, 0f, 1f );
		return (f * 100f).ToString( "F1" );
	}

	string PetBuyClass() => CanUpgradePet ? "afford" : "poor";

	string PetBuyLabel()
	{
		int cost = _save?.PetUpgradeCost ?? 0;
		int have = _save?.TotalFragments ?? 0;
		return CanUpgradePet
			? $"Feed {cost} Fragments"
			: $"Need {cost - have} more Fragments";
	}

	// ── Leaderboard ──
	string RankLabel( int rank ) => rank switch
	{
		1 => "🥇",
		2 => "🥈",
		3 => "🥉",
		_ => $"#{rank}",
	};

	string FluxEntryLabel( Leaderboards.Board2.Entry e ) => $"🔷 {(long)e.Value:N0}";

	static string ReactorTitle( long flux ) => flux switch
	{
		>= 5000 => "Singularity",
		>= 2000 => "Core Architect",
		>= 750  => "Reactor Chief",
		>= 250  => "Fluxbound",
		>= 60   => "Engineer",
		>= 10   => "Technician",
		_       => "Intern",
	};

	string GetName( Leaderboards.Board2.Entry e )
	{
		if ( !string.IsNullOrWhiteSpace( e.DisplayName ) ) return e.DisplayName;
		try
		{
			var friend = new Sandbox.Friend( e.SteamId );
			if ( !string.IsNullOrWhiteSpace( friend.Name ) ) return friend.Name;
		}
		catch { }
		return "Anonymous";
	}

	bool IsMe( Leaderboards.Board2.Entry e ) => e.SteamId == Sandbox.Connection.Local?.SteamId;

	string DiscordLabel() => _discordCopied ? "Link copied" : "💬 Join Discord";
}