using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Windows.Foundation;
using Windows.System;
using Windows.UI.Core;

namespace Sonex.Client.Controls.DataGridView;

// Odpowiedzialnosc pliku: UI i logika okna filtracji kolumn oraz budowania zestawu reguł.

public sealed partial class FilterFlyout : Flyout
{
    private sealed class ComparisonOption
    {
        public ComparisonOption(DataGridView.FilterComparison value, string label, string iconPath)
        {
            Value = value;
            Label = label;
            IconPath = iconPath;
        }

        public DataGridView.FilterComparison Value { get; }
        public string Label { get; }
        public string IconPath { get; }
    }

    private sealed class BoolOption
    {
        public BoolOption(object? value, string label)
        {
            Value = value;
            Label = label;
        }

        public object? Value { get; }
        public string Label { get; }
    }

    private sealed class EnumOption
    {
        public EnumOption(object? value, string label)
        {
            Value = value;
            Label = label;
        }

        public object? Value { get; }
        public string Label { get; }
    }

    private sealed class RowUi
    {
        public required Grid Root { get; init; }
        public required ComboBox ColumnCombo { get; init; }
        public required DropDownButton ComparisonButton { get; init; }
        public required ContentControl ValueHost { get; init; }
        public required ContentControl ValueHost2 { get; init; }
        public ComparisonOption? SelectedComparison { get; set; }
        public FrameworkElement? ValueEditor { get; set; }
        public FrameworkElement? ValueEditor2 { get; set; }
    }

    private readonly List<RowUi> _rows = new();
    private List<DataGridView.Column> _availableColumns = new();
    private const string FilterIconsBaseUri = "ms-appx:///Assets/Icons/FilterOperators/";
    private const double ComparisonIconWidth = 24;
    private const double ComparisonIconHeight = 16;
    private const double FilterControlHeight = 28;
    private const double FilterControlFontSize = 12;
    private static readonly char[] _tokenSeparators = { ',', '\r', '\n', '\t' };
    private bool _isRebuilding;
    private bool _suppressClosedApplyOnce;
    private DataGridView? _owner;
    private DataGridView.Column? _anchorColumn;
    private bool _reopenAfterClose;
    private DataGridView.Column? _pendingAnchorColumn;
    private bool _pendingPreferRightEdge;

    public FilterFlyout()
    {
        InitializeComponent();
        Placement = FlyoutPlacementMode.Bottom;
        Closed += FilterFlyout_Closed;
    }

    public void Show(DataGridView.Column column, bool preferRightEdge = false)
    {
        _owner = column.Root;
        _anchorColumn = column;

        if (XamlRoot == null)
        {
            XamlRoot = _owner.XamlRoot;
        }

        if (IsOpen)
        {
            _isRebuilding = true;
            _pendingAnchorColumn = column;
            _pendingPreferRightEdge = preferRightEdge;
            _reopenAfterClose = true;
            Hide();
            return;
        }

        ShowAtAnchor(column, preferRightEdge);
    }

    private void ShowAtAnchor(DataGridView.Column column, bool preferRightEdge)
    {
        RebuildRows();

        var content = Content as FrameworkElement;
        double flyoutW = 500d;
        if (content != null)
        {
            content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            flyoutW = Math.Max(flyoutW, content.DesiredSize.Width);
        }

        const double horizontalMargin = 10d;
        var hostWidth = Math.Max(_owner?.ActualWidth ?? 1d, 1d);
        var columnCenterX = column.LeftOnScreen + (column.Width * 0.5d);

        // Start from centered position relative to the target column.
        var desiredStartX = columnCenterX - (flyoutW * 0.5d);
        var maxStartX = hostWidth - flyoutW - horizontalMargin;
        if (maxStartX < horizontalMargin)
        {
            maxStartX = horizontalMargin;
        }

        // Shift the flyout start point so it stays within available horizontal space.
        var startX = Math.Clamp(desiredStartX, horizontalMargin, maxStartX);
        if (preferRightEdge)
        {
            startX = maxStartX;
        }
        var x = startX + (flyoutW * 0.5d);

        var showOptions = new FlyoutShowOptions
        {
            Position = new Point(x, (_owner?.HeaderHeight ?? 0d) + 8d),
            ShowMode = FlyoutShowMode.Transient,
            Placement = FlyoutPlacementMode.Bottom
        };

        if (_owner != null)
        {
            ShowAt(_owner, showOptions);
        }
    }

    private void RebuildRows()
    {
        if (_owner == null)
        {
            return;
        }

        _isRebuilding = true;
        RowsPanel.Children.Clear();
        _rows.Clear();

        _availableColumns = _owner
            .GetFilterColumns()
            .Where(c => !c.IsHiden)
            .OrderBy(c => c.Index)
            .ToList();

        var currentRules = _owner.GetActiveFilterRules();

        foreach (var rule in currentRules)
        {
            var matchingColumn = _availableColumns.FirstOrDefault(c => c.Name.Equals(rule.ColumnName, StringComparison.OrdinalIgnoreCase));
            if (matchingColumn != null)
            {
                AddRow(matchingColumn, rule);
            }
        }

        if (_anchorColumn != null && !_rows.Any(r => (r.ColumnCombo.SelectedItem as DataGridView.Column)?.Name == _anchorColumn.Name))
        {
            AddRow(_anchorColumn, null);
        }

        if (_rows.Count == 0)
        {
            AddRow(_availableColumns.FirstOrDefault(), null);
        }

        _isRebuilding = false;
    }

    private void AddRow(DataGridView.Column? selectedColumn, DataGridView.FilterRule? rule)
    {
        if (_availableColumns.Count == 0)
        {
            return;
        }

        var grid = new Grid
        {
            ColumnSpacing = 8,
            Margin = new Thickness(0, 0, 0, 2)
        };

        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(32) });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(120) });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(42) });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(123) });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(123) });

        var removeButton = new Button
        {
            Content = new FontIcon() { Glyph = "\uE894", FontSize = 10, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center },
            Style = (Style)Application.Current.Resources["SubtleButtonStyle"],
            Foreground = (Brush)Application.Current.Resources["SystemFillColorCriticalBrush"],
            Width = 32,
            Height = FilterControlHeight,
            MinHeight = FilterControlHeight,
            MaxHeight = FilterControlHeight,
            FontSize = FilterControlFontSize,
            VerticalAlignment = VerticalAlignment.Top,
            HorizontalAlignment = HorizontalAlignment.Center

        };

        var columnCombo = new ComboBox
        {
            ItemsSource = _availableColumns,
            DisplayMemberPath = nameof(DataGridView.Column.Title),
            Height = FilterControlHeight,
            MinHeight = FilterControlHeight,
            MaxHeight = FilterControlHeight,
            FontSize = FilterControlFontSize,
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Top
        };

        var comparisonButton = new DropDownButton
        {
            Width = 42,
            Height = FilterControlHeight,
            MinHeight = FilterControlHeight,
            MaxHeight = FilterControlHeight,
            FontSize = FilterControlFontSize,
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Top,
            Style = (Style)Application.Current.Resources["SubtleButtonStyle"],
            Padding = new Thickness(0)
        };

        var valueHost = new ContentControl
        {
            HorizontalAlignment = HorizontalAlignment.Left,
            HorizontalContentAlignment = HorizontalAlignment.Stretch,
            Width = 123
        };

        var valueHost2 = new ContentControl
        {
            HorizontalAlignment = HorizontalAlignment.Left,
            HorizontalContentAlignment = HorizontalAlignment.Stretch,
            Width = 123,
            Visibility = Visibility.Collapsed
        };



        Grid.SetColumn(removeButton, 0);
        Grid.SetColumn(columnCombo, 1);
        Grid.SetColumn(comparisonButton, 2);
        Grid.SetColumn(valueHost, 3);
        Grid.SetColumn(valueHost2, 4);

        grid.Children.Add(removeButton);
        grid.Children.Add(columnCombo);
        grid.Children.Add(comparisonButton);
        grid.Children.Add(valueHost);
        grid.Children.Add(valueHost2);

        RowsPanel.Children.Add(grid);

        var row = new RowUi
        {
            Root = grid,
            ColumnCombo = columnCombo,
            ComparisonButton = comparisonButton,
            ValueHost = valueHost,
            ValueHost2 = valueHost2
        };

        _rows.Add(row);

        columnCombo.SelectionChanged += (_, _) =>
        {
            if (_isRebuilding)
            {
                return;
            }
            ConfigureRow(row, null);
        };

        removeButton.Click += (_, _) =>
        {
            RowsPanel.Children.Remove(row.Root);
            _rows.Remove(row);

            if (_rows.Count == 0)
            {
                AddRow(_anchorColumn ?? _availableColumns.FirstOrDefault(), null);
            }
        };

        columnCombo.SelectedItem = selectedColumn ?? _availableColumns.FirstOrDefault();
        ConfigureRow(row, rule);
    }

    private void ConfigureRow(RowUi row, DataGridView.FilterRule? existingRule)
    {
        var column = row.ColumnCombo.SelectedItem as DataGridView.Column;
        if (column == null)
        {
            row.SelectedComparison = null;
            row.ComparisonButton.Content = null;
            row.ComparisonButton.Flyout = null;
            row.ValueHost.Content = null;
            row.ValueHost2.Content = null;
            row.ValueHost2.Visibility = Visibility.Collapsed;
            row.ValueEditor = null;
            row.ValueEditor2 = null;
            return;
        }

        var options = GetComparisonOptions(column.PropertyInfo.PropertyType);
        var selected = options.FirstOrDefault(x => existingRule != null && x.Value == existingRule.Comparison) ?? options.FirstOrDefault();
        ConfigureComparisonMenu(row, options, selected);

        var initialValue = NormalizeInitialValueForEditor(column.PropertyInfo.PropertyType, existingRule);
        RebuildValueEditors(row, initialValue, existingRule?.ValueTo);
    }

    private static object? NormalizeInitialValueForEditor(Type propertyType, DataGridView.FilterRule? rule)
    {
        if (rule == null)
        {
            return null;
        }

        if (IsBoolType(propertyType) && rule.Comparison == DataGridView.FilterComparison.IsEmpty)
        {
            return TryReadBool(rule.Value, out var shouldBeEmpty) && shouldBeEmpty
                ? DataGridView.BooleanFilterValue.Empty
                : DataGridView.BooleanFilterValue.NotEmpty;
        }

        return rule.Value;
    }

    private void RebuildValueEditors(RowUi row, object? initialValue, object? initialValueTo)
    {
        var column = row.ColumnCombo.SelectedItem as DataGridView.Column;
        if (column == null)
        {
            ConfigureValueHostsLayout(row, false);
            row.ValueHost.Content = null;
            row.ValueHost2.Content = null;
            row.ValueHost2.Visibility = Visibility.Collapsed;
            row.ValueEditor = null;
            row.ValueEditor2 = null;
            return;
        }

        var propertyType = Nullable.GetUnderlyingType(column.PropertyInfo.PropertyType) ?? column.PropertyInfo.PropertyType;
        var isBetween = IsBetweenSelected(row);
        ConfigureValueHostsLayout(row, isBetween);

        row.ValueEditor = CreateValueEditor(propertyType, row.SelectedComparison?.Value, initialValue, isBetween ? "From" : "Value");
        ApplyEditorLayout(row.ValueEditor, row.ValueHost.Width);
        row.ValueHost.Content = row.ValueEditor;
        AttachValueEditorHandlers(row.ValueEditor);

        if (isBetween)
        {
            row.ValueEditor2 = CreateValueEditor(propertyType, null, initialValueTo, "To");
            ApplyEditorLayout(row.ValueEditor2, row.ValueHost2.Width);
            row.ValueHost2.Content = row.ValueEditor2;
            row.ValueHost2.Visibility = Visibility.Visible;
            AttachValueEditorHandlers(row.ValueEditor2);
        }
        else
        {
            row.ValueHost2.Content = null;
            row.ValueHost2.Visibility = Visibility.Collapsed;
            row.ValueEditor2 = null;
        }

    }

    private static void ConfigureValueHostsLayout(RowUi row, bool isBetween)
    {
        Grid.SetColumn(row.ValueHost, 3);
        Grid.SetColumn(row.ValueHost2, 4);

        if (isBetween)
        {
            Grid.SetColumnSpan(row.ValueHost, 1);
            row.ValueHost.Width = 123;
            row.ValueHost.MinWidth = 123;
            row.ValueHost.MaxWidth = 123;
            row.ValueHost2.Width = 123;
            row.ValueHost2.MinWidth = 123;
            row.ValueHost2.MaxWidth = 123;
            return;
        }

        Grid.SetColumnSpan(row.ValueHost, 2);
        row.ValueHost.Width = 254;
        row.ValueHost.MinWidth = 254;
        row.ValueHost.MaxWidth = 254;
        row.ValueHost2.Width = 0;
        row.ValueHost2.MinWidth = 0;
        row.ValueHost2.MaxWidth = 0;
    }

    private static void ApplyEditorLayout(FrameworkElement? editor, double width)
    {
        if (editor == null)
        {
            return;
        }

        editor.HorizontalAlignment = HorizontalAlignment.Left;
        editor.Width = width;
        editor.MinWidth = width;
        editor.MaxWidth = width;
        editor.Height = FilterControlHeight;
        editor.MinHeight = FilterControlHeight;
        editor.MaxHeight = FilterControlHeight;

        if (editor is Control control)
        {
            control.FontSize = FilterControlFontSize;
        }
    }

    private void ConfigureComparisonMenu(RowUi row, IReadOnlyList<ComparisonOption> options, ComparisonOption? selected)
    {
        var menu = new MenuFlyout
        {
            Placement = FlyoutPlacementMode.Bottom
        };
        var comparisonItemStyle = (Content as FrameworkElement)?.Resources.TryGetValue("ComparisonMenuFlyoutItemStyle", out var styleValue) == true
            ? styleValue as Style
            : null;

        foreach (var option in options)
        {
            var selectedOption = option;
            var item = new MenuFlyoutItem
            {
                Text = selectedOption.Label,
                Icon = new ImageIcon
                {
                    Source = CreateComparisonSvgSource(selectedOption.IconPath),
                    Width = ComparisonIconWidth,
                    Height = ComparisonIconHeight
                }
            };
            if (comparisonItemStyle != null)
            {
                item.Style = comparisonItemStyle;
            }

            item.Click += (_, _) => OnComparisonSelected(row, selectedOption);
            menu.Items.Add(item);
        }

        row.ComparisonButton.Flyout = menu;
        SetSelectedComparison(row, selected ?? options.FirstOrDefault());
    }

    private void SetSelectedComparison(RowUi row, ComparisonOption? option)
    {
        row.SelectedComparison = option;

        if (option == null)
        {
            row.ComparisonButton.Content = null;
            row.ComparisonButton.IsEnabled = false;
            ToolTipService.SetToolTip(row.ComparisonButton, null);
            return;
        }

        row.ComparisonButton.IsEnabled = true;
        row.ComparisonButton.Content = new Image
        {
            Width = ComparisonIconWidth,
            Height = ComparisonIconHeight,
            Source = CreateComparisonSvgSource(option.IconPath)
        };
        ToolTipService.SetToolTip(row.ComparisonButton, option.Label);
    }

    private static SvgImageSource CreateComparisonSvgSource(string iconPath)
    {
        return new SvgImageSource(new Uri($"{FilterIconsBaseUri}{iconPath}"));
    }

    private void OnComparisonSelected(RowUi row, ComparisonOption selectedOption)
    {
        if (_isRebuilding)
        {
            return;
        }

        if (row.SelectedComparison?.Value == selectedOption.Value)
        {
            return;
        }

        var selectedColumnNow = row.ColumnCombo.SelectedItem as DataGridView.Column;
        object? currentValue = null;
        object? currentValueTo = null;
        if (selectedColumnNow != null)
        {
            currentValue = ReadEditorValue(row.ValueEditor, selectedColumnNow.PropertyInfo.PropertyType);
            currentValueTo = ReadEditorValue(row.ValueEditor2, selectedColumnNow.PropertyInfo.PropertyType);
        }

        SetSelectedComparison(row, selectedOption);
        RebuildValueEditors(row, currentValue, currentValueTo);

    }

    private FrameworkElement CreateValueEditor(
        Type propertyType,
        DataGridView.FilterComparison? comparison,
        object? initialValue,
        string placeholderText)
    {
        if (comparison == DataGridView.FilterComparison.IsEmpty)
        {
            return CreateIsEmptyEditor(initialValue);
        }

        if (IsMultiValueComparison(comparison))
        {
            return CreateTextEditor(FormatMultiValueText(initialValue), "Enter values separated by comma or line break", true);
        }

        var singleInitial = NormalizeSingleValue(initialValue);

        if (propertyType == typeof(bool))
        {
            var options = new List<BoolOption>
            {
                new BoolOption(DataGridView.BooleanFilterValue.True, "True"),
                new BoolOption(DataGridView.BooleanFilterValue.False, "False"),
                new BoolOption(DataGridView.BooleanFilterValue.Empty, "Empty"),
                new BoolOption(DataGridView.BooleanFilterValue.NotEmpty, "Not empty")
            };

            var boolCombo = new ComboBox
            {
                DisplayMemberPath = nameof(BoolOption.Label),
                ItemsSource = options
            };

            if (singleInitial is DataGridView.BooleanFilterValue booleanFilterValue)
            {
                boolCombo.SelectedItem = options.FirstOrDefault(x => Equals(x.Value, booleanFilterValue));
            }
            else if (singleInitial is bool boolValue)
            {
                var optionValue = boolValue
                    ? DataGridView.BooleanFilterValue.True
                    : DataGridView.BooleanFilterValue.False;
                boolCombo.SelectedItem = options.FirstOrDefault(x => Equals(x.Value, optionValue));
            }

            return boolCombo;
        }

        if (propertyType.IsEnum)
        {
            var enumOptions = new List<EnumOption>
            {
                new EnumOption(null, "Any")
            };
            enumOptions.AddRange(
                Enum.GetValues(propertyType)
                    .Cast<object>()
                    .Select(v => new EnumOption(v, v.ToString() ?? string.Empty))
            );

            var enumCombo = new ComboBox
            {
                DisplayMemberPath = nameof(EnumOption.Label),
                ItemsSource = enumOptions
            };

            if (singleInitial != null)
            {
                enumCombo.SelectedItem = enumOptions.FirstOrDefault(x => Equals(x.Value, singleInitial));
            }
            else
            {
                enumCombo.SelectedIndex = 0;
            }

            return enumCombo;
        }

        if (propertyType == typeof(DateTime) || propertyType == typeof(DateOnly))
        {
            var datePicker = new CalendarDatePicker
            {
                PlaceholderText = placeholderText
            };

            if (singleInitial is DateOnly dateOnly)
            {
                datePicker.Date = new DateTimeOffset(dateOnly.ToDateTime(TimeOnly.MinValue));
            }
            else if (singleInitial is DateTime dateTime)
            {
                datePicker.Date = new DateTimeOffset(dateTime);
            }

            return datePicker;
        }

        if (propertyType == typeof(TimeOnly) || propertyType == typeof(TimeSpan))
        {
            var timePicker = new TimePicker();

            if (singleInitial is TimeOnly timeOnly)
            {
                timePicker.SelectedTime = timeOnly.ToTimeSpan();
            }
            else if (singleInitial is TimeSpan span)
            {
                timePicker.SelectedTime = span;
            }
            else
            {
                timePicker.SelectedTime = null;
            }

            return timePicker;
        }

        return CreateTextEditor(singleInitial?.ToString() ?? string.Empty, placeholderText, false);
    }

    private static ComboBox CreateIsEmptyEditor(object? initialValue)
    {
        var options = new List<BoolOption>
        {
            new BoolOption(true, "Yes"),
            new BoolOption(false, "No")
        };

        var combo = new ComboBox
        {
            DisplayMemberPath = nameof(BoolOption.Label),
            ItemsSource = options
        };

        var shouldBeEmpty = true;
        if (TryReadBool(initialValue, out var parsed))
        {
            shouldBeEmpty = parsed;
        }

        combo.SelectedItem = options.FirstOrDefault(x => Equals(x.Value, shouldBeEmpty)) ?? options[0];
        return combo;
    }

    private static TextBox CreateTextEditor(string initialText, string placeholderText, bool isMultiValue)
    {
        var textBox = new TextBox
        {
            PlaceholderText = placeholderText,
            Text = NormalizeTextEditorLineBreaks(initialText),
            Tag = isMultiValue,
            AcceptsReturn = true,
            TextWrapping = TextWrapping.Wrap,
            HorizontalAlignment = HorizontalAlignment.Stretch,
            VerticalAlignment = VerticalAlignment.Top
        };

        textBox.TextChanged += TextEditor_TextChanged;
        return textBox;
    }

    private static void TextEditor_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (sender is not TextBox textBox)
        {
            return;
        }

        var text = textBox.Text ?? string.Empty;
        var normalized = NormalizeTextEditorLineBreaks(text);
        if (text == normalized)
        {
            return;
        }

        var selectionStart = Math.Clamp(textBox.SelectionStart, 0, text.Length);
        var normalizedSelectionStart = NormalizeTextEditorLineBreaks(text[..selectionStart]).Length;

        textBox.Text = normalized;
        textBox.SelectionStart = Math.Min(normalizedSelectionStart, normalized.Length);
        textBox.SelectionLength = 0;
    }

    private static string NormalizeTextEditorLineBreaks(string? text)
    {
        if (string.IsNullOrEmpty(text))
        {
            return string.Empty;
        }

        var result = new StringBuilder(text.Length);
        for (int i = 0; i < text.Length; i++)
        {
            var ch = text[i];
            if (ch != '\r' && ch != '\n')
            {
                result.Append(ch);
                continue;
            }

            result.Append(", ");
            while (i + 1 < text.Length && (text[i + 1] == '\r' || text[i + 1] == '\n'))
            {
                i++;
            }
        }

        return result.ToString();
    }

    private static string FormatMultiValueText(object? value)
    {
        if (value == null)
        {
            return string.Empty;
        }

        if (value is string text)
        {
            return text;
        }

        if (value is IEnumerable enumerable)
        {
            var tokens = new List<string>();
            foreach (var item in enumerable)
            {
                AddNormalizedToken(tokens, Convert.ToString(item));
            }

            return tokens.Count == 0 ? string.Empty : string.Join(", ", tokens);
        }

        return Convert.ToString(value) ?? string.Empty;
    }

    private static List<string> ParseMultiValueText(string? input)
    {
        var result = new List<string>();
        if (string.IsNullOrWhiteSpace(input))
        {
            return result;
        }

        var split = input.Split(_tokenSeparators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        foreach (var token in split)
        {
            AddNormalizedToken(result, token);
        }

        return result;
    }

    private static object? NormalizeSingleValue(object? value)
    {
        if (value == null || value is string)
        {
            return value;
        }

        if (value is IEnumerable enumerable)
        {
            foreach (var item in enumerable)
            {
                if (item == null)
                {
                    continue;
                }

                var text = Convert.ToString(item)?.Trim();
                if (!string.IsNullOrWhiteSpace(text))
                {
                    return text;
                }
            }

            return null;
        }

        return value;
    }

    private static void AddNormalizedToken(ICollection<string> target, string? value)
    {
        var trimmed = value?.Trim();
        if (string.IsNullOrWhiteSpace(trimmed))
        {
            return;
        }

        target.Add(trimmed);
    }

    private static bool TryReadBool(object? value, out bool result)
    {
        if (value is bool boolValue)
        {
            result = boolValue;
            return true;
        }

        if (value is string text)
        {
            var normalized = text.Trim().ToLowerInvariant();
            switch (normalized)
            {
                case "1":
                case "true":
                case "yes":
                case "y":
                    result = true;
                    return true;
                case "0":
                case "false":
                case "no":
                case "n":
                    result = false;
                    return true;
            }
        }

        result = false;
        return false;
    }

    private static bool IsBetweenSelected(RowUi row)
    {
        var comparison = row.SelectedComparison?.Value;
        return comparison == DataGridView.FilterComparison.Between;
    }

    private static bool IsBoolType(Type type)
    {
        var effective = Nullable.GetUnderlyingType(type) ?? type;
        return effective == typeof(bool);
    }

    private static bool IsMultiValueComparison(DataGridView.FilterComparison? comparison)
    {
        return comparison == DataGridView.FilterComparison.EqualsAny
            || comparison == DataGridView.FilterComparison.ContainsAny
            || comparison == DataGridView.FilterComparison.NotContainsAny
            || comparison == DataGridView.FilterComparison.StartsWithAny
            || comparison == DataGridView.FilterComparison.EndsWithAny;
    }

    private static List<ComparisonOption> GetComparisonOptions(Type type)
    {
        var effective = Nullable.GetUnderlyingType(type) ?? type;

        if (effective == typeof(string))
        {
            return new List<ComparisonOption>
            {
                new ComparisonOption(DataGridView.FilterComparison.Contains, "Contains", "contains.svg"),
                new ComparisonOption(DataGridView.FilterComparison.ContainsAny, "Contains one of", "contains_any.svg"),
                new ComparisonOption(DataGridView.FilterComparison.NotContainsAny, "Not Contains Any", "not_contains_any.svg"),
                new ComparisonOption(DataGridView.FilterComparison.StartsWith, "Starts with", "starts_with.svg"),
                new ComparisonOption(DataGridView.FilterComparison.StartsWithAny, "Starts with one of", "starts_with_any.svg"),
                new ComparisonOption(DataGridView.FilterComparison.EndsWith, "Ends with", "ends_with.svg"),
                new ComparisonOption(DataGridView.FilterComparison.EndsWithAny, "Ends with one of", "ends_with_any.svg"),
                new ComparisonOption(DataGridView.FilterComparison.Equals, "Equals", "equals.svg"),
                new ComparisonOption(DataGridView.FilterComparison.EqualsAny, "Is one of", "equals_any.svg"),
                new ComparisonOption(DataGridView.FilterComparison.NotEquals, "Does not equal", "not_equals.svg"),
                new ComparisonOption(DataGridView.FilterComparison.IsEmpty, "Is empty", "is_empty.svg")
            };
        }

        if (effective == typeof(bool))
        {
            return new List<ComparisonOption>
            {
                new ComparisonOption(DataGridView.FilterComparison.Equals, "Equals", "equals.svg")
            };
        }

        if (effective.IsEnum || effective == typeof(Guid))
        {
            return new List<ComparisonOption>
            {
                new ComparisonOption(DataGridView.FilterComparison.Equals, "Equals", "equals.svg"),
                new ComparisonOption(DataGridView.FilterComparison.EqualsAny, "Is one of", "equals_any.svg"),
                new ComparisonOption(DataGridView.FilterComparison.NotEquals, "Does not equal", "not_equals.svg"),
                new ComparisonOption(DataGridView.FilterComparison.IsEmpty, "Is empty", "is_empty.svg")
            };
        }

        if (DataGridView.IsRangeComparableType(effective))
        {
            return new List<ComparisonOption>
            {
                new ComparisonOption(DataGridView.FilterComparison.Equals, "Equals", "equals.svg"),
                new ComparisonOption(DataGridView.FilterComparison.EqualsAny, "Is one of", "equals_any.svg"),
                new ComparisonOption(DataGridView.FilterComparison.NotEquals, "Does not equal", "not_equals.svg"),
                new ComparisonOption(DataGridView.FilterComparison.GreaterThan, "Greater than", "greater_than.svg"),
                new ComparisonOption(DataGridView.FilterComparison.GreaterOrEqual, "Greater than or equal", "greater_or_equal.svg"),
                new ComparisonOption(DataGridView.FilterComparison.LessThan, "Less than", "less_than.svg"),
                new ComparisonOption(DataGridView.FilterComparison.LessOrEqual, "Less than or equal", "less_or_equal.svg"),
                new ComparisonOption(DataGridView.FilterComparison.Between, "Between", "between.svg"),
                new ComparisonOption(DataGridView.FilterComparison.IsEmpty, "Is empty", "is_empty.svg")
            };
        }

        return new List<ComparisonOption>
        {
            new ComparisonOption(DataGridView.FilterComparison.Contains, "Contains", "contains.svg"),
            new ComparisonOption(DataGridView.FilterComparison.Equals, "Equals", "equals.svg"),
            new ComparisonOption(DataGridView.FilterComparison.EqualsAny, "Is one of", "equals_any.svg"),
            new ComparisonOption(DataGridView.FilterComparison.NotEquals, "Does not equal", "not_equals.svg"),
            new ComparisonOption(DataGridView.FilterComparison.IsEmpty, "Is empty", "is_empty.svg")
        };
    }

    private object? ReadEditorValue(FrameworkElement? editor, Type propertyType)
    {
        var effective = Nullable.GetUnderlyingType(propertyType) ?? propertyType;

        if (editor is ComboBox combo)
        {
            if (combo.SelectedItem is BoolOption boolOption)
            {
                return boolOption.Value;
            }

            if (effective == typeof(bool))
            {
                return (combo.SelectedItem as BoolOption)?.Value;
            }

            if (effective.IsEnum)
            {
                return (combo.SelectedItem as EnumOption)?.Value;
            }

            return combo.SelectedItem;
        }

        if (editor is CalendarDatePicker datePicker)
        {
            if (datePicker.Date is not DateTimeOffset selectedDate)
            {
                return null;
            }

            var dt = selectedDate.DateTime;
            if (effective == typeof(DateOnly))
            {
                return DateOnly.FromDateTime(dt);
            }

            return dt;
        }

        if (editor is TimePicker timePicker)
        {
            if (timePicker.SelectedTime is not TimeSpan selectedTime)
            {
                return null;
            }

            if (effective == typeof(TimeOnly))
            {
                return TimeOnly.FromTimeSpan(selectedTime);
            }

            return selectedTime;
        }

        if (editor is TextBox textBox)
        {
            var text = textBox.Text?.Trim();
            if (string.IsNullOrWhiteSpace(text))
            {
                return null;
            }

            if (textBox.Tag is bool isMultiValue && isMultiValue)
            {
                var tokens = ParseMultiValueText(text);
                return tokens.Count == 0 ? null : tokens;
            }

            return text;
        }

        return null;
    }

    private IReadOnlyList<DataGridView.FilterRule> BuildRulesFromRows()
    {
        var result = new List<DataGridView.FilterRule>();

        foreach (var row in _rows)
        {
            var column = row.ColumnCombo.SelectedItem as DataGridView.Column;
            var comparison = row.SelectedComparison?.Value;

            if (column == null || comparison == null)
            {
                continue;
            }

            var value = ReadEditorValue(row.ValueEditor, column.PropertyInfo.PropertyType);
            if (value == null)
            {
                continue;
            }

            if (comparison == DataGridView.FilterComparison.Between)
            {
                var valueTo = ReadEditorValue(row.ValueEditor2, column.PropertyInfo.PropertyType);
                if (valueTo == null)
                {
                    continue;
                }

                result.Add(new DataGridView.FilterRule(column.Name, comparison.Value, value, valueTo));
                continue;
            }

            result.Add(new DataGridView.FilterRule(column.Name, comparison.Value, value, null));
        }

        return result;
    }

    private void AttachValueEditorHandlers(FrameworkElement? editor)
    {
        if (editor == null)
        {
            return;
        }

        if (editor is TextBox textBox)
        {
            textBox.PreviewKeyDown += ValueEditor_KeyDown;
            return;
        }

        editor.KeyDown += ValueEditor_KeyDown;
    }

    private void ApplyCurrentFilters()
    {
        if (_owner == null)
        {
            return;
        }

        _owner.ApplyColumnFiltersFromFlyout(BuildRulesFromRows());
    }

    private void ApplyAndClose()
    {
        if (_owner == null)
        {
            return;
        }

        ApplyCurrentFilters();
        _suppressClosedApplyOnce = true;
        Hide();
    }

    private void FilterFlyout_Closed(object? sender, object e)
    {
        if (_reopenAfterClose)
        {
            _reopenAfterClose = false;
            _suppressClosedApplyOnce = false;
            var pendingColumn = _pendingAnchorColumn;
            var pendingPreferRightEdge = _pendingPreferRightEdge;
            _pendingAnchorColumn = null;
            _pendingPreferRightEdge = false;
            _isRebuilding = false;

            if (pendingColumn != null)
            {
                Show(pendingColumn, pendingPreferRightEdge);
            }

            return;
        }

        if (_isRebuilding || _owner == null)
        {
            return;
        }

        if (_suppressClosedApplyOnce)
        {
            _suppressClosedApplyOnce = false;
            return;
        }

        ApplyCurrentFilters();
    }

    internal void ReleaseOwner()
    {
        _isRebuilding = true;

        try
        {
            if (IsOpen)
            {
                Hide();
            }
        }
        catch
        {
        }

        RowsPanel.Children.Clear();
        _rows.Clear();
        _availableColumns.Clear();
        _owner = null;
        _anchorColumn = null;
        _pendingAnchorColumn = null;
        _pendingPreferRightEdge = false;
        _reopenAfterClose = false;
        _suppressClosedApplyOnce = false;
        _isRebuilding = false;
    }

    private void AddRuleButton_Click(object sender, RoutedEventArgs e)
    {
        AddRow(_anchorColumn ?? _availableColumns.FirstOrDefault(), null);
    }

    private void ClearRulesButton_Click(object sender, RoutedEventArgs e)
    {
        _isRebuilding = true;
        RowsPanel.Children.Clear();
        _rows.Clear();
        _isRebuilding = false;

        AddRow(_anchorColumn ?? _availableColumns.FirstOrDefault(), null);
    }

    private void ApplyButton_Click(object sender, RoutedEventArgs e)
    {
        ApplyAndClose();
    }

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

        if (sender is TextBox)
        {
            e.Handled = true;
            ApplyAndClose();
            return;
        }

        var shiftPressed = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift)
            .HasFlag(CoreVirtualKeyStates.Down);
        if (shiftPressed)
        {
            return;
        }

        e.Handled = true;
        ApplyAndClose();
    }
}
