using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Localization;
using Robust.Shared.Maths;

namespace Robust.Client.UserInterface.Controls
{
    [Virtual]
    public class TabContainer : Container
    {
        public static readonly AttachedProperty<bool> TabVisibleProperty = AttachedProperty<bool>.Create("TabVisible", typeof(TabContainer), true);
        public static readonly AttachedProperty<string?> TabTitleProperty = AttachedProperty<string?>.CreateNull("TabTitle", typeof(TabContainer));

        public const string StylePropertyTabStyleBox = "tab-stylebox";
        public const string StylePropertyTabStyleBoxInactive = "tab-stylebox-inactive";
        public const string stylePropertyTabFontColor = "tab-font-color";
        public const string StylePropertyTabFontColorInactive = "tab-font-color-inactive";
        public const string StylePropertyPanelStyleBox = "panel-stylebox";

        private int _currentTab;
        private bool _tabsVisible = true;

        // the laid out tabs
        private List<TabBox> _tabBoxes = new();
        private float _enclosingTabHeight;

        public int CurrentTab
        {
            get => _currentTab;
            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(value), value, "Current tab must be positive.");
                }

                if (value >= ChildCount)
                {
                    throw new ArgumentOutOfRangeException(nameof(value), value,
                        "Current tab must less than the amount of tabs.");
                }

                if (_currentTab == value)
                {
                    return;
                }

                var old = _currentTab;
                _currentTab = value;

                GetChild(old).Visible = false;
                var newSelected = GetChild(value);
                newSelected.Visible = true;
                InvalidateMeasure();

                OnTabChanged?.Invoke(value);
            }
        }

        public bool TabsVisible
        {
            get => _tabsVisible;
            set
            {
                _tabsVisible = value;
                InvalidateMeasure();
            }
        }

        public StyleBox? PanelStyleBoxOverride { get; set; }
        public Color? TabFontColorOverride { get; set; }
        public Color? TabFontColorInactiveOverride { get; set; }

        public event Action<int>? OnTabChanged;

        public TabContainer()
        {
            MouseFilter = MouseFilterMode.Pass;
        }

        public string GetActualTabTitle(int tab)
        {
            var control = GetChild(tab);
            var title = control.GetValue(TabTitleProperty);

            return title ?? control.Name ?? Loc.GetString("tab-container-not-tab-title-provided");
        }

        public static string? GetTabTitle(Control control)
        {
            return control.GetValue(TabTitleProperty);
        }

        public bool GetTabVisible(int tab)
        {
            var control = GetChild(tab);
            return GetTabVisible(control);
        }

        public static bool GetTabVisible(Control control)
        {
            return control.GetValue(TabVisibleProperty);
        }

        public void SetTabTitle(int tab, string title)
        {
            var control = GetChild(tab);
            SetTabTitle(control, title);
        }

        public static void SetTabTitle(Control control, string title)
        {
            control.SetValue(TabTitleProperty, title);
        }

        public void SetTabVisible(int tab, bool visible)
        {
            var control = GetChild(tab);
            SetTabVisible(control, visible);
        }

        public static void SetTabVisible(Control control, bool visible)
        {
            control.SetValue(TabVisibleProperty, visible);
        }

        protected override void ChildAdded(Control newChild)
        {
            base.ChildAdded(newChild);

            if (ChildCount == 1)
            {
                // This is our first child so it must always be visible.
                newChild.Visible = true;
            }
            else
            {
                // If not this can't be the currently selected tab so just make it invisible immediately.
                newChild.Visible = false;
            }
        }

        protected internal override void Draw(DrawingHandleScreen handle)
        {
            base.Draw(handle);

            // First, draw panel.
            var headerSize = _enclosingTabHeight;
            var panel = _getPanel();
            var panelBox = new UIBox2(0, headerSize, PixelWidth, PixelHeight);

            panel?.Draw(handle, panelBox, UIScale);

            var font = _getFont();
            var fontColorActive = _getTabFontColorActive();
            var fontColorInactive = _getTabFontColorInactive();

            // Then draw the tabs
            foreach (var tabBox in _tabBoxes)
            {
                if (tabBox.Box is { } styleBox)
                {
                    styleBox.Draw(handle, tabBox.Bounding, UIScale);
                }

                var baseLine = new Vector2(0, font.GetAscent(UIScale)) + tabBox.Content.TopLeft;
                foreach (var rune in tabBox.Title.EnumerateRunes())
                {
                    if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
                        continue;

                    font.DrawChar(handle, rune, baseLine, UIScale, tabBox.Index == _currentTab ? fontColorActive : fontColorInactive);
                    baseLine += new Vector2(metrics.Advance, 0);
                }
            }
        }

        private readonly record struct TabBox(UIBox2 Bounding, UIBox2 Content, StyleBox? Box, string Title, int Index);

        private void CalculateTabBoxes(Vector2 availableSize)
        {
            availableSize *= UIScale;
            var tabLeft = 0f;
            var tabTop = 0f;
            var tabHeight = 0f;

            var font = _getFont();
            var boxActive = _getTabBoxActive();
            var boxInactive = _getTabBoxInactive();

            _tabBoxes.Clear();

            if (!_tabsVisible)
                return;

            for (var i = 0; i < ChildCount; i++)
            {
                if (!GetTabVisible(i))
                    continue;

                var title = GetActualTabTitle(i);

                var titleLength = 0;
                foreach (var rune in title.EnumerateRunes())
                {
                    if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
                        continue;

                    titleLength += metrics.Advance;
                }

                var active = _currentTab == i;
                var box = active ? boxActive : boxInactive;

                var topLeft = new Vector2(tabLeft, tabTop);
                var size = new Vector2(titleLength, font.GetHeight(UIScale));

                if (box != null)
                {
                    size = box.GetEnvelopBox(topLeft, size, UIScale).Size;
                }

                if (tabLeft + size.X > availableSize.X)
                {
                    tabLeft = 0;
                    tabTop += tabHeight;
                    tabHeight = 0;
                }

                topLeft = new(tabLeft, tabTop);
                size = new(titleLength, font.GetHeight(UIScale));

                UIBox2 boundingBox;
                UIBox2 contentBox;
                if (box != null)
                {
                    boundingBox = box.GetEnvelopBox(topLeft, size, UIScale);
                    contentBox = box.GetContentBox(boundingBox, UIScale);
                }
                else
                {
                    contentBox = UIBox2.FromDimensions(topLeft, size);
                    boundingBox = contentBox;
                }

                tabLeft += boundingBox.Size.X;
                tabHeight = Math.Max(tabHeight, boundingBox.Size.Y);
                _tabBoxes.Add(new(boundingBox, contentBox, box, title, i));
            }

            if (Math.Abs(_enclosingTabHeight - (tabTop + tabHeight)) >= 0.1)
            {
                InvalidateArrange();
            }
            _enclosingTabHeight = tabTop + tabHeight;
        }

        protected override Vector2 MeasureOverride(Vector2 availableSize)
        {
            CalculateTabBoxes(availableSize);
            var headerSize = Vector2.Zero;

            if (TabsVisible)
            {
                headerSize = new Vector2(0, _enclosingTabHeight / UIScale);
            }

            var panel = _getPanel();
            var panelSize = (panel?.MinimumSize ?? Vector2.Zero);

            var contentsSize = availableSize - headerSize - panelSize;

            var total = Vector2.Zero;
            foreach (var child in Children)
            {
                if (child.Visible)
                {
                    child.Measure(contentsSize);
                    total = Vector2.Max(child.DesiredSize, total);
                }
            }

            return total + headerSize + panelSize;
        }

        protected override Vector2 ArrangeOverride(Vector2 finalSize)
        {
            CalculateTabBoxes(finalSize);
            if (ChildCount == 0 || _currentTab >= ChildCount)
            {
                return finalSize;
            }

            var headerSize = (int)_enclosingTabHeight;
            var panel = _getPanel();
            var contentBox = new UIBox2i(0, headerSize, (int) (finalSize.X * UIScale), (int) (finalSize.Y * UIScale));
            if (panel != null)
            {
                contentBox = (UIBox2i) panel.GetContentBox(contentBox, UIScale);
            }

            var control = GetChild(_currentTab);
            control.Visible = true;
            control.ArrangePixel(contentBox);
            return finalSize;
        }

        protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
        {
            base.KeyBindDown(args);

            if (!TabsVisible || args.Function != EngineKeyFunctions.UIClick)
            {
                return;
            }

            // Outside of header size, ignore.
            if (args.RelativePixelPosition.Y < 0 || args.RelativePixelPosition.Y > _enclosingTabHeight)
            {
                return;
            }

            args.Handle();

            if (!TryGetHoveredTab(args.RelativePixelPosition, out var index))
            {
                return;
            }

            CurrentTab = index.Value;
        }

        protected internal override void MouseMove(GUIMouseMoveEventArgs args)
        {
            base.MouseMove(args);

            DefaultCursorShape = TryGetHoveredTab(args.RelativePixelPosition, out _)
                ? CursorShape.Pointer
                : CursorShape.Arrow;
        }

        protected internal override void MouseExited()
        {
            base.MouseExited();

            DefaultCursorShape = CursorShape.Arrow;
        }

        private bool TryGetHoveredTab(Vector2 position, [NotNullWhen(true)] out int? index)
        {
            index = null;

            if (!TabsVisible || position.Y < 0 || position.Y > _enclosingTabHeight)
            {
                return false;
            }

            foreach (var box in _tabBoxes)
            {
                if (box.Bounding.Contains(position))
                {
                    index = box.Index;
                    return true;
                }
            }

            return false;
        }

        [System.Diagnostics.Contracts.Pure]
        private StyleBox? _getTabBoxActive()
        {
            TryGetStyleProperty<StyleBox>(StylePropertyTabStyleBox, out var box);
            return box;
        }

        [System.Diagnostics.Contracts.Pure]
        private StyleBox? _getTabBoxInactive()
        {
            TryGetStyleProperty<StyleBox>(StylePropertyTabStyleBoxInactive, out var box);
            return box;
        }

        [System.Diagnostics.Contracts.Pure]
        private Color _getTabFontColorActive()
        {
            if (TabFontColorOverride != null)
                return TabFontColorOverride.Value;

            if (TryGetStyleProperty(stylePropertyTabFontColor, out Color color))
            {
                return color;
            }
            return Color.White;
        }

        [System.Diagnostics.Contracts.Pure]
        private Color _getTabFontColorInactive()
        {
            if (TabFontColorInactiveOverride != null)
                return TabFontColorInactiveOverride.Value;

            if (TryGetStyleProperty(StylePropertyTabFontColorInactive, out Color color))
            {
                return color;
            }
            return Color.Gray;
        }

        [System.Diagnostics.Contracts.Pure]
        private StyleBox? _getPanel()
        {
            if (PanelStyleBoxOverride != null)
                return PanelStyleBoxOverride;

            TryGetStyleProperty<StyleBox>(StylePropertyPanelStyleBox, out var box);
            return box;
        }

        [System.Diagnostics.Contracts.Pure]
        private Font _getFont()
        {
            if (TryGetStyleProperty<Font>("font", out var font))
            {
                return font;
            }

            return UserInterfaceManager.ThemeDefaults.DefaultFont;
        }
    }
}
