using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.VisualTree;
using CommunityToolkit.Mvvm.Input;
using InABox.Core;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;

namespace InABox.Avalonia.Components;

public class AvaloniaDataGridSelectionChangedEventArgs(object?[] selection)
{
    public object?[] Selection { get; set; } = selection;
}
public class AvaloniaDataGridRefreshRequestedEventArgs()
{
}

public enum AvaloniaDataGridSelectionMode
{
    None,
    Single,
    Multiple
}

public partial class AvaloniaDataGrid : UserControl, INotifyPropertyChanged
{
    public static StyledProperty<bool> CanSearchProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, bool>(nameof(CanSearch), true);
    public static StyledProperty<IEnumerable> ItemsSourceProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, IEnumerable>(nameof(ItemsSource));
    public static StyledProperty<DateTime> LastUpdatedProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, DateTime>(nameof(LastUpdated));
    public static StyledProperty<bool> ShowRecordCountProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, bool>(nameof(ShowRecordCount));
    public static StyledProperty<bool> RefreshVisibleProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, bool>(nameof(RefreshVisible));
    public static StyledProperty<AvaloniaDataGridSelectionMode> SelectionModeProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, AvaloniaDataGridSelectionMode>(nameof(SelectionMode), AvaloniaDataGridSelectionMode.Single);
    public static StyledProperty<double> RowHeightProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, double>(nameof(RowHeight), 30);

    public static StyledProperty<AvaloniaDataGridColumns?> ColumnsProperty =
        AvaloniaProperty.Register<AvaloniaDataGrid, AvaloniaDataGridColumns?>(nameof(Columns), null);

    public string SearchText { get; set; } = "";

    public bool CanSearch
    {
        get => GetValue(CanSearchProperty);
        set => SetValue(CanSearchProperty, value);
    }
    public IEnumerable ItemsSource
    {
        get => GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty, value);
    }
    public bool ShowRecordCount
    {
        get => GetValue(ShowRecordCountProperty);
        set => SetValue(ShowRecordCountProperty, value);
    }
    public bool RefreshVisible
    {
        get => GetValue(RefreshVisibleProperty);
        set => SetValue(RefreshVisibleProperty, value);
    }
    public AvaloniaDataGridSelectionMode SelectionMode
    {
        get => GetValue(SelectionModeProperty);
        set => SetValue(SelectionModeProperty, value);
    }
    public double RowHeight
    {
        get => GetValue(RowHeightProperty);
        set => SetValue(RowHeightProperty, value);
    }

    public int ItemCount { get; set; }

    public DateTime LastUpdated
    {
        get => GetValue(LastUpdatedProperty);
        set => SetValue(LastUpdatedProperty, value);
    }

    public AvaloniaDataGridColumns Columns { get; private set; }

    public IEnumerable<object?> SelectedItems => Grid.SelectedItems.Cast<object?>();

    public event EventHandler<AvaloniaDataGridSelectionChangedEventArgs>? SelectionChanged;
    public event EventHandler<AvaloniaDataGridRefreshRequestedEventArgs>? RefreshRequested;
    public event Predicate<object?>? FilterRow;

    public event EventHandler<DataGridRowEventArgs>? LoadingRow;

    #region Static Constructor and Property Changed Handlers

    static AvaloniaDataGrid()
    {
        ItemsSourceProperty.Changed.AddClassHandler<AvaloniaDataGrid>(ItemsSource_Changed);
        LastUpdatedProperty.Changed.AddClassHandler<AvaloniaDataGrid>(LastUpdated_Changed);
        ShowRecordCountProperty.Changed.AddClassHandler<AvaloniaDataGrid>(ShowRecordCount_Changed);

        ColumnsProperty.Changed.AddClassHandler<AvaloniaDataGrid>(ColumnsProperty_Changed);
    }

    private static void ColumnsProperty_Changed(AvaloniaDataGrid grid, AvaloniaPropertyChangedEventArgs args)
    {
        var columns = grid.GetValue(ColumnsProperty);
        if(columns is not null)
        {
            grid.Columns.BeginUpdate().AddRange(columns).EndUpdate();
        }
    }

    private static void ShowRecordCount_Changed(AvaloniaDataGrid grid, AvaloniaPropertyChangedEventArgs args)
    {
        grid.UpdateSummaryRow();
    }

    private static void LastUpdated_Changed(AvaloniaDataGrid grid, AvaloniaPropertyChangedEventArgs args)
    {
        grid.UpdateSummaryRow();
    }

    private static void ItemsSource_Changed(AvaloniaDataGrid grid, AvaloniaPropertyChangedEventArgs args)
    {
        grid.Grid.ItemsSource = grid.ItemsSource;
        if (grid.Grid.CollectionView is not null)
        {
            grid.Grid.CollectionView.CollectionChanged -= grid.CollectionView_CollectionChanged;
            grid.Grid.CollectionView.CollectionChanged += grid.CollectionView_CollectionChanged;
        }
        grid.ItemsChanged();
    }

    #endregion

    public AvaloniaDataGrid()
    {
        InitializeComponent();

        Columns = new AvaloniaDataGridColumns();
        Columns.Changed += Columns_Changed;

        Grid.Bind(DataGrid.SelectionModeProperty, new Binding(nameof(SelectionMode))
        {
            Source = this,
            Converter = new FuncValueConverter<AvaloniaDataGridSelectionMode, DataGridSelectionMode>(x => x switch
            {
                AvaloniaDataGridSelectionMode.Multiple => DataGridSelectionMode.Extended,
                AvaloniaDataGridSelectionMode.Single or AvaloniaDataGridSelectionMode.None or _ => DataGridSelectionMode.Single,
            })
        });
    }

    private void DataGrid_LoadingRow(object? sender, DataGridRowEventArgs e)
    {
        LoadingRow?.Invoke(this, e);
    }

    private void CollectionView_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        ItemsChanged();
    }

    private void ItemsChanged()
    {
        ItemCount = (Grid.CollectionView as DataGridCollectionView)?.ItemCount ?? 0;
        UpdateSummaryRow();
    }

    private void Columns_Changed(AvaloniaDataGridColumns columns)
    {
        Grid.Columns.Clear();
        foreach(var column in columns)
        {
            Grid.Columns.Add(column.CreateColumn());
        }
        // Summaries

        var searchableColumns = Columns.Any(x => x.Searchable);
        SearchBar.IsVisible = searchableColumns && CanSearch;
    }

    private void UpdateSummaryRow()
    {
        _lastUpdated.IsVisible = LastUpdated != DateTime.MinValue;
        _recordCount.Content = $" {ItemCount} records";
        _recordCount.IsVisible = ShowRecordCount && ItemsSource is IEnumerable;
        _recordCountBox.IsVisible = _recordCount.IsVisible || _lastUpdated.IsVisible;
    }

    public void ClearSelection()
    {
        Grid.SelectedItem = null;
        Grid.SelectedItems.Clear();
    }

    private bool DoSearch(object? item)
    {
        if (SearchText.IsNullOrWhiteSpace()) return true;
        if (item is null) return false;

        foreach(var column in Columns)
        {
            if(column.Filter(item, SearchText)) return true;
        }
        return false;
    }

    private bool DoFilter(object? item)
    {
        return DoSearch(item) && (FilterRow is null || FilterRow(item));
    }

    public void InvalidateGrid()
    {
        if (Grid.CollectionView is null) return;
        Grid.CollectionView.Filter = DoFilter;
        Grid.CollectionView.Refresh();
        UpdateSummaryRow();
    }

    [RelayCommand]
    private void Search()
    {
        if (Grid.CollectionView is null) return;
        Grid.CollectionView.Filter = DoFilter;
        Grid.CollectionView.Refresh();
        UpdateSummaryRow();
    }

    [RelayCommand]
    private void Refresh()
    {
        if (Grid.CollectionView is null) return;

        RefreshRequested?.Invoke(this, new());
        Grid.CollectionView.Refresh();
    }

    private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if(SelectionMode == AvaloniaDataGridSelectionMode.None && Grid.SelectedItems.Count > 0)
        {
            e.Handled = true;
            Grid.SelectedItem = null;
            return;
        }

        if (SelectionMode != AvaloniaDataGridSelectionMode.Multiple) return;

        SelectionChanged?.Invoke(this, new AvaloniaDataGridSelectionChangedEventArgs(SelectedItems.ToArray()));
    }

    private void DataGrid_Tapped(object sender, TappedEventArgs e)
    {
        if (SelectionMode == AvaloniaDataGridSelectionMode.Multiple) return;

        var position = e.GetPosition(Grid);

        var parent = (e.Source as Visual)?.GetVisualAncestors().Where(x => x is DataGridCell || x is DataGridColumnHeader).FirstOrDefault();
        if (parent is null) return;

        if(parent is DataGridCell cell)
        {
            var cellCollection = cell.GetVisualParent<DataGridCellsPresenter>();
            if (cellCollection is null) return;

            var colIdx = cellCollection.Children.IndexOf(cell);

            var row = cellCollection.GetVisualAncestors().OfType<DataGridRow>().FirstOrDefault();
            if (row is null) return;

            var rowCollection = row.GetVisualParent<DataGridRowsPresenter>();
            if (rowCollection is null) return;

            var rowIdx = row.Index;

            var item = (Grid.CollectionView as DataGridCollectionView)?.GetItemAt(rowIdx);

            var column = Columns[colIdx];
            if(column.Tapped is not null)
            {
                column.Tapped?.Invoke(column, item);
            }
            else
            {
                SelectionChanged?.Invoke(this, new AvaloniaDataGridSelectionChangedEventArgs(new object?[] { item }));
            }
        }
        else if(parent is DataGridColumnHeader header)
        {
            var headerCollection = header.GetVisualParent<DataGridColumnHeadersPresenter>();
            if (headerCollection is null) return;

            var colIdx = headerCollection.Children.IndexOf(header);
            var column = Columns[colIdx];
            if(column.Tapped is not null)
            {
                column.Tapped?.Invoke(column, null);
            }
            else
            {
                SelectionChanged?.Invoke(this, new AvaloniaDataGridSelectionChangedEventArgs(Array.Empty<object?>()));
            }
        }
    }
}