LibraryStore.cs
namespace PixelPusher;

public sealed class LibraryItem
{
	public string Id { get; set; }
	public string Title { get; set; }
	public string Kind { get; set; }
	public string Type { get; set; }
	public string When { get; set; }
	public bool Local { get; set; } = true;
	public string File { get; set; }
}

public static class LibraryStore
{
	public const string TypeProject = "pxproject";
	public const string TypeSprite = "pxsprite";
	public const string TypeTileset = "pxtileset";
	public const string TypeStage = "pxstage";
	public const string KindProject = "pixel.project";
	public const string KindSprite = "pixel.sprite";
	public const string KindTileset = "pixel.tileset";
	public const string KindStage = "pixel.stage";

	public static List<LibraryItem> Items { get; } = new();
	public static int Version;

	public static void Refresh()
	{
		Items.Clear();
		try
		{
			foreach ( var entry in Storage.GetAll( TypeProject ) )
				Items.Add( FromEntry( entry, TypeProject ) );
			foreach ( var entry in Storage.GetAll( TypeSprite ) )
				Items.Add( FromEntry( entry, TypeSprite ) );
			foreach ( var entry in Storage.GetAll( TypeTileset ) )
				Items.Add( FromEntry( entry, TypeTileset ) );
			foreach ( var entry in Storage.GetAll( TypeStage ) )
				Items.Add( FromEntry( entry, TypeStage ) );
		}
		catch ( Exception e )
		{
			Log.Info( $"[PIXEL PUSHER] Storage list: {e.Message}" );
		}

		TryAddLocal( "pxproject.json", TypeProject, KindProject, "local project" );
		TryAddLocal( "pxsprite.json", TypeSprite, KindSprite, "local sprite" );
		TryAddLocal( "pxtileset.json", TypeTileset, KindTileset, "local tileset" );
		TryAddLocal( "pxstage.json", TypeStage, KindStage, "local stage" );

		for ( var i = StarterPack.Shelf.Length - 1; i >= 0; i-- )
		{
			var s = StarterPack.Shelf[i];
			if ( Items.Exists( x => x.Id == s.Id ) )
				continue;
			Items.Insert( 0, new LibraryItem
			{
				Id = s.Id,
				Title = s.Title,
				Kind = s.Kind,
				Type = TypeOfKind( s.Kind ),
				When = s.When,
				Local = true
			} );
		}

		Version++;
		GameFlow.Bump();
	}

	public static void SaveCurrent( string kind )
	{
		Desk.Autosave( quiet: true );
		kind ??= TypeProject;
		var title = Desk.Name ?? "untitled";

		try
		{
			var entry = Storage.CreateEntry( kind );
			switch ( kind )
			{
				case TypeSprite:
					entry.Files.WriteJson( "sprite.json", Desk.Sprite.ToSave() );
					break;
				case TypeTileset:
					entry.Files.WriteJson( "tileset.json", Desk.Tileset.ToSave() );
					break;
				case TypeStage:
					entry.Files.WriteJson( "stage.json", Desk.Stage.ToSave() );
					break;
				default:
					kind = TypeProject;
					entry.Files.WriteJson( "project.json", Desk.ToSave() );
					break;
			}

			entry.SetMeta( "title", title );
			entry.SetMeta( "kind", KindOf( kind ) );
			GameFlow.ShowToast( $"saved to desk · {title}" );
		}
		catch ( Exception e )
		{
			try
			{
				var file = kind.Replace( '.', '_' ) + ".json";
				FileSystem.Data.WriteJson( file, Desk.ToSave() );
				GameFlow.ShowToast( $"saved locally · {title}" );
			}
			catch
			{
				GameFlow.ShowToast( "save failed" );
				Log.Warning( $"[PIXEL PUSHER] save: {e.Message}" );
			}
		}

		Refresh();
	}

	public static void PublishCurrent()
	{
		Desk.Autosave( quiet: true );
		try
		{
			var entry = Storage.CreateEntry( TypeProject );
			entry.Files.WriteJson( "project.json", Desk.ToSave() );
			entry.SetMeta( "title", Desk.Name ?? GameInfo.Title );
			entry.SetMeta( "kind", KindProject );
			entry.SetMeta( "rank", GameInfo.Rank );
			entry.Publish();
			GameFlow.ShowToast( "publish dialog opened" );
		}
		catch ( Exception e )
		{
			SaveCurrent( TypeProject );
			GameFlow.ShowToast( "published locally — workshop later" );
			Log.Info( $"[PIXEL PUSHER] publish: {e.Message}" );
		}
		Refresh();
	}

	public static void LoadStarter()
	{
		LoadShelf( "starter" );
	}

	public static bool LoadShelf( string id )
	{
		if ( !StarterPack.LoadShelf( id ) )
			return false;
		GameFlow.ShowToast( $"opened  {Desk.Name}" );
		GameFlow.Bump();
		return true;
	}

	public static void Open( LibraryItem item )
	{
		if ( item is null )
			return;

		if ( StarterPack.LoadShelf( item.Id ) )
		{
			GameFlow.ShowToast( $"opened  {Desk.Name}" );
			if ( item.Kind == KindSprite )
				GameFlow.OpenStudio( GameScreen.Pixel );
			else
				GameFlow.OpenStudio( GameScreen.Stage );
			Refresh();
			return;
		}

		try
		{
			if ( !string.IsNullOrWhiteSpace( item.File ) && FileSystem.Data.FileExists( item.File ) )
			{
				ApplyFile( item.Type, item.File );
				return;
			}

			foreach ( var entry in Storage.GetAll( item.Type ) )
			{
				if ( entry.Id.ToString() != item.Id )
					continue;
				ApplyEntry( entry, item.Type );
				return;
			}
		}
		catch ( Exception e )
		{
			Log.Warning( $"[PIXEL PUSHER] open: {e.Message}" );
		}

		GameFlow.ShowToast( "could not open" );
	}

	static void ApplyEntry( Storage.Entry entry, string type )
	{
		switch ( type )
		{
			case TypeSprite:
				Desk.Sprite = PixelDoc.FromSave( entry.Files.ReadJson<PixelSave>( "sprite.json" ) );
				GameFlow.OpenStudio( GameScreen.Pixel );
				break;
			case TypeTileset:
				Desk.Tileset = TilesetDoc.FromSave( entry.Files.ReadJson<TilesetSave>( "tileset.json" ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			case TypeStage:
				Desk.Stage = StageDoc.FromSave( entry.Files.ReadJson<StageSave>( "stage.json" ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			default:
				Desk.Apply( entry.Files.ReadJson<ProjectSave>( "project.json" ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
		}

		Desk.Touch();
		GameFlow.ShowToast( $"opened · {Desk.Name}" );
		Refresh();
	}

	static void ApplyFile( string type, string file )
	{
		switch ( type )
		{
			case TypeSprite:
				Desk.Sprite = PixelDoc.FromSave( FileSystem.Data.ReadJsonOrDefault<PixelSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Pixel );
				break;
			case TypeTileset:
				Desk.Tileset = TilesetDoc.FromSave( FileSystem.Data.ReadJsonOrDefault<TilesetSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			case TypeStage:
				Desk.Stage = StageDoc.FromSave( FileSystem.Data.ReadJsonOrDefault<StageSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
			default:
				Desk.Apply( FileSystem.Data.ReadJsonOrDefault<ProjectSave>( file, null ) );
				GameFlow.OpenStudio( GameScreen.Stage );
				break;
		}

		Desk.Touch();
		GameFlow.ShowToast( $"opened · {Desk.Name}" );
		Refresh();
	}

	static void TryAddLocal( string file, string type, string kind, string when )
	{
		try
		{
			if ( !FileSystem.Data.FileExists( file ) )
				return;
			if ( Items.Exists( i => i.File == file ) )
				return;
			Items.Add( new LibraryItem
			{
				Id = file,
				Title = file,
				Kind = kind,
				Type = type,
				When = when,
				Local = true,
				File = file
			} );
		}
		catch
		{
			// ignore
		}
	}

	static LibraryItem FromEntry( Storage.Entry entry, string kind )
	{
		var title = "untitled";
		try
		{
			title = entry.GetMeta( "title", title );
		}
		catch
		{
			// ignore
		}

		return new LibraryItem
		{
			Id = entry.Id.ToString(),
			Title = title,
			Kind = KindOf( kind ),
			Type = kind,
			When = entry.Created.ToString( "yyyy-MM-dd HH:mm" ),
			Local = true
		};
	}

	static string KindOf( string type ) => type switch
	{
		TypeSprite => KindSprite,
		TypeTileset => KindTileset,
		TypeStage => KindStage,
		_ => KindProject
	};

	static string TypeOfKind( string kind ) => kind switch
	{
		KindSprite => TypeSprite,
		KindTileset => TypeTileset,
		KindStage => TypeStage,
		_ => TypeProject
	};
}