using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;

namespace HSUI.Config.Tree
{
    public abstract class ConfigNode
    {
        public bool CollapseControl { get; set; }

        public bool IsChild { get; set; }

        public string Name { get; private set; }

        public bool Nest { get; set; } = true;

        public string? ParentName { get; set; }

        public int Position { get; set; } = Int32.MaxValue;

        public bool Separator { get; set; }

        public bool Spacing { get; set; }

        public bool CollapsingHeader { get; set; }

        public string? ID { get; private set; }

        protected PluginConfigObject ConfigObject { get; set; }

        public ConfigNode(PluginConfigObject configObject, string? id, string name)
        {
            ConfigObject = configObject;
            ID = id;
            Name = name;
        }

        public abstract bool Draw(ref bool changed, int depth = 0);

        protected void DrawSeparatorOrSpacing()
        {
            if (Separator)
            {
                ImGuiHelper.DrawSeparator(1, 1);
            }

            if (Spacing)
            {
                ImGuiHelper.DrawSpacing(1);
            }
        }

        protected static void DrawNestIndicator(int depth)
        {
            // This draws the L shaped symbols and padding to the left of config items collapsible under a checkbox.
            // Shift cursor to the right to pad for children with depth more than 1.
            // 26 is an arbitrary value I found to be around half the width of a checkbox
            ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(26, 0) * Math.Max((depth - 1), 0));

            if (ConfigurationManager.Instance.OverrideDalamudStyle)
            {
                ImGui.TextColored(new Vector4(229f / 255f, 57f / 255f, 57f / 255f, 1f), "\u2514");
            }
            else
            {
                ImGui.Text("\u2514");
            }

            ImGui.SameLine();
        }

        protected static ConfigAttribute? GetConfigAttribute(FieldInfo field)
        {
            return field.GetCustomAttributes(true).Where(a => a is ConfigAttribute).FirstOrDefault() as ConfigAttribute;
        }
    }

    public class FieldNode : ConfigNode
    {
        private SortedDictionary<int, ConfigNode> _childNodes;
        private ConfigAttribute? _configAttribute;
        private FieldInfo _mainField;

        public FieldInfo Field => _mainField;

        public FieldNode(FieldInfo mainField, PluginConfigObject configObject, string? id) : base(configObject, id, mainField.Name)
        {
            _mainField = mainField;
            _childNodes = new SortedDictionary<int, ConfigNode>();

            _configAttribute = GetConfigAttribute(mainField);
            if (_configAttribute is not null)
            {
                Separator = _configAttribute.separator;
                Spacing = _configAttribute.spacing;
            }
        }

        public void AddChild(int position, ConfigNode field)
        {
            field.IsChild = true;

            while (_childNodes.ContainsKey(position))
            {
                position++;
            }

            _childNodes.Add(position, field);
        }

        public override bool Draw(ref bool changed, int depth = 0)
        {
            bool reset = false;
            DrawSeparatorOrSpacing();

            if (!Nest)
            {
                depth = 0;
            }

            if (depth > 0)
            {
                DrawNestIndicator(depth);
            }

            bool collapsing = CollapsingHeader && ConfigObject.Disableable;

            // Draw the ConfigAttribute
            if (!collapsing)
            {
                DrawConfigAttribute(ref changed, _mainField);
            }

            bool enabled = _mainField.GetValue(ConfigObject) as bool? ?? false;

            // Draw children
            if (CollapseControl && Attribute.IsDefined(_mainField, typeof(CheckboxAttribute)))
            {
                if (collapsing)
                {
                    if (ImGui.CollapsingHeader(ID + "##CollapsingHeader"))
                    {
                        DrawNestIndicator(depth);
                        DrawConfigAttribute(ref changed, _mainField);

                        if (enabled)
                        {
                            reset |= DrawChildren(ref changed, depth);
                        }
                    }
                }
                else if (!collapsing && enabled)
                {
                    ImGui.BeginGroup();
                    reset |= DrawChildren(ref changed, depth);
                    ImGui.EndGroup();
                }
            }

            return reset;
        }

        private bool DrawChildren(ref bool changed, int depth)
        {
            bool reset = false;

            int childDepth = depth + 1;
            foreach (ConfigNode child in _childNodes.Values)
            {
                if (child.Separator)
                {
                    childDepth = 0;
                }

                reset |= child.Draw(ref changed, childDepth);
            }

            return reset;
        }

        private void DrawConfigAttribute(ref bool changed, FieldInfo field)
        {
            if (_configAttribute is not null)
            {
                changed |= _configAttribute.Draw(field, ConfigObject, ID, CollapsingHeader);
            }
        }
    }

    public class ManualDrawNode : ConfigNode
    {
        private MethodInfo _drawMethod;

        public ManualDrawNode(MethodInfo method, PluginConfigObject configObject, string? id) : base(configObject, id, id ?? "")
        {
            _drawMethod = method;
        }

        public override bool Draw(ref bool changed, int depth = 0)
        {
            object[] args = new object[] { false };
            bool? result = (bool?)_drawMethod.Invoke(ConfigObject, args);

            bool arg = (bool)args[0];
            changed |= arg;
            return result ?? false;
        }
    }
}