SoundBoardAchievements.cs

Achievement management for the soundboard. Tracks local one-shot toasts and cookie persistence, detects milestones from voice counts, clicks, loop plays, live playback and pad presses, shows local toasts, and queues platform achievement unlocks (Achievements.Unlock) with retry/backoff and persistent platform-synced cookies.

NetworkingFile Access
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox.Services;

namespace SSoundboard;

/// <summary>
/// Local achievement toasts + cookie persistence + package Achievements.Unlock.
///
/// Local toast/cookies and platform unlocks are independent:
/// - Local = one-time toast + "earned" cookie
/// - Platform = Achievements.Unlock(ident) with retries + persistent "synced" cookie
/// So a toast can never "use up" an unlock without the account receiving it.
///
/// Register matching idents on the package dashboard (Manual unlock type).
/// </summary>
public static class SoundBoardAchievements
{
	public const string FiveVoicesAchievementId = "five_deep";
	public const string TenVoicesAchievementId = "ten_deep";
	public const string FifteenVoicesAchievementId = "fifteen_deep";
	public const string TwentyFiveVoicesAchievementId = "twenty_five_deep";
	public const string FiftyVoicesAchievementId = "fifty_deep";
	public const string SixtyFourVoicesAchievementId = "sixty_four_deep";
	public const string ErrorAchievementId = "task_failed_successfully";
	public const string StopItAchievementId = "stop_it_100";
	public const string FlipperReturnsAchievementId = "flipper_returns_to_ocean";
	public const string IllBeBackAchievementId = "ill_be_back";
	public const string HastaLaVistaAchievementId = "hasta_la_vista";
	public const string ChoppaAchievementId = "get_to_da_choppa";
	public const string NotATumorAchievementId = "its_not_a_tumor";
	public const string VineBoomAchievementId = "vine_boom_10";
	public const string BruhAchievementId = "bruh_50";
	public const string YoureFiredAchievementId = "youre_fired";
	public const string PanicButtonAchievementId = "panic_button";
	public const string SerialSilencerAchievementId = "serial_silencer";
	public const string PoliticsJunkieAchievementId = "politics_junkie";
	public const string ArnoldForeverAchievementId = "arnold_forever";
	public const string FullStackArnoldAchievementId = "full_stack_arnold";
	public const string GasLeakAchievementId = "gas_leak";
	public const string TripleThreatAchievementId = "triple_threat_nature";
	public const int StopItRequired = 100;
	public const int VineBoomRequired = 10;
	public const int BruhRequired = 50;
	public const int KillAllSerialRequired = 50;
	public const int PoliticsJunkieRequired = 100;
	public const int ArnoldForeverRequired = 250;

	const string KillAllCountCookieKey = "ssoundboard.kill-all-count";

	/// <summary>Highest concurrent voice count this session (for milestones).</summary>
	public static int PeakVoiceCount { get; private set; }

	static long _killAllCount = -1;

	static readonly VoiceMilestone[] VoiceMilestones =
	{
		// Highest first so the toast shows the biggest new unlock when several fire at once.
		new( 64, SixtyFourVoicesAchievementId, "ssoundboard.ach.sixty_four_deep", "Sixty-Four Deep",
			"64 voices — the hard cap. The meter maxed out. Reality filed a noise complaint." ),
		new( 50, FiftyVoicesAchievementId, "ssoundboard.ach.fifty_deep", "Fifty Deep",
			"50 voices at once. The chaos meter has left the building. Hearing is optional now." ),
		new( 25, TwentyFiveVoicesAchievementId, "ssoundboard.ach.twenty_five_deep", "Twenty-Five Deep",
			"25 simultaneous voices. Neighbors are filing paperwork. The meter is glowing with pride." ),
		new( 15, FifteenVoicesAchievementId, "ssoundboard.ach.fifteen_deep", "Fifteen Deep",
			"15 voices stacked. You've graduated from chaos to performance art." ),
		new( 10, TenVoicesAchievementId, "ssoundboard.ach.ten_deep", "Ten Deep",
			"10 voices stacked. You've left \"soundboard\" and entered \"air raid\"." ),
		new( 5, FiveVoicesAchievementId, "ssoundboard.ach.five_deep", "Five Deep",
			"You stacked 5 voices on the chaos meter. Your ears filed a formal complaint. The meter is proud." ),
	};

	/// <summary>Lifetime pad-click milestones (highest first for batch unlock toasts).</summary>
	static readonly ClickMilestone[] ClickMilestones =
	{
		new( 100_000_000_000L, "click_100b", "ssoundboard.ach.click_100b", "Heat Death of the Board",
			"100,000,000,000 clicks. The universe ended. You kept mashing. Respect, monster." ),
		new( 100_000_000L, "click_100m", "ssoundboard.ach.click_100m", "Hundred Million Club",
			"100,000,000 clicks. Your finger filed for workers' comp. The board salutes you." ),
		new( 100_000L, "click_100k", "ssoundboard.ach.click_100k", "Century of Spam",
			"100,000 clicks. You are no longer a player. You are infrastructure." ),
		new( 10_000L, "click_10k", "ssoundboard.ach.click_10k", "Ten Thousand Strong",
			"10,000 clicks. The pads know your name. They fear it." ),
		new( 1_000L, "click_1k", "ssoundboard.ach.click_1k", "Thousand Cuts",
			"1,000 clicks. Dedication, delusion, or both. The meter is taking notes." ),
		new( 100L, "click_100", "ssoundboard.ach.click_100", "Century Clicker",
			"100 clicks. You're officially in too deep to quit now." ),
		new( 50L, "click_50", "ssoundboard.ach.click_50", "Fifty Fires",
			"50 clicks. Warming up. The chaos is just getting started." ),
		new( 25L, "click_25", "ssoundboard.ach.click_25", "Quarter Century",
			"25 clicks. A respectable warm-up mash session." ),
		new( 10L, "click_10", "ssoundboard.ach.click_10", "Double Digits",
			"10 clicks. The board has noticed you. So have the neighbors." ),
		new( 1L, "click_1", "ssoundboard.ach.click_1", "First Blood",
			"You pressed a button. There is no going back." ),
	};

	/// <summary>Lifetime LOOP re-fire milestones (highest first).</summary>
	static readonly LoopMilestone[] LoopMilestones =
	{
		new( 1_000L, "loop_1k", "ssoundboard.ach.loop_1k", "Infinite Jest",
			"1,000 loop re-fires. Time is a flat circle and it sounds like your soundboard." ),
		new( 100L, "loop_100", "ssoundboard.ach.loop_100", "Stuck on Repeat",
			"100 loop re-fires. The LOOP button has adopted you." ),
		new( 50L, "loop_50", "ssoundboard.ach.loop_50", "Half-Century Loop",
			"50 loop re-fires. Repetition is a feature, not a bug." ),
		new( 10L, "loop_10", "ssoundboard.ach.loop_10", "Encore Machine",
			"10 loop re-fires. Once was not enough. Ten was getting somewhere." ),
		new( 1L, "loop_1", "ssoundboard.ach.loop_1", "First Echo",
			"A sound came back for round two. Welcome to LOOP mode." ),
	};

	const string ErrorCookieKey = "ssoundboard.ach.task_failed_successfully";
	const string ErrorTitle = "Task Failed Successfully";
	const string ErrorBody =
		"You pressed a button and invented a brand new error. IT departments everywhere just felt a disturbance.";

	const string StopItCookieKey = "ssoundboard.ach.stop_it_100";
	const string StopItTitle = "I Said STOP IT";
	const string StopItBody =
		"You mashed Arnold's STOP IT button 100 times. He never stopped. You never stopped. The button is tired of both of you.";

	const string FlipperCookieKey = "ssoundboard.ach.flipper_returns_to_ocean";
	const string FlipperTitle = "Flipper Returns to Ocean";
	const string FlipperBody =
		"Dolphin and ocean at the same time. Flipper found the water. The board approves of this nature documentary.";

	// One-shot pad / action achievements (id, cookie, title, body).
	static readonly SpecialAchievement[] SpecialAchievements =
	{
		new( IllBeBackAchievementId, "ssoundboard.ach.ill_be_back", "I'll Be Back",
			"You pressed I'll Be Back. The board promises nothing about the sequel." ),
		new( HastaLaVistaAchievementId, "ssoundboard.ach.hasta_la_vista", "Hasta La Vista, Baby",
			"Hasta la vista, baby. The pad is liquidated. The vibe is terminatored." ),
		new( ChoppaAchievementId, "ssoundboard.ach.get_to_da_choppa", "Get To Da Choppa",
			"GET TO DA CHOPPA. Evacuation protocol: mash more pads." ),
		new( NotATumorAchievementId, "ssoundboard.ach.its_not_a_tumor", "It's Not a Tumor",
			"It's not a tumor. It's a lifestyle. And a button." ),
		new( VineBoomAchievementId, "ssoundboard.ach.vine_boom_10", "Industrial Strength Boom",
			"Vine boom ×10. Bass that walks into rooms uninvited." ),
		new( BruhAchievementId, "ssoundboard.ach.bruh_50", "Professional Bruh",
			"50 bruhs. You've said everything and nothing. Peak discourse." ),
		new( YoureFiredAchievementId, "ssoundboard.ach.youre_fired", "You're Fired",
			"You're fired. The pad. The mood. Possibly the neighbors' goodwill." ),
		new( PanicButtonAchievementId, "ssoundboard.ach.panic_button", "Panic Button",
			"First KILL ALL. Silence falls. The board knows what you did." ),
		new( SerialSilencerAchievementId, "ssoundboard.ach.serial_silencer", "Serial Silencer",
			"KILL ALL ×50. You don't play the board — you censor it." ),
		new( PoliticsJunkieAchievementId, "ssoundboard.ach.politics_junkie", "Politics Junkie",
			"100 politics pads. Democracy dies in darkness; you brought the soundboard." ),
		new( ArnoldForeverAchievementId, "ssoundboard.ach.arnold_forever", "Arnold Forever",
			"250 Arnold presses. There is no board without the Governator." ),
		new( FullStackArnoldAchievementId, "ssoundboard.ach.full_stack_arnold", "Full Stack Arnold",
			"5+ Arnold clips at once. Peak Austrian multi-threading." ),
		new( GasLeakAchievementId, "ssoundboard.ach.gas_leak", "Gas Leak",
			"5+ GAS pads stacked. Open a window. Or don't. Commit." ),
		new( TripleThreatAchievementId, "ssoundboard.ach.triple_threat_nature", "Triple Threat Nature Doc",
			"Dolphin + ocean + rain together. Nature is healing (loudly)." ),
	};

	const string PlatformSyncedPrefix = "ssoundboard.ach.platform.";

	// Local toast / cookie (one-time UI).
	static readonly HashSet<string> _unlockedLocal = new( StringComparer.OrdinalIgnoreCase );

	// Platform unlocks confirmed this process (also persisted under PlatformSyncedPrefix).
	static readonly HashSet<string> _platformSynced = new( StringComparer.OrdinalIgnoreCase );

	// One Unlock attempt per tick — avoids multi-unlock frame drops.
	static readonly Queue<string> _platformQueue = new();
	static readonly HashSet<string> _platformQueued = new( StringComparer.OrdinalIgnoreCase );
	static readonly Dictionary<string, int> _platformAttempts = new( StringComparer.OrdinalIgnoreCase );
	static readonly Dictionary<string, float> _platformNextTryAt = new( StringComparer.OrdinalIgnoreCase );

	const int MaxPlatformAttempts = 40;       // plenty — boot can fail for seconds
	const float PlatformRetryBaseSec = 1.5f;  // backoff base
	const float PeriodicResyncInterval = 45f; // re-queue anything still unsynced
	const float InitialSyncDelay = 2f;        // let Live Services wake up

	static RealTimeSince _sinceStart;
	static RealTimeSince _sincePeriodicResync;
	static bool _bootResyncDone;
	static bool _started;

	public static bool ToastVisible { get; private set; }
	public static string ToastTitle { get; private set; } = "";
	public static string ToastBody { get; private set; } = "";
	public static RealTimeSince ToastAge { get; private set; }

	public static event Action StateChanged;

	/// <summary>
	/// Called whenever concurrent voice count changes (pad press OR loop tick stacking).
	/// Uses a session high-water mark so brief spikes still count.
	/// </summary>
	public static void OnVoiceCountChanged( int voiceCount )
	{
		if ( voiceCount > PeakVoiceCount )
			PeakVoiceCount = voiceCount;

		var check = Math.Max( voiceCount, PeakVoiceCount );
		if ( check < 5 )
			return;

		string toastTitle = null;
		string toastBody = null;

		foreach ( var milestone in VoiceMilestones )
		{
			if ( check < milestone.Required )
				continue;

			// Always queue platform — even if local toast already fired earlier.
			RequestPlatformUnlock( milestone.Id );

			if ( !TryUnlockLocal( milestone.Id, milestone.CookieKey ) )
				continue;

			if ( toastTitle is null )
			{
				toastTitle = milestone.Title;
				toastBody = $"{milestone.Body} ({check} voices peak.)";
			}
		}

		if ( toastTitle is not null )
			ShowToast( toastTitle, toastBody );
	}

	/// <summary>
	/// Live-voice combos: Flipper, Full Stack Arnold, Gas Leak, Triple Threat Nature.
	/// </summary>
	public static void OnLivePlaybackChanged( IEnumerable<string> liveSoundIds, IEnumerable<string> liveLabels )
	{
		var ids = liveSoundIds as IList<string> ?? liveSoundIds?.ToList() ?? (IList<string>)Array.Empty<string>();
		var labels = liveLabels as IList<string> ?? liveLabels?.ToList() ?? (IList<string>)Array.Empty<string>();

		if ( LiveContains( ids, labels, "dolphin" ) && LiveContains( ids, labels, "ocean" ) )
		{
			RequestPlatformUnlock( FlipperReturnsAchievementId );
			TryUnlock( FlipperReturnsAchievementId, FlipperCookieKey, FlipperTitle, FlipperBody );
		}

		if ( LiveContains( ids, labels, "dolphin" )
			&& LiveContains( ids, labels, "ocean" )
			&& LiveContains( ids, labels, "rain" ) )
		{
			UnlockSpecial( TripleThreatAchievementId );
		}

		if ( CountLiveFolder( ids, "arnold" ) >= 5 )
			UnlockSpecial( FullStackArnoldAchievementId );

		if ( CountLiveGas( ids, labels ) >= 5 )
			UnlockSpecial( GasLeakAchievementId );
	}

	/// <summary>Lifetime pad presses across the whole board.</summary>
	public static void OnTotalClicksChanged( long totalClicks )
	{
		if ( totalClicks < 1 )
			return;

		string toastTitle = null;
		string toastBody = null;

		foreach ( var milestone in ClickMilestones )
		{
			if ( totalClicks < milestone.Required )
				continue;

			RequestPlatformUnlock( milestone.Id );

			if ( !TryUnlockLocal( milestone.Id, milestone.CookieKey ) )
				continue;

			if ( toastTitle is null )
			{
				toastTitle = milestone.Title;
				toastBody = $"{milestone.Body} ({FormatCount( totalClicks )} total clicks.)";
			}
		}

		if ( toastTitle is not null )
			ShowToast( toastTitle, toastBody );
	}

	/// <summary>Lifetime LOOP layer re-fires (timer repeats while LOOP is on).</summary>
	public static void OnLoopPlaysChanged( long totalLoopPlays )
	{
		if ( totalLoopPlays < 1 )
			return;

		string toastTitle = null;
		string toastBody = null;

		foreach ( var milestone in LoopMilestones )
		{
			if ( totalLoopPlays < milestone.Required )
				continue;

			// Always queue platform — independent of local toast (same as clicks / fifty_deep).
			RequestPlatformUnlock( milestone.Id );

			if ( !TryUnlockLocal( milestone.Id, milestone.CookieKey ) )
				continue;

			if ( toastTitle is null )
			{
				toastTitle = milestone.Title;
				toastBody = $"{milestone.Body} ({FormatCount( totalLoopPlays )} loop plays.)";
			}
		}

		if ( toastTitle is not null )
			ShowToast( toastTitle, toastBody );
	}

	public static void OnCreatedError( string padLabel = null )
	{
		var body = string.IsNullOrWhiteSpace( padLabel )
			? ErrorBody
			: $"\"{padLabel}\" yeeted itself into the void. {ErrorBody}";

		RequestPlatformUnlock( ErrorAchievementId );
		TryUnlock( ErrorAchievementId, ErrorCookieKey, ErrorTitle, body );
	}

	public static void OnPadPressed( SoundPad pad )
	{
		if ( pad is null || string.IsNullOrWhiteSpace( pad.SoundId ) )
			return;

		// Arnold one-shots.
		if ( MatchesPad( pad, "ill-be-back", "ill be back", "illbeback", "i'll be back", "i will be back" ) )
			UnlockSpecial( IllBeBackAchievementId );
		if ( MatchesPad( pad, "hasta-la-vista", "hasta la vista", "hastalavista" ) )
			UnlockSpecial( HastaLaVistaAchievementId );
		if ( MatchesPad( pad, "get-to-da-choppa", "get to da choppa", "choppa", "get-to-the-choppa" ) )
			UnlockSpecial( ChoppaAchievementId );
		if ( MatchesPad( pad, "its-not-a-tumor", "it's not a tumor", "not-a-tumor", "not a tumor", "tumor" ) )
			UnlockSpecial( NotATumorAchievementId );

		// You're fired (Trump / Apprentice memes).
		if ( MatchesPad( pad, "youre-fired", "you're fired", "you-are-fired", "donald-trump-fired", "trump-fired" )
			|| (PadHaystack( pad ).Contains( "fired", StringComparison.OrdinalIgnoreCase )
				&& (PadHaystack( pad ).Contains( "trump", StringComparison.OrdinalIgnoreCase )
					|| PadHaystack( pad ).Contains( "youre", StringComparison.OrdinalIgnoreCase )
					|| PadHaystack( pad ).Contains( "you're", StringComparison.OrdinalIgnoreCase )
					|| PadHaystack( pad ).Contains( "you are", StringComparison.OrdinalIgnoreCase ))) )
			UnlockSpecial( YoureFiredAchievementId );

		// Count-based (only re-sum when this press could advance the counter).
		if ( MatchesPad( pad, "vine-boom", "vine boom", "vineboom" ) )
		{
			if ( SumStemContains( "vine-boom", "vine boom", "vineboom" ) >= VineBoomRequired )
				UnlockSpecial( VineBoomAchievementId );
		}

		if ( MatchesPad( pad, "bruh" ) )
		{
			if ( SumStemContains( "bruh" ) >= BruhRequired )
				UnlockSpecial( BruhAchievementId );
		}

		var idPath = pad.SoundId.Replace( '\\', '/' );
		if ( idPath.Contains( "/politics/", StringComparison.OrdinalIgnoreCase )
			|| idPath.StartsWith( "sounds/politics/", StringComparison.OrdinalIgnoreCase ) )
		{
			if ( SumFolderPlays( "politics" ) >= PoliticsJunkieRequired )
				UnlockSpecial( PoliticsJunkieAchievementId );
		}

		if ( idPath.Contains( "/arnold/", StringComparison.OrdinalIgnoreCase )
			|| idPath.StartsWith( "sounds/arnold/", StringComparison.OrdinalIgnoreCase ) )
		{
			if ( SumFolderPlays( "arnold" ) >= ArnoldForeverRequired )
				UnlockSpecial( ArnoldForeverAchievementId );
		}

		if ( !IsStopItPad( pad ) )
			return;

		var count = SoundPlayTracker.GetCount( pad.SoundId );
		count = Math.Max( count, SumStopItPlays() );

		if ( count < StopItRequired )
			return;

		RequestPlatformUnlock( StopItAchievementId );
		TryUnlock( StopItAchievementId, StopItCookieKey, StopItTitle, $"{StopItBody} ({count}× and counting.)" );
	}

	/// <summary>KILL ALL pressed — pass how many voices were silenced (0 = noop spam).</summary>
	public static void OnKillAll( int silencedVoices )
	{
		if ( silencedVoices <= 0 )
			return;

		EnsureKillAllLoaded();
		_killAllCount++;
		try { Game.Cookies.Set( KillAllCountCookieKey, _killAllCount ); }
		catch { /* session-only */ }

		if ( _killAllCount >= 1 )
			UnlockSpecial( PanicButtonAchievementId );

		if ( _killAllCount >= KillAllSerialRequired )
			UnlockSpecial( SerialSilencerAchievementId );
	}

	public static void Tick()
	{
		if ( !_started )
		{
			_started = true;
			_sinceStart = 0;
			_sincePeriodicResync = 0;
		}

		// Delayed boot resync — Live Services often aren't ready on the first frames.
		if ( !_bootResyncDone && _sinceStart >= InitialSyncDelay )
		{
			_bootResyncDone = true;
			FullSyncFromProgress( forceRequeue: true );
			Log.Info( "[S&Soundboard] Achievement platform sync started." );
		}

		// Keep pushing any local unlocks that never got a platform ack this install.
		if ( _bootResyncDone && _sincePeriodicResync >= PeriodicResyncInterval )
		{
			_sincePeriodicResync = 0;
			FullSyncFromProgress( forceRequeue: false );
		}

		PumpPlatformUnlocks();

		if ( !ToastVisible || ToastAge < 5.5f )
			return;

		ToastVisible = false;
		StateChanged?.Invoke();
	}

	/// <summary>
	/// Queue every achievement that local progress/cookies say is earned.
	/// Safe to call multiple times; skips ones already platform-synced.
	/// </summary>
	public static void ResyncPlatformFromCookies()
	{
		FullSyncFromProgress( forceRequeue: false );
	}

	/// <summary>
	/// Full catch-up from actual progress (clicks, peak voices, cookies).
	/// forceRequeue=true clears session "sent" flags for unsynced items so boot retries work.
	/// </summary>
	public static void FullSyncFromProgress( bool forceRequeue )
	{
		// Re-evaluate live progress (not only cookies).
		OnTotalClicksChanged( SoundPlayTracker.TotalClicks );
		OnLoopPlaysChanged( SoundPlayTracker.TotalLoopPlays );
		if ( PeakVoiceCount >= 5 )
			OnVoiceCountChanged( PeakVoiceCount );

		// Anything marked local-earned must still reach the platform.
		foreach ( var milestone in VoiceMilestones )
			SyncIfLocalEarned( milestone.Id, milestone.CookieKey, forceRequeue );

		foreach ( var milestone in ClickMilestones )
			SyncIfLocalEarned( milestone.Id, milestone.CookieKey, forceRequeue );

		foreach ( var milestone in LoopMilestones )
			SyncIfLocalEarned( milestone.Id, milestone.CookieKey, forceRequeue );

		SyncIfLocalEarned( ErrorAchievementId, ErrorCookieKey, forceRequeue );
		SyncIfLocalEarned( StopItAchievementId, StopItCookieKey, forceRequeue );
		SyncIfLocalEarned( FlipperReturnsAchievementId, FlipperCookieKey, forceRequeue );

		foreach ( var special in SpecialAchievements )
			SyncIfLocalEarned( special.Id, special.CookieKey, forceRequeue );

		// Progress-based re-check (no toast spam — UnlockSpecial is one-shot).
		if ( SumStemContains( "vine-boom", "vine boom", "vineboom" ) >= VineBoomRequired )
			RequestPlatformUnlock( VineBoomAchievementId, forceRequeue );
		if ( SumStemContains( "bruh" ) >= BruhRequired )
			RequestPlatformUnlock( BruhAchievementId, forceRequeue );
		if ( SumFolderPlays( "politics" ) >= PoliticsJunkieRequired )
			RequestPlatformUnlock( PoliticsJunkieAchievementId, forceRequeue );
		if ( SumFolderPlays( "arnold" ) >= ArnoldForeverRequired )
			RequestPlatformUnlock( ArnoldForeverAchievementId, forceRequeue );

		EnsureKillAllLoaded();
		if ( _killAllCount >= 1 )
			RequestPlatformUnlock( PanicButtonAchievementId, forceRequeue );
		if ( _killAllCount >= KillAllSerialRequired )
			RequestPlatformUnlock( SerialSilencerAchievementId, forceRequeue );

		// Stop-it from live count even without cookie yet.
		var stopCount = Math.Max(
			SoundPlayTracker.GetCount( "sounds/arnold/stopit" ),
			Math.Max(
				SoundPlayTracker.GetCount( "sounds/arnold/stop-it" ),
				SoundPlayTracker.GetCount( "sounds/arnold/stop it" ) ) );
		if ( stopCount >= StopItRequired )
			RequestPlatformUnlock( StopItAchievementId, forceRequeue );
	}

	static void SyncIfLocalEarned( string id, string cookieKey, bool forceRequeue )
	{
		if ( !HasUnlockedLocal( id, cookieKey ) )
			return;

		// Already confirmed on platform for this install — leave it.
		if ( IsPlatformSynced( id ) )
			return;

		RequestPlatformUnlock( id, forceRequeue );
	}

	static string FormatCount( long n ) => n.ToString( "N0" );

	static void UnlockSpecial( string id )
	{
		foreach ( var special in SpecialAchievements )
		{
			if ( !special.Id.Equals( id, StringComparison.OrdinalIgnoreCase ) )
				continue;

			RequestPlatformUnlock( special.Id );
			TryUnlock( special.Id, special.CookieKey, special.Title, special.Body );
			return;
		}
	}

	static void EnsureKillAllLoaded()
	{
		if ( _killAllCount >= 0 )
			return;
		try { _killAllCount = Game.Cookies.Get( KillAllCountCookieKey, 0L ); }
		catch { _killAllCount = 0L; }
	}

	static int SumStopItPlays()
	{
		var total = 0;
		total = Math.Max( total, SoundPlayTracker.GetCount( "sounds/arnold/stopit" ) );
		total = Math.Max( total, SoundPlayTracker.GetCount( "sounds/arnold/stop-it" ) );
		total = Math.Max( total, SoundPlayTracker.GetCount( "sounds/arnold/stop it" ) );
		return total;
	}

	/// <summary>Sum play counts for every tracked sound id whose path/stem contains any needle.</summary>
	static int SumStemContains( params string[] needles )
	{
		SoundPlayTracker.EnsureLoaded();
		// Walk known pads via SoundPlayTracker cookie map is private — use folder scans of GetCount
		// for common ids isn't enough. Use Total via public GetCount on AllUniquePads.
		var total = 0;
		foreach ( var pad in SoundBoardData.AllUniquePads )
		{
			if ( pad is null || string.IsNullOrWhiteSpace( pad.SoundId ) )
				continue;
			var hay = PadHaystack( pad );
			var hit = false;
			foreach ( var n in needles )
			{
				if ( string.IsNullOrWhiteSpace( n ) )
					continue;
				if ( hay.Contains( n, StringComparison.OrdinalIgnoreCase ) )
				{
					hit = true;
					break;
				}
			}
			if ( hit )
				total += SoundPlayTracker.GetCount( pad.SoundId );
		}
		return total;
	}

	static int SumFolderPlays( string folder )
	{
		SoundPlayTracker.EnsureLoaded();
		var prefix = $"sounds/{folder}/";
		var total = 0;
		foreach ( var pad in SoundBoardData.AllUniquePads )
		{
			if ( pad?.SoundId is null )
				continue;
			var id = pad.SoundId.Replace( '\\', '/' );
			if ( id.StartsWith( prefix, StringComparison.OrdinalIgnoreCase ) )
				total += SoundPlayTracker.GetCount( pad.SoundId );
		}
		return total;
	}

	static string PadHaystack( SoundPad pad )
	{
		var id = (pad.SoundId ?? "").Replace( '\\', '/' );
		var label = pad.Label ?? "";
		return $"{id} {label}";
	}

	static string PadStem( SoundPad pad )
	{
		var id = (pad.SoundId ?? "").Replace( '\\', '/' ).Trim();
		var slash = id.LastIndexOf( '/' );
		return slash >= 0 && slash < id.Length - 1 ? id[(slash + 1)..] : id;
	}

	static bool MatchesPad( SoundPad pad, params string[] needles )
	{
		var hay = PadHaystack( pad );
		var stem = PadStem( pad );
		var compactStem = stem.Replace( " ", "" ).Replace( "-", "" ).Replace( "_", "" );
		foreach ( var n in needles )
		{
			if ( string.IsNullOrWhiteSpace( n ) )
				continue;
			if ( hay.Contains( n, StringComparison.OrdinalIgnoreCase ) )
				return true;
			var compact = n.Replace( " ", "" ).Replace( "-", "" ).Replace( "_", "" ).Replace( "'", "" );
			if ( compactStem.Equals( compact, StringComparison.OrdinalIgnoreCase ) )
				return true;
		}
		return false;
	}

	static bool LiveContains( IList<string> ids, IList<string> labels, string needle )
	{
		if ( string.IsNullOrWhiteSpace( needle ) )
			return false;
		if ( ids is not null )
		{
			foreach ( var id in ids )
			{
				if ( !string.IsNullOrWhiteSpace( id ) && id.Contains( needle, StringComparison.OrdinalIgnoreCase ) )
					return true;
			}
		}
		if ( labels is not null )
		{
			foreach ( var label in labels )
			{
				if ( !string.IsNullOrWhiteSpace( label ) && label.Contains( needle, StringComparison.OrdinalIgnoreCase ) )
					return true;
			}
		}
		return false;
	}

	static int CountLiveFolder( IList<string> ids, string folder )
	{
		if ( ids is null || string.IsNullOrWhiteSpace( folder ) )
			return 0;
		var token = $"/{folder}/";
		var count = 0;
		foreach ( var id in ids )
		{
			if ( string.IsNullOrWhiteSpace( id ) )
				continue;
			var path = id.Replace( '\\', '/' );
			if ( path.Contains( token, StringComparison.OrdinalIgnoreCase )
				|| path.StartsWith( $"sounds/{folder}/", StringComparison.OrdinalIgnoreCase ) )
				count++;
		}
		return count;
	}

	static int CountLiveGas( IList<string> ids, IList<string> labels )
	{
		// Prefer sound ids (unique live pads). Labels are a fallback only.
		var count = 0;
		if ( ids is not null && ids.Count > 0 )
		{
			foreach ( var id in ids )
			{
				if ( string.IsNullOrWhiteSpace( id ) )
					continue;
				if ( IsGasText( id ) )
					count++;
			}
			return count;
		}
		if ( labels is not null )
		{
			foreach ( var label in labels )
			{
				if ( IsGasText( label ) )
					count++;
			}
		}
		return count;
	}

	static bool IsGasText( string text )
	{
		if ( string.IsNullOrWhiteSpace( text ) )
			return false;
		return text.Contains( "fart", StringComparison.OrdinalIgnoreCase )
			|| text.Contains( "burp", StringComparison.OrdinalIgnoreCase )
			|| text.Contains( "poop", StringComparison.OrdinalIgnoreCase )
			|| text.Contains( "flatul", StringComparison.OrdinalIgnoreCase )
			|| text.Contains( "/gas/", StringComparison.OrdinalIgnoreCase )
			|| text.Contains( "sounds/gas/", StringComparison.OrdinalIgnoreCase );
	}

	static bool IsStopItPad( SoundPad pad )
	{
		if ( string.IsNullOrWhiteSpace( pad.SoundId ) )
			return false;

		var stem = PadStem( pad );
		if ( stem.Equals( "stopit", StringComparison.OrdinalIgnoreCase )
			|| stem.Equals( "stop-it", StringComparison.OrdinalIgnoreCase )
			|| stem.Equals( "stop_it", StringComparison.OrdinalIgnoreCase )
			|| stem.Equals( "stop it", StringComparison.OrdinalIgnoreCase ) )
			return true;

		var label = pad.Label?.Trim() ?? "";
		var compact = label.Replace( " ", "" ).Replace( "-", "" ).Replace( "_", "" );
		return compact.Equals( "STOPIT", StringComparison.OrdinalIgnoreCase );
	}

	static void TryUnlock( string id, string cookieKey, string title, string body )
	{
		if ( TryUnlockLocal( id, cookieKey ) )
			ShowToast( title, body );
	}

	static bool TryUnlockLocal( string achievementId, string cookieKey )
	{
		if ( HasUnlockedLocal( achievementId, cookieKey ) )
			return false;

		MarkUnlockedLocal( achievementId, cookieKey );
		return true;
	}

	static bool HasUnlockedLocal( string achievementId, string cookieKey )
	{
		if ( _unlockedLocal.Contains( achievementId ) )
			return true;

		if ( CookieSaysUnlocked( cookieKey ) )
		{
			_unlockedLocal.Add( achievementId );
			return true;
		}

		return false;
	}

	static bool CookieSaysUnlocked( string cookieKey )
	{
		try
		{
			return Game.Cookies.Get( cookieKey, false );
		}
		catch
		{
			return false;
		}
	}

	static void MarkUnlockedLocal( string achievementId, string cookieKey )
	{
		_unlockedLocal.Add( achievementId );
		try { Game.Cookies.Set( cookieKey, true ); }
		catch { /* session-only */ }
	}

	static string PlatformCookieKey( string achievementId )
		=> PlatformSyncedPrefix + achievementId;

	static bool IsPlatformSynced( string achievementId )
	{
		if ( _platformSynced.Contains( achievementId ) )
			return true;

		try
		{
			if ( Game.Cookies.Get( PlatformCookieKey( achievementId ), false ) )
			{
				_platformSynced.Add( achievementId );
				return true;
			}
		}
		catch { /* ignore */ }

		return false;
	}

	static void MarkPlatformSynced( string achievementId )
	{
		_platformSynced.Add( achievementId );
		try { Game.Cookies.Set( PlatformCookieKey( achievementId ), true ); }
		catch { /* session-only */ }
	}

	/// <summary>
	/// Queue a platform unlock. Does nothing if already platform-synced (cookie).
	/// forceRequeue resets attempt/backoff so a paused id can try again this session.
	/// </summary>
	static void RequestPlatformUnlock( string achievementId, bool forceRequeue = false )
	{
		if ( string.IsNullOrWhiteSpace( achievementId ) )
			return;

		// Only skip when Unlock already returned without error (cookie set).
		// Local toast cookies never block this path — that was the fifty_deep bug.
		if ( IsPlatformSynced( achievementId ) )
			return;

		if ( forceRequeue )
		{
			_platformAttempts.Remove( achievementId );
			_platformNextTryAt.Remove( achievementId );
		}

		if ( _platformQueued.Contains( achievementId ) )
			return;

		_platformQueued.Add( achievementId );
		_platformQueue.Enqueue( achievementId );
	}

	static void PumpPlatformUnlocks()
	{
		// Wait for services after boot.
		if ( _sinceStart < InitialSyncDelay )
			return;

		if ( _platformQueue.Count == 0 )
			return;

		// Find next ready item (backoff).
		var skipped = 0;
		var maxSkip = _platformQueue.Count;
		while ( skipped < maxSkip && _platformQueue.Count > 0 )
		{
			var id = _platformQueue.Dequeue();
			_platformQueued.Remove( id );

			if ( IsPlatformSynced( id ) )
				continue;

			if ( _platformNextTryAt.TryGetValue( id, out var nextAt ) && Time.Now < nextAt )
			{
				// Not ready yet — put back and try another.
				_platformQueued.Add( id );
				_platformQueue.Enqueue( id );
				skipped++;
				continue;
			}

			TryPlatformUnlockNow( id );
			return; // one attempt per tick
		}
	}

	static void TryPlatformUnlockNow( string id )
	{
		var attempts = _platformAttempts.TryGetValue( id, out var n ) ? n + 1 : 1;
		_platformAttempts[id] = attempts;

		try
		{
			Achievements.Unlock( id );
			MarkPlatformSynced( id );
			_platformAttempts.Remove( id );
			_platformNextTryAt.Remove( id );
			Log.Info( $"[S&Soundboard] Platform achievement unlock OK: {id}" );
		}
		catch ( Exception e )
		{
			var delay = PlatformRetryBaseSec * MathF.Min( attempts, 10 ); // 1.5s, 3s, … up to 15s
			_platformNextTryAt[id] = Time.Now + delay;

			Log.Warning( $"[S&Soundboard] Platform unlock failed ({attempts}/{MaxPlatformAttempts}): {id} — {e.GetType().Name}: {e.Message}" );

			if ( attempts < MaxPlatformAttempts )
			{
				_platformQueued.Add( id );
				_platformQueue.Enqueue( id );
			}
			else
			{
				// Don't mark synced — next session / periodic resync will try again.
				_platformAttempts.Remove( id );
				_platformNextTryAt.Remove( id );
				Log.Warning( $"[S&Soundboard] Pausing platform unlock for '{id}' this session. Will retry on next resync. Confirm Manual ident '{id}' on the package dashboard." );
			}
		}
	}

	static void ShowToast( string title, string body )
	{
		ToastTitle = title;
		ToastBody = body;
		ToastVisible = true;
		ToastAge = 0;
		StateChanged?.Invoke();
	}

	readonly record struct VoiceMilestone( int Required, string Id, string CookieKey, string Title, string Body );
	readonly record struct ClickMilestone( long Required, string Id, string CookieKey, string Title, string Body );
	readonly record struct LoopMilestone( long Required, string Id, string CookieKey, string Title, string Body );
	readonly record struct SpecialAchievement( string Id, string CookieKey, string Title, string Body );
}