UI/MedalStandingsPanel.razor
@using Sandbox
@using Sandbox.UI
@using System
@using System.Linq
@inherits Panel
@namespace BlockParty
@attribute [StyleSheet( "MedalStandingsPanel.razor.scss" )]

<root class="@(Expanded ? "expanded" : "")">
	<PixelTooltip @ref="TooltipLayer"></PixelTooltip>
	@if ( Expanded )
	{
		<div class="shade" onclick=@Close></div>
	}
	@if ( !HasLetterbox && !Expanded )
	{
		<button class="compact-trigger" onclick=@Open>MEDALS</button>
		<button class="compact-trigger completion-trigger" onclick=@OpenCompletions>LEVELS BEATEN</button>
	}
	else
	{
		<div class="@(Expanded ? "standings-content" : "standings-rail")" style=@CardStyle>
		@if ( !Expanded || !CompletionsExpanded )
		{
		<div class="medal-card podium-card">
			<div class="heading">
				<label class="title">MEDALS</label>
				@if ( Expanded )
				{
					<button class="square-btn i-close close" onclick=@Close><div class="img"></div></button>
				}
			</div>
			@if ( Current is not null && (!Expanded || Current.Players.Count > 0) )
			{
			<div class="columns">
				<div class="column-spacer"></div>
				<div class="medal gold"><MedalIcon Place=@(1) OnHover=@Tip( AwardPoints.Tip( 1 ) ) Size=@(Expanded ? 44 : 32)></MedalIcon></div>
				<div class="medal silver"><MedalIcon Place=@(2) OnHover=@Tip( AwardPoints.Tip( 2 ) ) Size=@(Expanded ? 44 : 32)></MedalIcon></div>
				<div class="medal bronze"><MedalIcon Place=@(3) OnHover=@Tip( AwardPoints.Tip( 3 ) ) Size=@(Expanded ? 44 : 32)></MedalIcon></div>
				@if ( Expanded ) { <div class="points"></div> }
			</div>
			}
			@if ( Current is not MedalSnapshot snapshot )
			{
				<label class="notice">@Notice</label>
			}
			else if ( snapshot.Players.Count == 0 )
			{
				<label class="notice empty-medals">No medals held yet.</label>
			}
			else
			{
				<div class="medal-list">
					@foreach ( var player in snapshot.Players.Take( Expanded ? snapshot.Players.Count : 5 ) )
					{
						<div class="medal-row @(player.AccountId == (long)Game.SteamId ? "you" : "")">
							<label class="rank">@player.Rank</label>
							<SteamAvatar [email protected] Size=@(Expanded ? 36 : 28)></SteamAvatar>
							<LiteralLabel class="name" [email protected] />
							<label class="medal gold @(player.Gold == 0 ? "zero" : "")">@player.Gold</label>
							<label class="medal silver @(player.Silver == 0 ? "zero" : "")">@player.Silver</label>
							<label class="medal bronze @(player.Bronze == 0 ? "zero" : "")">@player.Bronze</label>
							@if ( Expanded ) { <label class="points" onmouseover=@Tip( "Total points" )>@player.Points</label> }
						</div>
					}
				</div>
				@if ( !Expanded && snapshot.Players.Skip( 5 ).FirstOrDefault( player => player.AccountId == (long)Game.SteamId ) is MedalPlayer me )
				{
					<div class="medal-row you personal">
						<label class="rank">@me.Rank</label>
						<label class="name">YOU</label>
						<label class="medal gold @(me.Gold == 0 ? "zero" : "")">@me.Gold</label>
						<label class="medal silver @(me.Silver == 0 ? "zero" : "")">@me.Silver</label>
						<label class="medal bronze @(me.Bronze == 0 ? "zero" : "")">@me.Bronze</label>
					</div>
				}
			}
			@if ( Expanded )
			{
				@if ( Current is not null )
				{
					<label class="freshness">@Freshness</label>
				}
			}
			else
			{
				<button class="view-all" onclick=@Open>VIEW MORE</button>
			}
		</div>
		}
		@if ( !Expanded || CompletionsExpanded )
		{
			<CompletionStandingsCard Expanded=@Expanded OnOpen=@OpenCompletions OnClose=@Close></CompletionStandingsCard>
		}
		@if ( Expanded && !CompletionsExpanded )
		{
			@* Keep the picker above the entire card, outside the heading's stacking layer. *@
			<div class="medal-character-picker">
				<CharacterPicker LeaderboardMode=@true CenterOverlayOnScreen=@true
					Title="WHO DO YOU WANT TO SEE?" GetSelectedId=@(() => _characterId)
					OnPick=@SelectCharacter OnClear=@(() => SelectCharacter( null ))
					OnOpenChanged=@PickerOpenChanged @ref="_picker"></CharacterPicker>
			</div>
		}
		</div>
	}
</root>

@code
{
	// Keep the tooltip outside the scrolling rows so it cannot be clipped.
	private PixelTooltip TooltipLayer { get; set; }
	private Panel _tipPanel;
	private string _tipText;
	private Action<PanelEvent> Tip( string text ) => e => { _tipPanel = e.This; _tipText = text; };
	private void UpdateTooltip()
	{
		if ( !_pickerOpen && (!Expanded || !CompletionsExpanded) && _tipPanel.IsValid() && _tipPanel.HasHovered ) TooltipLayer?.Show( _tipText );
		else TooltipLayer?.Hide();
	}

	public bool Expanded { get; private set; }
	private bool CompletionsExpanded { get; set; }
	private string _characterId;
	private CharacterPicker _picker { get; set; }
	private bool _pickerOpen;
	private string ActiveCharacterId => Expanded && !CompletionsExpanded ? _characterId : null;
	private MedalSnapshot Current => MedalStandings.GetSnapshot( ActiveCharacterId );
	private string Notice => MedalStandings.GetNotice( ActiveCharacterId );
	// Wheel over this overlay must never walk the level-select spine behind it. The engine sends the
	// wheel to the hovered panel and bubbles it up the parents, so swallowing it here covers every
	// pointer-events child (rail, cards, triggers, expanded shade) — the rail's own overflow scroll
	// already consumed it before it got this far. Deliberately NOT a HasHovered test on the screen
	// side: :hover latches on a panel's ancestors when the hovered child is deleted mid-hover (any
	// razor rebuild), so it can read true forever and silently eat the map's wheel.
	public override void OnMouseWheel( Vector2 value ) { }
	private float ViewWidth => 1080f * Screen.Width / Math.Max( 1f, Screen.Height );
	private float LetterboxWidth => (ViewWidth - 1080f) * 0.5f;
	private bool HasLetterbox => LetterboxWidth >= 320f;
	private double AgeMinutes => Current is null ? 0 : Math.Max( 0, (DateTimeOffset.UtcNow - Current.UpdatedAt).TotalMinutes );
	private string Freshness => $"Updated {(AgeMinutes < 1 ? "just now" : AgeMinutes < 60 ? $"{(int)AgeMinutes}m ago" : $"{(int)(AgeMinutes / 60)}h ago")}";
	private string CardStyle => Expanded
		? $"width:{Px( Math.Min( CompletionsExpanded ? 720f : 820f, ViewWidth - 64f ) )};"
		: $"width:{Px( Math.Min( 360f, LetterboxWidth - 40f ) )};right:{Px( (LetterboxWidth - Math.Min( 360f, LetterboxWidth - 40f )) * 0.5f )};";
	private static string Px( float value ) => value.ToString( "0.###", System.Globalization.CultureInfo.InvariantCulture ) + "px";
	private void Open() { CompletionsExpanded = false; Expanded = true; StateHasChanged(); }
	private void OpenCompletions() { CompletionsExpanded = true; Expanded = true; StateHasChanged(); }
	private void SelectCharacter( string id )
	{
		if ( id is not null && !Characters.TryGet( id, out _ ) ) return;
		_characterId = id;
		MedalStandings.Tick( id );
		StateHasChanged();
	}
	private void PickerOpenChanged( bool open ) { _pickerOpen = open; StateHasChanged(); }
	public void Close()
	{
		if ( _pickerOpen ) { _picker?.RequestClose(); _pickerOpen = false; }
		else Expanded = false;
		StateHasChanged();
	}
	public override void Tick()
	{
		UpdateTooltip();
		MedalStandings.Tick();
		if ( ActiveCharacterId is not null ) MedalStandings.Tick( ActiveCharacterId );
		CompletionStandings.Tick();
	}
	protected override int BuildHash() => HashCode.Combine( Expanded, CompletionsExpanded, MedalStandings.Revision, (int)ViewWidth, (int)AgeMinutes, Notice, _characterId, _pickerOpen );
}