using InABox.Clients;
using InABox.Core;
using InABox.Scripting;
using InABox.Wpf;
using InABox.WPF;
using Microsoft.Xaml.Behaviors;
using org.omg.PortableInterceptor;
using sun.reflect.generics.tree;
using sun.util.resources.cldr.fr;
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;
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.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Syncfusion.UI.Xaml.Grid.RowFilter;
using Columns = InABox.Core.Columns;
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 : IDynamicGridUIComponent, IDynamicGridGridUIComponent
{
    private IDynamicGridUIComponentParent _parent = null!;
    public IDynamicGridUIComponentParent Parent
    {
        get => _parent;
        set
        {
            _parent = value;

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

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

    #region IDynamicGridGridUIComponent

    IList<DynamicColumnBase> IDynamicGridGridUIComponent.ColumnList => ColumnList;

    int IDynamicGridGridUIComponent.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>
    protected DataTable? DataGridItems => (DataGrid.ItemsSource as DataTable);

    #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,
            };
        }
    }

    private bool _showHeader = false;
    public bool ShowHeader
    {
        get => _showHeader;
        set
        {
            _showHeader = value;
            DataGrid.HeaderRowHeight = value ? 30 : 0;
        }
    }

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

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

    #endregion
    
    public class GridFilterRowTextBoxRendererExt : GridFilterRowTextBoxRenderer
    {

        public override void OnInitializeDisplayElement(
            DataColumnBase dataColumn,
            TextBlock uiElement,
            object dataContext)
        {
            base.OnInitializeDisplayElement(dataColumn, uiElement, dataContext);
            object filterValue = dataColumn.GridColumn.FilteredFrom != FilteredFrom.FilterRow || dataColumn.GridColumn.FilterPredicates.Count <= 0 
                ? (object) null 
                : dataColumn.GridColumn.FilterPredicates.FirstOrDefault<FilterPredicate>().FilterValue;
            uiElement.Text = filterValue != null ? (string) filterValue : string.Empty;
            
        }
    }

    public static GridFilterRowTextBoxRendererExt? renderer = null;
    
    
    public DynamicGridGridUIComponent()
    {

        
        ColumnsMenu = new ContextMenu();
        ColumnsMenu.Opened += ColumnsMenu_ContextMenuOpening;

        DataGrid = new SfDataGrid();
        DataGrid.FilterRowCellRenderers.Add(nameof(GridFilterRowTextBoxRendererExt), new GridFilterRowTextBoxRendererExt());
        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.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;
            return null;
        });
        DataGrid.RowDropIndicatorMode = DropIndicatorMode.Line;
        DataGrid.RowDragDropController ??= new GridRowDragDropController();
        
        DataGrid.CurrentCellBorderThickness = new Thickness(0);
        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.CurrentCellBeginEdit += DataGrid_CurrentCellBeginEdit;
        DataGrid.CurrentCellEndEdit += DataGrid_CurrentCellEndEdit;
        DataGrid.PreviewKeyUp += DataGrid_PreviewKeyUp;
        
        DataGrid.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Visible);
        
        DataGrid.FilterChanged += DataGrid_FilterChanged;

        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.CellToolTipOpening += DataGrid_CellToolTipOpening;

        DataGrid.SizeChanged += DataGrid_SizeChanged;

        DataGrid.SelectionController = new GridSelectionControllerExt(DataGrid, this);
    }

    public class GridSelectionControllerExt(SfDataGrid datagrid, DynamicGridGridUIComponent grid) : GridSelectionController(datagrid)
    {
        public override bool HandleKeyDown(KeyEventArgs args)
        {
            if (args.Key == Key.Escape)
            {
                grid.CancelEdit();
                return false;
            }
            else
            {
                return base.HandleKeyDown(args);
            }
        }
    }

    #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 && GetRow(datarow.Row) is CoreRow row)
            {
                result.Add(row);
            }
        }

        return result.ToArray();
    }

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

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


        var dataRows = rows.Select(x => _rowMap.FirstOrDefault(y => x == y.Value).Key);
        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

    #region Rows

    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)
        {
            if(GetRow(row.Row) is CoreRow coreRow)
            {
                result.Add(coreRow);
            }
        }

        return result.ToArray();
    }

    // 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>
    protected 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;

        return GetRow(dataRowView.Row);
    }

    private CoreRow? GetRow(DataRow row)
    {
        return _rowMap.GetValueOrDefault(row);
    }

    #endregion

    #region Input

    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);
        }
    }

    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;
        }
    }

    #endregion

    #region UI Stuff

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

        Parent.LoadColumnsMenu(menu);
    }

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

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

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

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

            e.ToolTip.Template = TemplateGenerator.CreateControlTemplate(
                typeof(ToolTip),
                () => toolTip.Invoke(ac, row)
            );
        }
        else if(column is DynamicGridColumn gc)
        {
            e.ToolTip.Content = gc.Editor.ToolTip;
        }
    }

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

    int IDynamicGridUIComponent.DesiredWidth()
    {
        return this.DesiredWidth();
    }

    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.FilterRowPosition = Parent.Options.FilterRows && Parent.CanFilter() ? FilterRowPosition.FixedTop : FilterRowPosition.None;

        if (Parent.Options.DragSource || Parent.Options.ReorderRows)
        {
            if (!DataGrid.AllowDraggingRows)
            {
                DataGrid.AllowDraggingRows = true;
                DataGrid.RowDragDropController.DragStart += RowDragDropController_DragStart;
                DataGrid.RowDragDropController.DragOver += RowDragDropController_DragOver;
            }
        }
        else
        {
            if (DataGrid.AllowDraggingRows)
            {
                DataGrid.AllowDraggingRows = false;
                DataGrid.RowDragDropController.DragStart -= RowDragDropController_DragStart;
                DataGrid.RowDragDropController.DragOver -= RowDragDropController_DragOver;
            }
        }

        if (Parent.Options.DragTarget || Parent.Options.ReorderRows)
        {
            if (!DataGrid.AllowDrop)
            {
                DataGrid.AllowDrop = true;
                DataGrid.RowDragDropController.Drop += RowDragDropController_Drop;
                DataGrid.RowDragDropController.Dropped += RowDragDropController_Dropped;
            }
        }
        else
        {
            if (DataGrid.AllowDrop)
            {
                DataGrid.AllowDrop = false;
                DataGrid.RowDragDropController.Drop -= RowDragDropController_Drop;
                DataGrid.RowDragDropController.Dropped -= RowDragDropController_Dropped;
            }
        }
        
        DataGrid.SelectionMode = Parent.Options.MultiSelect ? GridSelectionMode.Extended : GridSelectionMode.Single;

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

    #endregion

    #region Filter Predicates

    private readonly Dictionary<string, string> _filterpredicates = new();

    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];
            var filter = Parent.GetColumnFilter(col);

            if (col is DynamicGridColumn gridColumn)
            {
                var rowPredicate = DynamicGridGridUIComponentExtension.ConvertColumnPredicates(gridColumn, column.FilterPredicates);
                if(filter is not null)
                {
                    var oldPred = rowPredicate;
                    if(oldPred is not null)
                    {
                        rowPredicate = row => oldPred(row) && filter.FilterRow(row);
                    }
                    else
                    {
                        rowPredicate = filter.FilterRow;
                    }
                }
                if(rowPredicate is not null)
                {
                    list.Add(new(gridColumn.ColumnName, rowPredicate));
                }
            }
            else if(col is DynamicActionColumn dac)
            {
                if(filter is not null)
                {
                    list.Add(new(column.MappingName, filter.FilterRow));
                }
            }
        }
        return list;
    }

    private void DataGrid_FilterChanged(object? o, GridFilterEventArgs e)
    {
        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();
    }

    #endregion

    #region Styles

    private DynamicGridGridCellStyleConverter<System.Windows.Media.Brush?> CellBackgroundConverter = null!;
    private DynamicGridGridCellStyleConverter<System.Windows.Media.Brush?> CellForegroundConverter = null!;
    private DynamicGridGridCellStyleConverter<double?> CellFontSizeConverter = null!;
    private DynamicGridGridCellStyleConverter<System.Windows.FontStyle?> CellFontStyleConverter = null!;
    private DynamicGridGridCellStyleConverter<System.Windows.FontWeight?> CellFontWeightConverter = null!;

    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 GetCellStyle(DynamicColumnBase column)
    {
        var style = new Style(typeof(GridCell));
        return style;
    }

    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)
                    {
                        headStyle.AddSetter(GridHeaderCellControl.PaddingProperty, new Thickness(4.0));
                        headStyle.AddSetter(GridHeaderCellControl.ContentTemplateProperty,
                            TemplateGenerator.CreateDataTemplate(() =>
                            {
                                return new Image { Source = image };
                            }));
                    }
                }
            }
            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 GetColumnGroupHeaderCellStyle(DynamicGridColumnGroup group)
    {
        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)));

        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 if(column is DynamicTextColumn textColumn)
        {
            style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty, textColumn.Alignment.HorizontalAlignment(typeof(string))));
        }
        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;
    }

    private void AddCellStyleConverters(Style cellstyle, DynamicColumnBase column, string sColName)
    {
        cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
            WPFUtils.CreateMultiBinding(
                CellBackgroundConverter.WrapConverter(x => x[0]),
                parameter: new DynamicGridCellStyleParameters(column, DependencyProperty.UnsetValue))
                .AddBinding(new Binding("."))
                .AddBinding(new Binding(sColName))));
        cellstyle.Setters.Add(new Setter(Control.ForegroundProperty,
            WPFUtils.CreateMultiBinding(
                CellForegroundConverter.WrapConverter(x => x[0]),
                parameter: new DynamicGridCellStyleParameters(column, DependencyProperty.UnsetValue))
                .AddBinding(new Binding("."))
                .AddBinding(new Binding(sColName))));
        cellstyle.Setters.Add(new Setter(Control.FontSizeProperty,
            WPFUtils.CreateMultiBinding(
                CellFontSizeConverter.WrapConverter(x => x[0]),
                parameter: new DynamicGridCellStyleParameters(column, DependencyProperty.UnsetValue))
                .AddBinding(new Binding("."))
                .AddBinding(new Binding(sColName))));
        cellstyle.Setters.Add(new Setter(Control.FontStyleProperty,
            WPFUtils.CreateMultiBinding(
                CellFontStyleConverter.WrapConverter(x => x[0]),
                parameter: new DynamicGridCellStyleParameters(column, DependencyProperty.UnsetValue))
                .AddBinding(new Binding("."))
                .AddBinding(new Binding(sColName))));
        cellstyle.Setters.Add(new Setter(Control.FontWeightProperty,
            WPFUtils.CreateMultiBinding(
                CellFontWeightConverter.WrapConverter(x => x[0]),
                parameter: new DynamicGridCellStyleParameters(column, DependencyProperty.UnsetValue))
                .AddBinding(new Binding("."))
                .AddBinding(new Binding(sColName))));
    }


    #endregion

    #region Columns

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

    private List<DynamicActionColumn> ActionColumns = new();

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

    #region Column Grouping

    private class StackedHeaderRenderer(DynamicGridGridUIComponent grid) : GridStackedHeaderCellRenderer
    {
        public override void OnInitializeEditElement(DataColumnBase dataColumn, GridStackedHeaderCellControl uiElement, object dataContext)
        {
            if(dataContext is StackedColumn column && grid.ColumnGroupMap.TryGetValue(column.MappingName, out var group))
            {
                uiElement.Style = grid.GetColumnGroupHeaderCellStyle(group);
            }
            base.OnInitializeEditElement(dataColumn, uiElement, dataContext);
        }
    }

    private Dictionary<string, DynamicGridColumnGroup> ColumnGroupMap = new();

    private void LoadStackedHeaders(DynamicGridColumnGroupings groupings)
    {
        DataGrid.StackedHeaderRows.Clear();
        ColumnGroupMap.Clear();
        var j = 0;
        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 name = $"_GroupColumn{j}";

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

                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(this));
        }
    }

    #endregion

    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);
        });
    }

    #region Column Filtering

    private void ApplyFilterStyle(GridColumn column, bool filtering, bool allowSorting)
    {
        
        var filterRowCellStyle = new Style();
        if (filtering)
        {
            filterRowCellStyle.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 = allowSorting && Parent.CanSort();
        }
        else
        {
            filterRowCellStyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
            filterRowCellStyle.Setters.Add(new Setter(UIElement.IsEnabledProperty, false));
            column.ColumnFilter = ColumnFilter.Value;
            column.AllowSorting = false;
            column.FilterRowEditorType = "TextBox";
            column.FilterRowOptionsVisibility = Visibility.Collapsed;
        }

        
        column.FilterRowCellStyle = filterRowCellStyle;
    }

    private void Filter_FilterChanged(IDynamicGridColumnFilter filter)
    {
        AddRows(Parent.Data.Rows, true);
        Parent.UIFilterChanged(this);
    }

    private bool FilterRow(CoreRow row)
    {
        foreach(var column in ColumnList)
        {
            if(Parent.GetColumnFilter(column) is IDynamicGridColumnFilter filter && !filter.FilterRow(row))
            {
                return false;
            }
        }
        return true;
    }

    private void SetFilterUIButton(GridColumn gridColumn, DynamicColumnBase column)
    {
        if (!Parent.Options.FilterRows || !Parent.CanFilter()) return;

        if (Parent.GetColumnFilter(column) is not IDynamicGridColumnFilter filter) return;

        var vertical = column is DynamicActionColumn ac && ac.VerticalHeader && !ac.HeaderText.IsNullOrWhiteSpace();

        var horizontalAlignment = gridColumn.HorizontalHeaderContentAlignment;
        gridColumn.HorizontalHeaderContentAlignment = HorizontalAlignment.Stretch;
        gridColumn.HeaderTemplate = TemplateGenerator.CreateDataTemplate(() =>
        {
            var grid = new Grid();
            grid.AddColumn(GridUnitType.Star);
            var filterCol = grid.AddColumn(GridUnitType.Auto);

            var content = new ContentControl();
            content.HorizontalAlignment = horizontalAlignment;
            content.VerticalAlignment = VerticalAlignment.Center;
            content.SetBinding(ContentControl.ContentProperty, new Binding());
            grid.AddChild(content, 0, 0);

            var button = new DynamicGridColumnFilterUIButton(filter);
            grid.AddChild(button, 0, 1);

            if(vertical)
            {
                button.LayoutTransform = new RotateTransform(90);
                content.HorizontalAlignment = HorizontalAlignment.Stretch;
            }

            return grid;
        });

    }

    #endregion

    #region Summaries

    private readonly ObservableCollection<ISummaryColumn> Summaries = new();

    public class SummaryRowStyleSelector(Func<Style> selector) : StyleSelector
    {
        public override Style? SelectStyle(object item, DependencyObject container)
        {
            return selector();
        }
    }

    public class SummaryCellStyleSelector(DynamicGridGridUIComponent parent, Func<DynamicColumnBase, Style> selector) : StyleSelector
    {
        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;
        }
    }

    private GridSummaryColumn? ConvertSummary(IDynamicGridSummary? summary)
    {
        if(summary is DynamicGridSumSummary sum)
        {
            var newSummary = new GridSummaryColumn
            {
                Format = $"{{Sum:{sum.Format}}}"
            };
            if(sum.AggregateType == typeof(double))
            {
                newSummary.SummaryType = Syncfusion.Data.SummaryType.DoubleAggregate;
            }
            else if(sum.AggregateType == typeof(int))
            {
                newSummary.SummaryType = Syncfusion.Data.SummaryType.Int32Aggregate;
            }
            else if(sum.AggregateType == typeof(TimeSpan))
            {
                newSummary.SummaryType = Syncfusion.Data.SummaryType.Custom;
                newSummary.CustomAggregate = new DynamicGridDurationAggregate();
            }
            return newSummary;
        }
        else if(summary is DynamicGridCountSummary count)
        {
            return new GridSummaryColumn
            {
                Format = "{Count:N0}",
                SummaryType = Syncfusion.Data.SummaryType.CountAggregate
            };
        }
        else if(summary is DynamicGridCustomSummary custom)
        {
            return new GridSummaryColumn
            {
                Format = $"{{Sum:{custom.Format}}}",
                SummaryType = SummaryType.Custom,
                CustomAggregate = new InternalAggregate(this, custom.Aggregate)
            };
        }
        else if(summary is DynamicGridTemplateSummary template)
        {
            return new GridSummaryColumn
            {
                TemplateSelector = new FuncTemplateSelector((item, o) => template.Template())
            };
        }
        else
        {
            return null;
        }
    }

    private class InternalAggregate(DynamicGridGridUIComponent grid, DynamicGridCustomSummary.AggregateFunc aggregate) : ISummaryAggregate
    {
        public object? Sum { get; set; } = null;

        public Action<IEnumerable, string, PropertyDescriptor> CalculateAggregateFunc() => CalculateAggregate;

        private void CalculateAggregate(IEnumerable items, string property, PropertyDescriptor args)
        {
            if (items is IEnumerable<DataRowView> rows)
            {
                Sum = aggregate(rows.Select(x =>
                {
                    return grid.GetRow(x.Row);
                }).NotNull());
            }
            else
            {
                Logger.Send(LogType.Error, "", $"Attempting to calculate aggregate on invalid data type '{items.GetType()}'.");
            }
        }
    }

    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,
            });
        }
    }

    #endregion

    private void LoadActionColumn(DynamicActionColumn column)
    {
        var i = ActionColumns.Count;
        var sColName = string.Format("ActionColumn{0}", i);
        ActionColumns.Add(column);
        gridRowResizingOptions.ExcludeColumns.Add(sColName);

        var summary = ConvertSummary(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.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;
            newcol.AllowEditing = false;

            ApplyFilterStyle(newcol, true, false);
            SetFilterUIButton(newcol, column);

            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 = GetCellStyle(column);
            AddCellStyleConverters(cellstyle, column, sColName);
            newcol.CellStyle = cellstyle;

            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.TextAlignment(typeof(string));

            newcol.AllowEditing = false;
            newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
            newcol.MappingName = sColName;
            newcol.Width = column.Width;
            newcol.ColumnSizer = GridLengthUnitType.None;
            newcol.HeaderText = column.HeaderText;
            newcol.AllowSorting = false;
            newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
            newcol.ShowHeaderToolTip = column.ToolTip != null;
            
            newcol.ShowToolTip = column.ToolTip != null;
            newcol.ShowHeaderToolTip = column.ToolTip != null;

            ApplyFilterStyle(newcol, true, true);
            SetFilterUIButton(newcol, column);

            newcol.HeaderStyle = GetHeaderCellStyle(column);

            var cellstyle = GetCellStyle(column);
            AddCellStyleConverters(cellstyle, column, sColName);
            newcol.CellStyle = cellstyle;

            DataGrid.Columns.Add(newcol);
            ColumnList.Add(column);
        }
        else if (column is DynamicTemplateColumn tmplCol)
        {
            var newcol = new GridTemplateColumn();
            newcol.CellTemplate = TemplateGenerator.CreateDataTemplate(() =>
            {
                var content = new ContentControl();
                content.SetBinding(ContentControl.ContentProperty,
                    WPFUtils.CreateMultiBinding(new MultiFuncConverter(x =>
                    {
                        if(x[0] is DataRowView view && DataGridItems is DataTable table && GetRow(view.Row) is CoreRow row)
                        {
                            return tmplCol.Template(row);
                        }
                        return null;
                    }))
                        .AddBinding(new Binding("."))
                        .AddBinding(new Binding(sColName)));
                return content;
            });

            newcol.AllowEditing = false;
            newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
            newcol.MappingName = sColName;
            
            newcol.Width = tmplCol.Width;
            newcol.ColumnSizer = GridLengthUnitType.None;
            newcol.HeaderText = column.HeaderText;
            newcol.AllowSorting = false;
            newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
            
            newcol.ShowToolTip = column.ToolTip != null;
            newcol.ShowHeaderToolTip = column.ToolTip != null;

            SetFilterUIButton(newcol, column);

            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 = GetCellStyle(column);
            AddCellStyleConverters(cellstyle, column, sColName);
            newcol.CellStyle = cellstyle;

            DataGrid.Columns.Add(newcol);
            ColumnList.Add(column);
        }
    }

    protected virtual bool CreateEditorColumn(DynamicGridColumn column, [NotNullWhen(true)] out IDynamicGridEditorColumn? newColumn)
    {
        return (this as IDynamicGridGridUIComponent).CreateEditorColumn(column, out newColumn);
    }
    protected virtual void ConfigureColumn(DynamicGridColumn column, IDynamicGridEditorColumn newColumn)
    {
    }

    private void LoadDataColumn(DynamicGridColumn column)
    {
        
        column.Editor = Parent.CustomiseEditor(column, column.Editor);
        if (this.CreateEditorColumn(column, out var newcol))
        {
            
            if (!newcol.VariableHeight)
                gridRowResizingOptions.ExcludeColumns.Add(newcol.MappingName);

            var newColumn = newcol.CreateGridColumn();
            
            newColumn.FilterRowEditorType = nameof(GridFilterRowTextBoxRendererExt);
            
            var summary = ConvertSummary(newcol.Summary());
            if (summary != null)
            {
                summary.Name = newcol.MappingName;
                summary.MappingName = newcol.MappingName;
                Summaries.Add(summary);
            }
            
            ApplyFilterStyle(newColumn, newcol.Filtered, true);
            
            newColumn.HeaderStyle = GetHeaderCellStyle(column);
            SetFilterUIButton(newColumn, column);

            newColumn.ShowHeaderToolTip = !column.Editor.ToolTip.IsNullOrWhiteSpace();

            var cellstyle = GetCellStyle(column);
            if (Parent.IsDirectEditMode())
            {
                if (column.Editor is null || !newcol.Editable || !column.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
            {
                AddCellStyleConverters(cellstyle, column, newColumn.MappingName);
                newColumn.CellStyle = cellstyle;
            }

            ConfigureColumn(column, newcol);
            
            DataGrid.Columns.Add(newColumn);
            ColumnList.Add(column);
        }
    }

    public void RefreshColumns(IEnumerable<DynamicColumnBase> columns, DynamicGridColumnGroupings groupings)
    {
        if (DataGrid.View != null)
            DataGrid.View.Filter = null;
        
        DataGrid.ItemsSource = null;
        DataGrid.Columns.Suspend();

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

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

        foreach(var column in columns)
        {
            if(Parent.GetColumnFilter(column) is IDynamicGridColumnFilter filter)
            {
                filter.FilterChanged += Filter_FilterChanged;
            }
            if(column is DynamicActionColumn ac)
            {
                LoadActionColumn(ac);
            }
            else if(column is DynamicGridColumn gc)
            {
                LoadDataColumn(gc);
            }
        }

        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]);
                if(predicates is not null)
                {
                    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);

        if(groupings.Count > 0)
        {
            // THis here is to fix a problem with Syncfusion when we have stackedHeaderRows; the above setting of the ItemsSource to null
            // was causing the resetting of it to fail when reloading, due to some internal OutOfRange Exception. THe use case was selecting columns on the
            // Stock Forecast grid.
            RefreshData(new CoreTable());
        }

        if (DataGrid.View != null)
            DataGrid.View.Filter = (o) =>
            {
                return true;
            };
    }

    #endregion

    #region Data

    protected bool _invalidating = false;
    private Dictionary<DataRow, CoreRow> _rowMap = new();

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

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

        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(ImageSource)
                    : typeof(String)
            );

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

        result.ColumnChanged += Result_ColumnChanged;

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

        UpdateRecordCount();
    }

    private void AddRows(IEnumerable<CoreRow> rows, bool clearRows)
    {
        var table = DataGridItems;
        if(table is null)
        {
            return;
        }
        _invalidating = true;

        if (clearRows)
        {
            table.Rows.Clear();
            _rowMap.Clear();
        }

        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 rows.Where(FilterRow))
        {
            var newRow = table.NewRow();
            CoreRowToDataRow(newRow, row, defaults);
            table.Rows.Add(newRow);
            _rowMap[newRow] = row;
        }

        UpdateRecordCount();

        _invalidating = false;
    }

    public void AddPage(IEnumerable<CoreRow> page)
    {
        AddRows(page, 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 = table.Rows[row.Index];

        for (var i = 0; i < rowdata.Count; i++)
            datarow[i] = rowdata[i] ?? DBNull.Value;

        _invalidating = false;
    }

    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;

        return values.WithIndex().Select(x =>
        {
            var (i, value) = x;
            var defaultvalue = i < defaults.Count ? defaults[i] : null;
            return value is null || (value.Equals(defaultvalue) && !value.GetType().IsEnum) ? null : value;
        });
    }

    #endregion

    #region Direct Edit

    protected bool bChanged;

    protected class DirectEditingObject
    {
        public object? Object { get; set; }

        public CoreRow Row { get; set; }

        public DataRow? DataRow { get; set; }

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

    protected DirectEditingObject? _editingObject;

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

    protected virtual object? GetEditingObject(CoreRow row)
    {
        return null;
    }

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

    void IDynamicGridUIComponent.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.UpdateCell(CoreRow row, DynamicColumnBase column)
    {
        var dataRow = GetDataRow(row);
        if(dataRow is not null)
        {
            if(column is DynamicGridColumn gc)
            {
                var dataColName = gc.ColumnName.Replace(".", "_");

                dataRow[dataColName] = row[gc.ColumnName] ?? DBNull.Value;
            }
            else if(column is DynamicActionColumn ac)
            {
                var i = ActionColumns.IndexOf(ac);
                dataRow[$"ActionColumn{i}"] = ac.Data(row);
            }
        }
    }

    protected void UpdateRow(CoreRow row, DataRow dataRow)
    {
        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);
    }

    void IDynamicGridUIComponent.UpdateRow(CoreRow row)
    {
        var dataRow = GetDataRow(row);
        if(dataRow is not null)
        {
            UpdateRow(row, dataRow);
        }
    }

    protected virtual void DoEntityChanged(IDynamicColumnBase column, DynamicColumnEntityChangedEventArgs args)
    {
    }

    protected virtual void BeginEdit(CoreRow row, CurrentCellBeginEditEventArgs e)
    {
        EnsureEditingObject(row);
    }

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

        BeginEdit(row, e);

        bChanged = false;
    }

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

        var coreRow = _editingObject.Row;

        Parent.UpdateData(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 } });
            }
        }
    }

    protected virtual void ColumnChanged(CoreColumn dataCol, CoreRow row, object? value)
    {
        var col = ColumnList.OfType<DynamicGridColumn>()
            .FirstOrDefault(x => x.ColumnName.Equals(dataCol.ColumnName));
        
        if (col is null)
            return;

        if (col.Editor is CheckBoxEditor)
        {
            EnsureEditingObject(row);
            if(_editingObject is not null)
            {
                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_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)
        {
            if (e.OriginalSource is TimeSpanEdit editor && 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;
            }
        }
        else if(e.Key == Key.Escape)
        {
            if (Parent.IsDirectEditMode())
            {
                bChanged = false;
            }
        }
    }

    protected virtual void CancelEdit()
    {
        var obj = _editingObject;
        bChanged = false;
        _editingObject = null;
        DataGrid.SelectionController.CurrentCellManager.EndEdit(false);
        if(obj is not null && obj.DataRow is not null)
        {
            UpdateRow(obj.Row, obj.DataRow);
        }
    }


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

        var row = GetRow(e.Row);
        if (row is null)
            return;

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

        var dataCol = Parent.Data.Columns[colIdx];
        var value = e.Row[e.Column!];
        ColumnChanged(dataCol, row, value);
    }

    #endregion

    #region Drag + Drop

    private void DataGrid_DragOver(object sender, DragEventArgs e)
    {
        if (!Parent.Options.DragTarget)
        {
            return;
        }
        Parent.DragOver(sender, e);
    }

    private void DataGrid_Drop(object sender, DragEventArgs e)
    {
        if (!Parent.Options.DragTarget)
        {
            return;
        }
        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);
    }

    private void RowDragDropController_Drop(object? sender, GridRowDropEventArgs e)
    {
        if (!Parent.Options.ReorderRows)
        {
            e.Handled = true;
        }
        e.Handled = true;
    }

    private void RowDragDropController_Dropped(object? sender, GridRowDroppedEventArgs e)
    {
        if (!Parent.Options.ReorderRows)
        {
        }

        if(e.DropPosition != DropPosition.None)
        {
            var records = (e.Data.GetData("Records") as ObservableCollection<object>)!;
            var targetIdx = (int)e.TargetRecord;

            var rows = records.Select(x =>
            {
                if (x is DataRowView dataRow)
                {
                    return GetRow(dataRow.Row);
                }
                else
                {
                    return null;
                }
            }).NotNull().OrderBy(x => x.Index).ToArray();

            Parent.MoveRows(rows, e.DropPosition == DropPosition.DropBelow ? targetIdx + 1 : targetIdx);
        }
    }

    private void RowDragDropController_DragOver(object? sender, GridRowDragOverEventArgs e)
    {
        if (!Parent.Options.ReorderRows)
        {
            e.Handled = true;
        }
    }

    #endregion
}

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

    public new IDynamicGridUIComponentParent<T> Parent
    {
        get => (IDynamicGridUIComponentParent<T>)base.Parent;
        set => base.Parent = value;
    }

    public DynamicGridGridUIComponent()
    {
        DataGrid.CurrentCellDropDownSelectionChanged += DataGrid_CurrentCellDropDownSelectionChanged;
    }

    protected override bool CreateEditorColumn(DynamicGridColumn column, [NotNullWhen(true)] out IDynamicGridEditorColumn? newColumn)
    {
        if((this as IDynamicGridGridUIComponent).CreateEditorColumn<T>(column, out newColumn))
        {
            if(newColumn is IDynamicGridEditorColumn<T> typed)
            {
                typed.GetEntity = () => _editingObject?.Object as T;
                typed.EntityChanged += DoEntityChanged;
            }
            return true;
        }
        else
        {
            return false;
        }
    }

    protected override void ConfigureColumn(DynamicGridColumn column, IDynamicGridEditorColumn newColumn)
    {
        base.ConfigureColumn(column, newColumn);
        foreach (var extra in newColumn.ExtraColumns)
            Parent.AddHiddenColumn(extra);
    }

    #region Direct Edit

    protected override object? GetEditingObject(CoreRow row)
    {
        return Parent.LoadItem(row);
    }

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

    protected override void DoEntityChanged(IDynamicColumnBase column, DynamicColumnEntityChangedEventArgs args)
    {
        base.DoEntityChanged(column, args);

        if (_editingObject?.Object is not T obj) return;

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

    protected override void UpdateData(string column, Dictionary<CoreColumn, object?> updates)
    {
        if (_editingObject?.Object is not T obj) return;

        var coreRow = _editingObject.Row;

        Parent.UpdateData(obj, coreRow, column, updates);
    }

    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 dotIdx = corecol.LastIndexOf('.');
            string prefix, field;
            if(dotIdx == -1)
            {
                prefix = "";
                field = corecol;
            }
            else
            {
                prefix = corecol[..dotIdx];
                field = corecol[(dotIdx + 1)..];
            }

            var col = ColumnList.OfType<DynamicGridColumn>()
                .FirstOrDefault(x => x.ColumnName.Equals(corecol));

            if (col?.Editor is ILookupEditor editor)
            {
                var data = editor.Values(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;
                
            }
        }
    }

    protected override void BeginEdit(CoreRow row, CurrentCellBeginEditEventArgs e)
    {
        base.BeginEdit(row, e);

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

        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 col = ColumnList.OfType<DynamicGridColumn>()
                .FirstOrDefault(x => x.ColumnName.Equals(property));

            if (col?.Editor is ILookupEditor lookupEditor)
            {
                if (!Lookups.ContainsKey(property))
                    Lookups[property] = lookupEditor.Values(property);
                var combo = column;
                combo.ItemsSource = Lookups[property].ToDictionary(Lookups[property].Columns[0].ColumnName, "Display");
                combo.SelectedValuePath = "Key";
                combo.DisplayMemberPath = "Value";
            }
        }
    }

    #endregion
}
