using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using InABox.Clients;
using InABox.Core;
using InABox.Wpf;
using InABox.WPF;
using NPOI.HSSF.Record.Aggregates;

namespace InABox.DynamicGrid;

public delegate void OnUpdateOtherEditorHandler(string columnname, object value);

public delegate Dictionary<string, object?> EditorValueChangedHandler(IDynamicEditorForm sender, string name, object value);

/// <summary>
///     Interaction logic for DynamicEditorGrid.xaml
/// </summary>
public partial class DynamicEditorGrid : UserControl, IDynamicEditorHost
{
    public delegate void EditorCreatedHandler(object sender, double height, double width);

    public delegate object? GetPropertyValueHandler(object sender, string name);

    public delegate void SetPropertyValueHandler(object sender, string name, object value);

    public delegate BaseObject[] GetItemsEvent();

    // Column Definitions as defined by calling model
    private DynamicGridColumns _columns = new();

    public IDynamicEditorForm Form { get; set; }

    private Type? LayoutType;
    private DynamicEditorGridLayout? Layout;

    private bool _tabStripVisible = true;
    
    public bool TabStripVisible
    {
        get { return _tabStripVisible; }
        set
        {
            _tabStripVisible = value;
            if (Layout != null) 
                Layout.TabStripVisible = value;
        }
    }

    public double TotalWidth => Layout?.TotalWidth ?? 0;
    public double TotalHeight => Layout?.TotalHeight ?? 0;

    public DynamicEditorGrid()
    {
        InitializeComponent();

        Loaded += DynamicEditorGrid_Loaded;
    }

    private DynamicEditorPages _pages = new();
    public IEnumerable<IDynamicEditorPage> Pages => _pages;

    private void AddPage(IDynamicEditorPage page)
    {
        page.ReadOnly = ReadOnly;
        _pages.Add(page);
    }
    private void SetPages(DynamicEditorPages pages)
    {
        _pages = pages;
        foreach (var page in _pages)
        {
            page.ReadOnly = ReadOnly;
        }
    }

    public bool PreloadPages { get; set; }

    public Type UnderlyingType { get; set; }

    public OnLoadPage? OnLoadPage { get; set; }
    public event OnSelectPage? OnSelectPage;
    public event OnUnloadPage? OnUnloadPage;

    private bool _readOnly;
    public bool ReadOnly
    {
        get => _readOnly;
        set
        {
            _readOnly = value;
            foreach(var page in Pages)
            {
                page.ReadOnly = value;
            }
        }
    }

    public IEnumerable<IDynamicEditorControl> Editors
    {
        get
        {
            foreach (var page in Pages)
            {
                if (page is DynamicEditPage editPage)
                {
                    foreach(var editor in editPage.Editors)
                    {
                        yield return editor;
                    }
                }
            }
        }
    }

    public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
    {
        foreach (var page in Pages)
        {
            if (page is DynamicEditPage editPage)
            {
                if (editPage.TryFindEditor(columnname, out editor))
                    return true;
            }
        }
        editor = null;
        return false;
    }
    public IDynamicEditorControl? FindEditor(string columnname)
    {
        TryFindEditor(columnname, out var editor);
        return editor;
    }

    public virtual void ReconfigureEditors()
    {
        OnReconfigureEditors?.Invoke(this);
    }

    public object? GetPropertyValue(string columnname)
    {
        return OnGetPropertyValue?.Invoke(this, columnname);
    }

    public event EditorCreatedHandler? OnEditorCreated;

    public event OnCustomiseColumns? OnCustomiseColumns;

    public event OnGetEditor? OnGetEditor;

    public event OnGridCustomiseEditor? OnGridCustomiseEditor;

    public event OnGetEditorSequence? OnGetSequence;
    public event GetPropertyValueHandler? OnGetPropertyValue;
    public event SetPropertyValueHandler? OnSetPropertyValue;

    public delegate Dictionary<string, object?> EditorGridValueChangedHandler(DynamicEditorGrid sender, string name, object? value);
    public event EditorGridValueChangedHandler? OnEditorValueChanged;

    public event OnAfterEditorValueChanged? OnAfterEditorValueChanged;

    public event OnReconfigureEditors? OnReconfigureEditors;
    public event OnCreateEditorControl? OnCreateEditorControl;
    
    public event OnDefineLookupFilter? OnDefineFilter;

    public event OnDefineLookup? OnDefineLookups;

    public event GetItemsEvent? GetItems;

    public delegate void EditorGridOnReloadHandler(DynamicEditorGrid sender);
    public event EditorGridOnReloadHandler? OnReload;

    private void DynamicEditorGrid_Loaded(object sender, RoutedEventArgs e)
    {
        //Reload();
    }

    public void Reload()
    {
        LoadPages();
        ReconfigureEditors();

        OnReload?.Invoke(this);
    }

    #region Host Implementation

    public void LoadLookups(ILookupEditorControl editor)
    {
        OnDefineLookups?.Invoke(editor);
    }

    BaseObject[] IDynamicEditorHost.GetItems() => GetItems?.Invoke() ?? Array.Empty<BaseObject>();

    public BaseEditor? GetEditor(DynamicGridColumn column) => OnGetEditor?.Invoke(column);

    public Type GetEditorType() => UnderlyingType;

    #endregion

    #region Edit Page

    private void CreateEditorControl(string column, BaseEditor editor, IDynamicEditorControl control)
    {
        OnCreateEditorControl?.Invoke(column, editor, control);
    }

    public class DynamicEditPage : ContentControl, IDynamicEditorPage
    {

        private Grid Grid;

        public DynamicEditorGrid EditorGrid { get; set; } = null!; // Set by DynamicEditorGrid
        public bool Ready { get; set; }

        public bool Visible => true;

        private List<BaseDynamicEditorControl> EditorList { get; set; }

        public IEnumerable<IDynamicEditorControl> Editors => EditorList;

        public PageType PageType => PageType.Editor;

        public int PageOrder { get; set; }

        public string Header { get; set; }

        private double GeneralHeight = 30;

        private bool _readOnly;
        public bool ReadOnly
        {
            get => _readOnly;
            set
            {
                if(_readOnly != value)
                {
                    _readOnly = value;
                    foreach(var editor in EditorList)
                    {
                        editor.IsEnabled = !value && editor.EditorDefinition.Editable.IsEditable();
                    }
                }
            }
        }

        public DynamicEditPage(string header)
        {
            Header = header;

            EditorList = [];

            InitialiseContent();
        }

        public void AddEditor(string columnName, BaseEditor editor, Type valueType)
        {
            BaseDynamicEditorControl? element = DynamicEditorControlFactory.CreateControl(editor, valueType, EditorGrid);
            
            if (element != null)
            {
                element.IsEnabled = !ReadOnly && editor.Editable.IsEditable();

                if (!string.IsNullOrWhiteSpace(editor.ToolTip))
                {
                    element.ToolTip = new ToolTip() { Content = editor.ToolTip };
                }

                var label = new Label();
                label.Content = CoreUtils.Neatify(editor.Caption); // 2
                label.Margin = new Thickness(0F, 0F, 0F, 0F);
                label.HorizontalAlignment = HorizontalAlignment.Stretch;
                label.VerticalAlignment = VerticalAlignment.Stretch;
                label.HorizontalContentAlignment = HorizontalAlignment.Left;
                label.VerticalContentAlignment = VerticalAlignment.Center;
                label.SetValue(Grid.RowProperty, Grid.RowDefinitions.Count);
                label.SetValue(Grid.ColumnProperty, 0);
                label.Visibility = string.IsNullOrWhiteSpace(editor.Caption) ? Visibility.Collapsed : Visibility.Visible;

                Grid.Children.Add(label);

                element.ColumnName = columnName;
                element.Color = editor is UniqueCodeEditor ? Color.FromArgb(0xFF, 0xF6, 0xC9, 0xE8) : Colors.LightYellow;

                EditorList.Add(element);

                element.Margin = new Thickness(5F, 2.5F, 5F, 2.5F);

                double iHeight = element.DesiredHeight();
                if (iHeight == int.MaxValue)
                {
                    Grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                    GeneralHeight += element.MinHeight + 5.0F;
                }
                else
                {
                    Grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(iHeight + 5.0F) });
                    GeneralHeight += iHeight + 5.0F;
                }

                double iWidth = element.EditorDefinition?.Information != null ? int.MaxValue : element.DesiredWidth();
                if (iWidth == int.MaxValue)
                {
                    element.HorizontalAlignment = HorizontalAlignment.Stretch;
                }
                else
                {
                    element.HorizontalAlignment = HorizontalAlignment.Left;
                    element.Width = iWidth;
                }

                element.SetValue(Grid.RowProperty, Grid.RowDefinitions.Count - 1);
                element.SetValue(Grid.ColumnProperty, 1);
                Grid.Children.Add(element);

                EditorGrid.CreateEditorControl(columnName, editor, element);
            }
        }

        [MemberNotNull(nameof(Grid))]
        private void InitialiseContent()
        {
            Grid = new Grid
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
                Margin = new Thickness(0, 2.5, 0, 2.5)
            };
            Grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
            Grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });

            var scroll = new ScrollViewer
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
                VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
                Padding = new Thickness(2),
                Content = Grid
            };

            var border = new Border
            {
                BorderBrush = new SolidColorBrush(Colors.Gray),
                Background = new SolidColorBrush(Colors.White),
                BorderThickness = new Thickness(0.75),
                Child = scroll
            };

            Content = border;
        }

        public void AfterSave(object item)
        {
        }

        public event EventHandler? OnChanged;

        public void BeforeSave(object item)
        {
        }

        public void Cancel()
        {
            // Do nothing, since the cancelling of the items is handled by the editor form.
        }

        public string Caption() => Header;

        public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
        {
            editor = EditorList.FirstOrDefault(x => x.ColumnName.Equals(columnname));
            editor ??= EditorList.FirstOrDefault(x => columnname.StartsWith(x.ColumnName + '.'));
            return editor is not null;
        }

        public IEnumerable<BaseDynamicEditorControl> FindEditors(DynamicGridColumn column)
        {
            return EditorList.Where(x => string.Equals(x.ColumnName, column.ColumnName));
        }

        #region Configure Editors

        private void Lookup_OnUpdateOtherEditor(string columnname, object value)
        {
            var editor = EditorList.FirstOrDefault(x => x.ColumnName.Equals(columnname));
            if (editor != null)
                CoreUtils.SetPropertyValue(editor, "Value", value);
        }

        private void ConfigureEditors()
        {
            foreach (var editor in EditorList)
            {
                editor.Configure();
                editor.Loaded = true;

                editor.OnEditorValueChanged += EditorValueChanged;
            }
        }

        #endregion

        private void EditorValueChanged(IDynamicEditorControl sender, Dictionary<string, object?> values)
        {
            //Logger.Send(LogType.Information, "", string.Format("DynamicEditorGrid.EditorValueChanged({0})", values.Keys.Count));
            var changededitors = new Dictionary<string, object?>();

            void ExtractChanged(Dictionary<string, object?>? columns)
            {
                if (columns != null)
                    foreach (var (change, value) in columns)
                        if (!changededitors.ContainsKey(change))
                            changededitors[change] = value;
            }

            var name = sender.ColumnName;
            var resetAll = false;
            if(values.Remove(name, out var value))
            {
                var changedcolumns = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, name, value);

                resetAll = changedcolumns?.ContainsKey(name) ?? false;
                ExtractChanged(changedcolumns);
            }
            else
            {

            }

            foreach (var (key, otherValue) in values)
            {
                var changes = new Dictionary<string, object?>();
                if (resetAll)
                {
                    var prop = DatabaseSchema.Property(EditorGrid.UnderlyingType, key);
                    if(prop is not null)
                    {
                        var def = CoreUtils.GetDefault(prop.PropertyType);
                        var resetChanges = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, key, def);
                        if(resetChanges is not null)
                        {
                            changes = resetChanges;
                        }
                    }
                    else
                    {

                    }
                }
                var changedOtherColumns = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, key, otherValue);

                if(changedOtherColumns is not null)
                {
                    foreach (var (k, v) in changedOtherColumns)
                    {
                        changes[k] = v;
                    }
                }

                ExtractChanged(changes);
            }

            var afterchanged = EditorGrid.OnAfterEditorValueChanged?.Invoke(EditorGrid, new AfterEditorValueChangedArgs(sender.ColumnName, changededitors));
            ExtractChanged(afterchanged);

            changededitors.Remove(sender.ColumnName);

            if (changededitors.Count != 0)
                LoadEditorValues(changededitors);

            EditorGrid.ReconfigureEditors();
        }

        private void LoadEditorValues(Dictionary<string, object?>? changededitors = null)
        {
            var columnnames = changededitors != null ? changededitors.Keys.ToArray() : EditorList.Select(x => x.ColumnName).ToArray();
            foreach (var columnname in columnnames)
            {
                if (!EditorGrid.TryFindEditor(columnname, out var editor))
                    continue;

                var bLoaded = editor.Loaded;
                editor.Loaded = false;
                if (changededitors != null)
                {
                    if (changededitors.TryGetValue(columnname, out var value))
                    {
                        editor.SetValue(columnname, value);
                    }
                }
                else
                {
                    var curvalue = EditorGrid.GetPropertyValue(columnname);

                    try
                    {
                        editor.SetValue(columnname, curvalue);
                    }
                    catch (Exception e)
                    {
                        MessageWindow.ShowError($"Unable to set editor value for {columnname} -> {curvalue}", e);
                    }

                    editor.Changed = false;
                }

                editor.Loaded = bLoaded;
            }
        }

        public void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
        {
            ConfigureEditors();
            LoadEditorValues();

            foreach (var editor in EditorList)
            {
                foreach(var (column, editorValue) in editor.GetValues())
                {
                    var entityValue = EditorGrid.GetPropertyValue(column);
                    if (!Equals(editorValue, entityValue))
                    {
                        bool bLoaded = editor.Loaded;
                        editor.Loaded = false;
                        editor.SetValue(column, entityValue);
                        editor.Loaded = bLoaded;
                    }
                }
            }

            EditorList.FirstOrDefault()?.SetFocus();

            Ready = true;
        }

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

        public Size MinimumSize() => new Size(800, GeneralHeight);

        public int Order
        {
            get => PageOrder;
            set => PageOrder = value;
        }
    }

    #endregion

    #region Loading + Editing Layout

    private decimal GetSequence(DynamicGridColumn column)
    {
        if (OnGetSequence != null)
            return OnGetSequence.Invoke(column);
        return 999;
    }

    private DynamicEditPage GetEditPage(string name)
    {
        var page = Pages.Where(x => x is DynamicEditPage page && page.Header == name).FirstOrDefault() as DynamicEditPage;
        if(page is null)
        {
            page = new DynamicEditPage(name)
            {
                // Setting this here because it's needed now to be able to create the layout.
                EditorGrid = this
            };
            if (name == "General")
            {
                page.PageOrder = -1;
            }
            else
            {
                page.PageOrder = 0;
            }

            AddPage(page);
        }
        return page;
    }

    public void SetLayout(DynamicEditorGridLayout layout)
    {
        Layout = layout;
        Layout.OnSelectPage += Layout_SelectPage;
        Layout.TabStripVisible = _tabStripVisible;
        Content = Layout;
    }

    public void SetLayoutType<T>() where T : DynamicEditorGridLayout
    {
        LayoutType = typeof(T);
    }

    public void SetLayoutType(Type t)
    {
        if (!t.IsSubclassOf(typeof(DynamicEditorGridLayout)))
            throw new Exception($"{t.Name} is not a {nameof(DynamicEditorGridLayout)}!");
        LayoutType = t;
    }

    private void InitialiseLayout()
    {
        Layout = (Activator.CreateInstance(LayoutType ?? typeof(DefaultDynamicEditorGridLayout)) as DynamicEditorGridLayout)!;
        Layout.OnSelectPage += Layout_SelectPage;
        Layout.TabStripVisible = _tabStripVisible;

        Content = Layout;
    }

    private void CreateLayout()
    {
        if(Layout is null)
        {
            InitialiseLayout();
        }

        var editors = new List<(IProperty, string, BaseEditor)>();
        foreach (var column in _columns.OrderBy(x => GetSequence(x)))
        {
            var iProp = DatabaseSchema.PropertyStrict(UnderlyingType, column.ColumnName);
            var editor = OnGetEditor?.Invoke(column);

            if (editor != null && iProp.ShouldShowEditor() != true)
            {
                editor.Visible = Visible.Hidden;
                editor.Editable = Editable.Hidden;
            }

            if(editor is not null)
            {
                foreach(var security in editor.Security)
                {
                    if (!Security.IsAllowed(security.SecurityDescriptor))
                    {
                        editor.Editable = editor.Editable.Combine(security.Editable);
                        if (editor.Editable == Editable.Hidden)
                        {
                            break;
                        }
                    }
                }
            }

            if(editor is not null)
            {
                editor.EditorSequence = editors.Count;
                OnGridCustomiseEditor?.Invoke(this, column, editor);
            }

            if (editor != null && editor.Editable.EditorVisible())
            {
                editors.Add((iProp, column.ColumnName, editor));
            }
            else if (iProp?.HasParentEditor() == true)
            {
                // Add the parent editor if it hasn't been added already.
                var parent = iProp.GetParentWithEditor();
                if(parent is not null)
                {
                    var parentEditor = parent.Editor?.CloneEditor();

                    if(parentEditor is not null)
                    {
                        parentEditor.EditorSequence = editors.Count;
                        OnGridCustomiseEditor?.Invoke(this, new DynamicGridColumn(parent), parentEditor);
                    }
                    if(parentEditor is not null && parentEditor.Editable.EditorVisible())
                    {
                        editors.Add((parent, parent.Name, parentEditor));
                    }
                }
            }
        }
        editors.SortBy(x => x.Item3.EditorSequence);
        foreach(var (prop, name, editor) in editors)
        {
            var page = string.IsNullOrWhiteSpace(editor.Page) ? prop is StandardProperty ? "General" : "Custom Fields" : editor.Page;
            var editPage = GetEditPage(page);
            if (!editPage.TryFindEditor(name, out var editorControl))
            {
                editPage.AddEditor(name, editor, prop.PropertyType);
            }
        }
        
        OnEditorCreated?.Invoke(this, 0, 800);
    }

    #endregion

    #region Pages

    private void Layout_SelectPage(IDynamicEditorPage page)
    {
        if (!page.Ready)
            using (new WaitCursor())
            {
                OnLoadPage?.Invoke(page);
            }

        OnSelectPage?.Invoke(this, null);
    }

    public void UnloadPages(bool saved)
    {
        if(Pages is not null)
            foreach (var page in Pages)
                if (page.Ready)
                    OnUnloadPage?.Invoke(page, saved);
    }

    private void LoadPages()
    {
        if (Pages != null && Layout is not null)
            using (new WaitCursor())
            {
                foreach (var page in Pages)
                {
                    page.Ready = false;
                    page.EditorGrid = this;
                }
                Layout.LoadPages(Pages);

                if (PreloadPages)
                {
                    foreach(var page in Pages)
                    {
                        OnLoadPage?.Invoke(page);
                    }
                }
            }
    }

    public void Load(DynamicEditorPages pages)
    {
        SetPages(pages);

        _columns = new DynamicGridColumns();
        OnCustomiseColumns?.Invoke(this, _columns);

        CreateLayout();
        Reload();
    }

    #endregion

}