using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UIElements;

// ReSharper disable UsageOfDefaultStructEquality, to disable the warnings about IndexOf(currentPage). It's fine.
namespace Reset.Core.Tools{
    public class DebugOverlayDrawer : MonoBehaviour{
        
        [Header("Asset References")]
        [BoxGroup("References")] public VisualTreeAsset valueTemplate;
        [BoxGroup("References")] public VisualTreeAsset pageBaseTemplate;
        [BoxGroup("References")] public VisualTreeAsset pageButtonTemplate;
        [Header("Scene References")]
        [BoxGroup("References")][ShowInInspector] public UIDocument root;
        [BoxGroup("References")]public GameObject canvasRootGameObject;
        
        [Space(10)]
        [ShowInInspector]
        public Dictionary<GameObject, List<string>> Pages = new(); // Page and value names
        [Space(5)]
        [ShowInInspector]
        public Dictionary<object, Label> values = new(); // Value name and value value (lol)

        [Space]
        public string pageNamePrefix = "Debug Page: ";

        public static bool Locked;
        private static float defaultLabelAlpha;
        private static float defaultValueAlpha;
            
        // Private variables
        [ShowInInspector]
        public KeyValuePair<GameObject, List<string>> currentPage;
        
        public enum DebugOverlayAnchor{
            TopLeft,
            TopRight,
            BottomLeft,
            BottomRight,
            Center
        }

        void Awake(){
            if (Instance != null) {
                Destroy(this);
            }
            
            
            
            Instance = this;
        }

        void Start(){
            canvasRootGameObject = GameManager.UI;
            root = canvasRootGameObject.transform.Find("Debug Overlay").GetComponent<UIDocument>();
            
            SetCurrentPageVisible();
            
            Debug.Log(valueTemplate.Instantiate().resolvedStyle.backgroundColor);

            defaultLabelAlpha = valueTemplate.Instantiate().Query<VisualElement>("LabelBackground").First().resolvedStyle.backgroundColor.a;
            defaultValueAlpha = valueTemplate.Instantiate().Query<VisualElement>("ValueBackground").First().resolvedStyle.backgroundColor.a;
        }

        public static DebugOverlayDrawer Instance;

        public static void AddOnOverlay(string pageName, string sourceName){
            // Make sure the page exists. If it does, use it. If not, create it
            GameObject thisPageObject = null;
            bool alreadyExisted = false;

            // This checks for a gameobject with the page name. Sets it if it finds it
            foreach (var page in Instance.Pages) {
                if (page.Key.name == $"{Instance.pageNamePrefix}{pageName}") {
                    thisPageObject = page.Key;
                    alreadyExisted = true;
                    break;
                }
            }

            // Otherwise, this will make a new one and add it
            UIDocument thisDocument; 
            
            if (thisPageObject == null) {
                // Create a new object
                thisPageObject = new GameObject(name: $"{Instance.pageNamePrefix}{pageName}");
                
                // Add it to the list of page objects and set it's transform
                KeyValuePair<GameObject, List<string>> newPage = new(thisPageObject, new List<string>());
                Instance.Pages.Add(newPage.Key, newPage.Value);
                thisPageObject.transform.SetParent(Instance.canvasRootGameObject.transform);
                
                // Add a UI Document for it, give it the base template
                thisDocument = thisPageObject.AddComponent<UIDocument>();
                thisDocument.visualTreeAsset = Instance.pageBaseTemplate;

                // Then add a button/header to the main container
                VisualElement newPageButton = Instance.pageButtonTemplate.Instantiate();
                Instance.root.rootVisualElement.Query<VisualElement>("Pages").First().Add(newPageButton);
                ((Label)newPageButton.Query<VisualElement>("PageLabel").First()).text = pageName;
            } else {
                thisDocument = thisPageObject.GetComponent<UIDocument>();
            }
            
            // If this is the only page, set it to the current page
            if (Instance.Pages.Count == 1) {
                ChangeToPage(pageName);
                SetCurrentPageVisible();
            }

            // Warning for the page being null for whatever reason by this point
            if (thisPageObject == null) {
                Debug.LogError(
                    $"Debug Overlay Manager never found a page named: {sourceName}. It failed to create one and aborted.");
                return;
            }

            // Now, iterate through this page and see if the variable is already tracked
            if (alreadyExisted && Instance.Pages[thisPageObject].Contains(sourceName)) {
                return;
            }

            // Since it doesn't exist, add it to the list of tracked variables
            Instance.Pages[thisPageObject].Add(sourceName);
            
            // Then create a new on-screen element for this variable
            VisualElement thisElement = (Instance.valueTemplate.Instantiate());
            thisDocument.rootVisualElement.Query<VisualElement>("ValuesContainer").First().Add(thisElement);
            
            // Set it's label
            ((Label)thisElement.Query<VisualElement>("Label").First()).text = sourceName;
            
            Instance.values.Add($"{pageName}/{sourceName}", (Label)thisElement.Query<VisualElement>("Value"));
        }

        // Publicly accessible method to change the value
        public static void ChangeValue(string pageName, string sourceName, string newValue){
            try {
                Instance.values[$"{pageName}/{sourceName}"].text = newValue;
            } catch (Exception e) {
                try {
                    AddOnOverlay(pageName, sourceName);
                    Instance.values[$"{pageName}/{sourceName}"].text = newValue;
                    throw;
                } catch (Exception exception) {
                    Debug.LogError($"Failed to both update an existing or create a new debug overlay: {exception.Message}" );
                    throw;
                }
            }
        }

        // Publicly accessible method to change the color
        public static void ChangeColor(string pageName, string sourceName, Color newColor){
            VisualElement labelBG = Instance.values[$"{pageName}/{sourceName}"].parent.parent.Query<VisualElement>("LabelBackground").First();
            VisualElement valueBG = Instance.values[$"{pageName}/{sourceName}"].parent.parent.Query<VisualElement>("ValueBackground").First();

            labelBG.style.backgroundColor = ((newColor / 1.2f + Color.gray * .3f)* .7f).Alpha(defaultLabelAlpha);
            valueBG.style.backgroundColor = ((newColor / 1.5f + Color.gray * .4f) * .6f) .Alpha(defaultValueAlpha);  
        }
        
        // Change to a specific page
        public static void ChangeToPage(string pageName){
            if (Locked) {
                Debug.LogWarning($"Can't switch to page {pageName}: Page is currently locked");
                return;
            }
            
            try {
                Instance.currentPage = Instance.Pages.Where(pair => pair.Key.name == Instance.pageNamePrefix + pageName).First();
            } catch (Exception e) {
                Debug.LogError($"Couldn't switch page to {pageName}: {e.Message}");
                throw;
            }
        }

        // Back a page
        public static void PreviousPage(){
            // Do nothing if there's only one page
            if (Instance.Pages.Count == 1) {
                return;
            }
            
            // Lock check
            if (Locked) {
                Debug.LogWarning($"Attempted to switch to a new page but the page is locked.");
                return;
            }
            
            // Get the index of the current page
            int currentIndex = Instance.Pages.ToList().IndexOf(Instance.currentPage);
            
            // Change the page with wrapping
            if (Instance.Pages.First().Key == Instance.currentPage.Key) {
                Instance.currentPage = Instance.Pages.Last();
            } else {
                Instance.currentPage = Instance.Pages.ElementAt(currentIndex - 1);
            }

            // Set visibility
            SetCurrentPageVisible();
        }

        public static void NextPage(){
            // Do nothing if there's only one page
            if (Instance.Pages.Count == 1) {
                return;
            }
            
            // Lock check
            if (Locked) {
                Debug.LogWarning($"Attempted to switch to a new page but the page is locked.");
                return;
            }
            
            // Get the index of the current page
            int currentIndex = Instance.Pages.ToList().IndexOf(Instance.currentPage);
            
            // Change the page with wrapping
            if (Instance.Pages.Last().Key == Instance.currentPage.Key) {
                Instance.currentPage = Instance.Pages.First();
            } else {
                Instance.currentPage = Instance.Pages.ElementAt(currentIndex + 1);
            }
            
            // Set visibility
            SetCurrentPageVisible();
        }

        static void SetCurrentPageVisible(){
            // Set the display property for the page to enable/disable them
            foreach (var page in Instance.Pages) {
                if (page.Key == Instance.currentPage.Key) {
                    page.Key.GetComponent<UIDocument>().rootVisualElement.style.display = new StyleEnum<DisplayStyle>(DisplayStyle.Flex);
                } else {
                    page.Key.GetComponent<UIDocument>().rootVisualElement.style.display = new StyleEnum<DisplayStyle>(DisplayStyle.None);
                }
            }

            // Turn all the page labels into a list
            var pageButtons = Instance.root.rootVisualElement.Query<Label>("PageLabel").ToList();
            
            // Iterate through said list and set the opacity of the parent (so everything including the label gets turned down)
            foreach (Label page in pageButtons) {
                page.parent.style.opacity = .4f;
                if (Instance.currentPage.Key.name == $"{Instance.pageNamePrefix}{page.text}") {
                    page.parent.style.opacity = 1f;
                }
            }
        }
    }
}
