using System.Text;
using System.Text.Json;

using AIStudio.Chat;
using AIStudio.Dialogs;
using AIStudio.Tools.AIJobs;
using AIStudio.Tools.Media;
using AIStudio.Tools.Services;

using Microsoft.AspNetCore.Components;

using DialogOptions = AIStudio.Dialogs.DialogOptions;

namespace AIStudio.Components;

public partial class Workspaces : MSGComponentBase
{
    [Inject]
    private IDialogService DialogService { get; init; } = null!;
    
    [Inject]
    private ILogger<Workspaces> Logger { get; init; } = null!;

    [Inject]
    private AIJobService AIJobService { get; init; } = null!;

    [Inject]
    private MediaTranscriptionService MediaTranscriptionService { get; init; } = null!;
    
    [Parameter]
    public ChatThread? CurrentChatThread { get; set; }
    
    [Parameter]
    public EventCallback<ChatThread> CurrentChatThreadChanged { get; set; }

    [Parameter]
    public bool ExpandRootNodes { get; set; } = true;

    [Parameter]
    public bool SearchVisible { get; set; }

    [Parameter]
    public EventCallback<bool> SearchVisibleChanged { get; set; }

    private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom;
    private readonly SemaphoreSlim treeLoadingSemaphore = new(1, 1);
    private readonly List<TreeItemData<ITreeItem>> treeItems = [];
    private readonly HashSet<Guid> loadingWorkspaceChatLists = [];

    private CancellationTokenSource? prefetchCancellationTokenSource;
    private CancellationTokenSource? searchCancellationTokenSource;
    private bool isInitialLoading = true;
    private bool isDisposed;
    private bool includeThreadContents;
    private bool isSearchRunning;
    private string searchText = string.Empty;
    private long searchRevision;
    
    #region Overrides of ComponentBase

    protected override async Task OnInitializedAsync()
    {
        this.MediaTranscriptionService.StateChanged += this.OnMediaImportStateChanged;
        await base.OnInitializedAsync();
        this.ApplyFilters([], [ Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED, Event.CHAT_GENERATION_CHANGED, Event.WORKSPACE_CREATED ]);
        _ = this.LoadTreeItemsAsync(startPrefetch: true);
    }

    #endregion

    private async Task LoadTreeItemsAsync(bool startPrefetch = true, bool forceReload = false)
    {
        var shouldRunSearch = false;
        await this.treeLoadingSemaphore.WaitAsync();
        try
        {
            if (this.isDisposed)
                return;

            if (forceReload)
                await WorkspaceBehaviour.ForceReloadWorkspaceTreeAsync();

            var snapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
            if (this.HasSearchQuery)
                shouldRunSearch = true;
            else
                this.BuildTreeItems(snapshot);

            this.isInitialLoading = false;
        }
        finally
        {
            this.treeLoadingSemaphore.Release();
        }

        if (shouldRunSearch)
            await this.SearchWorkspaceItemsAsync();
        else
            await this.SafeStateHasChanged();

        if (startPrefetch)
            await this.StartPrefetchAsync();
    }

    private bool HasSearchQuery => this.SearchVisible && !string.IsNullOrWhiteSpace(this.searchText);

    private string GetAddChatToWorkspaceTooltip(string workspaceName) => string.Format(T("Start a new chat in workspace '{0}'"), workspaceName);

    private async Task<Func<string?, string?>> CreateWorkspaceNameValidationAsync(Guid excludedWorkspaceId = default, string? originalWorkspaceName = null)
    {
        var snapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
        return workspaceName =>
        {
            var normalizedWorkspaceName = WorkspaceBehaviour.NormalizeWorkspaceName(workspaceName ?? string.Empty);
            if (string.IsNullOrWhiteSpace(normalizedWorkspaceName))
                return null;

            if (!string.IsNullOrWhiteSpace(originalWorkspaceName) &&
                string.Equals(WorkspaceBehaviour.NormalizeWorkspaceName(originalWorkspaceName), normalizedWorkspaceName, StringComparison.OrdinalIgnoreCase))
                return null;

            var nameExists = snapshot.Workspaces.Any(workspace =>
                workspace.WorkspaceId != excludedWorkspaceId &&
                string.Equals(WorkspaceBehaviour.NormalizeWorkspaceName(workspace.Name), normalizedWorkspaceName, StringComparison.OrdinalIgnoreCase));

            return nameExists ? T("There is already a workspace with this name. Please choose a different name.") : null;
        };
    }

    private void BuildTreeItems(WorkspaceTreeCacheSnapshot snapshot)
    {
        this.treeItems.Clear();

        var workspaceChildren = new List<TreeItemData<ITreeItem>>();
        foreach (var workspace in snapshot.Workspaces)
            workspaceChildren.Add(this.CreateWorkspaceTreeItem(workspace));

        workspaceChildren.Add(new TreeItemData<ITreeItem>
        {
            Expandable = false,
            Value = new TreeButton(WorkspaceBranch.WORKSPACES, 1, T("Add workspace"), Icons.Material.Filled.LibraryAdd, this.AddWorkspaceAsync),
        });

        this.treeItems.Add(new TreeItemData<ITreeItem>
        {
            Expanded = this.ExpandRootNodes,
            Expandable = true,
            Value = new TreeItemData
            {
                Depth = 0,
                Branch = WorkspaceBranch.WORKSPACES,
                Text = T("Workspaces"),
                Icon = Icons.Material.Filled.Folder,
                Expandable = true,
                Path = "root",
                Children = workspaceChildren,
            },
        });
        
        this.treeItems.Add(new TreeItemData<ITreeItem>
        {
            Expandable = false,
            Value = new TreeDivider(),
        });
        
        var temporaryChatsChildren = new List<TreeItemData<ITreeItem>>();
        foreach (var temporaryChat in snapshot.TemporaryChats.OrderByDescending(x => x.LastEditTime))
            temporaryChatsChildren.Add(this.CreateChatTreeItem(temporaryChat, WorkspaceBranch.TEMPORARY_CHATS, depth: 1, icon: Icons.Material.Filled.Timer));

        this.treeItems.Add(new TreeItemData<ITreeItem>
        {
            Expanded = this.ExpandRootNodes,
            Expandable = true,
            Value = new TreeItemData
            {
                Depth = 0,
                Branch = WorkspaceBranch.TEMPORARY_CHATS,
                Text = T("Disappearing Chats"),
                Icon = Icons.Material.Filled.Timer,
                Expandable = true,
                Path = "temp",
                Children = temporaryChatsChildren,
            },
        });
    }

    private TreeItemData<ITreeItem> CreateWorkspaceTreeItem(WorkspaceTreeWorkspace workspace)
    {
        var children = new List<TreeItemData<ITreeItem>>();
        if (workspace.ChatsLoaded)
        {
            foreach (var workspaceChat in workspace.Chats.OrderByDescending(x => x.LastEditTime))
                children.Add(this.CreateChatTreeItem(workspaceChat, WorkspaceBranch.WORKSPACES, depth: 2, icon: Icons.Material.Filled.Chat));
        }
        else if (this.loadingWorkspaceChatLists.Contains(workspace.WorkspaceId))
            children.AddRange(this.CreateLoadingRows(workspace.WorkspacePath));

        children.Add(new TreeItemData<ITreeItem>
        {
            Expandable = false,
            Value = new TreeButton(WorkspaceBranch.WORKSPACES, 2, T("Add chat"), Icons.Material.Filled.AddComment, () => this.AddChatAsync(workspace.WorkspacePath)),
        });

        return new TreeItemData<ITreeItem>
        {
            Expandable = true,
            Value = new TreeItemData
            {
                Type = TreeItemType.WORKSPACE,
                Depth = 1,
                Branch = WorkspaceBranch.WORKSPACES,
                Text = workspace.Name,
                Icon = Icons.Material.Filled.Description,
                Expandable = true,
                Path = workspace.WorkspacePath,
                Children = children,
            },
        };
    }

    private IReadOnlyCollection<TreeItemData<ITreeItem>> CreateLoadingRows(string workspacePath)
    {
        return
        [
            this.CreateLoadingTreeItem(workspacePath, "loading_1"),
            this.CreateLoadingTreeItem(workspacePath, "loading_2"),
            this.CreateLoadingTreeItem(workspacePath, "loading_3"),
        ];
    }

    private TreeItemData<ITreeItem> CreateLoadingTreeItem(string workspacePath, string suffix)
    {
        return new TreeItemData<ITreeItem>
        {
            Expandable = false,
            Value = new TreeItemData
            {
                Type = TreeItemType.LOADING,
                Depth = 2,
                Branch = WorkspaceBranch.WORKSPACES,
                Text = T("Loading chats..."),
                Icon = Icons.Material.Filled.HourglassTop,
                Expandable = false,
                Path = Path.Join(workspacePath, suffix),
            },
        };
    }

    private TreeItemData<ITreeItem> CreateChatTreeItem(WorkspaceTreeChat chat, WorkspaceBranch branch, int depth, string icon)
    {
        return new TreeItemData<ITreeItem>
        {
            Expandable = false,
            Value = new TreeItemData
            {
                Type = TreeItemType.CHAT,
                Depth = depth,
                Branch = branch,
                Text = chat.Name,
                Icon = icon,
                DefaultIcon = icon,
                Expandable = false,
                Path = chat.ChatPath,
                ChatId = chat.ChatId,
                WorkspaceId = chat.WorkspaceId,
                LastEditTime = chat.LastEditTime,
            },
        };
    }

    private void BuildSearchTreeItems(WorkspaceSearchSnapshot snapshot)
    {
        this.treeItems.Clear();

        if (snapshot.Workspaces.Count == 0 && snapshot.TemporaryChats.Count == 0)
        {
            this.treeItems.Add(new TreeItemData<ITreeItem>
            {
                Expandable = false,
                Value = new TreeItemData
                {
                    Depth = 0,
                    Branch = WorkspaceBranch.NONE,
                    Text = T("No chats found"),
                    Icon = Icons.Material.Filled.Search,
                    Expandable = false,
                    Path = "search_empty",
                },
            });

            return;
        }

        if (snapshot.Workspaces.Count > 0)
        {
            var workspaceChildren = new List<TreeItemData<ITreeItem>>();
            foreach (var workspace in snapshot.Workspaces)
                workspaceChildren.Add(this.CreateSearchWorkspaceTreeItem(workspace));

            this.treeItems.Add(new TreeItemData<ITreeItem>
            {
                Expanded = true,
                Expandable = true,
                Value = new TreeItemData
                {
                    Depth = 0,
                    Branch = WorkspaceBranch.WORKSPACES,
                    Text = T("Workspaces"),
                    Icon = Icons.Material.Filled.Folder,
                    Expandable = true,
                    Path = "search_workspaces",
                    Children = workspaceChildren,
                },
            });
        }

        if (snapshot.Workspaces.Count > 0 && snapshot.TemporaryChats.Count > 0)
        {
            this.treeItems.Add(new TreeItemData<ITreeItem>
            {
                Expandable = false,
                Value = new TreeDivider(),
            });
        }

        if (snapshot.TemporaryChats.Count > 0)
        {
            var temporaryChatsChildren = new List<TreeItemData<ITreeItem>>();
            foreach (var temporaryChat in snapshot.TemporaryChats)
                temporaryChatsChildren.Add(this.CreateChatTreeItem(temporaryChat.Chat, WorkspaceBranch.TEMPORARY_CHATS, depth: 1, icon: Icons.Material.Filled.Timer));

            this.treeItems.Add(new TreeItemData<ITreeItem>
            {
                Expanded = true,
                Expandable = true,
                Value = new TreeItemData
                {
                    Depth = 0,
                    Branch = WorkspaceBranch.TEMPORARY_CHATS,
                    Text = T("Disappearing Chats"),
                    Icon = Icons.Material.Filled.Timer,
                    Expandable = true,
                    Path = "search_temp",
                    Children = temporaryChatsChildren,
                },
            });
        }
    }

    private TreeItemData<ITreeItem> CreateSearchWorkspaceTreeItem(WorkspaceSearchWorkspace workspace)
    {
        var children = new List<TreeItemData<ITreeItem>>();
        foreach (var chat in workspace.Chats)
            children.Add(this.CreateChatTreeItem(chat.Chat, WorkspaceBranch.WORKSPACES, depth: 2, icon: Icons.Material.Filled.Chat));

        return new TreeItemData<ITreeItem>
        {
            Expanded = true,
            Expandable = true,
            Value = new TreeItemData
            {
                Type = TreeItemType.WORKSPACE,
                Depth = 1,
                Branch = WorkspaceBranch.WORKSPACES,
                Text = workspace.Name,
                Icon = Icons.Material.Filled.Description,
                Expandable = true,
                Path = workspace.WorkspacePath,
                Children = children,
            },
        };
    }

    private string GetTreeItemIcon(TreeItemData treeItem)
    {
        if (treeItem.Type is not TreeItemType.CHAT)
            return treeItem.Icon;

        var defaultIcon = string.IsNullOrWhiteSpace(treeItem.DefaultIcon) ? treeItem.Icon : treeItem.DefaultIcon;
        return this.GetChatTreeIcon(treeItem.ChatId, defaultIcon);
    }

    private bool IsChatTreeItemBusy(TreeItemData treeItem)
    {
        return treeItem.Type is TreeItemType.CHAT
               && (this.AIJobService.IsChatGenerationActive(treeItem.ChatId)
                   || this.MediaTranscriptionService.IsBusy(MediaImportOwner.ForChat(treeItem.ChatId)));
    }

    private string GetChatTreeItemTextStyle(TreeItemData treeItem)
    {
        var status = this.MediaTranscriptionService.GetSnapshot(MediaImportOwner.ForChat(treeItem.ChatId))?.Status;
        var color = status switch
        {
            MediaImportStatus.QUEUED or MediaImportStatus.RUNNING or MediaImportStatus.CANCELING => " color: var(--mud-palette-info);",
            MediaImportStatus.SUCCEEDED => " color: var(--mud-palette-success);",
            MediaImportStatus.WARNING => " color: var(--mud-palette-warning);",
            MediaImportStatus.FAILED => " color: var(--mud-palette-error);",
            MediaImportStatus.CANCELLED => " color: var(--mud-palette-warning);",
            _ => string.Empty,
        };
        
        var weight = this.IsCurrentChatTreeItem(treeItem) ? " font-weight: 700;" : string.Empty;
        return $"justify-self: start;{weight}{color}";
    }

    private bool IsCurrentChatTreeItem(TreeItemData treeItem)
    {
        return treeItem.Type is TreeItemType.CHAT
               && this.CurrentChatThread is not null
               && treeItem.ChatId == this.CurrentChatThread.ChatId
               && treeItem.WorkspaceId == this.CurrentChatThread.WorkspaceId;
    }

    private string GetChatTreeIcon(Guid chatId, string defaultIcon)
    {
        var mediaStatus = this.MediaTranscriptionService.GetSnapshot(MediaImportOwner.ForChat(chatId))?.Status;
        if (mediaStatus is not null)
        {
            return mediaStatus switch
            {
                MediaImportStatus.QUEUED => Icons.Material.Filled.HourglassTop,
                MediaImportStatus.RUNNING or MediaImportStatus.CANCELING => Icons.Material.Filled.ChangeCircle,
                MediaImportStatus.SUCCEEDED => Icons.Material.Filled.TaskAlt,
                MediaImportStatus.WARNING => Icons.Material.Filled.WarningAmber,
                MediaImportStatus.FAILED => Icons.Material.Filled.Error,
                MediaImportStatus.CANCELLED => Icons.Material.Filled.Cancel,
                
                _ => defaultIcon,
            };
        }

        var snapshot = this.AIJobService.TryGetChatSnapshot(chatId);
        if (snapshot is null || !snapshot.IsActive)
            return defaultIcon;

        return snapshot.Status switch
        {
            AIJobStatus.WAITING_FOR_REMOTE => Icons.Material.Filled.HourglassTop,
            AIJobStatus.RUNNING => Icons.Material.Filled.ChangeCircle,
            _ => defaultIcon,
        };
    }

    private void OnMediaImportStateChanged(MediaImportOwner owner)
    {
        if (owner.Kind is MediaImportOwnerKind.CHAT)
            _ = this.SafeStateHasChanged();
    }

    private async Task SafeStateHasChanged()
    {
        if (this.isDisposed)
            return;

        await this.InvokeAsync(this.StateHasChanged);
    }

    private async Task StartPrefetchAsync()
    {
        if (this.prefetchCancellationTokenSource is not null)
        {
            await this.prefetchCancellationTokenSource.CancelAsync();
            this.prefetchCancellationTokenSource.Dispose();
        }

        this.prefetchCancellationTokenSource = new CancellationTokenSource();
        await this.PrefetchWorkspaceChatsAsync(this.prefetchCancellationTokenSource.Token);
    }

    private async Task PrefetchWorkspaceChatsAsync(CancellationToken cancellationToken)
    {
        try
        {
            await WorkspaceBehaviour.TryPrefetchRemainingChatsAsync(async _ =>
            {
                if (this.isDisposed || cancellationToken.IsCancellationRequested)
                    return;

                await this.LoadTreeItemsAsync(startPrefetch: false);
            }, cancellationToken);
        }
        catch (OperationCanceledException)
        {
            // Expected when the component is hidden or disposed.
        }
        catch (Exception ex)
        {
            this.Logger.LogWarning(ex, "Failed while prefetching workspace chats.");
        }
    }

    public async Task ToggleSearchAsync()
    {
        var searchVisible = !this.SearchVisible;
        this.SearchVisible = searchVisible;
        await this.SearchVisibleChanged.InvokeAsync(searchVisible);

        if (this.SearchVisible)
        {
            await this.SafeStateHasChanged();
            return;
        }

        await this.CancelSearchAsync();
        this.searchText = string.Empty;
        this.isSearchRunning = false;
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    private async Task CancelSearchAsync()
    {
        this.searchRevision++;
        if (this.searchCancellationTokenSource is not null)
        {
            await this.searchCancellationTokenSource.CancelAsync();
            this.searchCancellationTokenSource.Dispose();
            this.searchCancellationTokenSource = null;
        }
    }

    private async Task OnSearchTextChanged(string value)
    {
        this.searchText = value;
        if (string.IsNullOrWhiteSpace(this.searchText))
        {
            await this.CancelSearchAsync();
            this.isSearchRunning = false;
            await this.LoadTreeItemsAsync(startPrefetch: false);
            return;
        }

        await this.SearchWorkspaceItemsAsync();
    }

    private async Task IncludeThreadContentsChanged(bool value)
    {
        this.includeThreadContents = value;
        if (this.HasSearchQuery)
            await this.SearchWorkspaceItemsAsync();
    }

    private async Task ClearSearchAsync()
    {
        this.searchText = string.Empty;
        await this.CancelSearchAsync();
        this.isSearchRunning = false;
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    private async Task SearchWorkspaceItemsAsync()
    {
        await this.CancelSearchAsync();

        var text = this.searchText;
        if (string.IsNullOrWhiteSpace(text))
            return;

        this.searchCancellationTokenSource = new CancellationTokenSource();
        var token = this.searchCancellationTokenSource.Token;
        var revision = ++this.searchRevision;

        this.isSearchRunning = true;
        await this.SafeStateHasChanged();

        try
        {
            var snapshot = await WorkspaceBehaviour.SearchWorkspaceChatsAsync(text, this.includeThreadContents, token);
            if (this.isDisposed || token.IsCancellationRequested || revision != this.searchRevision)
                return;

            this.BuildSearchTreeItems(snapshot);
        }
        catch (OperationCanceledException)
        {
            // Expected when the user keeps typing or hides the search row.
        }
        catch (Exception ex)
        {
            this.Logger.LogWarning(ex, "Failed while searching workspace chats.");
            this.BuildSearchTreeItems(new([], []));
        }
        finally
        {
            if (revision == this.searchRevision)
            {
                this.isSearchRunning = false;
                await this.SafeStateHasChanged();
            }
        }
    }

    private async Task OnWorkspaceClicked(TreeItemData treeItem)
    {
        if (treeItem.Type is not TreeItemType.WORKSPACE)
            return;

        if (!Guid.TryParse(Path.GetFileName(treeItem.Path), out var workspaceId))
            return;

        await this.EnsureWorkspaceChatsLoadedAsync(workspaceId);
    }

    private async Task EnsureWorkspaceChatsLoadedAsync(Guid workspaceId)
    {
        var snapshot = await WorkspaceBehaviour.GetOrLoadWorkspaceTreeShellAsync();
        var hasWorkspace = false;
        var chatsLoaded = false;
        
        foreach (var workspace in snapshot.Workspaces)
        {
            if (workspace.WorkspaceId != workspaceId)
                continue;

            hasWorkspace = true;
            chatsLoaded = workspace.ChatsLoaded;
            break;
        }

        if (!hasWorkspace || chatsLoaded || !this.loadingWorkspaceChatLists.Add(workspaceId))
            return;

        await this.LoadTreeItemsAsync(startPrefetch: false);

        try
        {
            await WorkspaceBehaviour.GetWorkspaceChatsAsync(workspaceId);
        }
        finally
        {
            this.loadingWorkspaceChatLists.Remove(workspaceId);
        }

        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    public async Task ForceRefreshFromDiskAsync()
    {
        if (this.prefetchCancellationTokenSource is not null)
        {
            await this.prefetchCancellationTokenSource.CancelAsync();
            this.prefetchCancellationTokenSource.Dispose();
            this.prefetchCancellationTokenSource = null;
        }
        
        this.loadingWorkspaceChatLists.Clear();
        this.isInitialLoading = true;
        
        await this.SafeStateHasChanged();
        await this.LoadTreeItemsAsync(startPrefetch: true, forceReload: true);
    }

    public async Task StoreChatAsync(ChatThread chat, bool reloadTreeItems = false)
    {
        await WorkspaceBehaviour.StoreChatAsync(chat);
        
        if (reloadTreeItems)
            this.loadingWorkspaceChatLists.Clear();
        
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    private async Task<ChatThread?> LoadChatAsync(string? chatPath, bool switchToChat)
    {
        if (string.IsNullOrWhiteSpace(chatPath))
            return null;

        if (!Directory.Exists(chatPath))
            return null;
        
        if (switchToChat && await MessageBus.INSTANCE.SendMessageUseFirstResult<bool, bool>(this, Event.HAS_CHAT_UNSAVED_CHANGES))
        {
            var dialogParameters = new DialogParameters<ConfirmDialog>
            {
                { x => x.Message, T("Are you sure you want to load another chat? All unsaved changes will be lost.") },
            };
            
            var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Load Chat"), dialogParameters, DialogOptions.FULLSCREEN);
            var dialogResult = await dialogReference.Result;
            if (dialogResult is null || dialogResult.Canceled)
                return null;
        }

        try
        {
            var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
            var chat = JsonSerializer.Deserialize<ChatThread>(chatData, WorkspaceBehaviour.JSON_OPTIONS);
            if (chat is not null)
                chat = this.AIJobService.TryGetLiveChatThread(chat.ChatId) ?? chat;

            if (switchToChat)
            {
                this.CurrentChatThread = chat;
                await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
            }
            
            return chat;
        }
        catch (Exception e)
        {
            this.Logger.LogError($"Failed to load chat from '{chatPath}': {e.Message}");
        }
        
        return null;
    }

    public async Task DeleteChatAsync(string? chatPath, bool askForConfirmation = true, bool unloadChat = true)
    {
        var chat = await this.LoadChatAsync(chatPath, false);
        if (chat is null)
            return;

        var mediaOwner = MediaImportOwner.ForChat(chat.ChatId);
        if (this.AIJobService.IsChatGenerationActive(chat.ChatId) || this.MediaTranscriptionService.IsBusy(mediaOwner))
            return;

        if (askForConfirmation)
        {
            var workspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(chat.WorkspaceId);
            var dialogParameters = new DialogParameters<ConfirmDialog>
            {
                {
                    x => x.Message, (chat.WorkspaceId == Guid.Empty) switch
                    {
                        true => string.Format(T("Are you sure you want to delete the temporary chat '{0}'?"), chat.Name),
                        false => string.Format(T("Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"), chat.Name, workspaceName),
                    }
                },
            };

            var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete Chat"), dialogParameters, DialogOptions.FULLSCREEN);
            var dialogResult = await dialogReference.Result;
            if (dialogResult is null || dialogResult.Canceled)
                return;
        }

        await WorkspaceBehaviour.DeleteChatAsync(this.DialogService, chat.WorkspaceId, chat.ChatId, askForConfirmation: false);
        this.MediaTranscriptionService.ClearOwnerState(mediaOwner);
        await this.LoadTreeItemsAsync(startPrefetch: false);
        
        if (unloadChat && this.CurrentChatThread?.ChatId == chat.ChatId)
        {
            this.CurrentChatThread = null;
            await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
        }
    }

    private async Task RenameChatAsync(string? chatPath)
    {
        var chat = await this.LoadChatAsync(chatPath, false);
        if (chat is null)
            return;

        if (this.AIJobService.IsChatGenerationActive(chat.ChatId))
            return;
        
        var dialogParameters = new DialogParameters<SingleInputDialog>
        {
            { x => x.Message, string.Format(T("Please enter a new or edit the name for your chat '{0}':"), chat.Name) },
            { x => x.InputHeaderText, T("Chat Name") },
            { x => x.UserInput, chat.Name },
            { x => x.ConfirmText, T("Rename") },
            { x => x.ConfirmColor, Color.Info },
            { x => x.AllowEmptyInput, false },
            { x => x.EmptyInputErrorMessage, T("Please enter a chat name.") },
        };
        
        var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>(T("Rename Chat"), dialogParameters, DialogOptions.FULLSCREEN);
        var dialogResult = await dialogReference.Result;
        if (dialogResult is null || dialogResult.Canceled)
            return;

        chat.Name = (dialogResult.Data as string)!;
        if (this.CurrentChatThread?.ChatId == chat.ChatId)
        {
            this.CurrentChatThread.Name = chat.Name;
            await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
        }
        
        await WorkspaceBehaviour.StoreChatAsync(chat);
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    private async Task RenameWorkspaceAsync(string? workspacePath)
    {
        if (workspacePath is null)
            return;
        
        var workspaceId = Guid.Parse(Path.GetFileName(workspacePath));
        var workspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(workspaceId);
        var dialogParameters = new DialogParameters<SingleInputDialog>
        {
            { x => x.Message, string.Format(T("Please enter a new or edit the name for your workspace '{0}':"), workspaceName) },
            { x => x.InputHeaderText, T("Workspace Name") },
            { x => x.UserInput, workspaceName },
            { x => x.ConfirmText, T("Rename") },
            { x => x.ConfirmColor, Color.Info },
            { x => x.AllowEmptyInput, false },
            { x => x.EmptyInputErrorMessage, T("Please enter a workspace name.") },
            { x => x.AdditionalValidation, await this.CreateWorkspaceNameValidationAsync(workspaceId, workspaceName) },
        };
        
        var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>(T("Rename Workspace"), dialogParameters, DialogOptions.FULLSCREEN);
        var dialogResult = await dialogReference.Result;
        if (dialogResult is null || dialogResult.Canceled)
            return;
        
        var alteredWorkspaceName = (dialogResult.Data as string)!;
        if (!await WorkspaceBehaviour.RenameWorkspaceAsync(workspaceId, alteredWorkspaceName))
            return;

        await this.SendMessage(Event.WORKSPACE_RENAMED, workspaceId);
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    private async Task AddWorkspaceAsync()
    {
        var dialogParameters = new DialogParameters<SingleInputDialog>
        {
            { x => x.Message, T("Please name your workspace:") },
            { x => x.InputHeaderText, T("Workspace Name") },
            { x => x.UserInput, string.Empty },
            { x => x.ConfirmText, T("Add workspace") },
            { x => x.ConfirmColor, Color.Info },
            { x => x.AllowEmptyInput, false },
            { x => x.EmptyInputErrorMessage, T("Please enter a workspace name.") },
            { x => x.AdditionalValidation, await this.CreateWorkspaceNameValidationAsync() },
        };
        
        var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>(T("Add Workspace"), dialogParameters, DialogOptions.FULLSCREEN);
        var dialogResult = await dialogReference.Result;
        if (dialogResult is null || dialogResult.Canceled)
            return;
        
        var workspaceName = (dialogResult.Data as string)!;
        var result = await WorkspaceBehaviour.TryCreateWorkspaceAsync(workspaceName);
        if (!result.Success)
            return;
        
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    private async Task DeleteWorkspaceAsync(string? workspacePath)
    {
        if (workspacePath is null)
            return;
        
        var workspaceId = Guid.Parse(Path.GetFileName(workspacePath));
        var workspaceName = await WorkspaceBehaviour.LoadWorkspaceNameAsync(workspaceId);
        
        var chatCount = Directory.EnumerateDirectories(workspacePath).Count();
        var dialogParameters = new DialogParameters<ConfirmDialog>
        {
            { x => x.Message, string.Format(T("Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace."), workspaceName, chatCount) },
        };
        
        var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete Workspace"), dialogParameters, DialogOptions.FULLSCREEN);
        var dialogResult = await dialogReference.Result;
        if (dialogResult is null || dialogResult.Canceled)
            return;
        
        Directory.Delete(workspacePath, true);
        await WorkspaceBehaviour.RemoveWorkspaceFromCacheAsync(workspaceId);
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }
    
    private async Task MoveChatAsync(string? chatPath)
    {
        var chat = await this.LoadChatAsync(chatPath, false);
        if (chat is null)
            return;

        if (this.AIJobService.IsChatGenerationActive(chat.ChatId))
            return;
        
        var dialogParameters = new DialogParameters<WorkspaceSelectionDialog>
        {
            { x => x.Message, T("Please select the workspace where you want to move the chat to.") },
            { x => x.SelectedWorkspace, chat.WorkspaceId },
            { x => x.ConfirmText, T("Move chat") },
        };
        
        var dialogReference = await this.DialogService.ShowAsync<WorkspaceSelectionDialog>(T("Move Chat to Workspace"), dialogParameters, DialogOptions.FULLSCREEN_MANUAL_ESCAPE);
        var dialogResult = await dialogReference.Result;
        if (dialogResult is null || dialogResult.Canceled)
            return;
        
        var workspaceId = dialogResult.Data is Guid id ? id : Guid.Empty;
        if (workspaceId == Guid.Empty)
            return;
        
        await WorkspaceBehaviour.MoveChatAsync(chat, workspaceId);
        if (this.CurrentChatThread?.ChatId == chat.ChatId)
        {
            this.CurrentChatThread = chat;
            await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
        }

        await this.LoadTreeItemsAsync(startPrefetch: false);
    }
    
    private async Task AddChatAsync(string workspacePath)
    {
        if (await MessageBus.INSTANCE.SendMessageUseFirstResult<bool, bool>(this, Event.HAS_CHAT_UNSAVED_CHANGES))
        {
            var dialogParameters = new DialogParameters<ConfirmDialog>
            {
                { x => x.Message, T("Are you sure you want to create a another chat? All unsaved changes will be lost.") },
            };
        
            var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Create Chat"), dialogParameters, DialogOptions.FULLSCREEN);
            var dialogResult = await dialogReference.Result;
            if (dialogResult is null || dialogResult.Canceled)
                return;
        }
        
        var workspaceId = Guid.Parse(Path.GetFileName(workspacePath));
        var chat = new ChatThread
        {
            WorkspaceId = workspaceId,
            ChatId = Guid.NewGuid(),
            Name = string.Empty,
            SystemPrompt = SystemPrompts.DEFAULT,
            Blocks = [],
        };
        
        var chatPath = Path.Join(workspacePath, chat.ChatId.ToString());
        
        await WorkspaceBehaviour.StoreChatAsync(chat);
        await this.LoadChatAsync(chatPath, switchToChat: true);
        await this.LoadTreeItemsAsync(startPrefetch: false);
    }

    #region Overrides of MSGComponentBase

    protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
    {
        switch (triggeredEvent)
        {
            case Event.PLUGINS_RELOADED:
                await this.ForceRefreshFromDiskAsync();
                break;

            case Event.WORKSPACE_CREATED:
                await this.LoadTreeItemsAsync(startPrefetch: false);
                break;

            case Event.AI_JOB_CHANGED:
            case Event.AI_JOB_FINISHED:
            case Event.CHAT_GENERATION_CHANGED:
                await this.SafeStateHasChanged();
                break;
        }
    }

    protected override void DisposeResources()
    {
        this.MediaTranscriptionService.StateChanged -= this.OnMediaImportStateChanged;
        this.isDisposed = true;
        this.prefetchCancellationTokenSource?.Cancel();
        this.prefetchCancellationTokenSource?.Dispose();
        this.prefetchCancellationTokenSource = null;
        this.searchCancellationTokenSource?.Cancel();
        this.searchCancellationTokenSource?.Dispose();
        this.searchCancellationTokenSource = null;

        base.DisposeResources();
    }

    #endregion
}
