using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Lumi.Companion.Abstractions;
using Lumi.Companion.SongOverlay;

namespace Lumi.Companion.App;

public partial class App : Application
{
    private TrayIcon? _trayIcon;
    private MainWindow? _mainWindow;
    private SongOverlayRuntime? _songOverlay;
    private IReadOnlyList<ICompanionPluginContribution> _plugins = [];

    public override void Initialize() => AvaloniaXamlLoader.Load(this);

    public override void OnFrameworkInitializationCompleted()
    {
        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
            var paths = CompanionPaths.ForCurrentUser();
            var settings = new CompanionSettingsStore(paths.SettingsPath);
            var runtime = new CompanionRuntime(paths, settings);
            _songOverlay = new SongOverlayRuntime(
                Path.Combine(paths.Root, "plugins", "now_playing"),
                paths.LogsDirectory,
                runtime.CreatePluginTransport("now_playing"));
            _plugins = [new TranscriptionPluginContribution(runtime), _songOverlay];
            _mainWindow = new MainWindow(runtime, settings, _songOverlay, _plugins);
            desktop.MainWindow = _mainWindow;
            ConfigureTray(desktop, runtime, _plugins);
            runtime.UpdateRestartRequested += async () =>
            {
                _mainWindow.AllowExit();
                _trayIcon?.Dispose();
                if (_songOverlay is not null) await _songOverlay.DisposeAsync();
                await runtime.DisposeAsync();
                desktop.Shutdown();
            };
            Program.InstanceCoordinator!.ActivationRequested += () => Dispatcher.UIThread.Post(ShowMainWindow);
            if (!Program.LaunchInBackground) _mainWindow.Show();
            _ = InitializeRuntimesAsync(runtime, _songOverlay);
        }
        base.OnFrameworkInitializationCompleted();
    }

    private static async Task InitializeRuntimesAsync(CompanionRuntime runtime, SongOverlayRuntime songOverlay)
    {
        await runtime.InitializeAsync();
        try { await songOverlay.InitializeAsync(); }
        catch { /* The plugin publishes its own actionable status without taking down the shell. */ }
    }

    private void ConfigureTray(IClassicDesktopStyleApplicationLifetime desktop, CompanionRuntime runtime, IReadOnlyList<ICompanionPluginContribution> plugins)
    {
        var open = new NativeMenuItem("Open Lumi Companion");
        var web = new NativeMenuItem("Open Lumi WebUI");
        open.Click += (_, _) => ShowMainWindow();
        web.Click += (_, _) => runtime.OpenLumiWebUi();

        var menu = new NativeMenu();
        menu.Items.Add(open);
        menu.Items.Add(web);
        menu.Items.Add(new NativeMenuItemSeparator());
        menu.Items.Add(new NativeMenuItem("Plugins") { IsEnabled = false });

        var pluginViews = new List<PluginTrayView>();
        foreach (var plugin in plugins.OrderBy(item => item.Descriptor.Order).ThenBy(item => item.Descriptor.Name, StringComparer.OrdinalIgnoreCase))
        {
            var submenu = new NativeMenu();
            var firstPage = plugin.Pages.OrderBy(item => item.Order).FirstOrDefault();
            if (firstPage is not null)
            {
                var openPlugin = new NativeMenuItem($"Open {plugin.Descriptor.Name}");
                openPlugin.Click += (_, _) => ShowPluginPage(firstPage.Key);
                submenu.Items.Add(openPlugin);
                if (plugin.Actions.Count > 0) submenu.Items.Add(new NativeMenuItemSeparator());
            }

            var actionViews = new List<(CompanionPluginAction Action, NativeMenuItem Item)>();
            foreach (var action in plugin.Actions.OrderBy(item => item.Order))
            {
                var actionItem = new NativeMenuItem(action.Label());
                actionItem.Click += async (_, _) => await RunPluginActionAsync(plugin, action);
                submenu.Items.Add(actionItem);
                actionViews.Add((action, actionItem));
            }

            if (submenu.Items.Count > 0) submenu.Items.Add(new NativeMenuItemSeparator());
            var status = new NativeMenuItem($"Status: {plugin.Status.Summary}") { IsEnabled = false };
            submenu.Items.Add(status);
            var root = new NativeMenuItem(plugin.Descriptor.Name) { Menu = submenu };
            menu.Items.Add(root);
            var view = new PluginTrayView(plugin, root, status, actionViews);
            pluginViews.Add(view);
            plugin.Changed += () => Dispatcher.UIThread.Post(() => RefreshPluginTray(view));
        }

        menu.Items.Add(new NativeMenuItemSeparator());
        var health = new NativeMenuItem("Health: Starting") { IsEnabled = false };
        var update = new NativeMenuItem("Updates: Checking…") { IsEnabled = false };
        var logs = new NativeMenuItem("Logs & diagnostics");
        var settings = new NativeMenuItem("Settings");
        var quit = new NativeMenuItem("Quit");
        logs.Click += (_, _) => { runtime.OpenLogsDirectory(); };
        settings.Click += (_, _) => { ShowMainWindow(); _mainWindow?.ShowPage(CompanionPage.Settings); };
        update.Click += (_, _) => { ShowMainWindow(); _mainWindow?.ShowPage(CompanionPage.Overview); };
        quit.Click += async (_, _) =>
        {
            if (runtime.State.RequiresQuitConfirmation) ShowMainWindow();
            if (_mainWindow is null || !await _mainWindow.ConfirmQuitAsync()) return;
            _mainWindow.AllowExit();
            _trayIcon?.Dispose();
            if (_songOverlay is not null) await _songOverlay.DisposeAsync();
            await runtime.DisposeAsync();
            desktop.Shutdown();
        };
        menu.Items.Add(health);
        menu.Items.Add(update);
        menu.Items.Add(logs);
        menu.Items.Add(settings);
        menu.Items.Add(new NativeMenuItemSeparator());
        menu.Items.Add(quit);

        _trayIcon = new TrayIcon
        {
            Icon = LumiIconFactory.Create(TrayHealth.Ready),
            ToolTipText = "Lumi Companion — starting",
            IsVisible = true,
            Menu = menu
        };
        _trayIcon.Clicked += (_, _) => ShowMainWindow();
        TrayIcon.SetIcons(this, new TrayIcons { _trayIcon });
        var renderedHealth = TrayHealth.Ready;
        var renderedToolTip = _trayIcon.ToolTipText;
        var renderedHealthHeader = health.Header?.ToString();
        var renderedUpdateHeader = update.Header?.ToString();
        var renderedUpdateEnabled = update.IsEnabled;
        var refreshGate = new object();
        CompanionState? pendingState = null;
        var refreshQueued = false;

        void ApplyTrayState(CompanionState state)
        {
            if (_trayIcon is null) return;
            if (state.Health != renderedHealth)
            {
                _trayIcon.Icon = LumiIconFactory.Create(state.Health);
                renderedHealth = state.Health;
            }

            var nextToolTip = state.UpdateAvailable
                ? $"Lumi Companion — update {state.AvailableVersion} available"
                : $"Lumi Companion — {state.Summary}";
            if (!string.Equals(nextToolTip, renderedToolTip, StringComparison.Ordinal))
            {
                _trayIcon.ToolTipText = nextToolTip;
                renderedToolTip = nextToolTip;
            }

            var nextHealthHeader = $"Health: {state.Summary}";
            if (!string.Equals(nextHealthHeader, renderedHealthHeader, StringComparison.Ordinal))
            {
                health.Header = nextHealthHeader;
                renderedHealthHeader = nextHealthHeader;
            }

            var nextUpdateHeader = state.UpdateAvailable
                ? $"Update available: {state.AvailableVersion}"
                : "Updates: Current";
            if (!string.Equals(nextUpdateHeader, renderedUpdateHeader, StringComparison.Ordinal))
            {
                update.Header = nextUpdateHeader;
                renderedUpdateHeader = nextUpdateHeader;
            }
            if (state.UpdateAvailable != renderedUpdateEnabled)
            {
                update.IsEnabled = state.UpdateAvailable;
                renderedUpdateEnabled = state.UpdateAvailable;
            }
        }

        void DrainTrayRefresh()
        {
            CompanionState? latest;
            lock (refreshGate)
            {
                latest = pendingState;
                pendingState = null;
            }
            if (latest is not null) ApplyTrayState(latest);

            lock (refreshGate)
            {
                if (pendingState is null)
                {
                    refreshQueued = false;
                    return;
                }
            }
            Dispatcher.UIThread.Post(DrainTrayRefresh, DispatcherPriority.Background);
        }

        runtime.StateChanged += state =>
        {
            lock (refreshGate)
            {
                pendingState = state;
                if (refreshQueued) return;
                refreshQueued = true;
            }
            Dispatcher.UIThread.Post(DrainTrayRefresh, DispatcherPriority.Background);
        };
        foreach (var view in pluginViews) RefreshPluginTray(view);
    }

    private async Task RunPluginActionAsync(ICompanionPluginContribution plugin, CompanionPluginAction action)
    {
        try
        {
            await action.Execute(CancellationToken.None);
        }
        catch
        {
            var page = plugin.Pages.OrderBy(item => item.Order).FirstOrDefault();
            if (page is not null) ShowPluginPage(page.Key);
        }
    }

    private static void RefreshPluginTray(PluginTrayView view)
    {
        view.Root.Header = view.Plugin.Descriptor.Name;
        view.Status.Header = $"Status: {view.Plugin.Status.Summary}";
        foreach (var (action, item) in view.Actions)
        {
            item.Header = action.Label();
            item.IsEnabled = action.CanExecute?.Invoke() ?? true;
        }
    }

    private void ShowPluginPage(string pageKey)
    {
        ShowMainWindow();
        if (Enum.TryParse<CompanionPage>(pageKey, out var page)) _mainWindow?.ShowPage(page);
    }

    private void ShowMainWindow()
    {
        if (_mainWindow is null) return;
        _mainWindow.Show();
        _mainWindow.WindowState = WindowState.Normal;
        _mainWindow.Activate();
    }

    private sealed record PluginTrayView(
        ICompanionPluginContribution Plugin,
        NativeMenuItem Root,
        NativeMenuItem Status,
        IReadOnlyList<(CompanionPluginAction Action, NativeMenuItem Item)> Actions);
}
