using InABox.Core;
using InABox.Wpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

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

        public event OnBeforeLoad? OnBeforeLoad;
        public void BeforeLoad() => OnBeforeLoad?.Invoke(this);

        public event OnAfterLoad? OnAfterLoad;
        public void AfterLoad() => OnAfterLoad?.Invoke(this);

        public event IDynamicEditorForm.OnReloadEventHandler? OnReload;

        public event EventHandler OnChanged;

        private bool bChanged = false;
        private bool _validated = false;

        public void DoChanged()
        {
            bChanged = true;
            _validated = false;
            //OKButton.IsEnabled = true;
            //CancelButton.IsEnabled = true;
            OnChanged?.Invoke(this, EventArgs.Empty);
            UpdateButtonEnabled();
        }

        public delegate void OKEvent();

        public delegate void CancelEvent();

        public DynamicEditorPages Pages { get; private set; } = [];

        private BaseObject[] _items;

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

                bChanged = _items.Any(x => x is not Entity e || e.ID == Guid.Empty);
                UpdateButtonEnabled();

                foreach (var page in Pages)
                    page.OnChanged += (sender, args) => DoChanged();
            }
        }

        public bool ReadOnly
        {
            get => Editor.ReadOnly;
            set
            {
                Editor.ReadOnly = value;
                UpdateButtonEnabled();
            }
        }

        private bool _disableIfUnchanged = false;

        public bool DisableOKIfUnchanged
        {
            get => _disableIfUnchanged;
            set
            {
                _disableIfUnchanged = value;
                UpdateButtonEnabled();
            }
        }
        
        private bool _hideButtons = false;

        public bool HideButtons
        {
            get => _hideButtons;
            set
            {
                _hideButtons = value;
                OKButton.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
                CancelButton.Visibility = value ? Visibility.Collapsed : Visibility.Visible;
            }
        }

        private bool _highlightButtons = false;
        public bool HighlightButtons
        {
            get => _highlightButtons;
            set
            {
                _highlightButtons = value;
                UpdateButtonHighlight(OKButton, Colors.DarkGreen, Colors.LimeGreen, Colors.White);
                UpdateButtonHighlight(CancelButton, Colors.Firebrick, Colors.Red, Colors.White);
            }
        }

        public double ContentWidth => Editor.TotalWidth;
        public double ContentHeight => Editor.TotalHeight;

        private void UpdateButtonEnabled()
        {
            OKButton.IsEnabled = !ReadOnly && (!DisableOKIfUnchanged || bChanged);
            CancelButton.IsEnabled = !ReadOnly;
        }

        private void UpdateButtonHighlight(Button button, Color border, Color background, Color foreground)
        {
            button.BorderBrush = _highlightButtons
                ? new SolidColorBrush(border)
                : new SolidColorBrush(Colors.Gray);
            button.Background = _highlightButtons
                ? new SolidColorBrush(background)
                : new SolidColorBrush(Colors.Gainsboro);
            button.Foreground = _highlightButtons
                ? new SolidColorBrush(foreground)
                : new SolidColorBrush(Colors.Black);
            button.FontWeight = _highlightButtons
                ? FontWeights.Bold
                : FontWeights.Normal;
        }

        public static readonly DependencyProperty ButtonsVisibleProperty =
            DependencyProperty.Register(
                nameof(ButtonsVisible), 
                typeof(bool), 
                typeof(EmbeddedDynamicEditorForm), 
                new UIPropertyMetadata(true)
            );
        
        public bool ButtonsVisible
        {
            get => (bool)GetValue(ButtonsVisibleProperty);
            set
            {
                SetValue(ButtonsVisibleProperty, value);
                UpdateButtonsRowVisibility();
            }
        }
        
        private void UpdateButtonsRowVisibility()
        {
            ButtonRow.Height = ButtonsVisible
                ? new GridLength(1, GridUnitType.Auto)
                : new GridLength(0, GridUnitType.Pixel);
        }
        
        public static readonly DependencyProperty TabsVisibleProperty =
            DependencyProperty.Register(
                nameof(TabsVisible), 
                typeof(bool), 
                typeof(EmbeddedDynamicEditorForm), 
                new UIPropertyMetadata(true)
            );
        
        public bool TabsVisible
        {
            get => (bool)GetValue(TabsVisibleProperty);
            set
            {
                SetValue(TabsVisibleProperty, value);
                UpdateTabsVisibility();
            }
        }


        private void UpdateTabsVisibility()
        {
            Editor.TabStripVisible = TabsVisible;
        }

        #region Events


        public event OnValidateData? OnValidateData;

        public OnCustomiseColumns? OnCustomiseColumns { get; set; }
        public OnDefineLookupFilter? OnDefineFilter { get; set; }

        public OnDefineLookup? OnDefineLookups { get; set; }

        public DefineEditorEventHandler? OnDefineEditor { get; set; }
        public event OnFormCustomiseEditor? OnFormCustomiseEditor;
        public OnReconfigureEditors? OnReconfigureEditors { get; set; }

        public event OnCreateEditorControl? OnCreateEditorControl;
        public event EditorValueChangedHandler? OnEditorValueChanged;
        public event OnAfterEditorValueChanged? OnAfterEditorValueChanged;
        
        public event OnSelectPage? OnSelectPage;

        public DynamicGridSaveEvent? OnSaveItem { get; set; }

        public DynamicEditorGrid.EditorCreatedHandler? OnEditorCreated;

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

        #endregion

        public EmbeddedDynamicEditorForm()
        {
            InitializeComponent();
            ReadOnly = false;
            Editor.Form = this;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            UpdateButtonsRowVisibility();
            UpdateTabsVisibility();
        }

        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);
        }

        private IFilter? Editor_OnDefineFilter(Type type, string column)
        {
            return OnDefineFilter?.Invoke(type, column);
        }

        private void ClearEvents()
        {
            // These are events that are set by the grid/host.
            OnCreateEditorControl = null;
            OnEditorValueChanged = null;
            OnFormCustomiseEditor = null;
            OnAfterEditorValueChanged = null;
            OnSelectPage = null;
            OnValidateData = null;
        }

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

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

            Editor.PreloadPages = PreloadPages;

            Pages = pages ?? new DynamicEditorPages();

            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(0, 0, 5, 0);
                    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(IDynamicEditorForm 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;
        }

        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();
        }

        public bool Validate()
        {
            var errors = OnValidateData?.Invoke(this, Items);

            if (errors != null && errors.Any())
            {
                MessageWindow.ShowMessage(
                    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 false;
            }

            _validated = true;
            return true;
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            if (!Validate())
            {
                return;
            }
            OnOK?.Invoke();
            //OKButton.IsEnabled = false;
            //CancelButton.IsEnabled = false;

            // 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();

            foreach(var page in Editor.Pages)
            {
                page.Cancel();
            }

            OnCancel?.Invoke();
            //OKButton.IsEnabled = false;
            //CancelButton.IsEnabled = false;
            //Close();
        }

        public void SaveItem(CancelEventArgs e)
        {
            if(!_validated && !Validate())
            {
                e.Cancel = true;
                return;
            }
            OnSaveItem?.Invoke(this, e);
            _validated = false;
        }

        public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
        {
            return Editor.TryFindEditor(columnname, out editor);
        }

        public object? GetEditorValue(string columnName) => this.FindEditor(columnName).GetValue(columnName);
        public void SetEditorValue(string columnName, object? value) => this.FindEditor(columnName).SetValue(columnName, value);

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

        public void SetLayoutType(Type t) => Editor.SetLayoutType(t);

        public void SetLayout(DynamicEditorGridLayout layout) => Editor.SetLayout(layout);

        private void Editor_OnSelectPage(DynamicEditorGrid sender, BaseObject[] items)
        {
            OnSelectPage?.Invoke(sender, items);
        }

        private void Editor_OnUnloadPage(IDynamicEditorPage page, bool saved)
        {
            if (!saved)
                page.BeforeSave(Items.First());
            else
                page.AfterSave(Items.First());
        }

        private Dictionary<string, object?>? Editor_OnAfterEditorValueChanged(DynamicEditorGrid sender, AfterEditorValueChangedArgs args)
        {
            if(args.ChangedValues.Count > 0)
            {
                DoChanged();
            }
            return OnAfterEditorValueChanged?.Invoke(sender, args);
        }

        private void Editor_OnReconfigureEditors(DynamicEditorGrid sender)
        {
            OnReconfigureEditors?.Invoke(sender);
        }

        private void Editor_OnGridCustomiseEditor(DynamicEditorGrid sender, DynamicGridColumn column, BaseEditor editor)
        {
            OnFormCustomiseEditor?.Invoke(this, Items, column, editor);
        }

        private decimal Editor_OnGetSequence(DynamicGridColumn column)
        {
            if (_items.Any())
                return CoreUtils.GetPropertySequence(_items.First().GetType(), column.ColumnName);
            return 0.0M;
        }

        private void Editor_OnDefineLookups(ILookupEditorControl editor)
        {
            OnDefineLookups?.Invoke(editor);
        }

        private BaseObject[] Editor_GetItems()
        {
            return _items;
        }

        private BaseEditor Editor_OnGetEditor(DynamicGridColumn column)
        {
            if (_items != null && _items.Any())
            {
                var property = DatabaseSchema.Property(Editor.UnderlyingType, column.ColumnName);
                if (property == null) return new NullEditor();

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

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

                //if (ReadOnly && editor.Editable.IsEditable())
                //    editor.Editable = Editable.Disabled;
                return editor;
            }

            return new NullEditor();
        }

        private object? Editor_OnGetPropertyValue(object sender, string column)
        {
            if (!_items.Any())
                return null;

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

            if (result == null)
                return null;

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

                if (curvalue == null)
                    return null;

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

            return result;
        }

        private void Editor_OnSetPropertyValue(object sender, string column, object value)
        {
            foreach (var _item in _items)
                if (_item.UserProperties.ContainsKey(column))
                    _item.UserProperties[column] = value;
                else
                    CoreUtils.SetPropertyValue(_item, column, value);
        }

        //public void EditLayout() => Editor.EditLayout();
        //public void ResetLayout() => Editor.ResetLayout();
        public void AddButton(Button button)
        {
            Buttons.Children.Add(button);
        }

        private Dictionary<string, object?> Editor_OnEditorValueChanged(DynamicEditorGrid sender, string name, object value)
        {
            return EditorValueChanged(this, name, value);
        }

        private void Editor_OnReload(DynamicEditorGrid sender)
        {
            OnReload?.Invoke(this);
        }

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