using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using DragonISO.App.Services;
using DragonISO.App.ViewModels;

namespace DragonISO.App;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        SourceInitialized += OnSourceInitialized;
        Closing += OnClosing;
        // Esc dismisses the settings drawer when it's open. Bound at the
        // window level so any focused control inside the drawer also gets
        // the affordance.
        PreviewKeyDown += OnPreviewKeyDown;
    }

    public MainWindow(MainViewModel viewModel) : this()
    {
        DataContext = viewModel;
        // Hand the view-model the palette-opener callback so Ctrl+K's
        // KeyBinding (which lives on the VM as an ICommand) can reach
        // back into the view layer to materialize the window.
        viewModel.RegisterCommandPaletteOpener(() => OnCommandPaletteClick(this, new RoutedEventArgs()));
    }

    /// <summary>
    /// Restore the window's previous placement after the HWND is created (so
    /// SetWindowPos / WindowState transitions actually take effect). Falls
    /// silently back to the XAML-default startup location if no snapshot exists.
    /// </summary>
    private void OnSourceInitialized(object? sender, EventArgs e)
    {
        WindowStateStore.TryApply(this);
    }

    /// <summary>Persist the placement on close so next launch lands in the same spot.</summary>
    private void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e)
    {
        // A failure persisting window state must NEVER block the window from
        // closing â€” operator's shutdown comes first. WindowStateStore.Save
        // already swallows its own IO errors; this is defense-in-depth for
        // anything that escapes (NRE, future regression, etc.).
        try { WindowStateStore.Save(this); }
        catch { /* best-effort: forgo placement memory for one launch */ }
    }

    /// <summary>Opens the About dialog â€” version, NDI runtime, build SHA.</summary>
    private void OnAboutClick(object sender, RoutedEventArgs e)
    {
        var about = new AboutWindow { Owner = this };
        about.ShowDialog();
    }

    /// <summary>
    /// Opens the operator-presets dialog. Hands it the current participants
    /// snapshot (so Save captures live state) and the engine controller (so
    /// Apply can reconcile enable/disable). Owner is set so the chromeless
    /// dialog centers over the main window and inherits z-order.
    /// </summary>
    private void OnPresetsClick(object sender, RoutedEventArgs e)
    {
        if (DataContext is not MainViewModel vm) return;
        var dialog = new PresetsDialog(vm.Controller, vm.Participants.ToList(), vm.Toast)
        {
            Owner = this,
        };
        dialog.ShowDialog();
    }

    /// <summary>
    /// Tracks whether we have hidden Teams' windows so the next click reverses
    /// the action. We treat this as "intent" rather than a query of OS state
    /// because hidden windows still report as hidden if the operator manually
    /// re-opens them and we only care about Dragon-ISO's own toggle history.
    /// </summary>
    private bool _teamsWindowsHidden;

    /// <summary>
    /// Phase E.2 toggle. Hides every visible top-level Teams window on first
    /// click; shows them again on the next. Surfaces the result via the toast
    /// so the operator gets feedback even though the affected windows aren't
    /// visible anymore.
    /// </summary>
    private void OnToggleTeamsWindowClick(object sender, RoutedEventArgs e)
    {
        if (!TeamsLauncher.IsRunning())
        {
            MessageBox.Show(
                Properties.Strings.HideShowTeams_NotRunning_Message,
                Properties.Strings.HideShowTeams_Title,
                MessageBoxButton.OK,
                MessageBoxImage.Information);
            return;
        }

        var toast = (DataContext as MainViewModel)?.Toast;
        if (_teamsWindowsHidden)
        {
            var shown = TeamsLauncher.ShowWindows();
            _teamsWindowsHidden = false;
            toast?.Show(shown > 0 ? $"Restored {shown} Teams window(s)" : "No Teams windows to restore");
        }
        else
        {
            var hidden = TeamsLauncher.HideWindows();
            _teamsWindowsHidden = hidden > 0;
            toast?.Show(hidden > 0 ? $"Hid {hidden} Teams window(s)" : "Teams has no visible windows yet");
        }
    }

    /// <summary>
    /// Three-state click behavior matching operator intuition:
    ///   1. Teams not running â†’ launch it via TeamsLauncher's fallback chain.
    ///   2. Teams running but its windows are hidden â†’ restore + foreground them.
    ///   3. Teams running with visible windows â†’ bring the most recent to front.
    /// (Stopping Teams is a right-click action; see OnLaunchTeamsRightClick.)
    /// </summary>
    private void OnLaunchTeamsClick(object sender, RoutedEventArgs e)
    {
        var toast = (DataContext as MainViewModel)?.Toast;

        if (!TeamsLauncher.IsRunning())
        {
            if (!TeamsLauncher.TryLaunch(out var error))
            {
                MessageBox.Show(
                    string.Format(Properties.Strings.LaunchTeams_Failed_MessageFormat, error),
                    Properties.Strings.LaunchTeams_Title,
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
            }
            else
            {
                var autoHide = (DataContext as MainViewModel)?.Settings.AutoHideTeamsWindows ?? false;
                toast?.Show(autoHide
                    ? "Launching Microsoft Teams (will hide windows automatically)â€¦"
                    : "Launching Microsoft Teamsâ€¦");
                if (autoHide)
                {
                    _ = TeamsLauncher.AutoHideAfterLaunchAsync();
                    _teamsWindowsHidden = true;
                }
                else
                {
                    _teamsWindowsHidden = false;
                }
            }
            return;
        }

        var shown = TeamsLauncher.ShowWindows();
        _teamsWindowsHidden = false;
        toast?.Show(shown > 0
            ? $"Teams is already running â€” surfaced {shown} window(s)"
            : "Teams is running but has no visible windows yet");
    }

    /// <summary>
    /// Right-click on the Launch button asks to stop Teams. Split out from the
    /// left-click so a normal click is "open / surface" rather than the previous
    /// "open OR ambush you with a stop dialog". The confirmation dialog here is
    /// intentional â€” Stop Teams is a destructive mid-show action; explicit
    /// confirmation is the right pattern, not the "ambush" anti-pattern that
    /// was fixed for left-click. The palette also offers Stop Teams for
    /// keyboard-first operators.
    /// </summary>
    private void OnLaunchTeamsRightClick(object sender, MouseButtonEventArgs e)
    {
        if (!TeamsLauncher.IsRunning()) return;

        var confirm = MessageBox.Show(
            Properties.Strings.StopTeams_Confirm_Message,
            Properties.Strings.StopTeams_Title,
            MessageBoxButton.YesNo,
            MessageBoxImage.Question);
        if (confirm != MessageBoxResult.Yes) return;

        var asked = TeamsLauncher.StopAll();
        if (TeamsLauncher.IsRunning())
        {
            MessageBox.Show(
                asked == 0
                    ? Properties.Strings.StopTeams_NoneResponded
                    : string.Format(Properties.Strings.StopTeams_AskedFormat, asked),
                Properties.Strings.StopTeams_Title,
                MessageBoxButton.OK,
                MessageBoxImage.Information);
        }
        e.Handled = true;
    }

    /// <summary>
    /// Open the experimental Teams embed window. Operator enables the
    /// preference first; this button materializes the host.
    /// </summary>
    private void OnOpenEmbedWindowClick(object sender, RoutedEventArgs e)
    {
        var w = new TeamsEmbedWindow { Owner = this };
        w.Show();
    }

    /// <summary>
    /// Toggle the v2 settings drawer overlay. The header gear button and the
    /// drawer's own Close button both call this. State is held by the
    /// overlay's <see cref="UIElement.Visibility"/> directly â€” no separate
    /// flag â€” so the toggle is idempotent regardless of how many entry
    /// points open / close it.
    /// </summary>
    private void OnSettingsToggleClick(object sender, RoutedEventArgs e)
    {
        if (SettingsDrawerOverlay is null) return;
        SettingsDrawerOverlay.Visibility = SettingsDrawerOverlay.Visibility == Visibility.Visible
            ? Visibility.Collapsed
            : Visibility.Visible;
    }

    /// <summary>
    /// Clicking the scrim behind the drawer dismisses it â€” same affordance as
    /// every well-behaved slide-over on every platform.
    /// </summary>
    private void OnSettingsScrimClick(object sender, MouseButtonEventArgs e)
    {
        if (SettingsDrawerOverlay is null) return;
        SettingsDrawerOverlay.Visibility = Visibility.Collapsed;
    }

    /// <summary>
    /// Open the v2 Ctrl+K command palette. Bound to the header âŒ˜K button and
    /// to the Ctrl+K keyboard binding. The palette is a chromeless floating
    /// window owned by this MainWindow so it centers correctly, closes on
    /// Deactivated (click outside), and inherits z-order. We construct a
    /// fresh view-model each time so the filter starts empty.
    /// </summary>
    private void OnCommandPaletteClick(object sender, RoutedEventArgs e)
    {
        if (DataContext is not MainViewModel vm) return;
        var paletteVm = new ViewModels.CommandPaletteViewModel(vm, Dispatcher);
        var palette = new Views.CommandPaletteWindow(paletteVm) { Owner = this };
        palette.ShowDialog();
    }

    /// <summary>
    /// Open the per-participant ISO override editor. Bound to the gear button
    /// in the participant row. The dialog reads the engine's current override
    /// (if any) and lets the operator edit framerate / resolution / aspect /
    /// audio for that specific pipeline; Apply / Clear / Cancel are handled by
    /// the dialog's view-model, so this handler is just plumbing.
    /// </summary>
    private void OnIsoOverrideClick(object sender, RoutedEventArgs e)
    {
        if (DataContext is not MainViewModel vm) return;
        if (sender is not FrameworkElement fe) return;
        if (fe.DataContext is not ParticipantViewModel p) return;

        var currentOverride = vm.Controller.GetIsoOverride(p.Id);
        var dialogVm = new ViewModels.IsoOverrideDialogViewModel(
            vm.Controller,
            vm.Settings,
            p.Id,
            p.DisplayName,
            currentOverride,
            vm.Toast);
        var dialog = new Views.IsoOverrideDialog(dialogVm) { Owner = this };
        dialog.ShowDialog();
    }

    /// <summary>
    /// Esc closes the drawer when it's open. We use PreviewKeyDown rather than
    /// KeyDown so the drawer's nested inputs (TextBox, ComboBox) don't swallow
    /// the key before this handler sees it.
    /// </summary>
    private void OnPreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key != Key.Escape) return;
        if (SettingsDrawerOverlay?.Visibility == Visibility.Visible)
        {
            SettingsDrawerOverlay.Visibility = Visibility.Collapsed;
            e.Handled = true;
        }
    }
}
