using InABox.Core;
using InABox.Wpf;
using InABox.WPF;
using Syncfusion.Data.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Markup.Localizer;
using System.Windows.Media;

namespace InABox.DynamicGrid;

public class TagsEditorControl : DynamicEditorControl<string, TagsEditor>
{
    private List<string> Tags = new();
    private string CurrentTag = "";
    private string _editorText = "";

    private int? _selectedTagIdx;
    private string EditorText
    {
        get => _editorText;
        set
        {
            _editorText = value;
            Editor.Text = value;
        }
    }

    private TextBox Editor = null!;
    private Popup? Popup = null;
    private ListBox? TagList = null;

    public List<string> RecentTags = new();

    public event Action<IEnumerable<string>>? OnRecentTagsChanged;

    protected override FrameworkElement CreateEditor()
    {
        Editor = new TextBox
        {
            VerticalAlignment = VerticalAlignment.Stretch,
            VerticalContentAlignment = VerticalAlignment.Center,
            HorizontalAlignment = HorizontalAlignment.Stretch,
            AcceptsTab = true
        };
        Editor.TextChanged += Editor_TextChanged;
        Editor.PreviewKeyDown += Editor_PreviewKeyDown;
        Editor.SelectionChanged += Editor_SelectionChanged;

        return Editor;
    }

    private void CreatePopup()
    {
        if(Popup is not null && !Popup.IsOpen)
        {
            ClosePopup();
        }
        if(Popup is null)
        {
            Popup = new Popup();
            Popup.PlacementTarget = Editor;
            Popup.Placement = PlacementMode.Bottom;
            Popup.PopupAnimation = PopupAnimation.Slide;

            TagList = new ListBox();
            TagList.Width = Editor.ActualWidth;
            TagList.Height = 100;
            TagList.HorizontalContentAlignment = HorizontalAlignment.Stretch;
            TagList.ItemTemplate = TemplateGenerator.CreateDataTemplate(() =>
            {
                var panel = new DockPanel();

                var item = new TextBlock();
                item.Bind<string, string>(TextBlock.TextProperty, x => x);
                DockPanel.SetDock(item, Dock.Left);

                var btn = new Button();
                btn.Content = "X";
                btn.Width = 15;
                btn.Height = 15;
                btn.Padding = new();
                btn.FontSize = 10;
                btn.VerticalAlignment = VerticalAlignment.Center;
                btn.VerticalContentAlignment = VerticalAlignment.Center;
                btn.Background = Colors.Transparent.ToBrush();
                btn.BorderBrush = Colors.Transparent.ToBrush();
                btn.Bind<string, string>(Button.TagProperty, x => x);
                btn.Bind(Button.VisibilityProperty, panel, x => x.IsMouseOver, new WPF.BooleanToVisibilityConverter(Visibility.Visible, Visibility.Collapsed));
                DockPanel.SetDock(btn, Dock.Right);
                btn.Click += DeleteTag_Click;

                panel.Children.Add(btn);
                panel.Children.Add(item);

                return panel;
            });
            TagList.ItemsSource = RecentTags;
            TagList.MouseUp += TagList_MouseUp;

            Popup.Child = TagList;
            Popup.StaysOpen = false;

            Popup.IsOpen = true;
        }
    }

    private void DeleteTag_Click(object sender, RoutedEventArgs e)
    {
        if (sender is not Button btn || btn.Tag is not string tag) return;

        if (RecentTags.Remove(tag))
        {
            OnRecentTagsChanged?.Invoke(RecentTags);
            UpdateTagList();
        }
    }

    private void TagList_MouseUp(object sender, MouseButtonEventArgs e)
    {
        AddSelectedTag();
        Editor.Focus();
    }

    private void ClosePopup()
    {
        if(Popup is not null)
        {
            Popup.IsOpen = false;
            Popup = null;
            TagList = null;
        }
    }

    // Update if one writes a separator.

    private (int?, int) GetTagIdx(int idx)
    {
        for(int i = 0; i < Tags.Count; ++i)
        {
            var tagStr = EditorDefinition.Prefix + Tags[i] + EditorDefinition.Separator + 1;
            if(idx < tagStr.Length)
            {
                return (i, idx);
            }
            idx -= tagStr.Length;

            if(idx < 0)
            {
                return (null, idx + tagStr.Length);
            }
        }

        return (null, idx);
    }

    private void AddTag(string tag)
    {
        if (!Tags.Contains(tag))
        {
            Tags.Add(tag);
        }
        if (!RecentTags.Contains(tag))
        {
            RecentTags.Add(tag);
            OnRecentTagsChanged?.Invoke(RecentTags);
        }
    }

    private void UpdateTagList()
    {
        if (TagList is null) return;

        var current = CurrentTag;
        var currentSelected = TagList.SelectedItem;

        TagList.ItemsSource = RecentTags.Where(x => x.StartsWith(current))
            .Concat(RecentTags.Where(x => !x.StartsWith(current) && x.Contains(current)));
        TagList.SelectedItem = currentSelected;
        if(TagList.SelectedIndex == -1)
        {
            TagList.SelectedIndex = 0;
        }
    }

    private void UpdateEditorText(int? cursorIdx)
    {
        EditorText = string.Join("", Tags.Select(x => EditorDefinition.Prefix + x + EditorDefinition.Separator + " ")) + CurrentTag;

        if (cursorIdx.HasValue)
        {
            var startIdx = 0;
            for(int i = 0; i < Tags.Count; ++i)
            {
                startIdx += EditorDefinition.Prefix.Length + Tags[i].Length + EditorDefinition.Separator.Length + 1;
            }
            Editor.CaretIndex = startIdx + cursorIdx.Value;
        }
        else
        {
            Editor.CaretIndex = Editor.Text.Length;
        }
    }

    private void AddSelectedTag()
    {
        if (TagList is null || TagList.SelectedIndex == -1)
        {
            CurrentTag += EditorDefinition.Separator;
            AddCurrentTag();
            CheckChanged();
            ClosePopup();

            UpdateEditorText(null);
        }
        else
        {
            var tag = TagList.SelectedItem?.ToString() ?? "";
            if (!tag.IsNullOrWhiteSpace())
            {
                AddTag(tag);
            }

            CheckChanged();
            CurrentTag = "";
            ClosePopup();
            UpdateEditorText(null);
        }
    }

    private bool _updatingSelection;

    private void SelectTag(int idx)
    {
        _selectedTagIdx = idx;

        var startIdx = 0;
        for(int i = 0; i < idx; ++i)
        {
            startIdx += EditorDefinition.Prefix.Length + Tags[i].Length + EditorDefinition.Separator.Length + 1;
        }

        try
        {
            _updatingSelection = true;
            Editor.Select(startIdx, EditorDefinition.Prefix.Length + Tags[idx].Length + EditorDefinition.Separator.Length);
        }
        finally
        {
            _updatingSelection = false;
        }
    }

    private void Editor_SelectionChanged(object sender, RoutedEventArgs e)
    {
        if (_updatingSelection) return;

        var idx = Editor.CaretIndex;
        var (tagIdx, cIdx) = GetTagIdx(idx);
        if (tagIdx.HasValue)
        {
            SelectTag(tagIdx.Value);
        }
        else
        {
            _selectedTagIdx = null;
        }
    }

    private void Editor_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if(e.Key == Key.Down)
        {
            if(TagList is null) return;
            if(TagList.SelectedIndex >= TagList.Items.Count - 1)
            {
                TagList.SelectedIndex = 0;
            }
            else
            {
                ++TagList.SelectedIndex;
            }
            TagList.ScrollIntoView(TagList.SelectedItem);
            e.Handled = true;
        }
        else if(e.Key == Key.Up)
        {
            if(TagList is null) return;
            if(TagList.SelectedIndex <= 0)
            {
                TagList.SelectedIndex = TagList.Items.Count - 1;
            }
            else
            {
                --TagList.SelectedIndex;
            }
            TagList.ScrollIntoView(TagList.SelectedItem);
            e.Handled = true;
        }
        else if(e.Key == Key.Left)
        {
            if (_selectedTagIdx.HasValue && _selectedTagIdx.Value > 0)
            {
                SelectTag(_selectedTagIdx.Value - 1);
                e.Handled = true;
            }
        }
        else if(e.Key == Key.Right)
        {
            if (_selectedTagIdx.HasValue)
            {
                if(_selectedTagIdx.Value < Tags.Count - 1)
                {
                    SelectTag(_selectedTagIdx.Value + 1);
                    e.Handled = true;
                }
                else
                {
                    _selectedTagIdx = null;
                    Editor.CaretIndex = EditorText.Length;
                    e.Handled = true;
                }
            }
        }
        else if(e.Key == Key.Tab || e.Key == Key.Enter)
        {
            AddSelectedTag();

            e.Handled = true;
        }
        else if(e.Key == Key.Back)
        {
            if (_selectedTagIdx.HasValue)
            {
                var selectedIdx = _selectedTagIdx.Value;
                Tags.RemoveAt(selectedIdx);
                CheckChanged();
                UpdateEditorText(null);
                if(selectedIdx == 0)
                {
                    if(Tags.Count > 0)
                    {
                        SelectTag(0);
                    }
                }
                else
                {
                    SelectTag(selectedIdx - 1);
                }
                e.Handled = true;
            }
            else if (CurrentTag.IsNullOrWhiteSpace() && Tags.Count > 0)
            {
                Tags.RemoveAt(Tags.Count - 1);
                CheckChanged();
                ClosePopup();
                UpdateEditorText(null);
                e.Handled = true;
            }
            else
            {
                var (tagIdx, cIdx) = GetTagIdx(Editor.CaretIndex);
                if(tagIdx is null)
                {
                    if(cIdx == 0)
                    {
                        e.Handled = true;
                    }
                }
                else
                {
                    Tags.RemoveAt(tagIdx.Value);
                    CheckChanged();
                    UpdateEditorText(null);
                    e.Handled = true;
                }
            }
        }
    }

    private bool AddCurrentTag()
    {
        if (CurrentTag.Contains(EditorDefinition.Separator))
        {
            int startIdx = 0;
            while(startIdx < CurrentTag.Length)
            {
                var idx = CurrentTag.IndexOf(EditorDefinition.Separator, startIdx);
                if(idx == -1)
                {
                    break;
                }

                var tag = CurrentTag[startIdx..idx].Trim();
                while (tag.StartsWith(EditorDefinition.Prefix))
                {
                    tag = tag[EditorDefinition.Prefix.Length..].Trim();
                }
                if (!tag.IsNullOrWhiteSpace())
                {
                    AddTag(tag);
                }

                startIdx = idx + 1;
            }

            CurrentTag = CurrentTag[startIdx..].Trim();
            return true;
        }
        else
        {
            return false;
        }
    }

    private void Editor_TextChanged(object sender, TextChangedEventArgs e)
    {
        var (tagIdx, cIdx) = GetTagIdx(Editor.CaretIndex);
        int? curIdx = cIdx;

        if (tagIdx is null)
        {
            var idx = 0;
            for(int i = 0; i < Tags.Count; ++i)
            {
                idx += EditorDefinition.Prefix.Length + Tags[i].Length + EditorDefinition.Separator.Length + 1;
            }
            CurrentTag = Editor.Text[idx..]; // If idx is -1, then idx + 1 is 0, which we want.
        }
        if (AddCurrentTag())
        {
            curIdx = null;
        }

        CreatePopup();
        UpdateTagList();
        UpdateEditorText(curIdx);
    }

    public override void Configure()
    {
    }

    public override int DesiredHeight()
    {
        return 25;
    }

    public override int DesiredWidth()
    {
        return int.MaxValue;
    }

    public override void SetColor(Color color)
    {
        Editor.Background = color.ToBrush();
    }

    public override void SetFocus()
    {
        Editor.Focus();
        Editor.CaretIndex = Editor.Text.Length;
    }

    protected override string RetrieveValue()
    {
        return EditorDefinition.WriteTags(Tags);
    }

    protected override void UpdateValue(string value)
    {
        Tags = EditorDefinition.ReadTags(value).ToList();
        UpdateEditorText(null);
    }
}
