Game/RunContext.cs
namespace BlockParty;

public enum RunMode
{
	Normal,
	Daily
}

/// <summary>Small immutable descriptor for the run currently being launched/scored.</summary>
public readonly record struct RunContext( RunMode Mode, string DailyId, string LevelId, string CharacterId = null )
{
	public static RunContext Normal => new( RunMode.Normal, null, null );
	public static RunContext Daily( string dailyId, string characterId = null ) => new( RunMode.Daily, dailyId, null, characterId );

	/// <summary>A normal run on a specific level, played as the given character (null → Original).</summary>
	public static RunContext ForLevel( string levelId, string characterId = null ) => new( RunMode.Normal, null, levelId, characterId );

	public bool IsDaily => Mode == RunMode.Daily && !string.IsNullOrEmpty( DailyId );
	public int Seed => IsDaily ? DailyChallenge.SeedFor( DailyId ) : 0;

	/// <summary>The level this run plays: a daily challenge plays its generated-for-the-day level
	/// (cached in <see cref="DailyLevels"/> — null while the template library hasn't mounted yet,
	/// like every other Get caller; launch gates keep that out of live stages); otherwise resolved
	/// from <see cref="LevelId"/> (null → classic).</summary>
	public LevelDef Level => IsDaily ? DailyLevels.Get( DailyId )?.Level : Levels.Get( LevelId ) ?? Levels.Classic;

	/// <summary>The character this run is played as, resolved from <see cref="CharacterId"/>
	/// (null/unknown → Original).</summary>
	public CharacterDef Character => Characters.Get( CharacterId );
}