using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using UnityEditor;
using UnityEngine;

namespace Unity.VisualScripting
{
    public class GenerateDocumentationPage : Page
    {
        public GenerateDocumentationPage()
        {
            title = "Generate Documentation";
            shortTitle = "Documentation";
            icon = BoltCore.Resources.LoadIcon("GenerateDocumentationPage.png");
        }

        private readonly List<DocumentationGenerationStep> steps = new List<DocumentationGenerationStep>();

        private readonly Queue<DocumentationGenerationStep> queue = new Queue<DocumentationGenerationStep>();

        private bool generating;

        protected override void OnShow()
        {
            Paths.SyncUnitySolution();

            steps.Clear();

            if (Paths.runtimeAssemblyFirstPassProject != null)
            {
                steps.Add(new DocumentationGenerationStep(Paths.runtimeAssemblyFirstPassProject, "Runtime (First Pass)"));
            }

            if (Paths.runtimeAssemblySecondPassProject != null)
            {
                steps.Add(new DocumentationGenerationStep(Paths.runtimeAssemblySecondPassProject, "Runtime (Second Pass)"));
            }

            if (Paths.editorAssemblyFirstPassProject != null)
            {
                steps.Add(new DocumentationGenerationStep(Paths.editorAssemblyFirstPassProject, "Editor (First Pass)"));
            }

            if (Paths.editorAssemblySecondPassProject != null)
            {
                steps.Add(new DocumentationGenerationStep(Paths.editorAssemblySecondPassProject, "Editor (Second Pass)"));
            }

            queue.Clear();

            if (steps.Count == 0)
            {
                Complete();
            }
        }

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

            foreach (var query in steps)
            {
                query.Update();
            }

            if (queue.Count > 0 &&
                (queue.Peek().state == DocumentationGenerationStep.State.Success ||
                 queue.Peek().state == DocumentationGenerationStep.State.Failure))
            {
                queue.Dequeue();

                if (queue.Count > 0)
                {
                    queue.Peek().Generate();
                }
                else
                {
                    generating = false;
                    Selection.activeObject = null;
                }
            }
        }

        protected override void OnContentGUI()
        {
            var previousIconSize = EditorGUIUtility.GetIconSize();
            EditorGUIUtility.SetIconSize(new Vector2(IconSize.Small, IconSize.Small));

            var explanation = "Bolt plugins can automatically display documentation for Unity methods in graphs and in the inspector. ";
            explanation += "To also include documentation from your custom code and from third-party plugins, we need to generate it first.";

            GUILayout.BeginVertical(Styles.background, GUILayout.ExpandHeight(true));

            LudiqGUI.FlexibleSpace();
            LudiqGUI.BeginHorizontal();
            LudiqGUI.FlexibleSpace();
            GUILayout.Label(explanation, Styles.explanationLabel, GUILayout.MaxWidth(350));
            LudiqGUI.FlexibleSpace();
            LudiqGUI.EndHorizontal();
            LudiqGUI.FlexibleSpace();

            LudiqGUI.BeginHorizontal();
            LudiqGUI.FlexibleSpace();

            EditorGUI.BeginDisabledGroup(queue.Count > 0);

            if (GUILayout.Button("Generate Documentation", Styles.generateButton))
            {
                foreach (var step in steps)
                {
                    step.Reset();
                }

                foreach (var step in steps)
                {
                    queue.Enqueue(step);
                }

                queue.Peek().Generate();
                generating = true;
            }

            EditorGUI.EndDisabledGroup();

            LudiqGUI.FlexibleSpace();
            LudiqGUI.EndHorizontal();

            LudiqGUI.FlexibleSpace();

            LudiqGUI.BeginHorizontal();
            LudiqGUI.FlexibleSpace();
            LudiqGUI.BeginVertical();

            foreach (var step in steps)
            {
                step.OnGUI();

                LudiqGUI.Space(Styles.spaceBetweenSteps);
            }

            LudiqGUI.EndVertical();
            LudiqGUI.FlexibleSpace();
            LudiqGUI.EndHorizontal();

            LudiqGUI.FlexibleSpace();

            EditorGUI.BeginDisabledGroup(generating);
            LudiqGUI.BeginHorizontal();
            LudiqGUI.FlexibleSpace();

            if (GUILayout.Button(completeLabel, Styles.nextButton))
            {
                Complete();
            }

            LudiqGUI.FlexibleSpace();
            LudiqGUI.EndHorizontal();
            EditorGUI.EndDisabledGroup();

            LudiqGUI.FlexibleSpace();
            GUILayout.Label("You can regenerate documentation at any time from the tools menu.", EditorStyles.centeredGreyMiniLabel);

            LudiqGUI.EndVertical();

            EditorGUIUtility.SetIconSize(previousIconSize);
        }

        public static class Styles
        {
            static Styles()
            {
                background = new GUIStyle(LudiqStyles.windowBackground);
                background.padding = new RectOffset(10, 10, 10, 16);

                explanationLabel = new GUIStyle(LudiqStyles.centeredLabel);

                generateButton = new GUIStyle("Button");
                generateButton.padding = new RectOffset(16, 16, 8, 8);

                nextButton = new GUIStyle("Button");
                nextButton.padding = new RectOffset(10, 10, 6, 6);

                stepIcon = new GUIStyle();
                stepIcon.fixedWidth = IconSize.Small;
                stepIcon.fixedHeight = IconSize.Small;
                stepIcon.margin.right = 5;

                stepLabel = new GUIStyle(EditorStyles.label);
                stepLabel.alignment = TextAnchor.MiddleLeft;
                stepLabel.padding = new RectOffset(0, 0, 0, 0);
                stepLabel.margin = new RectOffset(0, 0, 0, 0);
                stepLabel.fixedHeight = stepIcon.fixedHeight;

                stepIdleIcon = new GUIStyle(stepLabel);
                stepIdleIcon.normal.textColor = ColorPalette.unityForegroundDim;

                stepShowLogButton = new GUIStyle(stepLabel);
                stepShowLogButton.normal.textColor = ColorPalette.hyperlink;
                stepShowLogButton.active.textColor = ColorPalette.hyperlinkActive;

                stepShowLogHiddenButton = new GUIStyle(stepLabel);
                stepShowLogHiddenButton.normal.textColor = new Color(0, 0, 0, 0);
            }

            public static readonly GUIStyle background;

            public static readonly GUIStyle explanationLabel;

            public static readonly GUIStyle generateButton;

            public static readonly GUIStyle nextButton;

            public static readonly GUIStyle stepLabel;

            public static readonly GUIStyle stepIdleIcon;

            public static readonly GUIStyle stepShowLogButton;

            public static readonly GUIStyle stepShowLogHiddenButton;

            public static readonly GUIStyle stepIcon;

            public static readonly float spaceBetweenSteps = 5;
        }

        private class DocumentationGenerationStep
        {
            public enum State
            {
                Idle,

                Generating,

                Success,

                Failure
            }

            public DocumentationGenerationStep(string projectPath, string label)
            {
                state = State.Idle;
                this.projectPath = projectPath;
                this.label = label;
            }

            private readonly string projectPath;

            private readonly string label;

            private State lastDrawnState;

            private string logPath;

            public State state { get; private set; }

            private EditorTexture GetStateIcon(State state)
            {
                switch (state)
                {
                    case State.Idle:
                        return BoltCore.Icons.empty;
                    case State.Generating:
                        return BoltCore.Icons.progress;
                    case State.Success:
                        return BoltCore.Icons.successState;
                    case State.Failure:
                        return BoltCore.Icons.errorState;
                    default:
                        throw new UnexpectedEnumValueException<State>(state);
                }
            }

            public void Generate()
            {
                state = State.Generating;
            }

            public void Reset()
            {
                state = State.Idle;
                logPath = null;
                lastDrawnState = state;
            }

            public void Update()
            {
                if (lastDrawnState == State.Generating)
                {
                    logPath = Path.GetTempPath() + Guid.NewGuid() + ".txt";

                    try
                    {
                        File.WriteAllText(logPath, DocumentationGenerator.GenerateDocumentation(projectPath));
                        state = State.Success;
                    }
                    catch (Exception ex)
                    {
                        state = State.Failure;
                        File.WriteAllText(logPath, ex.ToString());
                    }
                }
            }

            public void OnGUI()
            {
                LudiqGUI.BeginHorizontal();

                GUILayout.Box(GetStateIcon(state)?[IconSize.Small], Styles.stepIcon);

                GUILayout.Label(label, state == State.Idle ? Styles.stepIdleIcon : Styles.stepLabel, GUILayout.ExpandWidth(false));

                LudiqGUI.Space(5);

                if (logPath != null)
                {
                    if (GUILayout.Button("Show Log", Styles.stepShowLogButton, GUILayout.ExpandWidth(false)))
                    {
                        Process.Start(logPath);
                    }
                }
                else
                {
                    GUILayout.Label("Show Log", Styles.stepShowLogHiddenButton, GUILayout.ExpandWidth(false));
                }

                LudiqGUI.EndHorizontal();

                if (Event.current.type == EventType.Repaint)
                {
                    lastDrawnState = state;
                }
            }
        }
    }
}