UI/SpawnMenu/SaveMenu.razor
@using Sandbox;
@using Sandbox.UI;
@namespace Sandbox
@inherits Panel
@attribute [SpawnMenuHost.SpawnMenuMode<SpawnMenuHost.HostOnly>]
@attribute [Icon( "💾" )]
@attribute [Title( "#spawnmenu.tab.saves" )]
@attribute [Order( 150 )]

<root>
    <div class="save-window">
        <div class="save-header">💾 <label>#spawnmenu.saves.header</label></div>
        <div class="body">
            @if ( !SaveFiles.Any() )
            {
                <div class="empty-state">#spawnmenu.saves.empty</div>
            }
            @foreach ( var save in SaveFiles )
            {
                var s = save;
                <div class="save-card @(!s.IsCompatible ? "outdated" : "")"
                     @onclick="@(() => ConfirmLoad( s ))"
                     @onmousedown="@((PanelEvent e) => OnCardMouseDown( s, e ))">
                    @if ( s.Thumbnail != null )
                    {
                        <Image class="save-thumb" Texture="@s.Thumbnail" />
                    }
                    <div class="save-info @(s.Thumbnail == null ? "save-info--padded" : "")">
                        <label class="save-name">@s.DisplayName</label>
                        @if ( !string.IsNullOrEmpty( s.Timestamp ) )
                        {
                            <label class="save-timestamp">@s.Timestamp</label>
                        }
                        @if ( !s.IsCompatible )
                        {
                            <label class="save-outdated-badge">#spawnmenu.saves.outdated_badge</label>
                        }
                    </div>
                    <div class="save-dots">⋮</div>
                </div>
            }
        </div>
        <div class="footer">
            <TextEntry @ref="SaveNameEntry" Placeholder="#spawnmenu.saves.name_placeholder" @onsubmit="@OnSave"></TextEntry>
            <button @onclick="@OnSave">💾 <label>#spawnmenu.saves.save_button</label></button>
        </div>
    </div>
</root>

@code
{
    TextEntry SaveNameEntry;
    List<SaveFileEntry> SaveFiles = new();

    record SaveFileEntry( string FileName, string DisplayName, string Timestamp, Texture Thumbnail, bool IsCompatible );
    static string SavesPath => "saves";

    protected override void OnAfterTreeRender ( bool firstTime )
    {
        if ( firstTime )
        {
            RefreshSaveList();
        }
    }

    void RefreshSaveList()
    {
        SaveFiles.Clear();

        if ( !FileSystem.Data.DirectoryExists( SavesPath ) )
        {
            FileSystem.Data.CreateDirectory( SavesPath );
        }

        foreach ( var file in FileSystem.Data.FindFile( SavesPath, "*.sav" ) )
        {
            var filePath = $"{SavesPath}/{file}";
            var displayName = file;
            string timestamp = null;
            Texture thumbnail = null;

            var metadata = SaveSystem.GetFileMetadata( filePath );
            if ( metadata != null )
            {
                if ( metadata.TryGetValue( "Title", out var name ) )
                    displayName = name;
                if ( metadata.TryGetValue( "Timestamp", out var ts ) )
                    timestamp = ts;
            }

            var isCompatible = SaveSystem.GetFileSaveVersion( filePath ) == SaveSystem.SaveVersion;

            var thumbPath = $"{SavesPath}/{System.IO.Path.GetFileNameWithoutExtension( file )}.thumb.png";
            if ( FileSystem.Data.FileExists( thumbPath ) )
            {
                thumbnail = Texture.LoadFromFileSystem( thumbPath, FileSystem.Data );
            }

            SaveFiles.Add( new SaveFileEntry( file, displayName, timestamp, thumbnail, isCompatible ) );
        }

        StateHasChanged();
    }

    void OnCardMouseDown( SaveFileEntry save, PanelEvent e )
    {
        if ( e is MousePanelEvent { MouseButton: MouseButtons.Right } me )
        {
            me.StopPropagation();
            var menu = MenuPanel.Open( this );
            menu.AddOption( "download", "#spawnmenu.saves.load_context", () => ConfirmLoad( save ) );
            menu.AddOption( "delete", "#spawnmenu.saves.delete_context", () => ConfirmDelete( save ) );
        }
    }

    void ConfirmLoad( SaveFileEntry save )
    {
        if ( !save.IsCompatible )
        {
            _ = new StringQueryPopup
            {
                Title = "#spawnmenu.saves.incompatible_title",
                Prompt = Game.Language.GetPhrase( "spawnmenu.saves.incompatible_message", new() { { "name", save.DisplayName } } ),
                ConfirmLabel = "#spawnmenu.common.ok",
                ShowInput = false,
                Parent = FindPopupPanel()
            };
            return;
        }

        _ = new StringQueryPopup
        {
            Title = "#spawnmenu.saves.load_title",
            Prompt = Game.Language.GetPhrase( "spawnmenu.saves.load_confirm", new() { { "name", save.DisplayName } } ),
            ConfirmLabel = "#spawnmenu.saves.load_button",
            ShowInput = false,
            OnConfirm = _ => LoadSave( save.FileName ),
            Parent = FindPopupPanel()
        };
    }

    void ConfirmDelete( SaveFileEntry save )
    {
        var popup = new StringQueryPopup
        {
            Title = "#spawnmenu.saves.delete_title",
            Prompt = Game.Language.GetPhrase( "spawnmenu.saves.delete_confirm", new() { { "name", save.DisplayName } } ),
            ConfirmLabel = "#spawnmenu.saves.delete_button",
            ShowInput = false,
            OnConfirm = _ => DeleteSave( save.FileName ),
            Parent = FindPopupPanel()
        };
    }
    void OnSave()
    {
        var saveName = SaveNameEntry?.Text?.Trim();

        if ( string.IsNullOrEmpty( saveName ) )
            return;

        var now = DateTime.Now;
        var timeString = now.ToString( "yyyy-MM-dd HH:mm:ss" );
        var baseName = now.ToString( "yyyy-MM-dd-HH-mm-ss" );
        var fileName = $"{SavesPath}/{baseName}.sav";

        // Capture a 256x144 (16:9) thumbnail from the scene camera
        var camera = Game.ActiveScene?.Camera;
        if ( camera.IsValid() )
        {
            var bitmap = new Bitmap( 256, 144 );
            camera.RenderToBitmap( bitmap );
            FileSystem.Data.WriteAllBytes( $"{SavesPath}/{baseName}.thumb.png", bitmap.ToPng() );
        }

        SaveSystem.Current.SetMetadata( "Timestamp", timeString );
        SaveSystem.Current.SetMetadata( "Title", saveName );
        SaveSystem.Current.Save( fileName );

        SaveNameEntry.Text = "";
        RefreshSaveList();
    }

    void LoadSave( string fileName )
    {
        _ = SaveSystem.Current.Load( $"{SavesPath}/{fileName}" );
    }

    void DeleteSave( string fileName )
    {
        FileSystem.Data.DeleteFile( $"{SavesPath}/{fileName}" );
        var thumbPath = $"{SavesPath}/{System.IO.Path.GetFileNameWithoutExtension( fileName )}.thumb.png";
        if ( FileSystem.Data.FileExists( thumbPath ) )
            FileSystem.Data.DeleteFile( thumbPath );
        RefreshSaveList();
    }
}