UI/FittingScreen.razor
@using System;
@using Sandbox;
@using Sandbox.UI;

@* The Fitting Room: move, size, turn or mirror any S&S2 piece on the gladiator and save it.
   Saved to fitting.json in the game's data folder; only what you changed is written. *@
<root class="screen">
	<div class="sas-panel fit-panel">
		<div class="sas-label title">fitting room</div>

		<div class="cats">
			@foreach ( var c in Cats )
			{
				var cat = c;
				<div class="cat @(Cat == cat.Name ? "active" : "")" onclick=@(() => { Cat = cat.Name; Index = 0; StateHasChanged(); })>@cat.Name</div>
			}
		</div>

		<div class="row">
			<div class="sas-arrow left" onclick=@(() => Step( -1 ))></div>
			<div class="sas-arrow right" onclick=@(() => Step( 1 ))></div>
			<div class="key">@($"{Key}   ({Index + 1}/{Keys.Count})")</div>
		</div>

		@foreach ( var c in Controls )
		{
			var ctl = c;
			<div class="row">
				<div class="lbl">@ctl.Label</div>
				<div class="val">@ctl.Value( E )</div>
				<div class="small-btn" onclick=@(() => Move( ctl.What, -ctl.Step * 10 ))>--</div>
				<div class="small-btn" onclick=@(() => Move( ctl.What, -ctl.Step ))>-</div>
				<div class="small-btn" onclick=@(() => Move( ctl.What, ctl.Step ))>+</div>
				<div class="small-btn" onclick=@(() => Move( ctl.What, ctl.Step * 10 ))>++</div>
			</div>
		}
		<div class="row">
			<div class="lbl">mirror</div>
			<div class="small-btn @(E.Flip ? "on" : "")" onclick=@(() => Edit( E with { Flip = !E.Flip } ))>@(E.Flip ? "yes" : "no")</div>
			<div class="small-btn" onclick=@(() => { Fit.Reset( Key ); StateHasChanged(); })>reset this</div>
		</div>

		<div class="row poses">
			@foreach ( var a in Anims )
			{
				var anim = a;
				<div class="small-btn @(Anim == anim ? "on" : "")" onclick=@(() => { Anim = anim; StateHasChanged(); })>@anim</div>
			}
		</div>

		<div class="help">
			Drag the gladiator with the mouse, or use the buttons (arrow keys move too, hold shift for 10).
			Joints move a whole limb; left-side numbers are mirrored onto the right side.
		</div>

		<div class="row">
			<div class="btn" onclick=@Save>save</div>
			<div class="btn" onclick=@(() => GameManager.Instance.Screen = GameScreen.Title)>back</div>
		</div>
		<div class="saved">@Saved</div>
	</div>

	<div class="drag-area" onmousedown=@StartDrag onmouseup=@(() => dragging = false)></div>
</root>

@code
{
	record CatDef( string Name, Func<List<string>> Keys );

	static List<string> Range( string prefix, int count ) => Enumerable.Range( 1, count ).Select( i => $"{prefix}.{i}" ).ToList();

	static readonly CatDef[] Cats =
	{
		new( "body", () => new() { "body.head", "body.torso", "body.uarm", "body.farm", "body.thigh", "body.shin", "body.foot" } ),
		new( "joints", () => new() { "joint.root", "joint.neck", "joint.shoulder", "joint.elbow", "joint.hand", "joint.shield", "joint.hip", "joint.knee", "joint.ankle" } ),
		new( "face", () => new() { "face" } ),
		new( "race", () => new() { "race.elf", "race.orc", "race.devil", "race.undead" } ),
		new( "hair", () => Range( "hair", Catalog.HairCount ) ),
		new( "beard", () => Range( "beard", Catalog.BeardCount ) ),
		new( "helmets", () => Range( "helm", Catalog.Gear( GearSlot.Helm ).Length ) ),
		new( "chest", () => Range( "chest", Catalog.Gear( GearSlot.Chest ).Length ) ),
		new( "shoulders", () => Range( "shoulder", Catalog.Gear( GearSlot.Shoulders ).Length ) ),
		new( "gauntlets", () => Range( "gauntlet", Catalog.Gear( GearSlot.Gauntlets ).Length ) ),
		new( "thighs", () => Range( "thigh", Catalog.Gear( GearSlot.Thighs ).Length ) ),
		new( "greaves", () => Range( "greave", Catalog.Gear( GearSlot.Greaves ).Length ) ),
		new( "boots", () => Range( "boot", Catalog.Gear( GearSlot.Boots ).Length ) ),
		new( "shields", () => Range( "shield", Catalog.Gear( GearSlot.Shield ).Length ) ),
		new( "weapons", () => Range( "weapon", Catalog.Weapons.Length - 1 ) ),
	};

	static readonly string[] Anims = { "idle", "walk", "quick", "normal", "power", "taunt", "rest", "win", "chained" };

	string Cat = "body";
	int Index;
	string Anim = "idle";
	string Saved;

	bool dragging;
	Vector2 dragStart;
	FitEntry dragFrom;

	List<string> Keys => Cats.First( c => c.Name == Cat ).Keys();
	string Key => Keys[Math.Clamp( Index, 0, Keys.Count - 1 )];
	FitEntry E => Fit.Get( Key );
	int Number => int.TryParse( Key.Split( '.' ).Last(), out var n ) ? n : 0;

	void Step( int d )
	{
		Index = ((Index + d) % Keys.Count + Keys.Count) % Keys.Count;
		StateHasChanged();
	}

	void Edit( FitEntry e )
	{
		Fit.Set( Key, e );
		Saved = null;
		StateHasChanged();
	}

	void Move( string what, float by )
	{
		var e = E;
		Edit( what switch
		{
			"x" => e with { X = e.X + by },
			"y" => e with { Y = e.Y + by },
			"s" => e with { Scale = MathF.Max( 0.1f, e.Scale + by ) },
			_ => e with { Turn = e.Turn + by }
		} );
	}

	record Control( string Label, string What, float Step, Func<FitEntry, string> Value );

	static readonly Control[] Controls =
	{
		new( "left/right", "x", 1f, e => e.X.ToString( "0.0" ) ),
		new( "up/down", "y", 1f, e => e.Y.ToString( "0.0" ) ),
		new( "size", "s", 0.02f, e => e.Scale.ToString( "0.00" ) ),
		new( "turn", "t", 1f, e => e.Turn.ToString( "0" ) ),
	};

	void Save()
	{
		Fit.Save();
		Saved = $"Saved to {Fit.FileName} in the game's data folder.";
		StateHasChanged();
	}

	/// <summary>The gladiator wearing whatever you're fitting.</summary>
	Look Preview
	{
		get
		{
			var l = Profile.Current?.Look ?? Look.Default;
			if ( Cat is "body" or "joints" or "face" )
			{
				foreach ( var s in Catalog.Slots ) l.Set( s, -1 );
				l.Weapon = 1;
				return l;
			}

			int n = Number;
			switch ( Cat )
			{
				case "race": l.Race = Array.FindIndex( Catalog.Races, r => r.Name.ToLower() == Key.Split( '.' ).Last() ); l.Skin = 0; break;
				case "hair": l.Hair = n; l.Helm = -1; break;
				case "beard": l.Beard = n; break;
				case "helmets": l.Helm = n - 1; break;
				case "chest": l.Chest = n - 1; break;
				case "shoulders": l.Shoulders = n - 1; break;
				case "gauntlets": l.Gauntlets = n - 1; break;
				case "thighs": l.Thighs = n - 1; break;
				case "greaves": l.Greaves = n - 1; break;
				case "boots": l.Boots = n - 1; break;
				case "shields": l.Shield = n - 1; break;
				case "weapons": l.Weapon = n; break;
			}
			return l;
		}
	}

	void StartDrag()
	{
		dragging = true;
		dragStart = Mouse.Position;
		dragFrom = E;
	}

	/// <summary>Rig pixels per screen pixel at the gladiator, from the 3D camera.</summary>
	float RigPerScreenPixel()
	{
		var w = World3D.Instance;
		var cam = Scene?.Camera;
		if ( !w.IsValid() || !cam.IsValid() || !w.Hero.IsValid() ) return 1f;
		var feet = w.Hero.Feet;
		var right = cam.WorldRotation.Right;
		var a = cam.PointToScreenPixels( feet );
		var b = cam.PointToScreenPixels( feet + right * (World3D.U * 100f) );
		float d = (b - a).Length;
		return d > 0.01f ? 100f / d : 1f;
	}

	public override void Tick()
	{
		base.Tick();

		var w = World3D.Instance;
		if ( w.IsValid() )
		{
			w.HeroLook = Preview;
			w.HeroAnim = Anim;
			w.HeroMood = Mood.Neutral;
		}

		if ( dragging )
		{
			var delta = (Mouse.Position - dragStart) * RigPerScreenPixel();
			Fit.Set( Key, dragFrom with { X = dragFrom.X + delta.x, Y = dragFrom.Y + delta.y } );
			StateHasChanged();
		}

		float step = Input.Keyboard.Down( "shift" ) ? 10f : 1f;
		if ( Input.Keyboard.Pressed( "leftarrow" ) ) Move( "x", -step );
		if ( Input.Keyboard.Pressed( "rightarrow" ) ) Move( "x", step );
		if ( Input.Keyboard.Pressed( "uparrow" ) ) Move( "y", -step );
		if ( Input.Keyboard.Pressed( "downarrow" ) ) Move( "y", step );
	}

	protected override int BuildHash() => HashCode.Combine( Cat, Index, Anim, Saved, Fit.Version );
}