using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using FastReport.Editor;
using InABox.Core;
using Syncfusion.Data;
using static InABox.DynamicGrid.IDynamicGrid;
using Selection = InABox.Core.Selection;

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

        protected enum ClipAction
        {
            Cut,
            Copy
        }

        public BaseDynamicGrid()
        {
            UseWaitCursor = true;
        }

        public bool UseWaitCursor
        {
            get => (bool)GetValue(UseWaitCursorProperty);
            set => SetValue(UseWaitCursorProperty, value);
        }
        
        public static Brush SelectionBackground { get; set; }

        public static Brush SelectionForeground { get; set; }

        public static Brush FilterBackground { get; set; }

        static BaseDynamicGrid()
        {
            SelectionForeground = new SolidColorBrush(Colors.Black);
            SelectionBackground = new SolidColorBrush(Colors.Silver);
            FilterBackground = new SolidColorBrush(Color.FromArgb(0xFF, 0xE9, 0xF7, 0xC9));
        }
    }

    public abstract class BaseDynamicGrid<T> : BaseDynamicGrid, IDynamicGrid where T : BaseObject, new()
    {
        public delegate void ValidateEvent(object sender, T[] items, List<string> errors);
        
        public event OnPrintData? OnPrintData;
        
        public event BeforeRefreshEventHandler? BeforeRefresh;
        protected void NotifyBeforeRefresh(BeforeRefreshEventArgs args) => BeforeRefresh?.Invoke(this, args);
        protected abstract bool OnBeforeRefresh();
        
        public event AfterRefreshEventHandler? AfterRefresh;
        protected void NotifyAfterRefresh(AfterRefreshEventArgs args) => AfterRefresh?.Invoke(this, args);
        protected abstract void OnAfterRefresh();
        
        public abstract event EntitySaveEvent? OnAfterSave;
        public abstract event EntitySaveEvent? OnBeforeSave;

        public event OnDefineFilter? OnDefineFilter;
        public abstract event OnFilterRecord? OnFilterRecord;
        public event OnCreateItem? OnCreateItem;
        public abstract event OnCustomiseEditor<T>? OnCustomiseEditor;
        public virtual event OnCustomiseColumns? OnCustomiseColumns;
        public abstract event OnDoubleClick? OnDoubleClick;

        public OnGetDynamicGridRowStyle? OnGetRowStyle { get; set; }
        
        public ValidateEvent? OnValidate;
        
        protected DynamicGridRowStyleSelector<T> RowStyleSelector;

        public event IDynamicGrid.ReconfigureEvent? OnReconfigure;
        
        private bool _hasLoadedOptions = false;
        
        public BaseDynamicGrid()
        {
            Options = new FluentList<DynamicGridOption>();
            Options.OnChanged += (sender, args) =>
            {
                _hasLoadedOptions = true;
                OptionsChanged();
            };
            
            RowStyleSelector = GetRowStyleSelector();
            RowStyleSelector.GetStyle += (row, style) => GetRowStyle(row, style);
            
            HiddenColumns = new List<Expression<Func<T, object?>>>();
        }

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

        protected abstract void DoReconfigure(FluentList<DynamicGridOption> options);

        /// <summary>
        /// Configure custom buttons and options.
        /// </summary>
        private void Reconfigure(FluentList<DynamicGridOption> options)
        {
            options.BeginUpdate().Clear();
            DoReconfigure(options);
            OnReconfigure?.Invoke(options);
            options.EndUpdate();
            if (!_hasLoadedOptions)
            {
                _hasLoadedOptions = true;
                OptionsChanged();
            }
        }
        public void Reconfigure()
        {
            Reconfigure(Options);
        }
        public void Reconfigure(ReconfigureEvent onReconfigure)
        {
            OnReconfigure += onReconfigure;
            Reconfigure();
        }
        public bool HasOption(DynamicGridOption option, IEnumerable<DynamicGridOption>? options = null) => (options ?? Options).Contains(option);
        bool IDynamicGrid.HasOption(InABox.DynamicGrid.DynamicGridOption option) => HasOption(option, null);

        public List<Expression<Func<T, object?>>> HiddenColumns { get; }

        public void InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler = null, bool preloadPages = false)
        {
            InitialiseEditorForm(editor, items.Cast<T>().ToArray(), pageDataHandler, preloadPages);
        }
        public virtual bool EditItems(object[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
        {
            var values = items.Cast<T>().ToArray();
            return EditItems(values, PageDataHandler, PreloadPages);
        }

        //public abstract bool DirectEdit(CoreTable data);

        private FluentList<DynamicGridOption> Options { get; }

        public DynamicGridColumns MasterColumns { get; protected set; }
        public DynamicGridColumns VisibleColumns { get; protected set; }
        public CoreTable Data { get; set; }

        public virtual void ConfigureColumns(DynamicGridColumns columns)
        {
            OnCustomiseColumns?.Invoke(this, columns);
        }

        public abstract CoreRow[] SelectedRows { get; set; }

        public abstract void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);

        public abstract Button AddButton(string? caption, BitmapImage? image, string? tooltip, Func<Button, CoreRow[], bool> action,
            DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);

        public Button AddButton(string? caption, BitmapImage? image, Func<Button, CoreRow[], bool> action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
        {
            var result = AddButton(caption, image, null, action, position);
            return result;
        }

        public abstract void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null);

        public virtual double RowHeight { get; set; }
        public virtual double HeaderHeight { get; set; }
        public new virtual double FontSize { get; set; }

        public void AddHiddenColumn(string column)
        {
            var expression = CoreUtils.CreateLambdaExpression<T>(column);
            HiddenColumns.Add(expression);
        }

        public abstract int DesiredWidth();

        public abstract void Refresh(bool reloadcolumns, bool reloaddata);

        public abstract void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true);
        public abstract void UpdateRow<TRow, TType>(CoreRow row, Expression<Func<TRow, TType>> column, TType value, bool refresh = true);
        
        protected abstract DynamicGridRowStyleSelector<T> GetRowStyleSelector();

        protected virtual DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
        {
            return OnGetRowStyle != null ? OnGetRowStyle(row, style) : style;
        }
        
        public abstract void InitialiseEditorForm(IDynamicEditorForm editor, T[] items, Func<Type, CoreTable>? pageDataHandler = null, bool preloadPages = false);
        public abstract bool EditItems(T[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false);

        public Filter<T>? DefineFilter()
        {
            if (OnDefineFilter is null)
                return null;
            var result = OnDefineFilter.Invoke(typeof(T)) as Filter<T>;
            return result;
        }

        protected virtual T CreateItem()
        {
            var result = new T();
            OnCreateItem?.Invoke(this, result);
            return result;
        }

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

        protected abstract void OptionsChanged();
        
        public IEnumerable<TType> ExtractValues<TType>(Expression<Func<T, TType>> column, Selection selection)
        {
            var result = selection == Selection.None
                ? new TType[] { }
                : selection == Selection.Selected
                    ? SelectedRows.Select(r => r.Get(column)).ToArray()
                    : Data.ExtractValues(column);
            return result;
        }
    }
}