UI/CausalIntroVideo.razor
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits PanelComponent
@namespace Causal

<root class="@VideoClass" @onclick=@Finish>
	<div class="video-frame" @ref="_frame"></div>
	<label class="skip-hint">Left click to skip intro</label>
</root>

@code
{
	[Property] public string VideoPath { get; set; } = "videos/filamentsintro.webm";
	[Property] public float FadeSeconds { get; set; } = 0.4f;
	[Property] public float CleanupDelay { get; set; } = 0.5f;
	[Property] public float Volume { get; set; } = 1f;
	[Property] public float LoadGraceSeconds { get; set; } = 2f;

	private VideoPlayer _player;
	private Panel _frame;
	private bool _finished;
	private bool _textureSet;
	private bool _missingScheduled;
	private TimeSince _timeSinceStart;
	private int _generation;

	private string VideoClass => _finished ? "done" : "";

	protected override void OnStart()
	{
		_generation++;
		_timeSinceStart = 0;

		if ( string.IsNullOrWhiteSpace( VideoPath ) || !FileSystem.Mounted.FileExists( VideoPath ) )
		{
			Log.Warning( $"CausalIntroVideo found no video at '{VideoPath}', skipping to menu." );
			_missingScheduled = true;
			return;
		}

		_player = new VideoPlayer();
		_player.Audio.Volume = Volume;
		_player.OnFinished += Finish;
		_player.Play( FileSystem.Mounted, VideoPath );
	}

	protected override void OnUpdate()
	{
		if ( _missingScheduled )
		{
			_missingScheduled = false;
			Finish();
			return;
		}

		if ( _finished || _player is null )
		{
			return;
		}

		_player.Present();

		if ( !_textureSet )
		{
			if ( _player.Width > 4 && _frame.IsValid() )
			{
				_frame.Style.SetBackgroundImage( _player.Texture );
				_textureSet = true;
			}
			else if ( _timeSinceStart > LoadGraceSeconds )
			{
				Log.Warning( $"CausalIntroVideo failed to decode '{VideoPath}', skipping to menu." );
				Finish();
				return;
			}
		}

		if ( Input.Pressed( "Jump" ) || Input.Pressed( "Attack1" ) || Input.Pressed( "Use" ) || Input.EscapePressed )
		{
			Finish();
		}
	}

	protected override void OnDestroy()
	{
		_generation++;
		TeardownPlayer();
	}

	private void Finish()
	{
		if ( _finished )
		{
			return;
		}

		_finished = true;
		StateHasChanged();

		var manager = CausalGameManager.Instance;
		if ( manager.IsValid() )
		{
			manager.EnterMenu();
		}

		int gen = _generation;
		float delay = (FadeSeconds <= 0f ? 0f : FadeSeconds) + (CleanupDelay <= 0f ? 0f : CleanupDelay);
		HideAfterDelay( gen, delay );
	}

	private async void HideAfterDelay( int gen, float delay )
	{
		if ( delay > 0f )
		{
			await GameTask.DelaySeconds( delay );
		}

		if ( _generation != gen )
		{
			return;
		}

		if ( !GameObject.IsValid() )
		{
			return;
		}

		TeardownPlayer();
		GameObject.Enabled = false;
	}

	private void TeardownPlayer()
	{
		if ( _player is null )
		{
			return;
		}

		_player.OnFinished -= Finish;
		_player.Stop();
		_player.Dispose();
		_player = null;
	}

	protected override int BuildHash() => HashCode.Combine( _finished );
}