using InABox.Clients;
using InABox.Core;
using InABox.WPF;
using org.omg.PortableInterceptor;
using Syncfusion.Data;
using Syncfusion.Data.Extensions;
using Syncfusion.UI.Xaml.Grid;
using Syncfusion.UI.Xaml.Grid.Cells;
using Syncfusion.UI.Xaml.Grid.Helpers;
using Syncfusion.UI.Xaml.ScrollAxis;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using DataColumn = System.Data.DataColumn;
using DataRow = System.Data.DataRow;
using TimeSpanEdit = Syncfusion.Windows.Shared.TimeSpanEdit;

namespace InABox.DynamicGrid;

public enum DynamicGridLines
{
    Both,
    Horizontal,
    Vertical,
    None
}

public class DynamicGridGridUIComponent<T> : IDynamicGridUIComponent<T>, IDynamicGridGridUIComponent<T>
    where T : BaseObject, new()
{
    private readonly Dictionary<string, string> _filterpredicates = new();


    private Dictionary<string, CoreTable> Lookups = new();

    private IDynamicGridUIComponentParent<T> _parent;
    public IDynamicGridUIComponentParent<T> Parent
    {
        get => _parent;
        set
        {
            _parent = value;

            CellBackgroundConverter = new DynamicGridCellStyleConverter<System.Windows.Media.Brush?>(Parent, GetCellBackground);
            CellForegroundConverter = new DynamicGridCellStyleConverter<System.Windows.Media.Brush?>(Parent, GetCellForeground);
            CellFontSizeConverter = new DynamicGridCellStyleConverter<double?>(Parent, GetCellFontSize);
            CellFontStyleConverter = new DynamicGridCellStyleConverter<System.Windows.FontStyle?>(Parent, GetCellFontStyle);
            CellFontWeightConverter = new DynamicGridCellStyleConverter<System.Windows.FontWeight?>(Parent, GetCellFontWeight);

            DataGrid.RowStyleSelector = Parent.RowStyleSelector;
            DataGrid.TableSummaryRowStyleSelector = new SummaryRowStyleSelector(this, GetSummaryRowStyle);
            DataGrid.TableSummaryCellStyleSelector = new SummaryCellStyleSelector(this, GetSummaryCellStyle);
        }
    }
    FrameworkElement IDynamicGridUIComponent<T>.Control => DataGrid;

    #region IDynamicGridGridUIComponent

    IList<DynamicColumnBase> IDynamicGridGridUIComponent<T>.ColumnList => ColumnList;

    int IDynamicGridGridUIComponent<T>.RowHeight => (int)RowHeight;

    #endregion

    protected readonly SfDataGrid DataGrid;

    private readonly ContextMenu ColumnsMenu;

    private readonly GridRowSizingOptions gridRowResizingOptions = new() { CanIncludeHiddenColumns = false, AutoFitMode = AutoFitMode.Default };

    /// <summary>
    /// <see langword="null"/> when <see cref="DataGrid.ItemsSource"/> is <see langword="null"/>, generally while the grid is refreshing its columns.
    /// </summary>
    private DataTable? DataGridItems => (DataGrid.ItemsSource as DataTable);

    private DynamicGridCellStyleConverter<System.Windows.Media.Brush?> CellBackgroundConverter;
    private DynamicGridCellStyleConverter<System.Windows.Media.Brush?> CellForegroundConverter;
    private DynamicGridCellStyleConverter<double?> CellFontSizeConverter;
    private DynamicGridCellStyleConverter<System.Windows.FontStyle?> CellFontStyleConverter;
    private DynamicGridCellStyleConverter<System.Windows.FontWeight?> CellFontWeightConverter;

    #region Configuration

    private DynamicGridLines _gridLines = DynamicGridLines.Both;
    public DynamicGridLines GridLines
    {
        get => _gridLines;
        set
        {
            _gridLines = value;
            DataGrid.GridLinesVisibility = value switch
            {
                DynamicGridLines.Both => GridLinesVisibility.Both,
                DynamicGridLines.Vertical => GridLinesVisibility.Vertical,
                DynamicGridLines.Horizontal => GridLinesVisibility.Horizontal,
                _ => GridLinesVisibility.None,
            };
        }
    }

    public double RowHeight
    {
        get => DataGrid.RowHeight;
        set => DataGrid.RowHeight = value;
    }

    public double HeaderRowHeight
    {
        get => DataGrid.HeaderRowHeight;
        set => DataGrid.HeaderRowHeight = value;
    }

    #endregion

    public DynamicGridGridUIComponent()
    {
        ColumnsMenu = new ContextMenu();
        ColumnsMenu.Opened += ColumnsMenu_ContextMenuOpening;

        DataGrid = new SfDataGrid();
        DataGrid.VerticalAlignment = VerticalAlignment.Stretch;
        DataGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
        DataGrid.HeaderContextMenu = ColumnsMenu;
        DataGrid.CellTapped += DataGrid_CellTapped;
        DataGrid.CellDoubleTapped += DataGrid_CellDoubleTapped;
        DataGrid.SelectionChanging += DataGrid_SelectionChanging;
        DataGrid.SelectionChanged += DataGrid_SelectionChanged;
        DataGrid.SelectionMode = GridSelectionMode.Extended;
        DataGrid.SelectionUnit = GridSelectionUnit.Row;
        DataGrid.CanMaintainScrollPosition = true;
        DataGrid.SummaryCalculationUnit = SummaryCalculationUnit.AllRows;
        DataGrid.LiveDataUpdateMode = LiveDataUpdateMode.AllowSummaryUpdate;

        DataGrid.NavigationMode = NavigationMode.Row;
        DataGrid.AllowEditing = false;
        DataGrid.EditTrigger = EditTrigger.OnTap;

        DataGrid.CurrentCellBeginEdit += DataGrid_CurrentCellBeginEdit;
        DataGrid.CurrentCellEndEdit += DataGrid_CurrentCellEndEdit;
        DataGrid.CurrentCellDropDownSelectionChanged += DataGrid_CurrentCellDropDownSelectionChanged;
        DataGrid.PreviewKeyUp += DataGrid_PreviewKeyUp;

        DataGrid.BorderBrush = new SolidColorBrush(Colors.Gray);
        DataGrid.BorderThickness = new Thickness(0.75F);
        DataGrid.Background = new SolidColorBrush(Colors.DimGray);
        DataGrid.AutoGenerateColumns = false;
        DataGrid.ColumnSizer = GridLengthUnitType.AutoLastColumnFill;
        DataGrid.SelectionForegroundBrush = GetCellSelectionForegroundBrush() ?? DynamicGridUtils.SelectionForeground;
        DataGrid.RowSelectionBrush =  GetCellSelectionBackgroundBrush() ?? DynamicGridUtils.SelectionBackground;
        
        DataGrid.AllowDraggingRows = false;
        DataGrid.Drop += DataGrid_Drop;
        DataGrid.DragOver += DataGrid_DragOver;
        DataGrid.RowDragDropTemplate = TemplateGenerator.CreateDataTemplate(() =>
        {
            var border = new Border();
            border.Width = 100;
            border.Height = 100;
            border.BorderBrush = new SolidColorBrush(Colors.Firebrick);
            border.Background = new SolidColorBrush(Colors.Red);
            border.CornerRadius = new CornerRadius(5);
            return border;
        });
        
        DataGrid.CurrentCellBorderThickness = new Thickness(0);
        DataGrid.AllowFiltering = false;
        DataGrid.EnableDataVirtualization = true;
        DataGrid.RowHeight = 30;
        DataGrid.QueryRowHeight += DataGrid_QueryRowHeight;
        DataGrid.HeaderRowHeight = 30;

        DataGrid.MouseLeftButtonUp += DataGrid_MouseLeftButtonUp;
        DataGrid.MouseRightButtonUp += DataGrid_MouseRightButtonUp;
        DataGrid.KeyUp += DataGrid_KeyUp;
        DataGrid.PreviewGotKeyboardFocus += DataGrid_PreviewGotKeyboardFocus;
        //DataGrid.SelectionController = new GridSelectionControllerExt(DataGrid);
        
        DataGrid.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Visible);
        
        DataGrid.FilterChanged += DataGrid_FilterChanged;

        DataGrid.FilterItemsPopulating += DataGrid_FilterItemsPopulating;

        var fltstyle = new Style(typeof(GridFilterControl));
        fltstyle.Setters.Add(new Setter(GridFilterControl.FilterModeProperty, FilterMode.Both));
        fltstyle.Setters.Add(new Setter(GridFilterControl.SortOptionVisibilityProperty, Visibility.Collapsed));
        DataGrid.FilterPopupStyle = fltstyle;

        //DataGrid.MouseMove += DataGrid_MouseMove;
        DataGrid.CellToolTipOpening += DataGrid_CellToolTipOpening;

        DataGrid.SizeChanged += DataGrid_SizeChanged;

        //DataGrid.CellRenderers.Remove("Numeric");
        //DataGrid.CellRenderers.Add("Numeric", new CustomNumericCellRenderer(this));
        //DataGrid.CellRenderers.Remove("TextBox");
        //DataGrid.CellRenderers.Add("TextBox", new CustomTextCellRenderer(this));
    }

    //private class CustomTextCellRenderer : GridCellTextBoxRenderer
    //{
    //    private DynamicGridGridUIComponent<T> Component;

    //    public CustomTextCellRenderer(DynamicGridGridUIComponent<T> component)
    //    {
    //        Component = component;
    //    }

    //    public override void OnInitializeEditElement(DataColumnBase dataColumn, TextBox uiElement, object dataContext)
    //    {
    //        base.OnInitializeEditElement(dataColumn, uiElement, dataContext);
    //        uiElement.TextChanged += UiElement_TextChanged;
    //    }

    //    private void UiElement_TextChanged(object sender, TextChangedEventArgs e)
    //    {
    //        throw new NotImplementedException();
    //    }

    //    private void UiElement_ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    //    {
    //        Component.ChangeValue(e.OldValue, e.NewValue);
    //    }
    //}

    //private class CustomNumericCellRenderer : GridCellNumericRenderer
    //{
    //    private DynamicGridGridUIComponent<T> Component;

    //    public CustomNumericCellRenderer(DynamicGridGridUIComponent<T> component)
    //    {
    //        Component = component;
    //    }

    //    public override void OnInitializeEditElement(DataColumnBase dataColumn, DoubleTextBox uiElement, object dataContext)
    //    {
    //        base.OnInitializeEditElement(dataColumn, uiElement, dataContext);
    //        uiElement.ValueChanged += UiElement_ValueChanged;
    //    }

    //    private void UiElement_ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    //    {
    //        Component.ChangeValue(e.OldValue, e.NewValue);
    //    }
    //}

    private void ChangeValue(object oldValue, object newValue)
    {

    }

    private void ColumnsMenu_ContextMenuOpening(object sender, RoutedEventArgs e)
    {
        if (sender is not ContextMenu menu) return;
        menu.Items.Clear();

        Parent.LoadColumnsMenu(menu);
    }

    #region Selection

    public CoreRow[] SelectedRows
    {
        get => GetSelectedRows();
        set => SetSelectedRows(value);
    }

    private CoreRow[] GetSelectedRows()
    {
        var result = new List<CoreRow>();
        foreach (var item in DataGrid.SelectedItems)
        {
            if (item is DataRowView datarow)
            {
                var row = datarow.Row.Table.Rows.IndexOf(datarow.Row);
                result.Add(Parent.Data.Rows[row]);
            }
        }

        return result.ToArray();
    }

    private void SetSelectedRows(CoreRow[] rows)
    {           
        DataGrid.SelectedItems.Clear();

        var table = DataGridItems;
        if(table is null) return;

        var dataRows = rows.Where(x => x.Index > -1).Select(row =>
        {
            return table.Rows[row.Index];
        });
        if (!Parent.Options.MultiSelect)
        {
            dataRows = dataRows.Take(1);
        }

        foreach (var row in dataRows)
        {
            var record = DataGrid.View?.Records.FirstOrDefault(x => (x.Data as DataRowView)!.Row == row);
            if(record?.Data is DataRowView rowView)
            {
                DataGrid.SelectedItems.Add(rowView);
            }
        }
    }

    private void DataGrid_SelectionChanging(object? sender, Syncfusion.UI.Xaml.Grid.GridSelectionChangingEventArgs e)
    {
        var cancel = new CancelEventArgs();
        Parent.BeforeSelection(cancel);
        if (cancel.Cancel)
        {
            e.Cancel = true;
        }
    }

    private void DataGrid_SelectionChanged(object? sender, GridSelectionChangedEventArgs e)
    {
        if(Parent.IsReady && !Parent.IsRefreshing)
        {
            StartTimer();
        }
    }

    private DispatcherTimer? clicktimer;

    private void StartTimer()
    {
        if (clicktimer is null)
        {
            clicktimer = new DispatcherTimer();
            clicktimer.Interval = TimeSpan.FromMilliseconds(200);
            clicktimer.Tick += (o, e) =>
            {
                clicktimer.IsEnabled = false;
                Parent.SelectItems(SelectedRows);
            };
        }

        clicktimer.IsEnabled = true;
    }

    private void StopTimer()
    {
        if (clicktimer is not null)
            clicktimer.IsEnabled = false;
    }

    #endregion

    public CoreRow[] GetVisibleRows()
    {
        var items = DataGrid.ItemsSource;

        var result = new List<CoreRow>();

        var table = DataGridItems;
        if (table is null) return Array.Empty<CoreRow>();

        var rows = DataGrid.View.Records.Select(x => (x.Data as DataRowView)!).ToList();
        foreach (var row in rows)
        {
            var iRow = table.Rows.IndexOf(row.Row);
            result.Add(Parent.Data.Rows[iRow]);
        }

        return result.ToArray();
    }

    private bool bFilterVisible;

    private void DataGrid_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (!DataGrid.IsEnabled)
            return;

        var visualContainer = DataGrid.GetVisualContainer();
        var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer));
        var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex);

        var column = GetColumn(columnindex);
        if(column is not null)
        {
            Parent.OpenColumnMenu(column);
        }
    }

    private void DataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (!DataGrid.IsEnabled)
            return;

        var visualContainer = DataGrid.GetVisualContainer();
        var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer));
        var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex);

        // Header Click Here!
        if (rowcolumnindex.RowIndex < DataGrid.StackedHeaderRows.Count + 1)
        {

            var column = GetColumn(columnindex);
            if(column is DynamicActionColumn dac)
            {
                Parent.ExecuteActionColumn(dac, null);
            }
        }
        else if (!bFilterVisible)
        {
            StartTimer();
        }
    }

    private void DataGrid_CellTapped(object? sender, GridCellTappedEventArgs e)
    {
        if (!DataGrid.IsEnabled)
            return;

        if (GetColumn(e.RowColumnIndex.ColumnIndex) is DynamicActionColumn dac)
        {
            if(e.ChangedButton == MouseButton.Left || (e.ChangedButton == MouseButton.Right && dac is DynamicMenuColumn))
            {
                Parent.ExecuteActionColumn(dac, SelectedRows);
            }
        }
        else
        {
            StartTimer();
        }
    }

    private void DataGrid_KeyUp(object sender, KeyEventArgs e)
    {
        if (sender != DataGrid) return;

        Parent.HandleKey(e);
    }

    private void DataGrid_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        var bOld = bFilterVisible;
        if (e.NewFocus is GridFilterControl)
            bFilterVisible = true;
        else if (e.NewFocus is ScrollViewer || e.NewFocus is SfDataGrid)
            bFilterVisible = false;
        if (bOld && !bFilterVisible)
        {
            Parent.SelectItems(SelectedRows);
        }
    }


    // Please always use this function where possible!
    /// <summary>
    /// Get the <see cref="CoreRow"/> associated with a <paramref name="rowIndex"/> given from the Syncfusion DataGrid.
    /// </summary>
    /// <remarks>
    /// This is mandatory to use whenever we want the data associated with an index which Syncfusion has given us,
    /// because filtering and sorting will cause normal indexing operations to fail.
    /// </remarks>
    /// <param name="rowIndex"></param>
    /// <returns></returns>
    private CoreRow? GetRowFromIndex(int rowIndex)
    {
        // Syncfusion has given us the row index, so it also will give us the correct row, after sorting.
        // Hence, here we use the syncfusion DataGrid.GetRecordAtRowIndex, which *should* always return a DataRowView.
        var row = DataGrid.GetRecordAtRowIndex(rowIndex);
        if (row is not DataRowView dataRowView || DataGridItems is not DataTable table)
            return null;

        var rowIdx = table.Rows.IndexOf(dataRowView.Row);
        if (rowIdx < 0)
            return null;
        return Parent.Data.Rows[rowIdx];
    }

    private void DataGrid_CellDoubleTapped(object? sender, GridCellDoubleTappedEventArgs e)
    {
        StopTimer();
        Parent.DoubleClickCell(GetRowFromIndex(e.RowColumnIndex.RowIndex), GetColumn(e.RowColumnIndex.ColumnIndex));
    }
    
    private void DataGrid_QueryRowHeight(object? sender, QueryRowHeightEventArgs e)
    {
        if (e.RowIndex > 0)
        {
            e.Height = DataGrid.RowHeight;
            if (DataGrid.GridColumnSizer.GetAutoRowHeight(e.RowIndex, gridRowResizingOptions, out var autoHeight))
                if (autoHeight > DataGrid.RowHeight)
                    e.Height = autoHeight;
            e.Handled = true;
        }
    }

    private void DataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (Parent.IsReady && !Parent.IsRefreshing)
            ResizeColumns(DataGrid, e.NewSize.Width - 2, e.NewSize.Height - 2);
    }

    public bool OptionsChanged()
    {
        ColumnsMenu.Visibility = Parent.Options.SelectColumns ? Visibility.Visible : Visibility.Hidden;

        var allowEditing = Parent.IsDirectEditMode();
        var reloadColumns = false;

        if (DataGrid.AllowEditing != allowEditing)
        {
            DataGrid.NavigationMode = allowEditing ? NavigationMode.Cell : NavigationMode.Row;
            DataGrid.AllowEditing = allowEditing;
            reloadColumns = true;
        }
        DataGrid.AllowFiltering = Parent.Options.FilterRows;
        DataGrid.FilterRowPosition = Parent.Options.FilterRows ? FilterRowPosition.FixedTop : FilterRowPosition.None;

        if (Parent.Options.DragSource)
        {
            if (!DataGrid.AllowDraggingRows)
            {
                DataGrid.AllowDraggingRows = true;
                DataGrid.RowDragDropController.DragStart += RowDragDropController_DragStart;
            }
        }
        else
        {
            if (DataGrid.AllowDraggingRows)
            {
                DataGrid.AllowDraggingRows = false;
                DataGrid.RowDragDropController.DragStart -= RowDragDropController_DragStart;
            }
        }
        
        DataGrid.AllowDrop = Parent.Options.DragTarget;
        
        DataGrid.SelectionMode = Parent.Options.MultiSelect ? GridSelectionMode.Extended : GridSelectionMode.Single;

        return reloadColumns && DataGrid.Columns.Count > 0;
    }

    private void DataGrid_FilterChanged(object? o, GridFilterEventArgs e)
    {
        var col = DataGrid.Columns.IndexOf(e.Column);
        if (GetColumn(col) is DynamicActionColumn column)
        {
            if (e.FilterPredicates != null)
            {
                var filter = e.FilterPredicates.Select(x => x.FilterValue.ToString()!).ToArray();
                var include = e.FilterPredicates.Any(x => x.FilterType == FilterType.Equals);
                if (include)
                {
                    column.SelectedFilters = filter;
                    column.ExcludeFilters = null;
                }
                else if(column.Filters is not null)
                {
                    column.SelectedFilters = column.Filters.Except(filter).ToArray();
                    column.ExcludeFilters = null;
                }
                else
                {
                    column.SelectedFilters = null;
                    column.ExcludeFilters = filter;
                }
            }
            else
            {
                column.SelectedFilters = null;
                column.ExcludeFilters = null;
            }
            //DataGrid.ClearFilter(e.Column);

            //e.FilterPredicates?.Clear();
            //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.NotEquals, FilterValue = "" });
            //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.Equals, FilterValue = "" });
            //Parent.Refresh(false, false);
            e.Handled = true;
        }

        if (e.FilterPredicates == null)
        {
            if (_filterpredicates.ContainsKey(e.Column.MappingName))
                _filterpredicates.Remove(e.Column.MappingName);
        }
        else
        {
            _filterpredicates[e.Column.MappingName] = Serialization.Serialize(e.FilterPredicates, true);
        }
        Parent.UIFilterChanged(this);

        UpdateRecordCount();
    }

    private void UpdateRecordCount()
    {
        var count = DataGrid.View != null ? DataGrid.View.Records.Count : Parent.Data.Rows.Count;
        Parent.UpdateRecordCount(count);
    }

    private void DataGrid_FilterItemsPopulating(object? sender, GridFilterItemsPopulatingEventArgs e)
    {
        var colIdx = DataGrid.Columns.IndexOf(e.Column);
        var column = GetColumn(colIdx);
        if(column is not null)
        {
            var filterItems = Parent.GetColumnFilterItems(column);
            if(filterItems is not null)
            {
                e.ItemsSource = filterItems.Select(x =>
                {
                    var element = new FilterElement
                    {
                        DisplayText = x,
                        ActualValue = x,
                    };
                    if(column is DynamicActionColumn dac)
                    {
                        element.IsSelected = (dac.SelectedFilters is null || dac.SelectedFilters.Contains(x))
                            && (dac.ExcludeFilters is null || !dac.ExcludeFilters.Contains(x));
                    }
                    return element;
                });
            }
            else if (column is DynamicActionColumn dac && dac.Filters is not null)
            {
                e.ItemsSource = dac.Filters.Select(x => new FilterElement
                {
                    DisplayText = x,
                    ActualValue = x,
                    IsSelected = (dac.SelectedFilters is null || dac.SelectedFilters.Contains(x))
                        && (dac.ExcludeFilters is null || !dac.ExcludeFilters.Contains(x))
                });
            }
        }
    }

    private void DataGrid_CellToolTipOpening(object? sender, GridCellToolTipOpeningEventArgs e)
    {
        if (GetColumn(e.RowColumnIndex.ColumnIndex) is not DynamicActionColumn col)
            return;
        var toolTip = col.ToolTip;
        if (toolTip is null)
            return;

        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);

        e.ToolTip.Template = TemplateGenerator.CreateControlTemplate(
            typeof(ToolTip),
            () => toolTip.Invoke(col, row)
        );
    }
    public void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)
    {
        if (string.IsNullOrWhiteSpace(value))
            return;
        var col = DataGrid.Columns.FirstOrDefault((x=>String.Equals(x.MappingName?.ToUpper(),column?.Replace(".", "_").ToUpper())));
        if (col != null)
        {
            col.FilterPredicates.Add(new FilterPredicate { FilterType = filtertype, FilterValue = value });
            col.FilteredFrom = FilteredFrom.FilterRow;
        }
    }

    public List<Tuple<string, Func<CoreRow, bool>>> GetFilterPredicates()
    {
        var list = new List<Tuple<string, Func<CoreRow, bool>>>();
        foreach (var column in DataGrid.Columns)
        {
            var colIndex = DataGrid.Columns.IndexOf(column);
            var col = ColumnList[colIndex];
            if (col is DynamicGridColumn gridColumn)
            {
                var rowPredicate = DynamicGridGridUIComponentExtension.ConvertColumnPredicates(gridColumn, column.FilterPredicates);
                if(rowPredicate is not null)
                {
                    list.Add(new(gridColumn.ColumnName, rowPredicate));
                }
            }
            else if(col is DynamicActionColumn dac && dac.FilterRecord is not null)
            {
                if(dac.SelectedFilters is not null && dac.SelectedFilters.Length > 0)
                {
                    list.Add(new(column.MappingName, (row) => dac.FilterRecord(row, dac.SelectedFilters)));
                }
                if(dac.ExcludeFilters is not null && dac.ExcludeFilters.Length > 0)
                {
                    list.Add(new(column.MappingName, (row) => !dac.FilterRecord(row, dac.ExcludeFilters)));
                }
            }
        }
        return list;
    }

    public void ScrollIntoView(CoreRow row)
    {
        var rowIdx = DataGrid.ResolveToRowIndex(row.Index + 1);
        DataGrid.ScrollInView(new RowColumnIndex(rowIdx, 0));
    }

    protected virtual Brush? GetCellSelectionForegroundBrush() => DynamicGridUtils.SelectionForeground;
    protected virtual Brush? GetCellSelectionBackgroundBrush() => DynamicGridUtils.SelectionBackground;
    protected virtual Brush? GetCellBackground(CoreRow row, DynamicColumnBase column) => null;
    protected virtual Brush? GetCellForeground(CoreRow row, DynamicColumnBase column) => null;
    protected virtual double? GetCellFontSize(CoreRow row, DynamicColumnBase column) => null;
    protected virtual FontStyle? GetCellFontStyle(CoreRow row, DynamicColumnBase column) => null;
    protected virtual FontWeight? GetCellFontWeight(CoreRow row, DynamicColumnBase column) => null;

    protected virtual Style GetHeaderCellStyle(DynamicColumnBase column)
    {
        var headStyle = new Style(typeof(GridHeaderCellControl));

        headStyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
        headStyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
        headStyle.Setters.Add(new Setter(Control.FontSizeProperty, 12D));

        if(column is DynamicActionColumn actionColumn)
        {
            headStyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0.0)));
            headStyle.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0, 0, 1, 1)));
            if(column is DynamicImageColumn imgCol)
            {
                if (imgCol.HeaderText.IsNullOrWhiteSpace())
                {
                    var image = imgCol.Image?.Invoke(null);
                    if (image != null)
                    {
                        var template = new ControlTemplate(typeof(GridHeaderCellControl));
                        var border = new FrameworkElementFactory(typeof(Border));
                        border.SetValue(Border.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro));
                        border.SetValue(Border.PaddingProperty, new Thickness(4));
                        var img = new FrameworkElementFactory(typeof(Image));
                        img.SetValue(Image.SourceProperty, image);
                        border.AppendChild(img);
                        template.VisualTree = border;
                        headStyle.Setters.Add(new Setter(Control.TemplateProperty, template));
                    }
                }
            }
            if (actionColumn.VerticalHeader && !actionColumn.HeaderText.IsNullOrWhiteSpace())
            {
                headStyle.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty, HorizontalAlignment.Left));
                headStyle.Setters.Add(new Setter(Control.TemplateProperty,
                    Application.Current.Resources["VerticalColumnHeader"] as ControlTemplate));
            }
        }
        return headStyle;
    }
    protected virtual Style GetSummaryRowStyle()
    {
        var style = new Style(typeof(TableSummaryRowControl));

        return style;
    }

    protected virtual Style GetSummaryCellStyle(DynamicColumnBase column)
    {
        var style = new Style(typeof(GridTableSummaryCell));

        style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
        style.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));

        if(column is DynamicGridColumn gridColumn)
        {
            style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty,
                column != null ? gridColumn.HorizontalAlignment(typeof(double)) : HorizontalAlignment.Right));
        }
        else
        {
            style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty, HorizontalAlignment.Right));
        }

        style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Gray)));
        style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 0.75, 0)));
        style.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
        style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.DemiBold));

        return style;
    }

    #region Columns

    private class StackedHeaderRenderer : GridStackedHeaderCellRenderer
    {
        private Style Style;

        public StackedHeaderRenderer()
        {
            var headstyle = new Style(typeof(GridStackedHeaderCellControl));
            headstyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
            headstyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
            headstyle.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
            headstyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0.0, 0.0, 0, 0)));
            headstyle.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0, 0, 1, 1)));

            Style = headstyle;
        }

        public override void OnInitializeEditElement(DataColumnBase dataColumn, GridStackedHeaderCellControl uiElement, object dataContext)
        {
            uiElement.Style = Style;
            base.OnInitializeEditElement(dataColumn, uiElement, dataContext);
        }
    }

    private void LoadStackedHeaders(DynamicGridColumnGroupings groupings)
    {
        DataGrid.StackedHeaderRows.Clear();
        foreach(var grouping in groupings)
        {
            var row = new StackedHeaderRow();
            var i = 0;
            foreach(var group in grouping.Groups)
            {
                var start = Math.Max(i, ColumnList.IndexOf(group.StartColumn));
                var end = Math.Max(start, ColumnList.IndexOf(group.EndColumn));

                if(end < start)
                {
                    i = end + 1;
                    continue;
                }

                var cols = Enumerable.Range(start, end - start + 1).Select(i => DataGrid.Columns[i]).ToArray();

                var stackedColumn = new StackedColumn
                {
                    HeaderText = group.Header,
                    ChildColumns = string.Join(',', cols.Select(x => x.MappingName))
                };

                row.StackedColumns.Add(stackedColumn);

                i = end + 1;
            }

            DataGrid.StackedHeaderRows.Add(row);
        }

        if(groupings.Count > 0)
        {
            DataGrid.CellRenderers.Remove("StackedHeader");
            DataGrid.CellRenderers.Add("StackedHeader", new StackedHeaderRenderer());
        }
    }

    private readonly List<DynamicColumnBase> ColumnList = new();

    private List<DynamicActionColumn> ActionColumns = new();

    private DynamicColumnBase? GetColumn(int index) =>
        index >= 0 && index < ColumnList.Count ? ColumnList[index] : null;

    int IDynamicGridUIComponent<T>.DesiredWidth()
    {
        return this.DesiredWidth();
    }

    private void ResizeColumns(SfDataGrid grid, double width, double height)
    {
        if (Parent.Data == null || width <= 0)
            return;

        grid.Dispatcher.BeginInvoke(() =>
        {
            foreach (var (index, size) in this.CalculateColumnSizes(width))
                DataGrid.Columns[index].Width = Math.Max(0.0F, size);
        });
    }

    private ObservableCollection<ISummaryColumn> Summaries = new();

    private void LoadActionColumns(DynamicActionColumnPosition position)
    {
        for (var i = 0; i < ActionColumns.Count; i++)
        {
            var column = ActionColumns[i];
            if (column.Position == position)
            {
                //String sColName = String.Format("ActionColumn{0}{1}", i, position == DynamicActionColumnPosition.Start ? "L" : "R");
                var sColName = string.Format("ActionColumn{0}", i);
                gridRowResizingOptions.ExcludeColumns.Add(sColName);

                var summary = column.Summary();
                if (summary != null)
                {
                    summary.Name = sColName;
                    summary.MappingName = sColName;
                    Summaries.Add(summary);
                }

                if (column is DynamicImageColumn imgcol)
                {
                    var newcol = new GridImageColumn();
                    newcol.MappingName = sColName;
                    //newcol.Stretch = Stretch.Uniform;
                    newcol.Width = column.Width == 0 ? DataGrid.RowHeight : column.Width;
                    newcol.Padding = new Thickness(4);
                    newcol.ImageHeight = DataGrid.RowHeight - 8;
                    newcol.ImageWidth = DataGrid.RowHeight - 8;
                    newcol.ColumnSizer = GridLengthUnitType.None;
                    newcol.HeaderText = column.HeaderText;

                    ApplyFilterStyle(newcol, true, true);

                    newcol.ShowToolTip = column.ToolTip != null;
                    newcol.ShowHeaderToolTip = column.ToolTip != null;

                    var style = new Style();
                    style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
                    style.Setters.Add(new Setter(Control.IsEnabledProperty, false));
                    newcol.FilterRowCellStyle = style;

                    newcol.HeaderStyle = GetHeaderCellStyle(column);

                    DataGrid.Columns.Add(newcol);
                    ColumnList.Add(column);
                }
                else if (column is DynamicTextColumn txtCol)
                {

                    var newcol = new GridTextColumn();
                    gridRowResizingOptions.ExcludeColumns.Add(sColName);
                    newcol.TextWrapping = TextWrapping.NoWrap;

                    newcol.TextAlignment = txtCol.Alignment == Alignment.NotSet
                        ? TextAlignment.Left
                        : txtCol.Alignment == Alignment.BottomLeft || txtCol.Alignment == Alignment.MiddleLeft ||
                          txtCol.Alignment == Alignment.TopLeft
                            ? TextAlignment.Left
                            : txtCol.Alignment == Alignment.BottomCenter || txtCol.Alignment == Alignment.MiddleCenter ||
                              txtCol.Alignment == Alignment.TopCenter
                                ? TextAlignment.Center
                                : TextAlignment.Right;

                    newcol.AllowEditing = false;
                    newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
                    newcol.MappingName = sColName;
                    newcol.Width = column.Width;
                    newcol.ColumnSizer = GridLengthUnitType.None;
                    newcol.HeaderText = column.HeaderText;
                    newcol.AllowFiltering = (column.Filters != null && column.Filters.Length != 0) || column.FilterRecord is not null;
                    newcol.AllowSorting = false;
                    newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
                    newcol.ShowHeaderToolTip = column.ToolTip != null;
                    
                    newcol.ShowToolTip = column.ToolTip != null;
                    newcol.ShowHeaderToolTip = column.ToolTip != null;

                    var style = new Style();
                    style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
                    style.Setters.Add(new Setter(Control.IsEnabledProperty, false));
                    newcol.FilterRowCellStyle = style;

                    newcol.HeaderStyle = GetHeaderCellStyle(column);
                    
                    var cellstyle = new Style();
                    cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
                        new Binding()
                        {
                            Path = new PropertyPath("."), Converter = CellBackgroundConverter,
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.ForegroundProperty,
                        new Binding()
                        {
                            Converter = CellForegroundConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontSizeProperty,
                        new Binding()
                        { 
                            Converter = CellFontSizeConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                            
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontStyleProperty,
                        new Binding()
                        { 
                            Converter = CellFontStyleConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                            
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontWeightProperty,
                        new Binding()
                        {
                            Converter = CellFontWeightConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    newcol.CellStyle = cellstyle;

                    DataGrid.Columns.Add(newcol);
                    ColumnList.Add(column);
                }
                else if (column is DynamicTemplateColumn tmplCol)
                {
                    var newcol = new GridTemplateColumn();
                    newcol.CellTemplateSelector = new TemplateColumnSelector(this, tmplCol.Template);
                    newcol.AllowEditing = false;
                    newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
                    newcol.MappingName = sColName;
                    
                    newcol.Width = tmplCol.Width;
                    newcol.ColumnSizer = GridLengthUnitType.None;
                    newcol.HeaderText = column.HeaderText;
                    newcol.AllowFiltering = false;
                    newcol.AllowSorting = false;
                    newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
                    
                    newcol.ShowToolTip = column.ToolTip != null;
                    newcol.ShowHeaderToolTip = column.ToolTip != null;

                    var style = new Style();
                    style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
                    style.Setters.Add(new Setter(Control.IsEnabledProperty, false));
                    newcol.FilterRowCellStyle = style;

                    newcol.HeaderStyle = GetHeaderCellStyle(column);

                    var cellstyle = new Style();
                    cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
                        new Binding()
                        {
                            Path = new PropertyPath("."),
                            Converter = CellBackgroundConverter,
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.ForegroundProperty,
                        new Binding()
                        {
                            Converter = CellForegroundConverter,
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontSizeProperty,
                        new Binding()
                        {
                            Converter = CellFontSizeConverter,
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontStyleProperty,
                        new Binding()
                        {
                            Converter = CellFontStyleConverter,
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontWeightProperty,
                        new Binding()
                        {
                            Converter = CellFontWeightConverter,
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    newcol.CellStyle = cellstyle;

                    DataGrid.Columns.Add(newcol);
                    ColumnList.Add(column);
                }
            }
        }
    }
    public class SummaryRowStyleSelector(DynamicGridGridUIComponent<T> parent, Func<Style> selector) : StyleSelector
    {
        public DynamicGridGridUIComponent<T> Parent { get; init; } = parent;

        public Func<Style> Selector { get; set; } = selector;

        public override Style? SelectStyle(object item, DependencyObject container)
        {
            return Selector();
        }
    }

    public class SummaryCellStyleSelector(DynamicGridGridUIComponent<T> parent, Func<DynamicColumnBase, Style> selector) : StyleSelector
    {
        public DynamicGridGridUIComponent<T> Parent { get; init; } = parent;

        public Func<DynamicColumnBase, Style> Selector { get; set; } = selector;

        public override Style? SelectStyle(object item, DependencyObject container)
        {
            var vcol = ((GridTableSummaryCell)container).ColumnBase.ColumnIndex;

            var col = Parent.GetColumn(vcol);

            return col is not null ? Selector(col) : null;
        }
    }

    public class TemplateColumnSelector(DynamicGridGridUIComponent<T> parent, Func<CoreRow, FrameworkElement?> dataTemplate) : DataTemplateSelector
    {
        public Func<CoreRow, FrameworkElement?> DataTemplate { get; init; } = dataTemplate;

        public DynamicGridGridUIComponent<T> Parent { get; init; } = parent;

        public override DataTemplate? SelectTemplate(object item, DependencyObject container)
        {
            if (item is not DataRowView) return null;

            CoreRow? row;
            if(item is DataRowView view && Parent.DataGridItems is DataTable table)
            {
                var rowIdx = table.Rows.IndexOf(view.Row);
                if (rowIdx < 0)
                {
                    row = null;
                }
                else
                {
                    row = Parent.Parent.Data.Rows[rowIdx];
                }
            }
            else
            {
                row = null;
            }

            if (row is null) return null;

            return TemplateGenerator.CreateDataTemplate(() =>
            {
                return DataTemplate(row);
            });
        }
    }

    private void ApplyFilterStyle(GridColumn column, bool filtering, bool isactioncolumn)
    {
        var filterstyle = new Style();
        if (filtering)
        {
            filterstyle.Setters.Add(new Setter(Control.BackgroundProperty, DynamicGridUtils.FilterBackground));
            column.ImmediateUpdateColumnFilter = true;
            column.ColumnFilter = ColumnFilter.Value;
            column.FilterRowCondition = FilterRowCondition.Contains;
            column.FilterRowOptionsVisibility = Visibility.Collapsed;
            column.AllowBlankFilters = true;
            column.AllowSorting = !isactioncolumn && Parent.CanSort();
        }
        else
        {
            filterstyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
            filterstyle.Setters.Add(new Setter(Control.IsEnabledProperty, false));
            column.ColumnFilter = ColumnFilter.Value;
            column.AllowFiltering = false;
            column.AllowSorting = false;
            column.FilterRowEditorType = "TextBox";
            column.FilterRowOptionsVisibility = Visibility.Collapsed;
        }

        column.FilterRowCellStyle = filterstyle;
    }

    private void LoadDataColumns(DynamicGridColumns columns)
    {
        foreach (var column in columns)
        {
            if (this.CreateEditorColumn(column, out var newcol, out var prop))
            {
                newcol.GetEntity = () => _editingObject.Object;
                newcol.EntityChanged += DoEntityChanged;
                if (!newcol.VariableHeight)
                    gridRowResizingOptions.ExcludeColumns.Add(newcol.MappingName);

                var newColumn = newcol.CreateGridColumn();

                newColumn.AllowEditing = newcol.Editable && Parent.IsDirectEditMode();

                var summary = newcol.Summary();
                if (summary != null)
                    Summaries.Add(summary);

                
                ApplyFilterStyle(newColumn, newcol.Filtered, false);
                
                newColumn.HeaderStyle = GetHeaderCellStyle(column);

                var cellstyle = new Style();
                if (Parent.IsDirectEditMode())
                {
                    if (prop.Editor is null || !prop.Editor.Editable.IsDirectEditable())
                    {
                        cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
                            new Binding()
                            {
                                Path = new PropertyPath("."), Converter = CellBackgroundConverter,
                                ConverterParameter = new DynamicGridCellStyleParameters(column,new SolidColorBrush(Colors.WhiteSmoke))
                            }));
                        newColumn.AllowEditing = false;
                    }
                    else
                    {
                        cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
                            new Binding()
                            {
                                Path = new PropertyPath("."), Converter = CellBackgroundConverter,
                                ConverterParameter = new DynamicGridCellStyleParameters(column,new SolidColorBrush(Colors.LightYellow))
                            }));
                        newColumn.AllowEditing = true;
                    }

                    cellstyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
                    newColumn.CellStyle = cellstyle;
                }
                else
                {
                    cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
                        new Binding()
                        {
                            Path = new PropertyPath("."), Converter = CellBackgroundConverter,
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.ForegroundProperty,
                        new Binding()
                        {
                            Converter = CellForegroundConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontSizeProperty,
                        new Binding()
                        { 
                            Converter = CellFontSizeConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue) 
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontStyleProperty,
                        new Binding()
                        {
                            Converter = CellFontStyleConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    cellstyle.Setters.Add(new Setter(Control.FontWeightProperty,
                        new Binding()
                        {
                            Converter = CellFontWeightConverter, 
                            ConverterParameter = new DynamicGridCellStyleParameters(column,DependencyProperty.UnsetValue)
                        }));
                    newColumn.CellStyle = cellstyle;
                }
                
                DataGrid.Columns.Add(newColumn);
                ColumnList.Add(column);
                foreach (var extra in newcol.ExtraColumns)
                    Parent.AddHiddenColumn(extra);
            }
        }

    }

    private void LoadSummaries()
    {
        if (Summaries.Any())
        {
            DataGrid.CellRenderers.Remove("TableSummary");
            DataGrid.CellRenderers.Add("TableSummary", new DynamicGridAggregateRenderer());

            DataGrid.TableSummaryRows.Add(new GridTableSummaryRow
            {
                ShowSummaryInRow = false,
                Position = TableSummaryRowPosition.Bottom,
                SummaryColumns = Summaries,
            });
        }
    }

    public void RefreshColumns(DynamicGridColumns columns, DynamicActionColumns actionColumns, DynamicGridColumnGroupings groupings)
    {
        // Yo, please don't remove this.
        // The issue was when we were dynamically adding ActionColumns, and if we had to remove and then re-add them, we were getting massive performance hits
        // for no reason. I think perhaps the image columns were trying to refer to data that didn't exist anymore when calling DataGrid.Columns.Refresh(),
        // and thus some mega problems (perhaps even exceptions within Syncfusion) were occurring, and this seems to fix it.
        // I don't pretend to know why it works; this is probably the strangest problem I've ever come across.
        DataGrid.ItemsSource = null;

        DataGrid.Columns.Suspend();

        ColumnList.Clear();
        DataGrid.Columns.Clear();
        DataGrid.TableSummaryRows.Clear();

        gridRowResizingOptions.ExcludeColumns = new List<string>();

        ActionColumns = actionColumns.ToList();

        Summaries.Clear();
        LoadActionColumns(DynamicActionColumnPosition.Start);
        LoadDataColumns(columns);
        LoadActionColumns(DynamicActionColumnPosition.End);
        LoadSummaries();
        LoadStackedHeaders(groupings);

        DataGrid.Columns.Resume();
        DataGrid.RefreshColumns();

        foreach (var key in _filterpredicates.Keys.ToArray())
            if (DataGrid.Columns.Any(x => string.Equals(x.MappingName, key)))
            {
                var predicates = Serialization.Deserialize<List<FilterPredicate>>(_filterpredicates[key]);
                foreach (var predicate in predicates)
                {
                    DataGrid.Columns[key].FilterPredicates.Add(predicate);
                    DataGrid.Columns[key].FilteredFrom = FilteredFrom.FilterRow;
                }
            }
            else
            {
                _filterpredicates.Remove(key);
            }

        ResizeColumns(DataGrid, DataGrid.ActualWidth - 2, DataGrid.ActualHeight - 2);
    }

    #endregion

    #region Data

    private bool _invalidating = false;

    public void BeforeRefresh()
    {
        DataGrid.SelectionForegroundBrush = GetCellSelectionForegroundBrush();
        DataGrid.RowSelectionBrush = GetCellSelectionBackgroundBrush();
    }

    public void RefreshData(CoreTable data)
    {
        var result = new DataTable();

        var defaults = new List<object?>();
        foreach (var column in data.Columns)
        {
            var colname = column.ColumnName.Replace('.', '_');
            if (!result.Columns.Contains(colname))
            {
                result.Columns.Add(colname, column.DataType);
                if (!Parent.IsDirectEditMode())
                    defaults.Add(column.DataType.GetDefault());
            }
        }

        for (var i = 0; i < ActionColumns.Count; i++)
            result.Columns.Add(string.Format("ActionColumn{0}", i),
                ActionColumns[i] is DynamicImageColumn
                    ? typeof(BitmapImage)
                    : typeof(String)
            );

        foreach (var row in data.Rows)
        {
            var newrow = result.NewRow();
            CoreRowToDataRow(newrow, row, defaults);
            result.Rows.Add(newrow);
        }

        result.ColumnChanged += Result_ColumnChanged;

        //int rowIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
        //int columnIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
        //int scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
        DataGrid.ItemsSource = result;
        //this.DataGrid.ScrollInView(new Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex(scrollRowIndex, columnIndex));
        ResizeColumns(DataGrid, DataGrid.ActualWidth - 1, DataGrid.ActualHeight);

        UpdateRecordCount();
    }

    public void AddPage(IEnumerable<CoreRow> page)
    {
        var table = DataGridItems;
        if(table is null)
        {
            return;
        }
        _invalidating = true;

        var defaults = new List<object?>();
        if (!Parent.IsDirectEditMode())
            foreach (var column in table.Columns.Cast<DataColumn>())
            {
                defaults.Add(column.DataType.GetDefault());
            }

        foreach(var row in page)
        {
            var newRow = table.NewRow();
            CoreRowToDataRow(newRow, row, defaults);
            table.Rows.Add(newRow);
        }

        UpdateRecordCount();

        _invalidating = false;
    }

    public void InvalidateRow(CoreRow row)
    {
        var table = DataGridItems;
        if(table is null)
        {
            return;
        }

        _invalidating = true;

        var rowdata = new List<object?>(row.Values);
        foreach (var ac in ActionColumns)
            rowdata.Add(ac.Data(row));

        var datarow = DataGridItems.Rows[row.Index];
        for (var i = 0; i < rowdata.Count; i++)
            datarow[i] = rowdata[i] ?? DBNull.Value;
        _invalidating = false;
        //datarow.ItemArray = rowdata.ToArray(); 
    }

    private void CoreRowToDataRow(DataRow newrow, CoreRow row, List<object?> defaults)
    {
        var rowdata = new List<object?>(row.Values);
        foreach (var ac in ActionColumns)
            rowdata.Add(ac.Data(row));

        try
        {
            var data = ProcessRow(rowdata, defaults).ToArray();
            newrow.ItemArray = data;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private static IEnumerable<object?> ProcessRow(List<object?> values, List<object?> defaults)
    {
        if (defaults == null || defaults.Count == 0)
            return values;
        var result = new List<object?>();
        for (var i = 0; i < values.Count; i++)
        {
            var value = values[i];
            var defaultvalue = i < defaults.Count ? defaults[i] : null;
            result.Add(value == null || (value.Equals(defaultvalue) && !value.GetType().IsEnum) ? null : value);
        }

        return result;
    }

    #endregion

    #region Direct Edit

    private bool bChanged;

    private class DirectEditingObject
    {
        public T Object { get; set; }

        public CoreRow Row { get; set; }

        public DataRow? DataRow { get; set; }

        public DirectEditingObject(T obj, CoreRow row, DataRow? dataRow)
        {
            Object = obj;
            Row = row;
            DataRow = dataRow;
        }
    }

    private DirectEditingObject? _editingObject;

    private DirectEditingObject EnsureEditingObject(CoreRow row)
    {
        _editingObject ??= new(Parent.LoadItem(row), row, DataGridItems?.Rows[row.Index]);
        return _editingObject;
    }

    private DataRow? GetDataRow(CoreRow row)
    {
        return DataGridItems?.Rows[row.Index];
    }

    void IDynamicGridUIComponent<T>.UpdateCell(CoreRow row, string column, object? value)
    {
        var dataRow = GetDataRow(row);

        var datacolname = column.Replace(".", "_");
        if(dataRow is not null)
        {
            dataRow[datacolname] = value ?? DBNull.Value;
        }
    }

    void IDynamicGridUIComponent<T>.UpdateRow(CoreRow row)
    {
        var dataRow = GetDataRow(row);
        if(dataRow is not null)
        {
            foreach(var (key, value) in row)
            {
                var datacolname = key.Replace(".", "_");
                var dataValue = dataRow[datacolname];
                if (!Equals(dataValue, value) && !(value is null && dataValue == DBNull.Value))
                {
                    dataRow[datacolname] = value ?? DBNull.Value;
                }
            }
            for (var i = 0; i < ActionColumns.Count; i++)
                dataRow[$"ActionColumn{i}"] = ActionColumns[i].Data(row);
        }
    }

    private void DoEntityChanged(IDynamicColumnBase column, DynamicColumnEntityChangedEventArgs args)
    {
        if (_editingObject is null) return;

        Parent.EntityChanged(_editingObject.Object, _editingObject.Row, args.ColumnName, args.Changes);
    }

    private void UpdateData(string column, Dictionary<CoreColumn, object?> updates)
    {
        if (_editingObject is null)
            return;

        var coreRow = _editingObject.Row;

        Parent.UpdateData(_editingObject.Object, coreRow, column, updates);
    }

    private void UpdateData(int rowIndex, int columnIndex)
    {
        var table = DataGridItems;
        if (table is null)
            return;
        
        if (GetColumn(columnIndex) is DynamicGridColumn gridcol)
        {
            var datacol = Parent.Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(gridcol.ColumnName));
            if (datacol != null)
            {
                var datacolindex = Parent.Data.Columns.IndexOf(datacol);

                var value = table.Rows[rowIndex][datacolindex];
                if (value is DBNull)
                    value = CoreUtils.GetDefault(datacol.DataType);

                UpdateData(datacol.ColumnName, new Dictionary<CoreColumn, object?>() { { datacol, value } });
            }
        }
    }

    private void DataGrid_CurrentCellBeginEdit(object? sender, CurrentCellBeginEditEventArgs e)
    {
        var table = DataGridItems;
        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
        if (table is null || row is null)
            return;

        EnsureEditingObject(row);

        var column = DataGrid.Columns[e.RowColumnIndex.ColumnIndex] as GridComboBoxColumn;
        if (column != null && column.ItemsSource == null)
        {
            var colname = column.MappingName;
            var colno = table.Columns.IndexOf(colname);
            var property = Parent.Data.Columns[colno].ColumnName;
            var prop = CoreUtils.GetProperty(typeof(T), property);
            var editor = prop.GetEditor();
            if (editor is ILookupEditor lookupEditor)
            {
                if (!Lookups.ContainsKey(property))
                    Lookups[property] = lookupEditor.Values(typeof(T), property);
                var combo = column;
                combo.ItemsSource = Lookups[property].ToDictionary(Lookups[property].Columns[0].ColumnName, "Display");
                combo.SelectedValuePath = "Key";
                combo.DisplayMemberPath = "Value";
            }
        }

        bChanged = false;
    }

    private void Result_ColumnChanged(object sender, DataColumnChangeEventArgs e)
    {
        if (_invalidating) return;
        if (sender is not DataTable table) return;

        var rowIdx = table.Rows.IndexOf(e.Row);
        if (rowIdx < 0)
            return;

        var row = Parent.Data.Rows[rowIdx];

        var colIdx = table.Columns.IndexOf(e.Column);
        if (colIdx < 0 || colIdx >= Parent.Data.Columns.Count)
            return;

        var data = Parent.Data;

        var dataCol = Parent.Data.Columns[colIdx];
        var col = ColumnList.OfType<DynamicGridColumn>()
            .FirstOrDefault(x => x.ColumnName.Equals(dataCol.ColumnName));
        
        if (col is null)
            return;

        if (col is DynamicGridCheckBoxColumn<T>)
        {
            EnsureEditingObject(row);
            if(_editingObject is not null)
            {
                var value = e.Row[e.Column!];
                if (value is DBNull)
                    value = CoreUtils.GetDefault(dataCol.DataType);

                _invalidating = true;
                UpdateData(dataCol.ColumnName, new Dictionary<CoreColumn, object?>() { { dataCol, value } });
                _invalidating = false;
            }

            _editingObject = null;
        }
        if (_editingObject is not null)
            bChanged = true;
    }

    private void DataGrid_CurrentCellDropDownSelectionChanged(object? sender,
        CurrentCellDropDownSelectionChangedEventArgs e)
    {
        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
        if (row is null)
            return;
        EnsureEditingObject(row);
        if ((_editingObject is not null) && (e.SelectedItem is Tuple<object?, string> tuple))
        {
            var mappedname = DataGrid.Columns[e.RowColumnIndex.ColumnIndex].MappingName;

            var colno = DataGridItems.Columns.IndexOf(mappedname);
            var corecol = Parent.Data.Columns[colno].ColumnName;

            var updates = new Dictionary<CoreColumn, object?>();

            var prefix = String.Join(".", corecol.Split(".").Reverse().Skip(1).Reverse());
            var field = corecol.Split(".").Last();

            var prop = CoreUtils.GetProperty(typeof(T), corecol);
            if (prop.GetEditor() is ILookupEditor editor)
            {
                var data = editor.Values(typeof(T), corecol);
                var lookuprow = data.Rows.FirstOrDefault(r => Equals(r[field], tuple.Item1))
                    ?? data.NewRow(true);

                foreach (CoreColumn lookupcol in data.Columns)
                {
                    var columnname = String.IsNullOrWhiteSpace(prefix)
                        ? lookupcol.ColumnName
                        : String.Join(".", prefix, lookupcol.ColumnName);
                    var updatecol = Parent.Data.Columns.FirstOrDefault(x => String.Equals(x.ColumnName, columnname));
                    if (updatecol != null)
                        updates[updatecol] = lookuprow[lookupcol.ColumnName];
                }
                UpdateData(corecol, updates);
                bChanged = true;
                
            }
        }
    }

    private void DataGrid_CurrentCellEndEdit(object? sender, CurrentCellEndEditEventArgs e)
    {
        if (_editingObject is not null && bChanged)
        {
            UpdateData(_editingObject.Row.Index, e.RowColumnIndex.ColumnIndex);
        }
        if (bChanged)
            Parent.DoChanged();
        bChanged = false;
        _editingObject = null;

        // Commented out on 19/02/2024 by Kenric. I don't see this being necessary, though I could be wrong. Nevertheless, it was causing a bug when
        // editing the filter row. It seems that this causes Syncfusion to commit the filter predicates internally, which means that after leaving a 
        // filter row cell, the filter remained even once it was cleared, meaning a refresh was necessary to get the data back.
        // I've tested on Bills to see if editing works with this empty, and it seems so.
        //DataGridItems?.AcceptChanges();
    }

    private void DataGrid_PreviewKeyUp(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.OemPeriod)
        {
            var editor = e.OriginalSource as TimeSpanEdit;
            if (editor != null && editor.SelectionStart < 2) editor.SelectionStart = 3;
        }
        else if (e.Key == Key.Tab)
        {
            if (Parent.IsDirectEditMode())
            {
                DataGrid.SelectionController.CurrentCellManager.EndEdit();
                DataGrid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
                DataGrid.SelectionController.CurrentCellManager.BeginEdit();
                e.Handled = true;
            }
        }
    }

    #endregion

    #region Drag + Drop

    private void DataGrid_DragOver(object sender, DragEventArgs e)
    {
        Parent.DragOver(sender, e);
    }

    private void DataGrid_Drop(object sender, DragEventArgs e)
    {
        Parent.Drop(sender, e);
    }

    private void RowDragDropController_DragStart(object? sender, GridRowDragStartEventArgs e)
    {
        var rows = e.DraggingRecords.Select(record =>
        {
            var rowIndex = DataGrid.ResolveToRowIndex(record);
            return GetRowFromIndex(rowIndex);
        }).NotNull().ToArray();
        Parent.DragStart(sender, rows);
    }

    #endregion
}
