using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using InABox.Clients;
using InABox.Core;
using InABox.Wpf;
using InABox.Core.Reports;
using Syncfusion.Data.Extensions;
using System.Diagnostics.CodeAnalysis;
using System.Data;
using System.Windows.Media;
using AutoProperties;
using InABox.WPF;

namespace InABox.DynamicGrid;

[Caption("Set Default Column Selections")]
public class CanSetDefaultColumns : EnabledSecurityDescriptor<CoreLicense>
{
}

[LibraryInitializer]
public static class DynamicGridUtils
{
    private static IEnumerable<Type>? _allm2mtypes;
    private static IEnumerable<Type>? _allm2mpages;

    private static IEnumerable<Type>? _allo2mtypes;
    private static IEnumerable<Type>? _allo2mpages;

    private static IEnumerable<Type>? _allcepages;

    private static IEnumerable<Type>? _alleltypes;
    
    private static Dictionary<Type, IList<Type>> _onetomanypages = new();
    private static Dictionary<Type, IList<Type>> _manytomanytomanypages = new();
    private static Dictionary<Type, IList<Tuple<Type, PropertyInfo>>> _enclosedlistpages = new();
    private static Dictionary<Type, IList<Type>> _customeditorpages = new();

    // HACK: These are really dumb
    public static Action<ReportTemplate, DataModel>? PreviewReport { get; set; }
    public static Action<FrameworkElement?, string, DataModel, bool>? PrintMenu { get; set; }

    public static readonly MainResources Resources = new();

    public static void RegisterClasses()
    {
        // String assyname = "_" + Assembly.GetExecutingAssembly().GetName().Name;
        // AssemblyName assemblyName = new AssemblyName(assyname);
        // AppDomain appDomain = Thread.GetDomain();
        //
        // String assyFile = String.Format("{0}.dll", assemblyName.Name);
        // String path = "";
        // if (Assembly.GetEntryAssembly() != null)
        // {
        //     path = Path.Combine(
        //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        //         Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)
        //     );
        // }
        // else
        // {
        //     path = Path.Combine(
        //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        //         Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)
        //     );
        // }
        //
        // if (!Directory.Exists(path))
        //     Directory.CreateDirectory(path);

        // AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, path);
        //
        // ModuleBuilder module = assemblyBuilder.DefineDynamicModule(assyFile); //,true);
        //
        // if (_allm2mtypes == null)
        // {
        //     _allm2mtypes = CoreUtils.TypeList(
        //         AppDomain.CurrentDomain.GetAssemblies(),
        //         x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
        //     );
        // }
        //
        // var maps = _allm2mtypes.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document))));
        //
        // foreach (var map in maps)
        // {
        //     var intf = map.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document)));
        //     Type entity = intf.GenericTypeArguments.First();
        //     Type basetype = typeof(DynamicDocumentGrid<,>).MakeGenericType(map, entity);
        //     TypeBuilder tbService = module.DefineType(String.Format("{0}", map.EntityName().Replace(".", "_")), TypeAttributes.Public | TypeAttributes.Class);
        //     tbService.SetParent(basetype);
        //     Type final = tbService.CreateType();
        // }
        //
        // try
        // {
        //     assemblyBuilder.Save(assyFile);
        // }
        // catch (Exception e)
        // {
        //     Logger.Send(LogType.Error, "", String.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
        // }
        
    }

    #region Pages

    public static IEnumerable<Type> GetManyToManyTypes(Type type)
    {
        _allm2mtypes ??= CoreUtils.Entities.Where(x => x.HasInterface(typeof(IManyToMany<,>))).ToArray();
        return _allm2mtypes.Where(x => x.GetInterfaces().Any(
            i => i.IsGenericType
            && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)
            && i.GenericTypeArguments[0] == type)
        );
    }

    public static void LoadManyToManyPages(Type type, DynamicEditorPages pages)
    {
        if (!_manytomanytomanypages.TryGetValue(type, out var pageTypes))
        {
            pageTypes = new List<Type>();

            var maps = GetManyToManyTypes(type);

            foreach (var map in maps)
            {
                if (ClientFactory.IsSupported(map))
                {
                    _allm2mpages ??= CoreUtils.Entities.Where(
                            x => x.IsClass
                                 && !x.IsGenericType
                                 && x.HasInterface(typeof(IDynamicManyToManyGrid<,>)))
                        .ToArray();

                    var subtypes = _allm2mpages.Where(
                        x => x.GetInterfaces().Any(i =>
                            i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) &&
                            i.GenericTypeArguments.First().Equals(map) && i.GenericTypeArguments.Last().Equals(type))
                    );

                    if (subtypes.Any())
                    {
                        pageTypes.Add(subtypes.First());
                    }
                    else
                    {
                        pageTypes.Add(typeof(DynamicManyToManyGrid<,>).MakeGenericType(map, type));
                    }
                }

            }
            _manytomanytomanypages[type] = pageTypes.ToArray();
        }
        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
    }

    public static IEnumerable<Type> GetOneToManyTypes(Type type)
    {
        _allo2mtypes ??= CoreUtils.Entities.Where(
            x => x.HasInterface(typeof(IOneToMany<>)) && !x.HasAttribute<ObsoleteAttribute>())
            .ToArray();
        return _allo2mtypes
            .Where(x => x.GetInterfaces().Any(i =>
                i.IsGenericType
                && i.GetGenericTypeDefinition() == typeof(IOneToMany<>)
                && i.GenericTypeArguments.Contains(type)))
            .OrderBy(x => x.EntityName());
    }
    
    public static void LoadOneToManyPages(Type type, DynamicEditorPages pages)
    {
        if (!_onetomanypages.TryGetValue(type, out var pageTypes))
        {
            pageTypes = new List<Type>();

            var maps = GetOneToManyTypes(type);

            foreach (var map in maps)
            {
                if (ClientFactory.IsSupported(map))
                {
                    _allo2mpages ??= CoreUtils.Entities.Where(
                            x =>
                                x.IsClass
                                && !x.IsGenericType
                                && x.HasInterface(typeof(IDynamicOneToManyGrid<,>))
                                && !x.HasInterface<ISpecificGrid>())
                        .ToArray();

                    var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any(
                            i => i.IsGenericType
                                 && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
                                 && i.GenericTypeArguments.First().Equals(type)
                                 && i.GenericTypeArguments.Last().Equals(map)
                        )
                    );

                    if (subtypes.Any())
                    {
                        pageTypes.Add(subtypes.First());
                    }
                    else
                    {
                        pageTypes.Add(typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map));
                    }
                }
            }

            _onetomanypages[type] = pageTypes.ToArray();
        }
        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
    }

    public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages)
    {
        if (!_customeditorpages.TryGetValue(type, out var pageTypes))
        {
            _allcepages ??= CoreUtils.Entities.Where(
                    x => x.IsClass
                         && !x.IsGenericType
                         && x.HasInterface(typeof(IDynamicCustomEditorPage<>)))
                .ToArray();

            pageTypes = _allcepages.Where(x => x.GetInterfaces().Any(i =>
                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) &&
                i.GenericTypeArguments.First().Equals(type))).ToArray();

            _customeditorpages[type] = pageTypes;
        }
        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
    }

    public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages)
    {
        if (!_enclosedlistpages.TryGetValue(type, out var pageTypes))
        {
            pageTypes = new List<Tuple<Type, PropertyInfo>>();
            foreach (var property in type.GetProperties())
            {
                if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
                {
                    var curtype = property.PropertyType;
                    var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault();
                    while (gentype == null && curtype?.BaseType != null)
                    {
                        curtype = curtype.BaseType;
                        gentype = curtype?.GetGenericArguments().FirstOrDefault();
                    }

                    if (gentype != null)
                        if (gentype.IsSubclassOf(typeof(BaseObject)))
                        {
                            var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor);
                            if (editor == null || !(editor is NullEditor))
                            {
                                _alleltypes ??= CoreUtils.Entities.Where(
                                        x => x.IsClass
                                             && !x.IsGenericType
                                             && x.HasInterface(typeof(IDynamicEnclosedListGrid<,>)))
                                    .ToArray();

                                var subtypes = _alleltypes.Where(
                                    x => x.GetInterfaces().Any(
                                        i => i.IsGenericType
                                             && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
                                             && i.GenericTypeArguments.First().Equals(type)
                                             && i.GenericTypeArguments.Last().Equals(gentype)
                                    )
                                );

                                if (subtypes.Any())
                                {
                                    pageTypes.Add(new(subtypes.First(), property));
                                }
                                else
                                {
                                    subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => (i.GenericTypeArguments.LastOrDefault()?.Equals(gentype) == true)));

                                    if (subtypes.Any())
                                    {
                                        pageTypes.Add(new(subtypes.First().MakeGenericType(type), property));
                                    }
                                    else
                                    {
                                        try
                                        {
                                            pageTypes.Add(new(typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype), property));
                                        }
                                        catch (Exception e)
                                        {
                                            Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                                        }
                                    }
                                }
                            }
                        }
                }
            }

            _enclosedlistpages[type] = pageTypes.ToArray();
        }
        pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x.Item1, x.Item2) as IDynamicEditorPage)!));
    }

    #endregion

    #region Columns

    public static IColumns LoadEditorColumns(Type T, IColumns? additional = null)
    {
        var result = Columns.Create(T, ColumnTypeFlags.EditorColumns);

        if(additional is not null)
        {
            foreach (var col in additional)
                result.Add(col.Property);
        }

        foreach (var col in result.ColumnNames().ToArray())
        {
            var prop = DatabaseSchema.Property(T, col);
            if (prop?.Editor is DataLookupEditor dataLookup)
            {
                foreach (var lookupColumn in LookupFactory.DefineLookupFilterColumns(T, prop.Name))
                {
                    result.Add(lookupColumn);
                }
            }
        }

        return result;
    }
    public static Columns<T> LoadEditorColumns<T>(Columns<T>? additional = null) => (LoadEditorColumns(typeof(T), additional) as Columns<T>)!;

    #endregion

    #region Editor Values

    /// <summary>
    /// Set the property <paramref name="name"/> of <paramref name="items"/> to <paramref name="value"/>, and return all changed properties.
    /// </summary>
    /// <returns>A dictionary containing changed properties, with the property name as the key, and the new value as the value.</returns>
    public static Dictionary<string, object?> UpdateEditorValue(BaseObject[] items, string name, object? value)
    {
        Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value));
        var sw = new Stopwatch();

        var changes = new Dictionary<string, object?>(); 
        var props = DatabaseSchema.Properties(items.First().GetType()).ToArray();
        foreach (var item in items)
        {
            //Dictionary<String, object> previous = new Dictionary<string, object>();
            var previous = CoreUtils.GetValues(item, props);

            //if (item.OriginalValues != null)
            //{
            //    foreach (var key in item.OriginalValues.Keys)
            //        previous[key] = item.OriginalValues[key];
            //}

            var prop = DatabaseSchema.Property(item.GetType(), name);
            if (prop is CustomProperty)
            {
                if (!item.HasOriginalValue(name))
                    item.SetOriginalValue(name, item.UserProperties[name]);
                item.UserProperties[name] = value;
            }
            else
            {
                if (prop != null)
                    try
                    {
                        var getter = prop.Getter();
                        var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name);

                        var setter = prop.Setter();
                        if (setter != null && value != null)
                            setter.Invoke(item, value);
                        else
                            CoreUtils.SetPropertyValue(item, name, value);
                    }
                    catch (Exception)
                    {
                        Logger.Send(LogType.Error, "",
                            string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value));
                    }
            }
            var current = CoreUtils.GetValues(item, props);
            CoreUtils.MergeChanges(previous, current, changes);

        }
        
        return changes;
    }

    public static void UpdateEditorValue(BaseObject[] items, string name, object? value, Dictionary<string, object?> changes)
    {
        var results = UpdateEditorValue(items, name, value);
        foreach (var key in results.Keys)
            changes[key] = results[key];
    }

    #endregion

    #region Dynamic Grid Creation

    public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType)
    {
        var type = FindDynamicGrid(gridType, entityType);
        return (Activator.CreateInstance(type) as IDynamicGrid)
            ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
    }
    public static DynamicGrid<TEntity> CreateDynamicGrid<TEntity>(Type gridType)
        where TEntity : BaseObject, new()
    {
        var type = FindDynamicGrid(gridType, typeof(TEntity));
        return (Activator.CreateInstance(type) as DynamicGrid<TEntity>)
            ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
    }

    private static Dictionary<Type, Type[]> _dynamicGrids = new();

    public static bool TryFindDynamicGrid(Type gridType, Type entityType, [NotNullWhen(true)] out Type? grid)
    {
        if (!_dynamicGrids.TryGetValue(gridType, out var grids))
        {
            grids = CoreUtils.Entities.Where(
                myType =>
                    myType.IsClass
                    && !myType.IsGenericType
                    && myType.IsAssignableTo(typeof(IDynamicGrid))
                    && !myType.IsAssignableTo(typeof(ISpecificGrid))
            ).ToArray();
            _dynamicGrids[gridType] = grids;
        }
        grids = grids.Where(x => x.IsSubclassOfRawGeneric(gridType)).ToArray();
        var entityGrids = grids.Where(x =>
        {
            var baseGrid = x.GetSuperclassDefinition(typeof(DynamicGrid<>));
            return baseGrid?.GenericTypeArguments[0] == entityType;
        }).ToList();

        var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList();
        if (defaults.Count > 0)
        {
            if (defaults.Count > 1)
            {
                Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}");
            }
            grid = defaults.First();
            return true;
        }

        grid = entityGrids.FirstOrDefault();
        return grid is not null;
    }

    public static Type FindDynamicGrid(Type gridType, Type entityType)
    {
        if(TryFindDynamicGrid(gridType, entityType, out var grid))
        {
            return grid;
        }
        return gridType.MakeGenericType(entityType);
    }
    
    public static Window CreateGridWindow(string title, IDynamicGrid dynamicGrid, bool showbuttons = false, Func<bool>? okclicked = null)
    {
        dynamicGrid.Margin = new Thickness(5);
        
        var window = new ThemableWindow { Title = title };
        window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
        window.SetValue(WindowBehavior.HideCloseButtonProperty, showbuttons);

        var grid = new Grid();
        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
        grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
        grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
        
        var fe = dynamicGrid as FrameworkElement;
        fe.SetValue(Grid.RowProperty,0);
        fe.SetValue(Grid.ColumnProperty, 0);
        fe.SetValue(Grid.ColumnSpanProperty, 3);
        grid.Children.Add(fe);

        if (showbuttons)
        {
            var ok = new Button();
            ok.Content = "OK";
            ok.SetValue(Grid.RowProperty,1);
            ok.SetValue(Grid.ColumnProperty,1);
            ok.Margin = new Thickness(0,0,5,5);
            ok.Width = 80;
            ok.Height = 35;
            ok.Click += (sender, args) =>
            {
                if (okclicked?.Invoke() ?? true)
                    window.DialogResult = true;
            };
            grid.Children.Add(ok);
            
            var cancel = new Button();
            cancel.Content = "Cancel";
            cancel.SetValue(Grid.RowProperty,1);
            cancel.SetValue(Grid.ColumnProperty,2);
            cancel.Margin = new Thickness(0,0,5,5);
            cancel.Width = 80;
            cancel.Height = 35;
            cancel.Click += (sender, args) =>
            {
                window.DialogResult = false;
            };
            grid.Children.Add(cancel);
        }
        
        window.Content = grid;
        
        dynamicGrid.Refresh(true, true);

        return window;
    }
    
    public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null, bool showbuttons = false)
    {
        gridType ??= typeof(DynamicGrid<>);
        var grid = CreateDynamicGrid(gridType, entityType);
        return CreateGridWindow(title, grid, showbuttons);
    }
    public static Window CreateGridWindow<TGrid, TEntity>(string title, bool showbuttons = false)
        where TEntity : BaseObject
        where TGrid : IDynamicGrid
    {
        return CreateGridWindow(title, typeof(TEntity), typeof(TGrid), showbuttons);
    }
    public static Window CreateGridWindow<TEntity>(string title, bool showbuttons = false)
        where TEntity : BaseObject
    {
        return CreateGridWindow(title, typeof(TEntity), null, showbuttons);
    }

    #endregion

    #region Non-modal Editing

    public class DynamicGridEntityEditLock(IEnumerable<Guid> ids)
    {
        public HashSet<Guid> ObjectIDs { get; set; } = ids.ToHashSet();

        public ISubPanel? Panel { get; set; }
    }

    private static Dictionary<Guid, DynamicGridEntityEditLock> CurrentEditLocks = new();

    /// <summary>
    /// Attempt to begin editing <paramref name="objs"/>, failing if those entities are already being edited elsewhere.
    /// </summary>
    /// <remarks>
    /// If returns <see langword="true"/>, then <paramref name="editLock"/> will contain a new edit lock which contains the
    /// new entities. Otherwise, <paramref name="editLock"/> will be the lock on the entities that are already being edited.
    /// </remarks>
    public static bool TryEdit(BaseObject[] objs, out DynamicGridEntityEditLock editLock)
    {
        lock (CurrentEditLocks)
        {
            var ids = new List<Guid>();
            foreach(var obj in objs)
            {
                if(obj is Entity entity && entity.ID != Guid.Empty)
                {
                    if (CurrentEditLocks.TryGetValue(entity.ID, out editLock))
                    {
                        return false;
                    }
                    else
                    {
                        ids.Add(entity.ID);
                    }
                }
            }
            editLock = new(ids);
            foreach(var id in ids)
            {
                CurrentEditLocks.Add(id, editLock);
            }
            return true;
        }
    }

    public static void FinishEdit(BaseObject[] objs)
    {
        lock (CurrentEditLocks)
        {
            foreach(var obj in objs)
            {
                if(obj is Entity entity && entity.ID != Guid.Empty)
                {
                    if(CurrentEditLocks.Remove(entity.ID, out var editLock))
                    {
                        editLock.ObjectIDs.Remove(entity.ID);
                    }
                }
            }
        }
    }

    #endregion

    #region Editing BaseObject

    /// <summary>
    /// Edit (using <see cref="DynamicItemsListGrid{T}"/>) a list of <see cref="BaseObject"/>s. Use for objects not saved in the database.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="items"></param>
    /// <param name="pageDataHandler"></param>
    /// <param name="preloadPages"></param>
    /// <returns></returns>
    public static bool EditObjects<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
        where T : BaseObject, new()
    {
        var grid = new DynamicItemsListGrid<T>();
        customiseGrid?.Invoke(grid);
        return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
    }

    /// <summary>
    /// Edit (using <see cref="DynamicItemsListGrid{T}"/>) a <see cref="BaseObject"/>. Use for objects not saved in the database.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="items"></param>
    /// <param name="pageDataHandler"></param>
    /// <param name="preloadPages"></param>
    /// <returns></returns>
    public static bool EditObject<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
        where T : BaseObject, new()
    {
        var grid = new DynamicItemsListGrid<T>();
        customiseGrid?.Invoke(grid);
        return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
    }

    /// <summary>
    /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a <typeparamref name="T"/>.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="item"></param>
    /// <param name="pageDataHandler"></param>
    /// <param name="preloadPages"></param>
    /// <param name="customiseGrid"></param>
    /// <returns></returns>
    public static bool EditEntity<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
        where T : BaseObject, new()
    {
        var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
        customiseGrid?.Invoke(grid);
        return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
    }

    /// <summary>
    /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a list of <typeparamref name="T"/>.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="item"></param>
    /// <param name="pageDataHandler"></param>
    /// <param name="preloadPages"></param>
    /// <param name="customiseGrid"></param>
    /// <returns></returns>
    public static bool EditEntities<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
        where T : BaseObject, new()
    {
        var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
        customiseGrid?.Invoke(grid);
        return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
    }

    /// <summary>
    /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a <typeparamref name="T"/>, by loading it from the database.
    /// </summary>
    public static bool EditEntity<T>(Guid id, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
        where T : Entity, IRemotable, IPersistent, new()
    {
        var grid = (CreateDynamicGrid<T>(typeof(DynamicDataGrid<>)) as DynamicDataGrid<T>)!;
        customiseGrid?.Invoke(grid);
        var item = Client.Query<T>(
            new Filter<T>(x => x.ID).IsEqualTo(id),
            grid.LoadEditorColumns())
            .ToObjects<T>().FirstOrDefault();
        if (item is null) return false;
        return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
    }

    #endregion

    #region Drag + Drop

    public static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? "";

    /// <summary>
    /// Try to get data dragged from a <see cref="DynamicGrid{T}"/> from a <see cref="DragEventArgs"/>, returning <see langword="true"/>
    /// if data was present.
    /// </summary>
    /// <param name="e"></param>
    public static bool TryGetDropData(
        DragEventArgs e,
        [NotNullWhen(true)] out Type? type,
        [NotNullWhen(true)] out CoreTable? table)
    {
        if (e.Data.GetDataPresent(DragFormat))
        {
            var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat;
            if (data is not null)
            {
                table = new CoreTable();
                foreach (var column in data.Table.Columns)
                {
                    if (column is DataColumn dataColumn)
                    {
                        table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName.Replace('_', '.'), DataType = dataColumn.DataType });
                    }
                }
                foreach (var row in data.Table.Rows)
                {
                    if (row is DataRow dataRow)
                    {
                        var coreRow = table.NewRow();
                        coreRow.LoadValues(dataRow.ItemArray);
                        table.Rows.Add(coreRow);
                    }
                }

                type = data.Entity;
                return true;
            }
        }
        table = null;
        type = null;
        return false;
    }

    #endregion

    #region Style
    
    public static Brush SelectionBackground { get; set; } = new SolidColorBrush(Colors.Black);

    public static Brush SelectionForeground { get; set; } = new SolidColorBrush(Colors.Silver);

    public static Brush FilterBackground { get; set; } = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0xE9, 0xF7, 0xC9));
    
    public static Brush FilterForeground{ get; set; } = new SolidColorBrush(Colors.Black);

    #endregion

    public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(
        ItemsControl menu,
        Guid entityID,
        Func<TEntity> loadEntity,
        bool editOnAdd = false,
        DynamicFormEditWindow.CustomiseDynamicFormEditWindow? customiseEditor = null)
        where TEntityForm : EntityForm<TEntity, TEntityLink, TEntityForm>, new()
        where TEntity : Entity
        where TEntityLink : BaseObject, IEntityLink<TEntity>, new()
    {
        var task = Task.Run(() =>
        {
            return new Client<TEntityForm>().Query(
                new Filter<TEntityForm>(x => x.Parent.ID).IsEqualTo(entityID),
                null).Rows.Select(x => x.ToObject<TEntityForm>()).ToList();
        });

        var addForm = new MenuItem { Header = "Add Form" };
        addForm.Click += (o, e) =>
        {
            var entity = loadEntity();
            var filter = LookupFactory.DefineChildFilter<TEntity, DigitalForm>(new TEntity[] { entity })
                ?? LookupFactory.DefineLookupFilter<TEntityForm, DigitalForm, DigitalFormLink>(x => x.Form, Array.Empty<TEntityForm>());

            var select = new MultiSelectDialog<DigitalForm>(
                filter,
                LookupFactory.DefineLookupColumns<TEntityForm, DigitalForm, DigitalFormLink>(x => x.Form).Add(x => x.Description),
                false);
            if(select.ShowDialog() == true)
            {
                var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject<DigitalForm>();

                if(digitalForm is not null)
                {
                    var form = new TEntityForm
                    {
                        Description = digitalForm.Description
                    };
                    form.Parent.ID = entityID;
                    form.Form.ID = digitalForm.ID;
                    if (editOnAdd)
                    {
                        if (DynamicFormEditWindow.EditDigitalForm(form, out var dataModel, customise: customiseEditor))
                        {
                            dataModel.Update(null);
                        }
                    }
                    else
                    {
                        new Client<TEntityForm>().Save(form, "Added by user");
                    }
                }
            };
        };

        var manageForms = new MenuItem { Header = "Manage Forms..." };
        manageForms.Click += (o, e) =>
        {
            var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" };

            var grid = new DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>(loadEntity());

            grid.Refresh(true, true);
            grid.Margin = new Thickness(5);

            window.Content = grid;

            window.ShowDialog();
        };

        menu.Items.Add(addForm);
        menu.Items.Add(new Separator());
        menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false });
        menu.Items.Add(new Separator());
        menu.Items.Add(manageForms);

        task.ContinueWith((task) =>
        {
            var entityForms = task.Result;
            menu.Items.Clear();
            menu.Items.Add(addForm);
            menu.Items.Add(new Separator());

            if (entityForms.Any())
            {
                foreach (var entityForm in entityForms)
                {
                    var description = entityForm.Description;
                    if (string.IsNullOrWhiteSpace(description))
                    {
                        description = entityForm.Form.Description;
                    }
                    var formItem = new MenuItem { Header = $"{entityForm.Number} : {description}" };
                    formItem.Click += (o, e) =>
                    {
                        if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel, customise: customiseEditor))
                        {
                            dataModel.Update(null);
                        }
                    };

                    menu.Items.Add(formItem);
                }
            }
            else
            {
                menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false });
            }
            menu.Items.Add(new Separator());
            menu.Items.Add(manageForms);
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }

    #region Alignment

    public static VerticalAlignment VerticalAlignment(this Alignment alignment)
    {
        if (alignment.Equals(Alignment.NotSet))
            return System.Windows.VerticalAlignment.Center;
        if (alignment.Equals(Alignment.TopLeft) || alignment.Equals(Alignment.TopCenter) || alignment.Equals(Alignment.TopRight))
            return System.Windows.VerticalAlignment.Top;
        if (alignment.Equals(Alignment.MiddleLeft) || alignment.Equals(Alignment.MiddleCenter) || alignment.Equals(Alignment.MiddleRight))
            return System.Windows.VerticalAlignment.Center;
        return System.Windows.VerticalAlignment.Bottom;
    }

    public static HorizontalAlignment HorizontalAlignment(this Alignment alignment, Type datatype)
    {
        if (alignment.Equals(Alignment.NotSet))
            return datatype.IsNumeric() ? System.Windows.HorizontalAlignment.Right :
                datatype == typeof(bool) ? System.Windows.HorizontalAlignment.Center : System.Windows.HorizontalAlignment.Left;
        if (alignment.Equals(Alignment.TopLeft) || alignment.Equals(Alignment.MiddleLeft) || alignment.Equals(Alignment.BottomLeft))
            return System.Windows.HorizontalAlignment.Left;
        if (alignment.Equals(Alignment.TopCenter) || alignment.Equals(Alignment.MiddleCenter) || alignment.Equals(Alignment.BottomCenter))
            return System.Windows.HorizontalAlignment.Center;
        return System.Windows.HorizontalAlignment.Right;
    }

    public static TextAlignment TextAlignment(this Alignment alignment, Type datatype)
    {
        if (alignment.Equals(Alignment.NotSet))
            return datatype.IsNumeric() ? System.Windows.TextAlignment.Right :
                datatype == typeof(bool) ? System.Windows.TextAlignment.Center : System.Windows.TextAlignment.Left;
        if (alignment.Equals(Alignment.TopLeft) || alignment.Equals(Alignment.MiddleLeft) || alignment.Equals(Alignment.BottomLeft))
            return System.Windows.TextAlignment.Left;
        if (alignment.Equals(Alignment.TopCenter) || alignment.Equals(Alignment.MiddleCenter) || alignment.Equals(Alignment.BottomCenter))
            return System.Windows.TextAlignment.Center;
        return System.Windows.TextAlignment.Right;
    }

    #endregion
}