using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using System.Windows;
using WinForms = System.Windows.Forms;

namespace TeamsISO.App.Services;

/// <summary>
/// Wraps a WinForms <see cref="WinForms.NotifyIcon"/> so the WPF host can
/// minimize-to-tray during long shows. Operators with a Stream Deck setup
/// often want TeamsISO running but invisible — the tray icon keeps the
/// process alive (and the engine routing live) while the window stays
/// hidden.
///
/// Lifecycle pattern: instantiate from <c>App.OnStartup</c> after the main
/// window exists; dispose from <c>App.OnExit</c>. The host hooks the main
/// window's <c>StateChanged</c> to detect minimize and toggles
/// <c>WindowState.Minimized</c> + <c>ShowInTaskbar=false</c> + <c>Hide()</c>.
/// </summary>
[SupportedOSPlatform("windows")]
public sealed class TrayIconHost : IDisposable
{
    private readonly Window _mainWindow;
    private readonly WinForms.NotifyIcon _notifyIcon;
    private bool _enabled;

    public TrayIconHost(Window mainWindow)
    {
        _mainWindow = mainWindow;
        _notifyIcon = new WinForms.NotifyIcon
        {
            Text = "TeamsISO",
            Icon = LoadEmbeddedIcon(),
            Visible = false,
        };
        _notifyIcon.DoubleClick += (_, _) => RestoreFromTray();
        _notifyIcon.ContextMenuStrip = BuildMenu();
    }

    /// <summary>
    /// Toggles the minimize-to-tray behavior. When on, minimizing the window
    /// hides it and shows a tray icon; when off, minimize is normal Windows
    /// behavior. Read by the operator's checkbox in DISPLAY settings; the
    /// setting persists via <see cref="UIPreferences"/>.
    /// </summary>
    public bool Enabled
    {
        get => _enabled;
        set
        {
            if (_enabled == value) return;
            _enabled = value;
            if (value)
            {
                _mainWindow.StateChanged += OnMainWindowStateChanged;
            }
            else
            {
                _mainWindow.StateChanged -= OnMainWindowStateChanged;
                // If we're currently minimized + hidden, restore so the user
                // doesn't lose the window when they disable the setting.
                RestoreFromTray();
                _notifyIcon.Visible = false;
            }
        }
    }

    private void OnMainWindowStateChanged(object? sender, EventArgs e)
    {
        if (_mainWindow.WindowState != WindowState.Minimized) return;
        // Hide from taskbar + hide the window, show the tray icon.
        _mainWindow.ShowInTaskbar = false;
        _mainWindow.Hide();
        _notifyIcon.Visible = true;
        _notifyIcon.ShowBalloonTip(
            timeout: 1500,
            tipTitle: "TeamsISO is still running",
            tipText: "Engine + control surface are live. Double-click the tray icon to restore the window.",
            tipIcon: WinForms.ToolTipIcon.Info);
    }

    private void RestoreFromTray()
    {
        _mainWindow.Show();
        _mainWindow.WindowState = WindowState.Normal;
        _mainWindow.ShowInTaskbar = true;
        _mainWindow.Activate();
        _notifyIcon.Visible = false;
    }

    private WinForms.ContextMenuStrip BuildMenu()
    {
        var menu = new WinForms.ContextMenuStrip();
        menu.Items.Add("Show TeamsISO", null, (_, _) => RestoreFromTray());
        menu.Items.Add("-");
        menu.Items.Add("Stop all ISOs", null, (_, _) =>
        {
            // Reach into the VM via the main window. Using string-keyed
            // command lookup would be more decoupled but adds overhead.
            if (_mainWindow.DataContext is ViewModels.MainViewModel vm
                && vm.StopAllIsosCommand.CanExecute(null))
            {
                vm.StopAllIsosCommand.Execute(null);
            }
        });
        menu.Items.Add("-");
        menu.Items.Add("Exit TeamsISO", null, (_, _) => System.Windows.Application.Current.Shutdown());
        return menu;
    }

    /// <summary>
    /// Load the bundled teamsiso.ico from this assembly's resources. We use
    /// the embedded resource rather than the file-system path because the
    /// app may be run from any CWD (via the MSI install or a developer dotnet run).
    /// </summary>
    private static Icon LoadEmbeddedIcon()
    {
        try
        {
            var asm = Assembly.GetExecutingAssembly();
            var uri = new Uri("pack://application:,,,/Assets/teamsiso.ico");
            using var stream = System.Windows.Application.GetResourceStream(uri)?.Stream;
            if (stream is not null) return new Icon(stream);
        }
        catch
        {
            // Fall through to the OS default
        }
        return SystemIcons.Application;
    }

    public void Dispose()
    {
        try { _mainWindow.StateChanged -= OnMainWindowStateChanged; } catch { /* ignore */ }
        try { _notifyIcon.Visible = false; } catch { /* ignore */ }
        try { _notifyIcon.Dispose(); } catch { /* ignore */ }
    }
}
