using InABox.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Document = InABox.Core.Document;

namespace InABox.DynamicGrid
{
    /// <summary>
    /// Interaction logic for EmbeddedDynamicEditorForm.xaml
    /// </summary>
    public partial class EmbeddedDynamicEditorForm : UserControl, IDynamicEditorForm
    {

        public delegate void OKEvent();

        public delegate void CancelEvent();

        public DynamicEditorPages Pages { get; private set; } = new();

        private BaseObject[] _items;

        public BaseObject[] Items
        {
            get => _items;
            set
            {
                _items = value;
                DynamicEditorFormModel.Slug = Items != null ? Items.Any() ? Items.First().GetType().EntityName().Split('.').Last() : "" : "";
                Editor.Load(Pages);
            }
        }

        public bool ReadOnly { get; set; }

        #region Events


        public event OnValidateData? OnValidateData;

        public event OnCustomiseColumns? OnCustomiseColumns;
        public event OnDefineFilter? OnDefineFilter;

        public event OnDefineLookup? OnDefineLookups;
        public event OnLookupsDefined? OnLookupsDefined;

        public event DefineEditorEventHandler? OnDefineEditor;
        public event OnFormCustomiseEditor? OnFormCustomiseEditor;
        public event OnReconfigureEditors? OnReconfigureEditors;

        public event EditorValueChangedHandler? OnEditorValueChanged;
        public event OnAfterEditorValueChanged? OnAfterEditorValueChanged;

        public event IDynamicEditorForm.GetDocumentEvent? OnGetDocument;
        public event IDynamicEditorForm.FindDocumentEvent? OnFindDocument;
        public event IDynamicEditorForm.SaveDocumentEvent? OnSaveDocument;

        public event OnSelectPage? OnSelectPage;

        public event DynamicGridSaveEvent? OnSaveItem;

        public event DynamicEditorGrid.EditorCreatedHandler? OnEditorCreated;

        public event OKEvent? OnOK;
        public event CancelEvent? OnCancel;

        #endregion

        public EmbeddedDynamicEditorForm()
        {
            InitializeComponent();
        }
        public EmbeddedDynamicEditorForm(Type type, DynamicEditorPages? pages = null, DynamicEditorButtons? buttons = null,
            Func<Type, CoreTable>? PageDataHandler = null, bool PreloadPages = false): this()
        {
            Setup(type, pages, buttons, PageDataHandler, PreloadPages);
        }

        public void Setup(Type type, DynamicEditorPages? pages = null, DynamicEditorButtons? buttons = null,
            Func<Type, CoreTable>? PageDataHandler = null, bool PreloadPages = false)
        {
            ReadOnly = false;

            //this.Loaded += new RoutedEventHandler(ConfigureSystemMenu);

            Editor.UnderlyingType = type;

            Editor.OnCustomiseColumns += Editor_OnCustomiseColumns;

            Editor.OnDefineFilter += (sender, t) => OnDefineFilter?.Invoke(sender, t);

            Editor.OnEditorCreated += Editor_OnEditorCreated;

            Editor.OnLoadPage += page => { page.Load(Items.First(), PageDataHandler); };

            Editor.OnSelectPage += (tab, items) => { OnSelectPage?.Invoke(tab, items); };

            Editor.PreloadPages = PreloadPages;

            Editor.OnUnloadPage += (page, saved) =>
            {
                if (!saved)
                    page.BeforeSave(Items.First());
                else
                    page.AfterSave(Items.First());
            };

            //Editor.OnGetPropertyInfo += (o, c) => { return CoreUtils.GetProperty(_item.GetType(), c); };

            Editor.OnAfterEditorValueChanged += (g, n) => { return OnAfterEditorValueChanged?.Invoke(g, n); };

            Editor.OnReconfigureEditors += g => { OnReconfigureEditors?.Invoke(g); };

            Editor.OnGetEditor += c =>
            {
                if (_items != null && _items.Any())
                {
                    var property = DatabaseSchema.Property(type, c.ColumnName);
                    if (property == null) return new NullEditor();

                    if (property.Editor is NullEditor)
                        return property.Editor;

                    BaseEditor editor;
                    if (property is CustomProperty)
                    {
                        editor = property.Editor.CloneEditor();
                    }
                    else
                    {
                        editor = OnDefineEditor?.Invoke(_items[0], c) ?? c.Editor.CloneEditor();
                        var propEditor = property.Editor;
                        editor.Page = propEditor.Page;
                        editor.Caption = propEditor.Caption;
                    }


                    //defaultEditor.EditorSequence

                    //EditorUtils.GetPropertyEditor(type, property, defaultEditor);

                    /*BaseEditor editor = new NullEditor();
                    var caption = "";
                    var page = "";

                    try
                    {
                        var comps = c.ColumnName.Split('.');
                        for (var i = 0; i < comps.Length; i++)
                        {
                            var column = string.Join(".", comps.Take(i + 1));
                            var prop = CoreUtils.GetProperty(type, column);

                            if (column.Equals(c.ColumnName))
                            {
                                if (OnDefineEditor != null)
                                    editor = OnDefineEditor(_items[0], c);
                                else
                                    editor = c.Editor != null ? c.Editor : new NullEditor();
                            }
                            else
                            {
                                var pedit = prop.GetEditor();
                                if (pedit is NullEditor)
                                    return pedit;
                            }

                            editor = editor == null ? new NullEditor() : editor.Clone() as BaseEditor;

                            var capattr = prop.GetCustomAttribute<Caption>();
                            var subcap = capattr != null ? capattr.Text : comps[i];
                            var path = capattr != null ? capattr.IncludePath : true;
                            if (!string.IsNullOrWhiteSpace(subcap))
                                caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap);

                            if (string.IsNullOrWhiteSpace(page))
                            {
                                var pageattr = prop.GetCustomAttribute<EditorSequence>();
                                if (pageattr != null)
                                    page = pageattr.Page;
                            }
                        }

                        editor.Caption = caption;
                        editor.Page = page;
                    }
                    catch (Exception e)
                    {
                        var dmprop = DatabaseSchema.Property(_items[0].GetType(), c.ColumnName);
                        if (dmprop is CustomProperty)
                        {
                            editor = dmprop.Editor.Clone() as BaseEditor;
                            editor.Caption = dmprop.Caption;
                            editor.Page = string.IsNullOrWhiteSpace(dmprop.Page) ? "Custom Fields" : dmprop.Page;
                        }
                    }*/

                    if (ReadOnly && editor.Editable.Equals(Editable.Enabled))
                        editor.Editable = Editable.Disabled;
                    return editor;
                }

                return new NullEditor();
            };

            Editor.OnGridCustomiseEditor += (sender, column, editor) => OnFormCustomiseEditor?.Invoke(this, Items, column, editor);

            Editor.OnGetSequence += c => CoreUtils.GetPropertySequence(_items.First().GetType(), c.ColumnName);

            Editor.OnGetPropertyValue += (o, c) =>
            {
                if (!_items.Any())
                    return null;

                object? result;
                try
                {
                    result = CoreUtils.GetPropertyValue(_items.First(), c);
                }
                catch
                {
                    result = _items.First().UserProperties.ContainsKey(c) ? _items.First().UserProperties[c] : null;
                }

                if (result == null)
                    return null;

                foreach (var _item in _items)
                {
                    object? curvalue;
                    try
                    {
                        curvalue = CoreUtils.GetPropertyValue(_item, c);
                    }
                    catch
                    {
                        curvalue = _item.UserProperties.ContainsKey(c) ? _item.UserProperties[c] : null;
                    }

                    if (curvalue == null)
                        return null;

                    if (!curvalue.Equals(result))
                        return null;
                }

                return result;
            };

            Editor.OnSetPropertyValue += (o, c, v) =>
            {
                foreach (var _item in _items)
                    if (_item.UserProperties.ContainsKey(c))
                        _item.UserProperties[c] = v;
                    else
                        CoreUtils.SetPropertyValue(_item, c, v);
            };

            Editor.OnEditorValueChanged += EditorValueChanged;

            Editor.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
            Editor.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };

            Editor.OnGetDocument += id => { return OnGetDocument?.Invoke(id); };
            Editor.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); };
            Editor.OnFindDocument += file => { return OnFindDocument?.Invoke(file); };

            Editor.GetItems += () => _items;

            Pages = pages ?? new DynamicEditorPages();

            if (Pages == null || Pages.Count == 0)
                Editor.Margin = new Thickness(5, 5, 5, 0);

            Buttons.Children.Clear();
            if (buttons != null)
                foreach (var button in buttons)
                {
                    var btn = new Button();
                    UpdateButton(btn, button.Image, button.Name);
                    btn.Tag = button;
                    btn.Margin = new Thickness(5, 5, 0, 5);
                    btn.Padding = new Thickness(5, 0, 5, 0);
                    btn.Click += Btn_Click;
                    Buttons.Children.Add(btn);
                    button.Button = btn;
                    button.Form = this;
                }
        }

        public void UnloadEditorPages(bool saved)
        {
            Editor.UnloadPages(saved);
        }

        protected void UpdateButton(Button button, BitmapImage? image, string text)
        {
            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);
                stackPnl.Children.Add(img);
            }

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

            button.Content = stackPnl;
        }

        private Dictionary<string, object?> EditorValueChanged(object sender, string name, object value)
        {
            if (OnEditorValueChanged != null)
                return OnEditorValueChanged(sender, name, value);
            return DynamicGridUtils.UpdateEditorValue(_items, name, value);
        }

        private void Editor_OnEditorCreated(object sender, double height, double width)
        {
            OnEditorCreated?.Invoke(sender, height, width);

            Editor.VerticalAlignment = VerticalAlignment.Stretch;
            Editor.HorizontalAlignment = HorizontalAlignment.Stretch;

            OKButton.IsEnabled = !ReadOnly;
        }

        private void Editor_OnCustomiseColumns(object sender, DynamicGridColumns columns)
        {
            columns.Clear();
            if (_items != null && _items.Any())
                columns.ExtractColumns(_items.First().GetType());
            OnCustomiseColumns?.Invoke(this, columns);
        }

        private void Btn_Click(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender;
            var deb = (DynamicEditorButton)button.Tag;
            deb.Click();
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            var errors = OnValidateData?.Invoke(this, Items);

            if (errors != null && errors.Any())
            {
                MessageBox.Show(
                    string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}",
                        string.Join("\n- ", errors)), "Validation Error");
                return;
            }
            OnOK?.Invoke();

            // Don't Commit the changes here, because we want to refer back to thos changes when we save the item
            // to trigger specific processes in the database
            //Close();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            // However, if we cancel the edits, then we can safely revert the items back to their original (loaded) state
            foreach (var item in _items)
                item.CancelChanges();

            OnCancel?.Invoke();
            //Close();
        }

        public void SaveItem(CancelEventArgs e)
        {
            OnSaveItem?.Invoke(this, e);
        }

        public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
        {
            return Editor.TryFindEditor(columnname, out editor);
        }
        public IDynamicEditorControl FindEditor(string columnname)
        {
            return Editor.FindEditor(columnname);
        }
        public object? GetEditorValue(string columnName) => FindEditor(columnName).GetValue(columnName);
        public void SetEditorValue(string columnName, object? value) => FindEditor(columnName).SetValue(columnName, value);

        public void SetLayoutType<T>() where T : DynamicEditorGridLayout => Editor.SetLayoutType<T>();

        //public void EditLayout() => Editor.EditLayout();
        //public void ResetLayout() => Editor.ResetLayout();

    }
}
