using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.System;

namespace Sonex.Client.Controls;

public sealed partial class MultiLineSuggestTextBox : UserControl
{
    private static readonly char[] _tokenSeparators = { ' ', ',', '\r', '\n', '\t' };

    private readonly Flyout _suggestionsFlyout;
    private readonly ListView _suggestionsList;

    private IReadOnlyList<string> _allSuggestions = Array.Empty<string>();
    private List<string> _currentSuggestions = new();
    private bool _isApplyingSuggestionInternally;
    private bool _allowListFocus;

    public event EventHandler? SuggestionFlyoutOpened;
    public event EventHandler? SuggestionFlyoutClosed;

    public MultiLineSuggestTextBox()
    {
        InitializeComponent();

        _suggestionsList = new ListView
        {
            SelectionMode = ListViewSelectionMode.Single,
            IsItemClickEnabled = true,
            MaxHeight = 220,
            HorizontalAlignment = HorizontalAlignment.Stretch,
            IsTabStop = false,
            BorderThickness = new Thickness(0)
        };
        _suggestionsList.ItemClick += SuggestionsList_ItemClick;
        _suggestionsList.KeyDown += SuggestionsList_KeyDown;
        _suggestionsList.GettingFocus += SuggestionsList_GettingFocus;
        _suggestionsList.PointerPressed += (_, _) => _allowListFocus = true;

        _suggestionsFlyout = new Flyout
        {
            Placement = FlyoutPlacementMode.Bottom,
            ShouldConstrainToRootBounds = true,
            Content = _suggestionsList
        };
        _suggestionsFlyout.Opened += (_, _) => SuggestionFlyoutOpened?.Invoke(this, EventArgs.Empty);
        _suggestionsFlyout.Closed += (_, _) => SuggestionFlyoutClosed?.Invoke(this, EventArgs.Empty);
    }

    public bool IsSuggestionOpen => _suggestionsFlyout.IsOpen;

    public string Text
    {
        get => InputBox.Text ?? string.Empty;
        set => InputBox.Text = value ?? string.Empty;
    }

    public string PlaceholderText
    {
        get => InputBox.PlaceholderText;
        set => InputBox.PlaceholderText = value;
    }

    public void FocusInput()
    {
        InputBox.DispatcherQueue.TryEnqueue(() =>
        {
            InputBox.Focus(FocusState.Programmatic);
            var textLength = InputBox.Text?.Length ?? 0;
            InputBox.SelectionStart = textLength;
            InputBox.SelectionLength = 0;
        });
    }

    public void SetSuggestions(IReadOnlyList<string>? suggestions)
    {
        _allSuggestions = suggestions ?? Array.Empty<string>();

        if (_allSuggestions.Count == 0)
        {
            HideSuggestions();
            return;
        }

        if (IsSuggestionOpen)
        {
            UpdateSuggestions(allowEmptyQuery: true);
        }
    }

    private void InputBox_GotFocus(object sender, RoutedEventArgs e)
    {
        UpdateSuggestions(allowEmptyQuery: true);
    }

    private void InputBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (_isApplyingSuggestionInternally)
        {
            return;
        }

        UpdateSuggestions(allowEmptyQuery: false);
    }

    private void InputBox_KeyDown(object sender, KeyRoutedEventArgs e)
    {
        if (e.Key == VirtualKey.Escape && IsSuggestionOpen)
        {
            HideSuggestions();
            e.Handled = true;
            return;
        }

        if (e.Key == VirtualKey.Down && IsSuggestionOpen && _currentSuggestions.Count > 0)
        {
            _allowListFocus = true;
            _suggestionsList.SelectedIndex = Math.Max(_suggestionsList.SelectedIndex, 0);
            _suggestionsList.Focus(FocusState.Keyboard);
            e.Handled = true;
            return;
        }

        if (e.Key != VirtualKey.Enter || !IsSuggestionOpen)
        {
            return;
        }

        CommitSuggestion(_suggestionsList.SelectedItem as string ?? _currentSuggestions.FirstOrDefault());
        e.Handled = true;
    }

    private void SuggestionsList_KeyDown(object sender, KeyRoutedEventArgs e)
    {
        if (e.Key != VirtualKey.Enter)
        {
            return;
        }

        CommitSuggestion(_suggestionsList.SelectedItem as string ?? _currentSuggestions.FirstOrDefault());
        e.Handled = true;
    }

    private void SuggestionsList_ItemClick(object sender, ItemClickEventArgs e)
    {
        CommitSuggestion(e.ClickedItem as string);
    }

    private void SuggestionsList_GettingFocus(UIElement sender, GettingFocusEventArgs args)
    {
        if (_allowListFocus)
        {
            return;
        }

        args.Cancel = true;
        RestoreInputFocusPreservingCaret();
    }

    private void UpdateSuggestions(bool allowEmptyQuery)
    {
        if (_allSuggestions.Count == 0)
        {
            HideSuggestions();
            return;
        }

        var text = InputBox.Text ?? string.Empty;
        var caret = Math.Clamp(InputBox.SelectionStart, 0, text.Length);
        var query = GetCurrentQueryToken(text, caret, out var isSeparatorContext);

        if (string.IsNullOrWhiteSpace(query))
        {
            if (!allowEmptyQuery && !isSeparatorContext)
            {
                HideSuggestions();
                return;
            }

            ShowSuggestions(_allSuggestions.Take(60).ToList());
            return;
        }

        var filtered = _allSuggestions
            .Where(x => x.Contains(query, StringComparison.CurrentCultureIgnoreCase))
            .Take(60)
            .ToList();

        ShowSuggestions(filtered);
    }

    private void ShowSuggestions(List<string> suggestions)
    {
        _currentSuggestions = suggestions;
        if (_currentSuggestions.Count == 0)
        {
            HideSuggestions();
            return;
        }

        _allowListFocus = false;
        _suggestionsList.ItemsSource = _currentSuggestions;
        _suggestionsList.SelectedIndex = 0;
        _suggestionsList.Width = Math.Max(120, InputBox.ActualWidth);

        if (!_suggestionsFlyout.IsOpen)
        {
            _suggestionsFlyout.ShowAt(InputBox);
            RestoreInputFocusPreservingCaret();
        }
    }

    private void HideSuggestions()
    {
        _currentSuggestions.Clear();
        _allowListFocus = false;
        _suggestionsList.ItemsSource = null;

        if (_suggestionsFlyout.IsOpen)
        {
            _suggestionsFlyout.Hide();
        }
    }

    private void CommitSuggestion(string? suggestion)
    {
        var selected = suggestion?.Trim();
        if (string.IsNullOrWhiteSpace(selected))
        {
            return;
        }

        var currentText = InputBox.Text ?? string.Empty;
        var caret = Math.Clamp(InputBox.SelectionStart, 0, currentText.Length);
        var (start, end) = GetCurrentTokenBounds(currentText, caret);
        var replacement = $"{selected}, ";

        var updatedText = $"{currentText[..start]}{replacement}{currentText[end..]}";

        _isApplyingSuggestionInternally = true;
        InputBox.Text = updatedText;
        _isApplyingSuggestionInternally = false;

        HideSuggestions();
        RestoreInputFocusAtEnd();
    }

    private void RestoreInputFocusPreservingCaret()
    {
        var selectionStart = InputBox.SelectionStart;
        var selectionLength = InputBox.SelectionLength;

        InputBox.DispatcherQueue.TryEnqueue(() =>
        {
            var textLength = InputBox.Text?.Length ?? 0;
            var start = Math.Clamp(selectionStart, 0, textLength);
            var length = Math.Clamp(selectionLength, 0, textLength - start);

            InputBox.Focus(FocusState.Programmatic);
            InputBox.SelectionStart = start;
            InputBox.SelectionLength = length;
        });
    }

    private void RestoreInputFocusAtEnd()
    {
        InputBox.DispatcherQueue.TryEnqueue(() =>
        {
            var textLength = InputBox.Text?.Length ?? 0;
            InputBox.Focus(FocusState.Programmatic);
            InputBox.SelectionStart = textLength;
            InputBox.SelectionLength = 0;
        });
    }

    private static string GetCurrentQueryToken(string text, int caret, out bool isSeparatorContext)
    {
        var (start, end) = GetCurrentTokenBounds(text, caret);
        isSeparatorContext = caret == 0 || (caret > 0 && _tokenSeparators.Contains(text[caret - 1]));

        if (end <= start)
        {
            return string.Empty;
        }

        return text[start..end].Trim();
    }

    private static (int Start, int End) GetCurrentTokenBounds(string text, int caret)
    {
        var safeText = text ?? string.Empty;
        var safeCaret = Math.Clamp(caret, 0, safeText.Length);

        var start = safeCaret;
        while (start > 0 && !_tokenSeparators.Contains(safeText[start - 1]))
        {
            start--;
        }

        var end = safeCaret;
        while (end < safeText.Length && !_tokenSeparators.Contains(safeText[end]))
        {
            end++;
        }

        return (start, end);
    }
}
