using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using GsaEditor.Core.Compression;
using GsaEditor.Core.IO;
using GsaEditor.Core.Models;
using GsaEditor.Helpers;

namespace GsaEditor.ViewModels;

public partial class MainWindowViewModel : ViewModelBase
{
    private static readonly HashSet<string> TextExtensions = new(StringComparer.OrdinalIgnoreCase)
    {
        ".txt", ".nml", ".xml", ".json", ".csv", ".ini", ".cfg", ".log", ".nut"
    };

    private static readonly HashSet<string> ImageExtensions = new(StringComparer.OrdinalIgnoreCase)
    {
        ".png", ".jpg", ".jpeg", ".bmp"
    };

    private static readonly HashSet<string> UnsupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
    {
        ".tga", ".dds"
    };

    private static readonly FilePickerFileType GsaFileType = new("GSA Archives")
    {
        Patterns = new[] { "*.gsa" }
    };

    private static readonly FilePickerFileType AllFilesType = new("All Files")
    {
        Patterns = new[] { "*" }
    };

    private Window? _window;
    private GsaArchive? _archive;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(Title))]
    [NotifyPropertyChangedFor(nameof(StatusArchivePath))]
    [NotifyPropertyChangedFor(nameof(HasArchive))]
    private string? _archivePath;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(Title))]
    private bool _isDirty;

    [ObservableProperty]
    private bool _isLoading;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasSelectedFile))]
    [NotifyPropertyChangedFor(nameof(HasSelectedFolder))]
    [NotifyPropertyChangedFor(nameof(HasSelectedEntry))]
    private EntryTreeNodeViewModel? _selectedNode;

    [ObservableProperty]
    private EntryViewModel? _selectedEntry;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(IsTextPreview))]
    [NotifyPropertyChangedFor(nameof(IsImagePreview))]
    [NotifyPropertyChangedFor(nameof(IsHexPreview))]
    [NotifyPropertyChangedFor(nameof(HasPreview))]
    private PreviewMode _previewMode = PreviewMode.None;

    [ObservableProperty]
    private string? _previewText;

    [ObservableProperty]
    private Bitmap? _previewImage;

    [ObservableProperty]
    private string? _hexDumpText;

    [ObservableProperty]
    private bool _isEditingText;

    [ObservableProperty]
    private string? _searchQuery;

    [ObservableProperty]
    private bool _searchInContents;

    [ObservableProperty]
    private string? _searchStatus;

    [ObservableProperty]
    private SearchResultViewModel? _selectedSearchResult;

    public ObservableCollection<EntryTreeNodeViewModel> TreeRoots { get; } = new();

    public ObservableCollection<SearchResultViewModel> SearchResults { get; } = new();

    public bool HasSearchResults => SearchResults.Count > 0;

    // --- Derived properties ---

    public string Title => ArchivePath != null
        ? $"GsaEditor - {Path.GetFileName(ArchivePath)}{(IsDirty ? " *" : "")}"
        : "GsaEditor";

    public bool HasArchive => _archive != null;
    public bool HasSelectedEntry => SelectedNode?.Entry != null;
    public bool HasSelectedFile => SelectedNode != null && !SelectedNode.IsFolder;
    public bool HasSelectedFolder => SelectedNode != null && SelectedNode.IsFolder;
    public bool IsTextPreview => PreviewMode == PreviewMode.Text;
    public bool IsImagePreview => PreviewMode == PreviewMode.Image;
    public bool IsHexPreview => PreviewMode == PreviewMode.HexDump;
    public bool HasPreview => PreviewMode != PreviewMode.None;

    // --- Status bar ---

    public string StatusArchivePath => ArchivePath ?? "No archive loaded";

    public string StatusEntryCount => _archive != null
        ? $"{_archive.Entries.Count} entries"
        : "";

    public string StatusSizeInfo
    {
        get
        {
            if (_archive == null) return "";
            long totalOriginal = _archive.Entries.Sum(e => (long)e.OriginalLength);
            long totalCompressed = _archive.Entries.Sum(e => (long)e.CompressedLength);
            return $"Total: {FormatSize(totalOriginal)} / Compressed: {FormatSize(totalCompressed)}";
        }
    }

    /// <summary>
    /// Sets the parent window reference for dialog display.
    /// </summary>
    public void SetWindow(Window window)
    {
        _window = window;
    }

    // =========================================================================
    // Selection changed
    // =========================================================================

    partial void OnSelectedNodeChanged(EntryTreeNodeViewModel? value)
    {
        IsEditingText = false;

        if (value?.Entry != null)
        {
            var entryVm = new EntryViewModel(value.Entry);
            entryVm.OnModified = () =>
            {
                IsDirty = true;
                NotifyStatusChanged();
            };
            SelectedEntry = entryVm;
            UpdatePreview(value.Entry);
        }
        else
        {
            SelectedEntry = null;
            ClearPreview();
        }
    }

    private void UpdatePreview(GsaEntry entry)
    {
        var ext = Path.GetExtension(entry.Alias).ToLowerInvariant();

        if (TextExtensions.Contains(ext))
        {
            PreviewMode = PreviewMode.Text;
            var data = entry.GetDecompressedData();
            PreviewText = Encoding.UTF8.GetString(data);
            PreviewImage?.Dispose();
            PreviewImage = null;
            HexDumpText = null;
        }
        else if (ImageExtensions.Contains(ext))
        {
            try
            {
                var data = entry.GetDecompressedData();
                using var ms = new MemoryStream(data);
                PreviewImage?.Dispose();
                PreviewImage = new Bitmap(ms);
                PreviewMode = PreviewMode.Image;
            }
            catch
            {
                PreviewImage = null;
                PreviewMode = PreviewMode.HexDump;
                HexDumpText = "Preview not available for this image.";
            }
            PreviewText = null;
        }
        else if (UnsupportedImageExtensions.Contains(ext))
        {
            PreviewMode = PreviewMode.HexDump;
            HexDumpText = $"Preview not available for {ext.ToUpperInvariant()} files.";
            PreviewText = null;
            PreviewImage?.Dispose();
            PreviewImage = null;
        }
        else
        {
            PreviewMode = PreviewMode.HexDump;
            var data = entry.GetDecompressedData();
            HexDumpText = HexDumper.Dump(data, 512);
            PreviewText = null;
            PreviewImage?.Dispose();
            PreviewImage = null;
        }
    }

    private void ClearPreview()
    {
        PreviewMode = PreviewMode.None;
        PreviewText = null;
        PreviewImage?.Dispose();
        PreviewImage = null;
        HexDumpText = null;
    }

    // =========================================================================
    // File menu commands
    // =========================================================================

    [RelayCommand]
    private async Task OpenArchive()
    {
        if (_window == null) return;

        if (IsDirty)
        {
            var confirmed = await Dialogs.ConfirmAsync(_window, "Unsaved Changes",
                "You have unsaved changes. Open a new archive without saving?");
            if (!confirmed) return;
        }

        var files = await _window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
        {
            Title = "Open GSA Archive",
            FileTypeFilter = new[] { GsaFileType, AllFilesType },
            AllowMultiple = false
        });

        if (files.Count == 0) return;

        var path = files[0].Path.LocalPath;
        await LoadArchiveAsync(path);
    }

    private async Task LoadArchiveAsync(string path)
    {
        IsLoading = true;
        ClearPreview();
        SelectedEntry = null;
        SelectedNode = null;

        try
        {
            var archive = await Task.Run(() => GsaReader.Read(path));
            _archive = archive;
            ArchivePath = path;
            IsDirty = false;
            SearchQuery = null;
            ClearSearchResults();
            BuildTree();
            NotifyStatusChanged();
        }
        catch (Exception ex)
        {
            if (_window != null)
                await Dialogs.ShowMessageAsync(_window, "Error", $"Failed to open archive:\n{ex.Message}");
        }
        finally
        {
            IsLoading = false;
        }
    }

    [RelayCommand]
    private async Task Save()
    {
        if (_archive == null || _window == null) return;

        if (string.IsNullOrEmpty(ArchivePath))
        {
            await SaveAs();
            return;
        }

        try
        {
            await Task.Run(() => GsaWriter.Write(_archive, ArchivePath!));

            // Generate .idx alongside
            var idxPath = Path.ChangeExtension(ArchivePath, ".idx");
            await Task.Run(() => GsaIndexWriter.Write(_archive, idxPath));

            IsDirty = false;
        }
        catch (Exception ex)
        {
            await Dialogs.ShowMessageAsync(_window, "Error", $"Failed to save archive:\n{ex.Message}");
        }
    }

    [RelayCommand]
    private async Task SaveAs()
    {
        if (_archive == null || _window == null) return;

        var file = await _window.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
        {
            Title = "Save GSA Archive As",
            FileTypeChoices = new[] { GsaFileType },
            DefaultExtension = "gsa",
            SuggestedFileName = ArchivePath != null ? Path.GetFileName(ArchivePath) : "archive.gsa"
        });

        if (file == null) return;

        var path = file.Path.LocalPath;

        try
        {
            await Task.Run(() => GsaWriter.Write(_archive, path));

            var idxPath = Path.ChangeExtension(path, ".idx");
            await Task.Run(() => GsaIndexWriter.Write(_archive, idxPath));

            ArchivePath = path;
            IsDirty = false;
        }
        catch (Exception ex)
        {
            await Dialogs.ShowMessageAsync(_window, "Error", $"Failed to save archive:\n{ex.Message}");
        }
    }

    [RelayCommand]
    private async Task CloseArchive()
    {
        if (_window == null) return;

        if (IsDirty)
        {
            var confirmed = await Dialogs.ConfirmAsync(_window, "Unsaved Changes",
                "You have unsaved changes. Close without saving?");
            if (!confirmed) return;
        }

        _archive = null;
        ArchivePath = null;
        IsDirty = false;
        TreeRoots.Clear();
        SelectedEntry = null;
        SelectedNode = null;
        SearchQuery = null;
        ClearSearchResults();
        ClearPreview();
        NotifyStatusChanged();
    }

    [RelayCommand]
    private void Exit()
    {
        _window?.Close();
    }

    // =========================================================================
    // Edit menu commands
    // =========================================================================

    [RelayCommand]
    private async Task AddFiles()
    {
        if (_archive == null || _window == null) return;

        var files = await _window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
        {
            Title = "Add Files to Archive",
            AllowMultiple = true,
            FileTypeFilter = new[] { AllFilesType }
        });

        if (files.Count == 0) return;

        string prefix = "";
        if (SelectedNode != null && SelectedNode.IsFolder)
        {
            prefix = SelectedNode.FullPath;
            if (!prefix.EndsWith("/")) prefix += "/";
        }

        foreach (var file in files)
        {
            var filePath = file.Path.LocalPath;
            var fileName = Path.GetFileName(filePath);
            var alias = prefix + fileName;

            var data = await Task.Run(() => File.ReadAllBytes(filePath));
            var entry = new GsaEntry();
            entry.Alias = alias;
            entry.SetData(data, compress: true);

            _archive.Entries.Add(entry);
        }

        IsDirty = true;
        BuildTree();
        NotifyStatusChanged();
    }

    [RelayCommand]
    private async Task ExtractAll()
    {
        if (_archive == null || _window == null) return;

        var folders = await _window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
        {
            Title = "Select Extraction Folder"
        });

        if (folders.Count == 0) return;

        var basePath = folders[0].Path.LocalPath;

        try
        {
            await Task.Run(() =>
            {
                foreach (var entry in _archive.Entries)
                {
                    var outputPath = Path.Combine(basePath, entry.Alias.Replace('/', Path.DirectorySeparatorChar));
                    var dir = Path.GetDirectoryName(outputPath);
                    if (dir != null) Directory.CreateDirectory(dir);

                    var data = entry.GetDecompressedData();
                    File.WriteAllBytes(outputPath, data);
                }
            });

            await Dialogs.ShowMessageAsync(_window, "Extract All",
                $"Successfully extracted {_archive.Entries.Count} files to:\n{basePath}");
        }
        catch (Exception ex)
        {
            await Dialogs.ShowMessageAsync(_window, "Error", $"Extraction failed:\n{ex.Message}");
        }
    }

    [RelayCommand]
    private async Task Repack()
    {
        if (_archive == null || _window == null) return;

        IsLoading = true;
        try
        {
            await Task.Run(() =>
            {
                foreach (var entry in _archive.Entries)
                {
                    var decompressed = entry.GetDecompressedData();
                    entry.SetData(decompressed, entry.IsCompressed);
                }
            });

            IsDirty = true;
            BuildTree();
            NotifyStatusChanged();

            if (SelectedNode?.Entry != null)
            {
                var entryVm = new EntryViewModel(SelectedNode.Entry);
                entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
                SelectedEntry = entryVm;
            }
        }
        finally
        {
            IsLoading = false;
        }
    }

    // =========================================================================
    // Context menu commands
    // =========================================================================

    [RelayCommand]
    private async Task ExtractEntry()
    {
        if (SelectedNode?.Entry == null || _window == null) return;

        var entry = SelectedNode.Entry;
        var file = await _window.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
        {
            Title = "Extract File",
            SuggestedFileName = Path.GetFileName(entry.Alias),
            FileTypeChoices = new[] { AllFilesType }
        });

        if (file == null) return;

        try
        {
            var data = entry.GetDecompressedData();
            var path = file.Path.LocalPath;
            await File.WriteAllBytesAsync(path, data);
        }
        catch (Exception ex)
        {
            await Dialogs.ShowMessageAsync(_window, "Error", $"Extraction failed:\n{ex.Message}");
        }
    }

    [RelayCommand]
    private async Task ReplaceEntry()
    {
        if (SelectedNode?.Entry == null || _archive == null || _window == null) return;

        var files = await _window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
        {
            Title = "Replace Entry With",
            AllowMultiple = false,
            FileTypeFilter = new[] { AllFilesType }
        });

        if (files.Count == 0) return;

        var filePath = files[0].Path.LocalPath;
        var data = await Task.Run(() => File.ReadAllBytes(filePath));

        var entry = SelectedNode.Entry;
        entry.SetData(data, entry.IsCompressed);

        IsDirty = true;
        NotifyStatusChanged();

        // Refresh the selected entry view
        var entryVm = new EntryViewModel(entry);
        entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
        SelectedEntry = entryVm;
        UpdatePreview(entry);
    }

    [RelayCommand]
    private void DeleteEntry()
    {
        if (SelectedNode?.Entry == null || _archive == null) return;

        _archive.Entries.Remove(SelectedNode.Entry);
        IsDirty = true;
        SelectedNode = null;
        SelectedEntry = null;
        ClearPreview();
        BuildTree();
        NotifyStatusChanged();
    }

    [RelayCommand]
    private async Task RenameEntry()
    {
        if (SelectedNode?.Entry == null || _window == null) return;

        var entry = SelectedNode.Entry;
        var result = await Dialogs.InputAsync(_window, "Rename Entry", "New alias:", entry.Alias);

        if (result != null && result != entry.Alias && result.Length > 0)
        {
            entry.Alias = result;
            IsDirty = true;
            BuildTree();
            NotifyStatusChanged();

            if (SelectedEntry != null)
            {
                var entryVm = new EntryViewModel(entry);
                entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
                SelectedEntry = entryVm;
            }
        }
    }

    [RelayCommand]
    private async Task ExtractFolder()
    {
        if (SelectedNode == null || !SelectedNode.IsFolder || _archive == null || _window == null) return;

        var folders = await _window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
        {
            Title = "Select Extraction Folder"
        });

        if (folders.Count == 0) return;

        var basePath = folders[0].Path.LocalPath;
        var prefix = SelectedNode.FullPath;
        if (!prefix.EndsWith("/")) prefix += "/";

        try
        {
            var matching = _archive.Entries.Where(e => e.Alias.StartsWith(prefix, StringComparison.Ordinal)).ToList();

            await Task.Run(() =>
            {
                foreach (var entry in matching)
                {
                    var relativePath = entry.Alias.Substring(prefix.Length);
                    var outputPath = Path.Combine(basePath, relativePath.Replace('/', Path.DirectorySeparatorChar));
                    var dir = Path.GetDirectoryName(outputPath);
                    if (dir != null) Directory.CreateDirectory(dir);

                    var data = entry.GetDecompressedData();
                    File.WriteAllBytes(outputPath, data);
                }
            });

            await Dialogs.ShowMessageAsync(_window, "Extract Folder",
                $"Successfully extracted files to:\n{basePath}");
        }
        catch (Exception ex)
        {
            await Dialogs.ShowMessageAsync(_window, "Error", $"Extraction failed:\n{ex.Message}");
        }
    }

    // =========================================================================
    // Text editing commands
    // =========================================================================

    [RelayCommand]
    private void StartEdit()
    {
        IsEditingText = true;
    }

    [RelayCommand]
    private void SaveEdit()
    {
        if (SelectedNode?.Entry == null) return;

        // Get the current text from the AvaloniaEdit editor via the window
        string? editorText = null;
        if (_window is GsaEditor.Views.MainWindow mainWindow)
        {
            editorText = mainWindow.GetEditorText();
        }

        if (editorText == null) editorText = PreviewText ?? string.Empty;

        var entry = SelectedNode.Entry;
        var newData = Encoding.UTF8.GetBytes(editorText);
        entry.SetData(newData, entry.IsCompressed);

        IsDirty = true;
        IsEditingText = false;
        PreviewText = editorText;

        var entryVm = new EntryViewModel(entry);
        entryVm.OnModified = () => { IsDirty = true; NotifyStatusChanged(); };
        SelectedEntry = entryVm;
        NotifyStatusChanged();
    }

    [RelayCommand]
    private void CancelEdit()
    {
        IsEditingText = false;
        if (SelectedNode?.Entry != null)
        {
            PreviewText = Encoding.UTF8.GetString(SelectedNode.Entry.GetDecompressedData());
        }
    }

    [RelayCommand]
    private async Task OpenAsText()
    {
        if (SelectedNode?.Entry == null) return;

        var data = SelectedNode.Entry.GetDecompressedData();

        try
        {
            new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(data);
        }
        catch (DecoderFallbackException)
        {
            if (_window != null)
            {
                var confirmed = await Dialogs.ConfirmAsync(_window, "Not a Text File",
                    "This file is not valid UTF-8 text. Editing and saving it as text will corrupt its content.\n\nOpen as text anyway?");
                if (!confirmed) return;
            }
        }

        PreviewMode = PreviewMode.Text;
        PreviewText = Encoding.UTF8.GetString(data);
        PreviewImage?.Dispose();
        PreviewImage = null;
        HexDumpText = null;
    }

    // =========================================================================
    // Search
    // =========================================================================

    private const int MaxNameResults = 200;
    private const int MaxContentResults = 1000;

    partial void OnSearchQueryChanged(string? value)
    {
        // Name search is live; content search only runs on Enter / the Search button
        if (!SearchInContents)
            RunNameSearch();
        else if (string.IsNullOrWhiteSpace(value))
            ClearSearchResults();
    }

    partial void OnSearchInContentsChanged(bool value)
    {
        if (!value)
            RunNameSearch();
        else
            ClearSearchResults();
    }

    partial void OnSelectedSearchResultChanged(SearchResultViewModel? value)
    {
        if (value != null && value.Alias.Length > 0)
            SelectEntryNode(value.Alias);
    }

    [RelayCommand]
    private async Task Search()
    {
        if (_archive == null) return;

        if (!SearchInContents)
        {
            RunNameSearch();
            return;
        }

        if (string.IsNullOrEmpty(SearchQuery)) return;

        Regex regex;
        try
        {
            regex = new Regex(SearchQuery, RegexOptions.None, TimeSpan.FromSeconds(5));
        }
        catch (ArgumentException ex)
        {
            if (_window != null)
                await Dialogs.ShowMessageAsync(_window, "Invalid Regex", $"Invalid regular expression:\n{ex.Message}");
            return;
        }

        IsLoading = true;
        try
        {
            var entries = _archive.Entries.ToList();
            var results = await Task.Run(() =>
            {
                var found = new List<SearchResultViewModel>();
                foreach (var entry in entries)
                {
                    string text;
                    try
                    {
                        text = Encoding.UTF8.GetString(entry.GetDecompressedData());
                    }
                    catch
                    {
                        continue;
                    }

                    int lineNo = 0;
                    foreach (var rawLine in text.Split('\n'))
                    {
                        lineNo++;
                        var line = rawLine.TrimEnd('\r');

                        bool isMatch;
                        try
                        {
                            isMatch = regex.IsMatch(line);
                        }
                        catch (RegexMatchTimeoutException)
                        {
                            break;
                        }

                        if (!isMatch) continue;

                        var snippet = line.Trim();
                        if (snippet.Length > 120) snippet = snippet.Substring(0, 120) + "…";
                        found.Add(new SearchResultViewModel
                        {
                            Alias = entry.Alias,
                            Detail = $"line {lineNo}: {snippet}"
                        });

                        if (found.Count >= MaxContentResults)
                            return found;
                    }
                }
                return found;
            });

            SearchResults.Clear();
            foreach (var r in results)
                SearchResults.Add(r);
            OnPropertyChanged(nameof(HasSearchResults));

            SearchStatus = results.Count >= MaxContentResults
                ? $"{results.Count} matches (stopped at {MaxContentResults})"
                : $"{results.Count} match(es)";
        }
        finally
        {
            IsLoading = false;
        }
    }

    private void RunNameSearch()
    {
        SearchResults.Clear();

        var query = SearchQuery;
        if (_archive == null || string.IsNullOrWhiteSpace(query))
        {
            SearchStatus = null;
            OnPropertyChanged(nameof(HasSearchResults));
            return;
        }

        int total = 0;
        foreach (var entry in _archive.Entries)
        {
            if (!entry.Alias.Contains(query, StringComparison.OrdinalIgnoreCase)) continue;
            total++;
            if (SearchResults.Count < MaxNameResults)
                SearchResults.Add(new SearchResultViewModel { Alias = entry.Alias });
        }

        SearchStatus = total > MaxNameResults
            ? $"{total} file(s) (showing first {MaxNameResults})"
            : $"{total} file(s)";
        OnPropertyChanged(nameof(HasSearchResults));
    }

    private void ClearSearchResults()
    {
        SearchResults.Clear();
        SearchStatus = null;
        SelectedSearchResult = null;
        OnPropertyChanged(nameof(HasSearchResults));
    }

    /// <summary>
    /// Selects the tree node matching the given alias, expanding parent folders along the way.
    /// </summary>
    private void SelectEntryNode(string alias)
    {
        var parts = alias.Split('/');
        var level = TreeRoots;
        EntryTreeNodeViewModel? node = null;

        foreach (var part in parts)
        {
            node = level.FirstOrDefault(n => n.Name == part);
            if (node == null) return;
            if (node.IsFolder)
            {
                node.IsExpanded = true;
                level = node.Children;
            }
        }

        if (node != null && !node.IsFolder)
            SelectedNode = node;
    }

    // =========================================================================
    // About
    // =========================================================================

    [RelayCommand]
    private async Task ShowAbout()
    {
        if (_window == null) return;

        var appVersion = Assembly.GetExecutingAssembly().GetName().Version;
        var versionStr = appVersion != null ? $"{appVersion.Major}.{appVersion.Minor}.{appVersion.Build}" : "1.0.0";
        var avaloniaVersion = typeof(Avalonia.Application).Assembly.GetName().Version;
        var avaloniaStr = avaloniaVersion != null ? $"{avaloniaVersion.Major}.{avaloniaVersion.Minor}.{avaloniaVersion.Build}" : "unknown";

        await Dialogs.ShowMessageAsync(_window, "About GsaEditor",
            $"GsaEditor v{versionStr}\n\n" +
            $"GSA Archive Viewer & Editor\n" +
            $"By Develter Innovation (Nicolas RACOT)\n\n" +
            $"Built with Avalonia UI v{avaloniaStr}");
    }

    // =========================================================================
    // Tree building
    // =========================================================================

    private void BuildTree()
    {
        TreeRoots.Clear();
        if (_archive == null) return;

        foreach (var entry in _archive.Entries)
        {
            var parts = entry.Alias.Split('/');
            var currentLevel = TreeRoots;
            var currentPath = "";

            for (int i = 0; i < parts.Length; i++)
            {
                var part = parts[i];
                currentPath = i == 0 ? part : currentPath + "/" + part;
                bool isLeaf = (i == parts.Length - 1);

                var existing = currentLevel.FirstOrDefault(n => n.Name == part);
                if (existing == null)
                {
                    existing = new EntryTreeNodeViewModel
                    {
                        Name = part,
                        FullPath = currentPath,
                        IsFolder = !isLeaf,
                        Entry = isLeaf ? entry : null
                    };
                    currentLevel.Add(existing);
                }

                if (!isLeaf)
                {
                    currentLevel = existing.Children;
                }
            }
        }
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    private void NotifyStatusChanged()
    {
        OnPropertyChanged(nameof(StatusEntryCount));
        OnPropertyChanged(nameof(StatusSizeInfo));
        OnPropertyChanged(nameof(HasArchive));
    }

    private static string FormatSize(long bytes)
    {
        if (bytes < 1024) return $"{bytes} B";
        if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
        return $"{bytes / (1024.0 * 1024.0):F1} MB";
    }
}
