using InABox.Core;
using InABox.Wpf;
using InABox.WPF;
using Syncfusion.Data;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
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.Animation;
using System.Windows.Media.Imaging;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;
using Color = System.Drawing.Color;

namespace InABox.DynamicGrid;

public abstract class BaseDynamicGrid : ContentControl, IDynamicGridUIComponentParent
{
    public static readonly DependencyProperty UseWaitCursorProperty =
        DependencyProperty.Register(nameof(UseWaitCursor), typeof(bool), typeof(BaseDynamicGrid));

    public bool UseWaitCursor
    {
        get => (bool)GetValue(UseWaitCursorProperty);
        set => SetValue(UseWaitCursorProperty, value);
    }
    
    public static readonly DependencyProperty ItemsSourceProperty = 
        DependencyProperty.Register(
            nameof(ItemsSource),
            typeof(object),
            typeof(BaseDynamicGrid),
            new PropertyMetadata(null, DoItemsSourceChanged)
        );

    private static void DoItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is BaseDynamicGrid dynamicGrid)
        {
            dynamicGrid.OnItemSourceChanged(e.NewValue);
        }
    }

    public object? ItemsSource
    {
        get => GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty, value);
    }

    public abstract void OnItemSourceChanged(object value);
    

    protected enum ClipAction
    {
        Cut,
        Copy
    }

    private IDynamicGridUIComponent UIComponent;

    private UIElement? _header;
    private readonly Button Add;

    public bool bRefreshing;
    bool IDynamicGridUIComponentParent.IsRefreshing => bRefreshing;

    private readonly Label ClipboardSpacer;
    private readonly Button Copy;

    private readonly Label Count;

    private readonly Border Disabler;

    private readonly DynamicActionColumn? drag;

    private readonly Button Delete;
    private readonly DockPanel Docker;
    private readonly Button Edit;
    private readonly Label EditSpacer;
    private readonly Button? ExportButton;
    private readonly Label ExportSpacer;
    private readonly Button? DuplicateBtn;
    private readonly Button SwitchViewBtn;

    private readonly Button? Help;
    private readonly Button? ImportButton;

    private readonly Grid Layout;
    private readonly Label Loading;

    private readonly DoubleAnimation LoadingFader = new(1d, 0.2d, new Duration(TimeSpan.FromSeconds(2))) { AutoReverse = true };

    private readonly Button Print;
    private readonly Label PrintSpacer;

    private readonly StackPanel LeftButtonStack;
    private readonly StackPanel RightButtonStack;

    protected DynamicGridRowStyleSelector RowStyleSelector;

    protected virtual bool CanDuplicate { get; } = false;

    #region Events

    private event IDynamicGrid.ReconfigureEvent? _onReconfigure;
    public event IDynamicGrid.ReconfigureEvent? OnReconfigure
    {
        add
        {
            _onReconfigure += value;
            Reconfigure();
        }
        remove
        {
            _onReconfigure -= value;
            Reconfigure();
        }
    }

    public OnGetDynamicGridRowStyle? OnGetRowStyle { get; set; }

    public event OnPrintData? OnPrintData;
    
    public event BeforeRefreshEventHandler? BeforeRefresh;
    public event AfterRefreshEventHandler? AfterRefresh;

    /// <summary>
    /// Called when an item is selected in the grid. It is not called if <see cref="IsReady"/> is not <see langword="true"/>.
    /// </summary>
    /// <remarks>
    /// It is unnecessary to use this if within a grid. Instead, override <see cref="SelectItems(CoreRow[]?)"/>.
    /// </remarks>
    public event SelectItemHandler? OnSelectItem;

    public event OnCellDoubleClick? OnCellDoubleClick;

    public event EventHandler? OnChanged;

    public delegate void BeforeSelectionEvent(CancelEventArgs cancel);
    public event BeforeSelectionEvent? OnBeforeSelection;
    
    protected virtual void Changed()
    {
    }

    public virtual void DoChanged()  
    {
        Changed();
        OnChanged?.Invoke(this, EventArgs.Empty);
    }

    public event OnFilterRecord? OnFilterRecord;

    public event OnDoubleClick? OnDoubleClick;

    #endregion

    protected DynamicGridSettings Settings { get; set; }

    
    public BaseDynamicGrid() : base()
    {
        UseWaitCursor = true;

        Options = new DynamicGridOptions();
        Options.OnChanged += () =>
        {
            _hasLoadedOptions = true;
            OptionsChanged();
        };

        ActionColumns = new DynamicActionColumns();
        ColumnGroupings = new DynamicGridColumnGroupings();
        
        RowStyleSelector = GetRowStyleSelector();
        RowStyleSelector.GetStyle += (row, style) => GetRowStyle(row, style);
        
        IsReady = false;

        Data = new CoreTable();

        drag = new DynamicImageColumn(InABox.Wpf.Resources.drag.AsBitmapImage()) { Position = DynamicActionColumnPosition.Start };

        VisibleColumns = new DynamicGridColumns();

        PreInit();

        UIComponent = CreateUIComponent();
        
        Loading = new Label();
        Loading.Content = "Loading...";
        Loading.Foreground = new SolidColorBrush(Colors.White);
        Loading.VerticalContentAlignment = VerticalAlignment.Center;
        Loading.HorizontalContentAlignment = HorizontalAlignment.Center;
        Loading.Visibility = Visibility.Collapsed;
        Loading.SetValue(Panel.ZIndexProperty, 999);
        Loading.SetValue(Grid.RowProperty, 1);
        Loading.FontSize = 14.0F;
        LoadingFader.Completed += (sender, args) =>
        {
            if (Loading.Visibility == Visibility.Visible)
            {
                //Logger.Send(LogType.Information, this.GetType().EntityName().Split(".").Last(), "Loading Fader Restarting");
                Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);
            }
        };

        if(this is IHelpDynamicGrid helpGrid)
        {
            Help = CreateButton(Wpf.Resources.help.AsBitmapImage(Color.White));
            Help.Margin = new Thickness(0, 2, 2, 0);
            Help.SetValue(DockPanel.DockProperty, Dock.Right);
            Help.Click += (o, e) => ShowHelp(helpGrid.HelpSlug());
        }

        Add = CreateButton(Wpf.Resources.add.AsBitmapImage(Color.White));
        Add.Margin = new Thickness(0, 2, 2, 0);
        Add.Click += Add_Click;

        Edit = CreateButton(Wpf.Resources.pencil.AsBitmapImage(Color.White));
        Edit.Margin = new Thickness(0, 2, 2, 0);
        Edit.Click += Edit_Click;

        SwitchViewBtn = CreateButton(Wpf.Resources.alter.AsBitmapImage());
        SwitchViewBtn.Margin = new Thickness(0, 2, 2, 0);
        SwitchViewBtn.Click += SwitchView_Click;

        EditSpacer = new Label { Width = 5 };

        Print = CreateButton(Wpf.Resources.print.AsBitmapImage(Color.White));
        Print.Margin = new Thickness(0, 2, 2, 0);
        Print.Click += (o, e) => DoPrint(o);

        PrintSpacer = new Label { Width = 5 };

        Copy = CreateButton(Wpf.Resources.duplicate.AsBitmapImage(Color.White), tooltip: "Duplicate Rows");
        Copy.Margin = new Thickness(0, 2, 2, 0);
        Copy.Click += Copy_Click;

        ClipboardSpacer = new Label { Width = 5 };

        if(this is IExportDynamicGrid)
        {
            ExportButton = CreateButton(ImageUtils.SvgToBitmapImage(Wpf.Resources.download));
            ExportButton.ToolTip = "Export to File";
            ExportButton.Margin = new Thickness(0, 2, 2, 0);
            ExportButton.Click += ExportButtonClick;
        }

        if(this is IImportDynamicGrid)
        {
            ImportButton = CreateButton(ImageUtils.SvgToBitmapImage(Wpf.Resources.upload));
            ImportButton.ToolTip = "Import from File";
            ImportButton.Margin = new Thickness(0, 2, 2, 0);
            ImportButton.Click += ImportButton_Click;
        }

        ExportSpacer = new Label { Width = 5 };
        

        LeftButtonStack = new StackPanel();
        LeftButtonStack.Orientation = Orientation.Horizontal;
        LeftButtonStack.SetValue(DockPanel.DockProperty, Dock.Left);

        if(Help is not null)
        {
            LeftButtonStack.Children.Add(Help);
        }
        LeftButtonStack.Children.Add(Add);
        LeftButtonStack.Children.Add(Edit);
        LeftButtonStack.Children.Add(SwitchViewBtn);
        //Stack.Children.Add(MultiEdit);
        LeftButtonStack.Children.Add(EditSpacer);

        LeftButtonStack.Children.Add(Print);
        LeftButtonStack.Children.Add(PrintSpacer);

        LeftButtonStack.Children.Add(Copy);
        LeftButtonStack.Children.Add(ClipboardSpacer);

        if(ExportButton is not null)
        {
            LeftButtonStack.Children.Add(ExportButton);
        }
        if(ImportButton is not null)
        {
            LeftButtonStack.Children.Add(ImportButton);
        }
        if(ExportButton is not null || ImportButton is not null)
        {
            LeftButtonStack.Children.Add(ExportSpacer);
        }

        RightButtonStack = new StackPanel();
        RightButtonStack.Orientation = Orientation.Horizontal;
        RightButtonStack.SetValue(DockPanel.DockProperty, Dock.Right);

        Delete = CreateButton(Wpf.Resources.delete.AsBitmapImage(Color.White));
        Delete.Margin = new Thickness(2, 2, 0, 0);
        Delete.SetValue(DockPanel.DockProperty, Dock.Right);
        Delete.Click += Delete_Click;

        if(this is IDuplicateDynamicGrid)
        {
            DuplicateBtn = AddButton("Duplicate", Wpf.Resources.paste.AsBitmapImage(Color.White), DuplicateButton_Click);
        }

        Count = new Label();
        Count.Height = 30;
        Count.Margin = new Thickness(0, 2, 0, 0);
        Count.VerticalContentAlignment = VerticalAlignment.Center;
        Count.HorizontalContentAlignment = HorizontalAlignment.Center;
        Count.SetValue(DockPanel.DockProperty, Dock.Left);

        Docker = new DockPanel();

        Docker.SetValue(Grid.RowProperty, 2);
        Docker.SetValue(Grid.ColumnProperty, 0);
        Docker.Children.Add(LeftButtonStack);
        Docker.Children.Add(Delete);
        Docker.Children.Add(RightButtonStack);
        Docker.Children.Add(Count);


        Layout = new Grid();
        Layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
        Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
        Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
        Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });

        var control = UIComponent.Control;
        control.SetValue(Grid.RowProperty, 1);

        Layout.Children.Add(control);
        Layout.Children.Add(Loading);
        Layout.Children.Add(Docker);
        
        Disabler = new Border()
        {
            BorderBrush = new SolidColorBrush(Colors.Transparent),
            Background = new SolidColorBrush(Colors.DimGray) { Opacity = 0.2 },
            Visibility = Visibility.Collapsed,
        };
        Disabler.SetValue(Canvas.ZIndexProperty, 99);
        Disabler.SetValue(Grid.RowSpanProperty, 3);

        Layout.Children.Add(Disabler);
        
        //Scroll.ApplyTemplate();

        Content = Layout;

        IsEnabledChanged += (sender, args) =>
        {
            Disabler.Visibility = Equals(args.NewValue, true)
                ? Visibility.Collapsed
                : Visibility.Visible;
        };

        Settings = LoadSettings();
        
        Init();
        Reconfigure();
    }

    protected virtual void PreInit()
    {
    }

    #region IDynamicGridUIComponentParent

    protected virtual IDynamicGridUIComponent CreateUIComponent()
    {
        return new DynamicGridGridUIComponent()
        {
            Parent = this
        };
    }

    protected IDynamicGridUIComponent GetUIComponent() => UIComponent;

    bool IDynamicGridUIComponentParent.CanFilter()
    {
        return !Options.ReorderRows || !Options.EditRows;
    }

    bool IDynamicGridUIComponentParent.CanSort()
    {
        return !Options.ReorderRows || !Options.EditRows;
    }

    DynamicGridRowStyleSelector IDynamicGridUIComponentParent.RowStyleSelector => RowStyleSelector;

    void IDynamicGridUIComponentParent.BeforeSelection(CancelEventArgs cancel)
    {
        BeforeSelection(cancel);
    }

    void IDynamicGridUIComponentParent.SelectItems(CoreRow[] rows)
    {
        SelectItems(rows);
    }

    void IDynamicGridUIComponentParent.HandleKey(KeyEventArgs e)
    {
        if (Options.ReorderRows)
        {
            if (e.Key == Key.X && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                CutToClipBuffer();
            }
            else if (e.Key == Key.C && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                CopyToClipBuffer();
            }
            else if (e.Key == Key.V && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                PasteFromClipBuffer();
            }
            else if (e.Key == Key.Escape)
            {
                ResetClipBuffer();
                InvalidateGrid();
            }
        }
    }

    void IDynamicGridUIComponentParent.DoubleClickCell(CoreRow? row, DynamicColumnBase? column)
    {
        var args = new DynamicGridCellClickEventArgs(row, column);
        if (OnCellDoubleClick is not null)
        {
            
            OnCellDoubleClick?.Invoke(this, args);
            if (args.Handled)
                return;
        }

        if (row is not null)
            DoDoubleClick(this, args);
    }

    void IDynamicGridUIComponentParent.ExecuteActionColumn(DynamicActionColumn column, CoreRow[]? rows)
    {
        var bRefresh = false;
        if(rows is null)
        {
            bRefresh = column.Action?.Invoke(null) ?? false;
        }
        else
        {
            foreach (var row in rows)
                if (column.Action?.Invoke(row) == true)
                    bRefresh = true;
        }
        if (bRefresh)
            Dispatcher.BeginInvoke(() => { Refresh(true, true); });
    }

    void IDynamicGridUIComponentParent.OpenColumnMenu(DynamicColumnBase column)
    {
        if(column is DynamicMenuColumn menuColumn)
        {
            menuColumn.Action?.Invoke(SelectedRows.FirstOrDefault());
        }
        else if(column is DynamicActionColumn actionColumn)
        {
            var menu = actionColumn?.ContextMenu?.Invoke(SelectedRows);
            if (menu != null && menu.Items.Count > 0)
            {
                menu.IsOpen = true;
            }
        }
    }

    void IDynamicGridUIComponentParent.UpdateRecordCount(int count)
    {
        Count.Content = FormatRecordCount(count);
    }

    protected virtual string FormatRecordCount(int count) => $"{count} Records";
    

    void IDynamicGridUIComponentParent.LoadColumnsMenu(ContextMenu menu)
    {
        menu.AddItem("Select Columns", null, SelectColumnsClick);
        LoadColumnsMenu(menu);
    }


    void IDynamicGridUIComponentParent.DragOver(object sender, DragEventArgs e)
    {
        HandleDragOver(sender, e);
    }

    void IDynamicGridUIComponentParent.Drop(object sender, DragEventArgs e)
    {
        if (!Options.DragTarget)
            return;

        if(DynamicGridUtils.TryGetDropData(e, out var entityType, out var table))
        {
            OnDragEnd(entityType, table, e);
        }
        else
        {
            HandleDragDrop(sender, e);
        }
    }

    void IDynamicGridUIComponentParent.DragStart(object? sender, CoreRow[] rows)
    {
        Logger.Send(LogType.Information, "", "RowDragDropController_DragStart");
        
        if (!Options.DragSource)
            return;
        
        OnRowsDragStart(rows);
    }
    
    public void UIFilterChanged(object sender) => DoFilterChanged();
    

    //void IDynamicGridUIComponentParent<T>.UIFilterChanged(object sender) => DoFilterChanged();

    protected virtual void DoFilterChanged()
    {
        
    }

    private Dictionary<DynamicColumnBase, IDynamicGridColumnFilter?> ColumnFilters { get; set; } = new();

    IDynamicGridColumnFilter? IBaseDynamicGrid.GetColumnFilter(DynamicColumnBase column) => GetColumnFilter(column);

    protected IDynamicGridColumnFilter? GetColumnFilter(DynamicColumnBase column)
    {
        if(!ColumnFilters.TryGetValue(column, out var filter))
        {
            filter = GenerateColumnFilter(column);
            ColumnFilters.Add(column, filter);
        }
        return filter;
    }

    protected virtual IDynamicGridColumnFilter? GenerateColumnFilter(DynamicColumnBase column)
    {
        if(column is DynamicGridColumn gc)
        {
            if(gc.Editor is DateTimeEditor || gc.Editor is DateEditor)
            {
                return new DateTreeDynamicGridColumnFilter(this, column);
            }
            else
            {
                return new StandardDynamicGridColumnFilter(this, column);
            }
        }
        else if(column is DynamicActionColumn ac)
        {
            if(ac.GetFilter is not null)
            {
                return ac.GetFilter();
            }
            else if(ac is DynamicTextColumn textColumn)
            {
                return new StandardDynamicGridColumnFilter(this, textColumn);
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

    #endregion

    protected virtual DynamicGridRowStyleSelector GetRowStyleSelector()
    {
        return new SimpleDynamicGridRowStyleSelector<DynamicGridRowStyle>();
    }

    protected virtual DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
    {
        DynamicGridStyle? result = null;

        if (ClipBuffer != null)
            if (ClipBuffer.Item2.Contains(row))
            {
                var bgbrush = style.Background as SolidColorBrush;
                var bgcolor = bgbrush != null ? bgbrush.Color : Colors.Transparent;

                result = new DynamicGridRowStyle(style);

                result.Background = ClipBuffer.Item1 == ClipAction.Cut
                    ? new SolidColorBrush(bgcolor.MixColors(0.5, Colors.Orchid))
                    : new SolidColorBrush(bgcolor.MixColors(0.5, Colors.LightGreen));

                result.Foreground = new SolidColorBrush(Colors.Gray);
                result.FontStyle = FontStyles.Italic;
            }
        result ??= OnGetRowStyle != null ? OnGetRowStyle(row, style) : style;

        return result;
    }

    protected virtual void BeforeSelection(CancelEventArgs cancel)
    {
        OnBeforeSelection?.Invoke(cancel);
    }

    public bool IsReady { get; protected set; }

    public UIElement? Header
    {
        get => _header;
        set
        {
            if (_header is not null && Layout.Children.Contains(_header))
                Layout.Children.Remove(_header);
            _header = value;
            if (_header is not null)
            {
                _header.SetValue(Grid.RowProperty, 0);
                _header.SetValue(Grid.ColumnProperty, 0);
                _header.SetValue(Grid.ColumnSpanProperty, 2);
                Layout.Children.Add(_header);
            }
        }
    }

    /// <summary>
    /// Represents the unfiltered data in the grid. This is <see langword="null"/> until <see cref="Refresh(bool, bool)"/> is called.
    /// </summary>
    /// <remarks>
    /// This differs from <see cref="Data"/> in that <see cref="Data"/> has been filtered by <see cref="FilterRecord(CoreRow)"/>,
    /// whereas <see cref="MasterData"/> contains every record loaded from the database.
    /// </remarks>
    public CoreTable? MasterData { get; set; }

    public DynamicGridColumns VisibleColumns { get; protected set; }
    public DynamicActionColumns ActionColumns { get; protected set; }
    private List<DynamicColumnBase> ColumnList = new();
    IList<DynamicColumnBase> IBaseDynamicGrid.ColumnList => ColumnList;

    public CoreTable Data { get; set; }

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

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

    #region Options

    /// <summary>
    /// Initialise things like custom buttons; called once during construction.
    /// </summary>
    protected abstract void Init();

    protected abstract void DoReconfigure(DynamicGridOptions options);

    private bool _hasLoadedOptions = false;

    protected virtual void OptionsChanged()
    {
        var reloadColumns = false;

        if(Help is not null)
        {
            Help.Visibility = Options.ShowHelp ? Visibility.Visible : Visibility.Collapsed;
        }

        Add.Visibility = Options.AddRows ? Visibility.Visible : Visibility.Collapsed;
        Edit.Visibility = Options.EditRows ? Visibility.Visible : Visibility.Collapsed;

        EditSpacer.Visibility = Options.AddRows || Options.EditRows
            ? Visibility.Visible
            : Visibility.Collapsed;

        Print.Visibility = Options.Print ? Visibility.Visible : Visibility.Collapsed;
        PrintSpacer.Visibility = Options.Print ? Visibility.Visible : Visibility.Collapsed;

        Copy.Visibility = Options.ReorderRows ? Visibility.Visible : Visibility.Collapsed;
        ClipboardSpacer.Visibility = Options.ReorderRows ? Visibility.Visible : Visibility.Collapsed;

        if(ExportButton is not null)
        {
            ExportButton.Visibility = Options.ExportData ? Visibility.Visible : Visibility.Collapsed;
        }
        if(ImportButton is not null)
        {
            ImportButton.Visibility = Options.ImportData ? Visibility.Visible : Visibility.Collapsed;
        }
        ExportSpacer.Visibility = Options.ExportData || Options.ImportData
            ? Visibility.Visible
            : Visibility.Collapsed;

        SwitchViewBtn.Visibility = Options.DirectEdit 
            ? Options.HideDirectEditButton
                ? Visibility.Collapsed 
                : Visibility.Visible 
            : Visibility.Collapsed;

        Count.Visibility = Options.RecordCount ? Visibility.Visible : Visibility.Collapsed;

        Delete.Visibility = Options.DeleteRows ? Visibility.Visible : Visibility.Collapsed;

        if (drag is not null)
        {
            var hasSequence = drag.Position == DynamicActionColumnPosition.Start;
            if (Options.ReorderRows)
            {
                if (!ActionColumns.Contains(drag))
                {
                    ActionColumns.Insert(0, drag);
                }
            }
            else
            {
                ActionColumns.Remove(drag);
            }
            if(hasSequence != Options.ReorderRows)
            {
                drag.Position = Options.ReorderRows ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden;
                reloadColumns = true;
            }
        }

        if (DuplicateBtn != null)
            DuplicateBtn.Visibility = Visibility.Collapsed;

        if (UIComponent.OptionsChanged())
        {
            reloadColumns = true;
        }

        if(reloadColumns && IsReady)
        {
            Refresh(true, false);
        }
    }

    public bool IsDirectEditMode()
    {
        return Options.DirectEdit
            && (Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.DirectEdit
                || Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.Default);
    }

    private void SwitchView_Click(object sender, RoutedEventArgs e)
    {
        Settings.ViewMode = Settings.ViewMode switch
        {
            DynamicGridSettings.DynamicGridViewMode.Default => DynamicGridSettings.DynamicGridViewMode.Normal,
            DynamicGridSettings.DynamicGridViewMode.Normal => DynamicGridSettings.DynamicGridViewMode.DirectEdit,
            DynamicGridSettings.DynamicGridViewMode.DirectEdit or _ => DynamicGridSettings.DynamicGridViewMode.Normal
        };
        SaveSettings(Settings);
        Reconfigure();
    }
    public DynamicGridOptions Options { get; }
    
    protected void OnReconfigureEvent(DynamicGridOptions options)
    {
        _onReconfigure?.Invoke(options);
    }

    /// <summary>
    /// Configure custom buttons and options.
    /// </summary>
    public void Reconfigure(DynamicGridOptions options)
    {
        options.BeginUpdate().Clear();
        DoReconfigure(options);
        OnReconfigureEvent(options);
        options.EndUpdate();
        if (!_hasLoadedOptions)
        {
            _hasLoadedOptions = true;
            OptionsChanged();
        }
    }
    
    public void Reconfigure()
    {
        Reconfigure(Options);
    }
    
    public void Reconfigure(IDynamicGrid.ReconfigureEvent onReconfigure)
    {
        OnReconfigure += onReconfigure;
        Reconfigure();
    }

    #endregion

    protected virtual DynamicGridSettings LoadSettings()
    {
        return new DynamicGridSettings();
    }
    protected virtual void SaveSettings(DynamicGridSettings settings)
    {
    }

    protected virtual void LoadColumnsMenu(ContextMenu menu)
    {
    }

    protected void UpdateCell(int row, string colname, object? value)
    {
        var coreRow = Data.Rows[row];
        coreRow[colname] = value;
        UIComponent.UpdateCell(coreRow, colname, value);
    }

    protected void UpdateCell(CoreRow row, DynamicColumnBase column)
    {
        UIComponent.UpdateCell(row, column);
    }

    #region Row Selections

    protected CoreRow[] GetVisibleRows()
    {
        return UIComponent.GetVisibleRows();
    }


    public CoreRow[] SelectedRows
    {
        get => UIComponent.SelectedRows;
        set => UIComponent.SelectedRows = value;
    }

    /// <summary>
    /// Call the <see cref="OnSelectItem"/> event, and do any updating which needs to occur when items are selected.
    /// </summary>
    /// <param name="rows"></param>
    protected virtual void SelectItems(CoreRow[]? rows)
    {
        if (IsReady)
            OnSelectItem?.Invoke(this, new DynamicGridSelectionEventArgs(rows));

        if(DuplicateBtn is not null)
        {
            DuplicateBtn.Visibility = CanDuplicate && rows != null && rows.Length >= 1 ? Visibility.Visible : Visibility.Collapsed;
        }
    }

    protected virtual void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
    {
        if (IsDirectEditMode())
            return;

        //SelectItems(SelectedRows);
        var e = new HandledEventArgs(false);
        OnDoubleClick?.Invoke(sender, e);
        if (e.Handled)
            return;
        if (Options.EditRows)
            DoEdit();
    }

    #endregion

    #region Column Handling

    #region Column Grouping

    public DynamicGridColumnGroupings ColumnGroupings { get; set; }

    /// <summary>
    /// Create a new column header group, and return it for editing.
    /// </summary>
    /// <returns></returns>
    public DynamicGridColumnGrouping AddColumnGrouping()
    {
        var group = new DynamicGridColumnGrouping();
        ColumnGroupings.Add(group);
        return group;
    }

    /// <summary>
    /// Gets the current column header group, and if there is none, create a new one.
    /// </summary>
    /// <returns></returns>
    public DynamicGridColumnGrouping GetColumnGrouping()
    {
        if(ColumnGroupings.Count == 0)
        {
            return AddColumnGrouping();
        }
        return ColumnGroupings[^1];
    }

    #endregion
    
    protected virtual DynamicGridColumns LoadColumns()
    {
        return GenerateColumns();
    }

    /// <summary>
    /// Provide a set of columns which is the default for this grid.
    /// </summary>
    public abstract DynamicGridColumns GenerateColumns();

    protected abstract void SaveColumns(DynamicGridColumns columns);

    public int DesiredWidth()
    {
        return UIComponent.DesiredWidth();
    }

    /// <summary>
    /// Handle to configure column groups.
    /// </summary>
    /// <remarks>
    /// This is called after <see cref="LoadColumns"/>, so by the time this is called, both <see cref="VisibleColumns"/>
    /// and <see cref="ActionColumns"/> will be loaded, which means one can reference these in the column groups.
    /// <br/>
    /// <b>Note:</b> <see cref="ColumnGroupings"/> is cleared before this function is called.
    /// </remarks>
    protected virtual void ConfigureColumnGroups()
    {
    }

    protected virtual void ConfigureColumns(DynamicGridColumns columns)
    {
    }

    public class ColumnsLoadedEventArgs : EventArgs
    {
        public List<DynamicColumnBase> Columns { get; private set; }
        public DynamicGridColumnGroupings ColumnGroupings { get; private set; }

        public IEnumerable<DynamicActionColumn> ActionColumns => Columns.OfType<DynamicActionColumn>();
        public IEnumerable<DynamicGridColumn> DataColumns => Columns.OfType<DynamicGridColumn>();

        public ColumnsLoadedEventArgs(List<DynamicColumnBase> columns, DynamicGridColumnGroupings columnGroupings)
        {
            Columns = columns;
            ColumnGroupings = columnGroupings;
        }

        public DynamicGridColumn Add<T>(
            Expression<Func<T, object?>> member,
            int? width = null,
            string? caption = null,
            string? format = null,
            Alignment? alignment = null)
        {
            var col = DynamicGridColumns.CreateColumn(member, width: width, caption: caption, format: format, alignment: alignment);
            Columns.Add(col);
            return col;
        }
    }
    
    public delegate void ColumnsLoadedEvent(BaseDynamicGrid sender, ColumnsLoadedEventArgs args);

    public event ColumnsLoadedEvent? ColumnsLoaded;

    protected virtual void OnColumnsLoaded(List<DynamicColumnBase> columns, DynamicGridColumnGroupings groupings)
    {
        ColumnsLoaded?.Invoke(this, new ColumnsLoadedEventArgs(columns, groupings));
    }
    
    private void ReloadColumns()
    {
        ColumnFilters.Clear();

        VisibleColumns = LoadColumns();
        ConfigureColumns(VisibleColumns);
        
        ColumnGroupings.Clear();
        ConfigureColumnGroups();

        ColumnList = new List<DynamicColumnBase>();
        ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.Start));
        ColumnList.AddRange(VisibleColumns);
        ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.End));

        OnColumnsLoaded(ColumnList, ColumnGroupings);

        VisibleColumns.Clear();
        VisibleColumns.AddRange(ColumnList.OfType<DynamicGridColumn>());
        
        UIComponent.RefreshColumns(ColumnList, ColumnGroupings);
    }

    #endregion

    #region Refresh / Reload

    protected bool IsPaging { get; set; } = false;
    
    protected virtual bool FilterRecord(CoreRow row)
    {
        if (OnFilterRecord is not null)
            return OnFilterRecord(row);
        return true;
    }

    private class RowRange(int rowIdx, int size)
    {
        public int RowIdx { get; set; } = rowIdx;

        public int Size { get; set; } = size;
    }

    protected abstract void ReloadData(CancellationToken token, Action<CoreTable?, Exception?> action);

    private CancellationTokenSource? RefreshCancellationToken;
    public virtual void Refresh(bool reloadcolumns, bool reloaddata)
    {
        if (bRefreshing)
            return;

        if (!DoBeforeRefresh())
            return;

        UIComponent.BeforeRefresh();

        using var cursor = UseWaitCursor ? new WaitCursor() : null;

        Loading.Visibility = Visibility.Visible;
        Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);

        bRefreshing = true;

        if (reloadcolumns)
        {
            ReloadColumns();
        }

        if (reloaddata)
        {
            RefreshCancellationToken?.Cancel();

            var tokenSource = new CancellationTokenSource();
            RefreshCancellationToken = tokenSource;
            var token = tokenSource.Token;

            ReloadData(token, (table, exception) =>
            {
                if (token.IsCancellationRequested) return; // Don't bother even checking exceptions if task was cancelled.

                if (exception != null)
                {
                    Dispatcher.Invoke(() =>
                    {
                        MessageWindow.ShowError("Sorry! We couldn't load the data.", exception);
                    });
                }
                else if (table is not null)
                {
                    if (table.Offset == 0 || MasterData is null)
                    {
                        MasterData = table;
                        Dispatcher.Invoke(() =>
                        {
                            try
                            {
                                ProcessData(null);
                            }
                            catch (Exception e)
                            {
                                CoreUtils.LogException("", e);
                            }
                            DoAfterRefresh();
                            bRefreshing = false;
                            IsReady = true;
                        });
                    }
                    else
                    {
                        int idx = MasterData.Rows.Count;
                        MasterData.AddPage(table);
                        Dispatcher.Invoke(() =>
                        {
                            try
                            {
                                ProcessData(new(idx, table.Rows.Count));
                            }
                            catch (Exception)
                            {

                            }
                        });
                    }
                }
            });
        }
        else
        {
            ProcessData(null);
            DoAfterRefresh();
            bRefreshing = false;
            IsReady = true;
        }
    }

    public void Shutdown()
    {
        RefreshCancellationToken?.Cancel();
    }
    
    protected void NotifyBeforeRefresh(BeforeRefreshEventArgs args) => BeforeRefresh?.Invoke(this, args);
    
    protected void NotifyAfterRefresh(AfterRefreshEventArgs args) => AfterRefresh?.Invoke(this, args);

    protected bool OnBeforeRefresh()
    {
        return true;
    }

    private bool DoBeforeRefresh()
    {
        var result = OnBeforeRefresh();
        if (result)
        {
            var args = new BeforeRefreshEventArgs() { Cancel = false };
            NotifyBeforeRefresh(args);
            result = args.Cancel == false;
        }

        return result;
    }

    protected virtual void OnAfterRefresh()
    {
    }

    protected void DoAfterRefresh()
    {
        OnAfterRefresh();
        NotifyAfterRefresh(new AfterRefreshEventArgs());
    }

    /// <summary>
    /// Process the data from <see cref="MasterData"/> according to <paramref name="range"/>.
    /// </summary>
    /// <remarks>
    /// Set <paramref name="range"/> to <see langword="null"/> if this is the first page of data to be loaded. This will thus update the grid accordingly,
    /// clearing all current rows, resetting columns, selection, etc. If the <paramref name="range"/> is provided, this will add to the grid the rows
    /// according to the range from <see cref="MasterData"/>.
    /// </remarks>
    private void ProcessData(RowRange? range)
    {
        if(range is null)
        {
            Data.Columns.Clear();
            Data.Setters.Clear();
            if (MasterData != null)
                foreach (var column in MasterData.Columns)
                    Data.Columns.Add(column);
        }

        LoadData(range);
    }

    protected readonly Dictionary<CoreRow, CoreRow> _recordmap = new();

    public void UpdateRow<TRow, TType>(CoreRow row, Expression<Func<TRow, TType>> column, TType value, bool refresh = true)
    {
        row.Set(column, value);
        _recordmap[row].Set(column, value);
        if (refresh)
            InvalidateRow(row);
    }

    public void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true)
    {
        row.Set(column, value);
        _recordmap[row].Set(column, value);
        if (refresh)
            InvalidateRow(row);
    }

    void IDynamicGridUIComponentParent.UpdateData(CoreRow row, string changedColumn, Dictionary<CoreColumn, object?> updates)
    {
        var result = new Dictionary<string, object?>();

        foreach (var (col, value) in updates)
        {
            UpdateRow(row, col.ColumnName, value, refresh: false);
        }
    }

    public void AddRow(CoreRow row)
    {
        if (MasterData is null) return;

        var masterrow = MasterData.NewRow();
        MasterData.FillRow(masterrow, row);
        Refresh(false, false);
    }

    public void DeleteRow(CoreRow row)
    {
        if (MasterData is null) return;

        var masterrow = _recordmap[row];
        MasterData.Rows.Remove(masterrow);
        Refresh(false, false);
    }

    /// <summary>
    /// Filter all given rows into <paramref name="into"/>, given that they match <paramref name="filter"/> and <see cref="FilterRecord(CoreRow)"/>.
    /// If <paramref name="recordMap"/> is given, also updates the map from <paramref name="from"/> to <paramref name="into"/>.
    /// </summary>
    protected IList<CoreRow> FilterRows(
        IEnumerable<CoreRow> from,
        CoreTable into,
        Dictionary<CoreRow, CoreRow>? recordMap = null,
        Func<CoreRow, bool>? filter = null)
    {
        var newRows = new List<CoreRow>();
        foreach (var row in from)
            if (FilterRecord(row) && filter?.Invoke(row) != false)
            {
                var newrow = into.NewRow();
                for (var i = 0; i < into.Columns.Count; i++)
                {
                    var value = i < row.Values.Count ? row.Values[i] : null;
                    if (into.Columns[i].DataType.IsNumeric())
                        value = into.Columns[i].DataType.IsDefault(value) ? null : value;
                    newrow.Values.Add(value);
                }

                newRows.Add(newrow);
                into.Rows.Add(newrow);
                recordMap?.TryAdd(newrow, row);
            }
        return newRows;
    }
    
    private void LoadData(RowRange? range)
    {
        if (MasterData is null)
            return;
        if(range is null)
        {
            ResetClipBuffer();
            Data.Rows.Clear();
            _recordmap.Clear();

            FilterRows(MasterData.Rows, Data, _recordmap);

            InvalidateGrid();
            SelectedRows = Array.Empty<CoreRow>();
        }
        else
        {
            var _newRows = FilterRows(Enumerable.Range(range.RowIdx, range.Size).Select(i => MasterData.Rows[i]), Data, _recordmap);
            UIComponent.AddPage(_newRows);
        }
    }

    public void InvalidateRow(CoreRow row)
    {
        UIComponent.InvalidateRow(row);
    }

    protected void InvalidateGrid()
    {
        if (RowStyleSelector != null)
            RowStyleSelector.Data = Data;

        UIComponent.RefreshData(Data);

        Loading.BeginAnimation(Label.OpacityProperty, null);
        Loading.Visibility = Visibility.Collapsed;
    }

    public void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)
    {
        UIComponent.AddVisualFilter(column, value, filtertype);
    }

    protected List<Tuple<string, Func<CoreRow, bool>>> GetFilterPredicates()
    {
        return UIComponent.GetFilterPredicates();
    }

    public object? GetData(CoreRow row, DynamicColumnBase column)
    {
        if(column is DynamicActionColumn ac)
        {
            return ac.Data(row);
        }
        else if(column is DynamicGridColumn gc)
        {
            return row[gc.ColumnName];
        }
        else
        {
            return null;
        }
    }

    #endregion

    #region Item Manipulation

    #region Abstract/Virtual Functions

    /// <summary>
    /// Create a new row.
    /// </summary>
    protected abstract void NewRow();

    /// <summary>
    /// Edit <paramref name="rows"/> or create a new row and edit it. This should update the rows, and if it creates a new row,
    /// it should be added to the grid.
    /// </summary>
    protected abstract bool EditRows(CoreRow[]? rows);

    public abstract void DeleteRows(params CoreRow[] rows);

    protected virtual bool CanDeleteRows(params CoreRow[] rows)
    {
        return true;
    }

    private bool DuplicateButton_Click(Button button, CoreRow[] rows)
    {
        return DoDuplicate(rows);
    }

    private bool DoDuplicate(CoreRow[] rows)
    {
        return this is IDuplicateDynamicGrid grid && grid.DoDuplicate(rows);
    }

    #endregion

    #region Load/Save/Delete

    protected virtual void DoDelete()
    {
        var rows = SelectedRows.ToArray();

        if (rows.Any())
            if (CanDeleteRows(rows))
                if (MessageBox.Show("Are you sure you wish to delete the selected records?", "Confirm Delete", MessageBoxButton.YesNo) ==
                    MessageBoxResult.Yes)
                {
                    DeleteRows(rows);
                    SelectedRows = Array.Empty<CoreRow>();
                    Refresh(false, true);
                    DoChanged();
                    SelectItems(null);
                }
    }

    private void Delete_Click(object sender, RoutedEventArgs e)
    {
        DoDelete();
    }

    #endregion

    #region Edit

    protected virtual void DoEdit()
    {
        if (SelectedRows.Length == 0)
            return;

        if (AddEditClick(SelectedRows))
        {
            SelectItems(SelectedRows);
        }
    }

    private void Edit_Click(object sender, RoutedEventArgs e)
    {
        DoEdit();
    }

    protected virtual void DoAdd(bool openEditorOnDirectEdit = false)
    {
        if (IsDirectEditMode() && !openEditorOnDirectEdit)
        {
            NewRow();
        }
        else if (AddEditClick(null))
        {
            Refresh(false, true);
        }
    }

    private void Add_Click(object sender, RoutedEventArgs e)
    {
        if (CanCreateRows())
            DoAdd();
    }

    BaseEditor IDynamicGridUIComponentParent.CustomiseEditor(DynamicGridColumn column, BaseEditor editor) => CustomiseEditor(column, editor);

    protected virtual BaseEditor CustomiseEditor(DynamicGridColumn column, BaseEditor editor)
    {
        return editor.CloneEditor();
    }

    protected virtual bool CanCreateRows()
    {
        return true;
    }

    private bool AddEditClick(CoreRow[]? rows)
    {
        if (!IsEnabled || bRefreshing)
            return false;

        if (rows == null || rows.Length == 0)
        {
            if (!CanCreateRows())
                return false;

            return EditRows(null);
        }
        else
        {
            return EditRows(rows);
        }
    }

    #endregion

    protected virtual void DoPrint(object sender)
    {
        OnPrintData?.Invoke(sender);
    }

    protected virtual void ShowHelp(string slug)
    {
        Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + slug) { UseShellExecute = true });
    }

    void IDynamicGridUIComponentParent.MoveRows(InABox.Core.CoreRow[] rows, int index) => MoveRows(rows, index);

    #region ClipBuffer

    private Tuple<ClipAction, CoreRow[]>? ClipBuffer;

    protected void ResetClipBuffer()
    {
        ClipBuffer = null;
    }

    protected void SetClipBuffer(ClipAction action, CoreRow[] rows)
    {
        ClipBuffer = new Tuple<ClipAction, CoreRow[]>(action, rows);
    }

    private void CutToClipBuffer()
    {
        SetClipBuffer(ClipAction.Cut, SelectedRows);
        InvalidateGrid();
    }

    private void CopyToClipBuffer()
    {
        SetClipBuffer(ClipAction.Copy, SelectedRows);
        InvalidateGrid();
    }

    private void PasteFromClipBuffer()
    {
        if (ClipBuffer == null)
            return;
        var row = SelectedRows.FirstOrDefault();
        MoveRows(ClipBuffer.Item2, row is not null ? (int)row.Index + 1 : Data.Rows.Count, isCopy: ClipBuffer.Item1 == ClipAction.Copy);
    }

    /// <summary>
    /// Reorder the given rows, to place them at <paramref name="index"/>; that is, the first row of <paramref name="rows"/> will
    /// be at <paramref name="index"/> after this method executes. If <paramref name="isCopy"/> is <see langword="true"/>, the items will copied, rather
    /// than moved.
    /// </summary>
    /// <remarks>
    /// To move the rows to the end, <paramref name="index"/> should be equal to the number of rows in <see cref="Data"/>.
    /// </remarks>
    protected abstract void MoveRows(CoreRow[] rows, int index, bool isCopy = false);

    private void Copy_Click(object sender, RoutedEventArgs e)
    {
        var rows = SelectedRows;
        if (rows.Length == 0) return;
        MoveRows(rows, rows[^1].Index + 1, isCopy: true);
    }

    #endregion

    #region Import / Export

    private void DoImport()
    {
        if(this is IImportDynamicGrid grid)
        {
            grid.DoImport();
        }
    }

    private void ImportButton_Click(object sender, RoutedEventArgs e)
    {
        DoImport();
    }

    public void Import() => DoImport();

    private void DoExport()
    {
        if(this is IExportDynamicGrid grid)
        {
            grid.DoExport();
        }
    }

    private void ExportButtonClick(object sender, RoutedEventArgs e)
    {
        DoExport();
    }

    #endregion

    public void ScrollIntoView(CoreRow row)
    {
        UIComponent.ScrollIntoView(row);
    }

    #endregion

    #region Custom Buttons

    private Button CreateButton(ImageSource? image = null, string? text = null, string? tooltip = null)
    {
        var button = new Button();
        button.SetValue(BorderBrushProperty, new SolidColorBrush(Colors.Gray));
        button.SetValue(BorderThicknessProperty, new Thickness(0.75));
        button.Height = 30;
        button.MinWidth = 30;
        UpdateButton(button, image, text, tooltip);
        return button;
    }

    public void UpdateButton(Button button, ImageSource? image, string? text, string? tooltip = null)
    {
        var stackPnl = new StackPanel();
        stackPnl.Orientation = Orientation.Horizontal;
        //stackPnl.Margin = new Thickness(2);

        if (image != null)
        {
            var img = new Image();
            img.Source = image;
            img.Margin = new Thickness(2);
            img.ToolTip = tooltip;
            stackPnl.Children.Add(img);
        }

        if (!string.IsNullOrEmpty(text))
        {
            button.MaxWidth = double.MaxValue;
            var lbl = new Label();
            lbl.Content = text;
            lbl.VerticalAlignment = VerticalAlignment.Stretch;
            lbl.VerticalContentAlignment = VerticalAlignment.Center;
            lbl.Margin = new Thickness(2, 0, 5, 0);
            lbl.ToolTip = ToolTip;
            stackPnl.Children.Add(lbl);
        }
        else
            button.MaxWidth = 30;

        button.Content = stackPnl;
        button.ToolTip = tooltip;
    }

    private bool bFirstButtonAdded = true;

    private bool AnyButtonsVisible()
    {
        if (Add.Visibility != Visibility.Collapsed)
            return true;
        if (Edit.Visibility != Visibility.Collapsed)
            return true;
        /*if (MultiEdit.Visibility != Visibility.Collapsed)
            return true;*/
        if (ExportButton is not null && ExportButton.Visibility != Visibility.Collapsed)
            return true;
        return false;
    }


    public Button AddButton(string? caption, ImageSource? image, string? tooltip, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
    {
        var button = CreateButton(image, caption, tooltip);
        button.Margin = position == DynamicGridButtonPosition.Right
            ? new Thickness(2, 2, 0, 0)
            : bFirstButtonAdded && AnyButtonsVisible()
                ? new Thickness(0, 2, 0, 0)
                : new Thickness(0, 2, 2, 0);
        button.Padding = !String.IsNullOrWhiteSpace(caption) ? new Thickness(5, 1, 5, 1) : new Thickness(1);
        button.Tag = action;
        button.Click += Button_Click;
        if (position == DynamicGridButtonPosition.Right)
            RightButtonStack.Children.Add(button);
        else
            LeftButtonStack.Children.Add(button);
        bFirstButtonAdded = false;
        return button;
    }

    public Button AddButton(string? caption, ImageSource? image, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
    {
        var result = AddButton(caption, image, null, action, position);
        return result;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var button = (Button)sender;
        var action = (DynamicGridButtonClickEvent)button.Tag;

        //CoreRow row = (CurrentRow > -1) && (CurrentRow < Data.Rows.Count) ? Data.Rows[this.CurrentRow] : null;
        if (action.Invoke(button, SelectedRows))
            Refresh(false, true);
    }

    #endregion

    #region Header Actions

    protected abstract bool SelectColumns([NotNullWhen(true)] out DynamicGridColumns? columns);

    private void SelectColumnsClick()
    {
        if (SelectColumns(out var columns))
        {
            VisibleColumns.Clear();
            VisibleColumns.AddRange(columns);
            SaveColumns(columns);
            Refresh(true, true);
        }
    }

    #endregion

    #region Drag + Drop

    /// <summary>
    /// Handle a number of rows from a different <see cref="DynamicGrid{T}"/> being dragged into this one.
    /// </summary>
    /// <param name="entity">The type of entity that that the rows of <paramref name="table"/> represent.</param>
    /// <param name="table">The data being dragged.</param>
    /// <param name="e"></param>
    protected virtual void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
    {
        Logger.Send(LogType.Information,"","OnDragEnd");
    }

    /// <summary>
    /// Handle all types of items being dragged onto this grid that aren't handled by <see cref="OnDragEnd(Type, CoreTable, DragEventArgs)"/>,
    /// i.e., data which is not a <see cref="CoreTable"/> from another <see cref="DynamicGrid{T}"/>
    /// </summary>
    /// <remarks>
    /// Can be used to handle files, for example.
    /// </remarks>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected virtual void HandleDragDrop(object sender, DragEventArgs e)
    {
    }

    protected virtual void HandleDragOver(object sender, DragEventArgs e)
    {
    }

    protected virtual DragDropEffects OnRowsDragStart(CoreRow[] rows)
    {
        return DragDropEffects.None;
    }

    #endregion

}

/// <summary>
/// Shows that this <see cref="BaseDynamicGrid"/> can be used to import data.
/// </summary>
public interface IImportDynamicGrid
{
    void DoImport();
}

/// <summary>
/// Shows that this <see cref="BaseDynamicGrid"/> can be used to export data.
/// </summary>
public interface IExportDynamicGrid
{
    void DoExport();
}

/// <summary>
/// Shows that this <see cref="BaseDynamicGrid"/> can be used to duplicate data.
/// </summary>
public interface IDuplicateDynamicGrid
{
    bool DoDuplicate(CoreRow[] rows);
}

/// <summary>
/// Shows that this <see cref="BaseDynamicGrid"/> can show a help menu.
/// </summary>
public interface IHelpDynamicGrid
{
    string HelpSlug();
}
