using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Input;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Windows.Graphics;
using WinRT.Interop;

namespace YMhut.Box.WinUI.Views;

internal enum IndependentWindowShellMode
{
    Browser,
    Tool
}

internal sealed class IndependentWindowShell
{
    private const double CaptionButtonsWidth = 146;

    private readonly ContentControl _contentHost = new()
    {
        HorizontalContentAlignment = HorizontalAlignment.Stretch,
        VerticalContentAlignment = VerticalAlignment.Stretch
    };
    private readonly StackPanel _leftActions;
    private readonly StackPanel _rightActions;
    private readonly FrameworkElement _centerSurface;
    private readonly TextBox? _commandBox;
    private readonly TextBlock? _titleText;

    public IndependentWindowShell(
        string placeholder,
        string iconGlyph,
        string commandText = "",
        IndependentWindowShellMode mode = IndependentWindowShellMode.Browser)
    {
        Root = new Grid { Background = ModernUi.AppBackground };
        Root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(46) });
        Root.RowDefinitions.Add(new RowDefinition());

        IconElement = new FontIcon
        {
            Glyph = iconGlyph,
            Width = 28,
            Height = 34,
            FontSize = 18,
            Foreground = ModernUi.Brush("#111418"),
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Center
        };

        BackButton = ShellIconButton("\uE72B", "Back");
        ForwardButton = ShellIconButton("\uE72A", "Forward");
        StopButton = ShellIconButton("\uE711", "Stop");
        ExtensionButton = ShellIconButton("\uE8A7", "Open");
        MenuButton = ShellIconButton("\uE712", "Menu");

        var effectiveMode = mode == IndependentWindowShellMode.Browser && !string.IsNullOrWhiteSpace(commandText)
            ? IndependentWindowShellMode.Tool
            : mode;

        if (effectiveMode == IndependentWindowShellMode.Browser)
        {
            _commandBox = new TextBox
            {
                PlaceholderText = BrowserPlaceholder(placeholder),
                Text = commandText,
                BorderThickness = new Thickness(0),
                Background = ModernUi.Brush("#00FFFFFF"),
                Foreground = ModernUi.Brush("#29313A"),
                Padding = new Thickness(0),
                FontSize = 14,
                Height = 26,
                MinHeight = 0,
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Center
            };
            _commandBox.KeyDown += CommandBox_KeyDown;
            _centerSurface = BuildBrowserAddressBox(_commandBox);
        }
        else
        {
            _titleText = ModernUi.Text(commandText, 15, FontWeights.SemiBold, ModernUi.Brush("#29313A"), maxLines: 1);
            _titleText.HorizontalAlignment = HorizontalAlignment.Center;
            _titleText.VerticalAlignment = VerticalAlignment.Center;
            _centerSurface = BuildToolTitleBox(_titleText);
        }

        TitleBar = new Grid
        {
            Background = ModernUi.Brush("#FFF2F2F5"),
            BorderBrush = ModernUi.Brush("#FFD7D8DD"),
            BorderThickness = new Thickness(0, 0, 0, 1),
            ColumnSpacing = 8,
            Padding = new Thickness(8, 5, 0, 5),
            ColumnDefinitions =
            {
                new ColumnDefinition { Width = GridLength.Auto },
                new ColumnDefinition(),
                new ColumnDefinition { Width = GridLength.Auto },
                new ColumnDefinition { Width = new GridLength(CaptionButtonsWidth) }
            }
        };

        _leftActions = new StackPanel
        {
            Orientation = Orientation.Horizontal,
            Spacing = 4,
            VerticalAlignment = VerticalAlignment.Center,
            Children = { IconElement, BackButton, ForwardButton, StopButton }
        };
        TitleBar.Children.Add(_leftActions);

        Grid.SetColumn(_centerSurface, 1);
        TitleBar.Children.Add(_centerSurface);

        _rightActions = new StackPanel
        {
            Orientation = Orientation.Horizontal,
            Spacing = 4,
            Margin = new Thickness(4, 0, 8, 0),
            VerticalAlignment = VerticalAlignment.Center,
            Children = { ExtensionButton, MenuButton }
        };
        Grid.SetColumn(_rightActions, 2);
        TitleBar.Children.Add(_rightActions);

        Root.Children.Add(TitleBar);
        Grid.SetRow(_contentHost, 1);
        Root.Children.Add(_contentHost);
    }

    public event EventHandler<string>? CommandSubmitted;

    public Grid Root { get; }

    public Grid TitleBar { get; }

    public FrameworkElement IconElement { get; }

    public Button BackButton { get; }

    public Button ForwardButton { get; }

    public Button StopButton { get; }

    public Button ExtensionButton { get; }

    public Button MenuButton { get; }

    public IReadOnlyList<FrameworkElement> PassthroughElements => [_leftActions, _centerSurface, _rightActions];

    public void SetContent(UIElement content)
    {
        _contentHost.Content = content;
    }

    public void SetCommandText(string text)
    {
        if (_commandBox is not null)
        {
            if (!string.Equals(_commandBox.Text, text, StringComparison.Ordinal))
            {
                _commandBox.Text = text;
            }

            return;
        }

        if (_titleText is not null && !string.Equals(_titleText.Text, text, StringComparison.Ordinal))
        {
            _titleText.Text = text;
        }
    }

    public void SetButtonGlyph(Button button, string glyph)
    {
        if (button.Content is FontIcon icon)
        {
            icon.Glyph = glyph;
        }
    }

    public void SetButtonToolTip(Button button, string text)
    {
        ToolTipService.SetToolTip(button, text);
        AutomationProperties.SetName(button, text);
    }

    private void CommandBox_KeyDown(object sender, KeyRoutedEventArgs e)
    {
        if (e.Key != Windows.System.VirtualKey.Enter || _commandBox is null)
        {
            return;
        }

        e.Handled = true;
        CommandSubmitted?.Invoke(this, _commandBox.Text);
    }

    private static Border BuildBrowserAddressBox(TextBox textBox)
    {
        var grid = new Grid
        {
            ColumnSpacing = 8,
            ColumnDefinitions =
            {
                new ColumnDefinition { Width = GridLength.Auto },
                new ColumnDefinition()
            },
            Children =
            {
                new FontIcon
                {
                    Glyph = "\uE83D",
                    FontSize = 15,
                    Foreground = ModernUi.Brush("#4B535C"),
                    VerticalAlignment = VerticalAlignment.Center
                }
            }
        };

        Grid.SetColumn(textBox, 1);
        grid.Children.Add(textBox);

        return new Border
        {
            Height = 35,
            MinWidth = 320,
            MaxWidth = 860,
            Padding = new Thickness(12, 0, 12, 0),
            CornerRadius = new CornerRadius(8),
            Background = ModernUi.Brush("#FFE2E3E8"),
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Center,
            Child = grid
        };
    }

    private static string BrowserPlaceholder(string placeholder)
    {
        if (string.IsNullOrWhiteSpace(placeholder) ||
            placeholder.Contains("Baidu", StringComparison.OrdinalIgnoreCase) ||
            placeholder.Contains("百度", StringComparison.OrdinalIgnoreCase))
        {
            return AppLocalizer.T("使用 Bing 搜索，或者输入网址", "Search with Bing or enter address");
        }

        return placeholder;
    }

    private static Border BuildToolTitleBox(TextBlock titleText)
    {
        return new Border
        {
            Height = 35,
            MinWidth = 240,
            MaxWidth = 700,
            Padding = new Thickness(14, 0, 14, 0),
            CornerRadius = new CornerRadius(8),
            Background = ModernUi.Brush("#FFE2E3E8"),
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Center,
            Child = titleText
        };
    }

    private static Button ShellIconButton(string glyph, string tooltip)
    {
        var button = new Button
        {
            Width = 34,
            Height = 34,
            Padding = new Thickness(0),
            CornerRadius = new CornerRadius(7),
            Background = ModernUi.Brush("#00FFFFFF"),
            BorderBrush = ModernUi.Brush("#00FFFFFF"),
            Content = new FontIcon
            {
                Glyph = glyph,
                FontSize = 17,
                Foreground = ModernUi.Brush("#58606A")
            }
        };
        ToolTipService.SetToolTip(button, tooltip);
        AutomationProperties.SetName(button, tooltip);
        return button;
    }
}

internal sealed class TitleBarDragRegionHelper
{
    private readonly Window _window;
    private readonly FrameworkElement _titleBar;
    private readonly IReadOnlyList<FrameworkElement> _passthroughElements;
    private readonly double _captionButtonsWidth;
    private InputNonClientPointerSource? _nonClientPointerSource;
    private DispatcherQueueTimer? _updateTimer;
    private RectInt32[] _lastCaptionRects = [];
    private RectInt32[] _lastPassthroughRects = [];

    public TitleBarDragRegionHelper(
        Window window,
        FrameworkElement titleBar,
        IReadOnlyList<FrameworkElement> passthroughElements,
        double captionButtonsWidth = 146)
    {
        _window = window;
        _titleBar = titleBar;
        _passthroughElements = passthroughElements;
        _captionButtonsWidth = captionButtonsWidth;
        Configure();
    }

    private void Configure()
    {
        _window.ExtendsContentIntoTitleBar = true;
        _window.SetTitleBar(_titleBar);

        try
        {
            var hwnd = WindowNative.GetWindowHandle(_window);
            var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
            _nonClientPointerSource = InputNonClientPointerSource.GetForWindowId(windowId);

            _titleBar.Loaded += (_, _) => ScheduleUpdate(TimeSpan.FromMilliseconds(1));
            _titleBar.SizeChanged += (_, _) => ScheduleUpdate();
            foreach (var element in _passthroughElements)
            {
                element.SizeChanged += (_, _) => ScheduleUpdate();
            }
        }
        catch (Exception exception)
        {
            CrashLog.Write(exception);
        }
    }

    public void Update()
    {
        var source = _nonClientPointerSource;
        if (source is null ||
            _titleBar.XamlRoot is null ||
            _titleBar.ActualWidth <= 0 ||
            _titleBar.ActualHeight <= 0)
        {
            return;
        }

        try
        {
            var scale = _titleBar.XamlRoot.RasterizationScale;
            var rightLimit = Math.Max(0, _titleBar.ActualWidth - _captionButtonsWidth);
            var segments = new List<(double Left, double Right)> { (0, rightLimit) };
            var passthrough = new List<RectInt32>();

            foreach (var element in _passthroughElements)
            {
                var bounds = BoundsInTitleBar(element);
                if (bounds is null)
                {
                    continue;
                }

                var expandedLeft = Math.Max(0, bounds.Value.Left - 6);
                var expandedRight = Math.Min(rightLimit, bounds.Value.Right + 6);
                passthrough.Add(ToPixelRect(bounds.Value.Left, bounds.Value.Top, bounds.Value.Width, bounds.Value.Height, scale));
                segments = SubtractSegment(segments, expandedLeft, expandedRight);
            }

            var captionRects = segments
                .Where(segment => segment.Right - segment.Left > 8)
                .Select(segment => ToPixelRect(segment.Left, 0, segment.Right - segment.Left, _titleBar.ActualHeight, scale))
                .ToArray();
            var passthroughRects = passthrough.ToArray();

            if (RectsEqual(_lastCaptionRects, captionRects) &&
                RectsEqual(_lastPassthroughRects, passthroughRects))
            {
                return;
            }

            source.SetRegionRects(NonClientRegionKind.Caption, captionRects);
            source.SetRegionRects(NonClientRegionKind.Passthrough, passthroughRects);
            _lastCaptionRects = captionRects;
            _lastPassthroughRects = passthroughRects;
        }
        catch (Exception exception)
        {
            CrashLog.Write(exception);
        }
    }

    private void ScheduleUpdate(TimeSpan? delay = null)
    {
        _updateTimer ??= CreateOneShotTimer(delay ?? TimeSpan.FromMilliseconds(48));
        _updateTimer.Interval = delay ?? TimeSpan.FromMilliseconds(48);
        _updateTimer.Stop();
        _updateTimer.Start();
    }

    private DispatcherQueueTimer CreateOneShotTimer(TimeSpan interval)
    {
        var timer = _titleBar.DispatcherQueue.CreateTimer();
        timer.Interval = interval;
        timer.IsRepeating = false;
        timer.Tick += (_, _) => Update();
        return timer;
    }

    private Windows.Foundation.Rect? BoundsInTitleBar(FrameworkElement element)
    {
        if (element.Visibility != Visibility.Visible || element.ActualWidth <= 0 || element.ActualHeight <= 0)
        {
            return null;
        }

        try
        {
            var point = element.TransformToVisual(_titleBar).TransformPoint(new Windows.Foundation.Point(0, 0));
            return new Windows.Foundation.Rect(point.X, point.Y, element.ActualWidth, element.ActualHeight);
        }
        catch
        {
            return null;
        }
    }

    private static List<(double Left, double Right)> SubtractSegment(
        List<(double Left, double Right)> segments,
        double excludeLeft,
        double excludeRight)
    {
        if (excludeRight <= excludeLeft)
        {
            return segments;
        }

        var result = new List<(double Left, double Right)>();
        foreach (var segment in segments)
        {
            if (excludeRight <= segment.Left || excludeLeft >= segment.Right)
            {
                result.Add(segment);
                continue;
            }

            if (excludeLeft > segment.Left)
            {
                result.Add((segment.Left, excludeLeft));
            }

            if (excludeRight < segment.Right)
            {
                result.Add((excludeRight, segment.Right));
            }
        }

        return result;
    }

    private static RectInt32 ToPixelRect(double x, double y, double width, double height, double scale)
    {
        return new RectInt32
        {
            X = (int)Math.Round(x * scale),
            Y = (int)Math.Round(y * scale),
            Width = Math.Max(1, (int)Math.Round(width * scale)),
            Height = Math.Max(1, (int)Math.Round(height * scale))
        };
    }

    private static bool RectsEqual(IReadOnlyList<RectInt32> left, IReadOnlyList<RectInt32> right)
    {
        if (left.Count != right.Count)
        {
            return false;
        }

        for (var i = 0; i < left.Count; i++)
        {
            if (left[i].X != right[i].X ||
                left[i].Y != right[i].Y ||
                left[i].Width != right[i].Width ||
                left[i].Height != right[i].Height)
            {
                return false;
            }
        }

        return true;
    }
}
