using UnityEditor;
using UnityEditor.AI;
using UnityEditor.Overlays;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UIElements;

namespace Unity.AI.Navigation.Editor
{
    [Overlay(typeof(SceneView), "AINavigationOverlay", "AI Navigation", defaultDisplay = true)]
    [Icon(NavMeshComponentsGUIUtility.k_PackageEditorResourcesFolder + "Overlay/NavigationOverlay.png")]
    internal class NavigationOverlay : Overlay
    {
        static class Style
        {
            internal static readonly GUIContent SurfacesSectionTexts =
                EditorGUIUtility.TrTextContent("Surfaces");

            internal static readonly GUIContent SurfacesSelectedOnlyTexts =
                EditorGUIUtility.TrTextContent("Show Only Selected", "Check this to hide surfaces which are not part of the selection hierarchy");

            internal static readonly GUIContent SurfacesNavMeshTexts =
                EditorGUIUtility.TrTextContent("Show NavMesh", "Display navigation mesh using the associated area's color");

            internal static readonly GUIContent SurfacesHeightMeshTexts =
                EditorGUIUtility.TrTextContent("Show HeightMesh", "Display height mesh used for accurate vertical placement");

            internal static readonly GUIContent AgentsSectionTexts =
                EditorGUIUtility.TrTextContent("Agents");

            internal static readonly GUIContent AgentsPathPolysTexts =
                EditorGUIUtility.TrTextContent("Show Path Polygons", "Shows the polygons leading to goal.");

            internal static readonly GUIContent AgentsPathNodesTexts =
                EditorGUIUtility.TrTextContent("Show Path Query Nodes", "Shows the nodes expanded during last path query.");

            internal static readonly GUIContent AgentsNeighboursTexts =
                EditorGUIUtility.TrTextContent("Show Neighbours", "Show the agent neighbours considered during simulation.");

            internal static readonly GUIContent AgentsWallsTexts =
                EditorGUIUtility.TrTextContent("Show Walls", "Shows the wall segments handled during simulation.");

            internal static readonly GUIContent AgentsAvoidanceTexts =
                EditorGUIUtility.TrTextContent("Show Avoidance", "Shows the processed avoidance geometry from simulation.");

            internal static readonly GUIContent AgentsAvoidancePendingDebugRequestTexts =
                EditorGUIUtility.TrTextContent("Avoidance display is not valid until after next game update.", "Avoidance information will be computed on the next game update");

            internal static readonly GUIContent AgentsAvoidanceDebugRequestsCountExceededTexts =
                EditorGUIUtility.TrTextContent("", "Avoidance information display is limited to a fixed number of agents"); // This text is dynamic

            internal static readonly GUIContent ObstaclesSectionTexts =
                EditorGUIUtility.TrTextContent("Obstacles");

            internal static readonly GUIContent ObstaclesCarveHullText =
                EditorGUIUtility.TrTextContent("Show Carve Hull", "Shows the hull used to carve the obstacle from the NavMesh.");

            internal static readonly GUIContent DeveloperModeSectionTexts =
                EditorGUIUtility.TrTextContent("Developer Mode");

            internal static readonly GUIContent SurfacesPortalsTexts =
                EditorGUIUtility.TrTextContent("Show NavMesh Portals");

            internal static readonly GUIContent SurfacesTileLinksTexts =
                EditorGUIUtility.TrTextContent("Show NavMesh Tile Links");

            internal static readonly GUIContent SurfacesHeightMeshBVTreeTexts =
                EditorGUIUtility.TrTextContent("Show HeightMesh BV-Tree");

            internal static readonly GUIContent SurfacesHeightMapsTexts =
                EditorGUIUtility.TrTextContent("Show HeightMaps", "Display terrain height maps used for accurate vertical placement");

            internal static readonly GUIContent SurfacesProximityGridTexts =
                EditorGUIUtility.TrTextContent("Show Proximity Grid");

            internal static readonly GUIContent NavigationVisualizationDisabledTexts =
                EditorGUIUtility.TrTextContent("Navigation visualization is not available in prefab edition.", "");
        }

        VisualElement m_RootPanel;
        VisualElement m_OptionsPanel;
        VisualElement m_AgentFoldOut;
        HelpBox m_VisualizationDisabledHelpBox;
        HelpBox m_AgentCountWarning;
        HelpBox m_AgentPendingRequestWarning;

        public override void OnCreated()
        {
            base.OnCreated();

            NavMeshEditorHelpers.agentRejectedDebugInfoRequestsCountChanged += OnAgentRejectedDebugInfoRequestsCountChanged;
            NavMeshEditorHelpers.agentDebugRequestsPending += DisplayAgentPendingRequestWarningBox;
            NavMeshEditorHelpers.agentDebugRequestsProcessed += HideAgentPendingRequestWarningBox;

            PrefabStage.prefabStageOpened += OnPrefabStageOpened;
            PrefabStage.prefabStageClosing += OnPrefabStageClosing;
        }

        public override void OnWillBeDestroyed()
        {
            NavMeshEditorHelpers.agentRejectedDebugInfoRequestsCountChanged -= OnAgentRejectedDebugInfoRequestsCountChanged;
            NavMeshEditorHelpers.agentDebugRequestsPending -= DisplayAgentPendingRequestWarningBox;
            NavMeshEditorHelpers.agentDebugRequestsProcessed -= HideAgentPendingRequestWarningBox;

            PrefabStage.prefabStageOpened -= OnPrefabStageOpened;
            PrefabStage.prefabStageClosing -= OnPrefabStageClosing;

            base.OnWillBeDestroyed();
        }

        public override VisualElement CreatePanelContent()
        {
            m_RootPanel = new VisualElement();

            m_OptionsPanel = new VisualElement();
            m_RootPanel.Add(m_OptionsPanel);

            m_VisualizationDisabledHelpBox = AddHelpBox(HelpBoxMessageType.Info, Style.NavigationVisualizationDisabledTexts, 200, 10, false);
            var visualizationEnabled = PrefabStageUtility.GetCurrentPrefabStage() == null;
            SetVisualizationEnabled(visualizationEnabled);

            // Surfaces
            var surfacesFoldout = AddFoldout(m_OptionsPanel, Style.SurfacesSectionTexts, 5);
            AddToggle(surfacesFoldout, Style.SurfacesSelectedOnlyTexts, NavMeshVisualizationSettings.showOnlySelectedSurfaces,
                (evt => NavMeshVisualizationSettings.showOnlySelectedSurfaces = evt.newValue));
            AddToggle(surfacesFoldout, Style.SurfacesNavMeshTexts, NavMeshVisualizationSettings.showNavMesh,
                evt => NavMeshVisualizationSettings.showNavMesh = evt.newValue);
            AddToggle(surfacesFoldout, Style.SurfacesHeightMeshTexts, NavMeshVisualizationSettings.showHeightMesh,
                evt => NavMeshVisualizationSettings.showHeightMesh = evt.newValue);

            // Agents
            m_AgentFoldOut = AddFoldout(m_OptionsPanel, Style.AgentsSectionTexts, 5);
            AddToggle(m_AgentFoldOut, Style.AgentsPathPolysTexts, NavMeshVisualizationSettings.showAgentPath, evt => NavMeshVisualizationSettings.showAgentPath = evt.newValue);
            AddToggle(m_AgentFoldOut, Style.AgentsPathNodesTexts, NavMeshVisualizationSettings.showAgentPathInfo,
                evt => NavMeshVisualizationSettings.showAgentPathInfo = evt.newValue);
            AddToggle(m_AgentFoldOut, Style.AgentsNeighboursTexts, NavMeshVisualizationSettings.showAgentNeighbours,
                evt => NavMeshVisualizationSettings.showAgentNeighbours = evt.newValue);
            AddToggle(m_AgentFoldOut, Style.AgentsWallsTexts, NavMeshVisualizationSettings.showAgentWalls, evt => NavMeshVisualizationSettings.showAgentWalls = evt.newValue);
            AddToggle(m_AgentFoldOut, Style.AgentsAvoidanceTexts, NavMeshVisualizationSettings.showAgentAvoidance,
                evt => NavMeshVisualizationSettings.showAgentAvoidance = evt.newValue);

            // Create avoidance requests count warning box and display it if needed
            m_AgentCountWarning = AddHelpBox(HelpBoxMessageType.Warning, Style.AgentsAvoidanceDebugRequestsCountExceededTexts, 180, 5, false);
            NavMeshEditorHelpers.GetAgentsDebugInfoRejectedRequestsCount(out var rejected, out var allowed);
            if (rejected > 0)
            {
                DisplayAgentCountWarningBox(rejected, allowed);
            }

            // Create avoidance pending requests warning box and display it if needed
            m_AgentPendingRequestWarning = AddHelpBox(HelpBoxMessageType.Warning, Style.AgentsAvoidancePendingDebugRequestTexts, 180, 5, false);
            if (NavMeshEditorHelpers.HasPendingAgentDebugInfoRequests())
            {
                DisplayAgentPendingRequestWarningBox();
            }

            // Obstacles
            var obstaclesFoldout = AddFoldout(m_OptionsPanel, Style.ObstaclesSectionTexts, 5);
            AddToggle(obstaclesFoldout, Style.ObstaclesCarveHullText, NavMeshVisualizationSettings.showObstacleCarveHull,
                evt => NavMeshVisualizationSettings.showObstacleCarveHull = evt.newValue);

            // Developer Mode only
            if (Unsupported.IsDeveloperMode())
            {
                var developerModeFoldout = AddFoldout(m_OptionsPanel, Style.DeveloperModeSectionTexts, 5);
                AddToggle(developerModeFoldout, Style.SurfacesPortalsTexts, NavMeshVisualizationSettings.showNavMeshPortals,
                    evt => NavMeshVisualizationSettings.showNavMeshPortals = evt.newValue);
                AddToggle(developerModeFoldout, Style.SurfacesTileLinksTexts, NavMeshVisualizationSettings.showNavMeshLinks,
                    evt => NavMeshVisualizationSettings.showNavMeshLinks = evt.newValue);
                AddToggle(developerModeFoldout, Style.SurfacesHeightMeshBVTreeTexts, NavMeshVisualizationSettings.showHeightMeshBVTree,
                    evt => NavMeshVisualizationSettings.showHeightMeshBVTree = evt.newValue);
                AddToggle(developerModeFoldout, Style.SurfacesHeightMapsTexts, NavMeshVisualizationSettings.showHeightMaps,
                    evt => NavMeshVisualizationSettings.showHeightMaps = evt.newValue);
                AddToggle(developerModeFoldout, Style.SurfacesProximityGridTexts, NavMeshVisualizationSettings.showProximityGrid,
                    evt => NavMeshVisualizationSettings.showProximityGrid = evt.newValue);
            }

            return m_RootPanel;
        }

        void OnAgentRejectedDebugInfoRequestsCountChanged(int rejected, int allowed)
        {
            if (rejected == 0)
            {
                HideAgentCountWarningBox();
            }
            else
            {
                DisplayAgentCountWarningBox(rejected, allowed);
            }
        }

        void OnPrefabStageOpened(PrefabStage obj)
        {
            SetVisualizationEnabled(false);
        }

        void OnPrefabStageClosing(PrefabStage obj)
        {
            SetVisualizationEnabled(true);
        }

        static Foldout AddFoldout(VisualElement parent, GUIContent text, int padding)
        {
            var prefName = $"AINavigationOverlay_Foldout_{text.text}";

            var foldout = new Foldout
            {
                text = text.text,
                tooltip = text.tooltip,
                style =
                {
                    paddingBottom = padding
                },
                value = EditorPrefs.GetBool(prefName, true)
            };

            foldout.RegisterValueChangedCallback(evt =>
                EditorPrefs.SetBool(prefName, evt.newValue));

            parent.Add(foldout);

            return foldout;
        }

        static void AddToggle(VisualElement parent, GUIContent text, bool parameter, EventCallback<ChangeEvent<bool>> callback)
        {
            // Create toggle element with the desired text content
            var toggle = new Toggle
            {
                label = text.text,
                value = parameter,
                tooltip = text.tooltip,
                style =
                {
                    marginBottom = 0 // To compact a bit the checkboxes list layout
                }
            };

            // Add padding to guarantee a minimum separation between labels and checkboxes
            toggle.labelElement.style.paddingRight = 20;

            // Look for the checkbox container and make it align the checkbox to the right (so that all checkboxes are justified) 
            foreach (VisualElement child in toggle.Children())
            {
                if (child != toggle.labelElement)
                {
                    child.style.justifyContent = Justify.FlexEnd;
                    break;
                }
            }

            toggle.RegisterCallback(callback);

            parent.Add(toggle);
        }

        static HelpBox AddHelpBox(HelpBoxMessageType messageType, GUIContent text, int maxWidth, int verticalMargin, bool visible)
        {
            var helpBox = new HelpBox(text.text, messageType)
            {
                tooltip = text.tooltip,
                style =
                {
                    marginBottom = verticalMargin,
                    marginTop = verticalMargin,
                    maxWidth = maxWidth,
                    alignSelf = Align.Center,
                },
                visible = visible
            };

            return helpBox;
        }

        void DisplayAgentCountWarningBox(int rejected, int allowed)
        {
            m_AgentCountWarning.text = $"Avoidance visualization can be drawn for {allowed} agents ({allowed + rejected} selected).";
            if (!m_AgentCountWarning.visible)
            {
                m_AgentCountWarning.visible = true;
                m_AgentFoldOut.Add(m_AgentCountWarning);
            }
        }

        void HideAgentCountWarningBox()
        {
            if (m_AgentCountWarning.visible)
            {
                m_AgentCountWarning.visible = false;
                m_AgentFoldOut.Remove(m_AgentCountWarning);
            }
        }

        void DisplayAgentPendingRequestWarningBox()
        {
            if (!m_AgentPendingRequestWarning.visible)
            {
                m_AgentPendingRequestWarning.visible = true;
                m_AgentFoldOut.Add(m_AgentPendingRequestWarning);
            }
        }

        void HideAgentPendingRequestWarningBox()
        {
            if (m_AgentPendingRequestWarning.visible)
            {
                m_AgentPendingRequestWarning.visible = false;
                m_AgentFoldOut.Remove(m_AgentPendingRequestWarning);
            }
        }

        void SetVisualizationEnabled(bool enabled)
        {
            if (m_VisualizationDisabledHelpBox == null)
                return;
            
            if (enabled)
            {
                if (m_VisualizationDisabledHelpBox.visible)
                {
                    m_RootPanel.Remove(m_VisualizationDisabledHelpBox);
                    m_OptionsPanel.SetEnabled(true);
                    m_OptionsPanel.tooltip = "";
                    m_VisualizationDisabledHelpBox.visible = false;
                }
            }
            else
            {
                if (!m_VisualizationDisabledHelpBox.visible)
                {
                    m_RootPanel.Insert(0, m_VisualizationDisabledHelpBox);
                    m_OptionsPanel.SetEnabled(false);
                    m_OptionsPanel.tooltip = Style.NavigationVisualizationDisabledTexts.tooltip;
                    m_VisualizationDisabledHelpBox.visible = true;
                }
            }
        }
    }
}
