using System;
using System.Collections.Generic;
using Intrepid.Gameplay.Journey;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Random = UnityEngine.Random;

namespace Editor.GON.WorldEditor
{
    public sealed class WorldEditorWindow : EditorWindow
    {
        private WorldLayoutSO layout;
        private JGraphSO graph;
        private WorldEditorState state;
        private VisualElement topBar;
        private VisualElement layerBar;
        private VisualElement toolBar;
        private WorldMapCanvas canvas;
        private WorldInspectorPanel inspector;
        private Label infoLabel;

        [MenuItem("Tools/Gardens Of Nil/World Editor")]
        public static void Open()
        {
            var window = GetWindow<WorldEditorWindow>();
            window.titleContent = new GUIContent("Gardens World Editor");
            window.minSize = new Vector2(900, 600);
            window.Show();
        }

        public void CreateGUI()
        {
            state ??= new WorldEditorState();

            rootVisualElement.Clear();
            rootVisualElement.style.flexDirection = FlexDirection.Column;

            topBar = new VisualElement
            {
                style =
                {
                    flexDirection = FlexDirection.Row,
                    alignItems = Align.Center,
                    paddingLeft = 8,
                    paddingRight = 8,
                    paddingTop = 6,
                    paddingBottom = 6,
                    borderBottomWidth = 1,
                    borderBottomColor = new Color(1f, 1f, 1f, 0.12f)
                }
            };
            rootVisualElement.Add(topBar);

            var main = new VisualElement
            {
                style =
                {
                    flexGrow = 1f,
                    flexDirection = FlexDirection.Row
                }
            };
            rootVisualElement.Add(main);

            layerBar = new VisualElement
            {
                style =
                {
                    width = 74,
                    flexShrink = 0f,
                    paddingTop = 6,
                    paddingLeft = 6,
                    paddingRight = 6,
                    borderRightWidth = 1,
                    borderRightColor = new Color(1f, 1f, 1f, 0.12f)
                }
            };
            main.Add(layerBar);

            canvas = new WorldMapCanvas(this, state);
            canvas.SetLayout(layout);
            main.Add(canvas);

            inspector = new WorldInspectorPanel(this, state);
            inspector.SetLayout(layout);
            main.Add(inspector);

            infoLabel = new Label
            {
                style =
                {
                    paddingLeft = 8,
                    paddingTop = 4,
                    paddingBottom = 4
                }
            };
            rootVisualElement.Add(infoLabel);

            toolBar = new VisualElement
            {
                style =
                {
                    flexDirection = FlexDirection.Row,
                    alignItems = Align.Center,
                    paddingLeft = 8,
                    paddingRight = 8,
                    paddingTop = 6,
                    paddingBottom = 6,
                    borderTopWidth = 1,
                    borderTopColor = new Color(1f, 1f, 1f, 0.12f)
                }
            };
            rootVisualElement.Add(toolBar);

            RebuildTopBar();
            RebuildLayerBar();
            RebuildToolBar();
            RebuildInfo();
        }

        public void MutateLayout(string undoName, Action mutation)
        {
            if (layout is null || mutation is null)
            {
                return;
            }

            var previousStageId = state.selectedStageId;
            var previousEndpointId = state.selectedEndpointId;
            var previousConnectionId = state.selectedConnectionId;
            Undo.RecordObject(layout, undoName);
            mutation();
            layout.EnsureDefaults();
            layout.PruneEmptyStagesAndDanglingConnections();
            ClearInvalidSelection();
            EditorUtility.SetDirty(layout);

            if (previousStageId != state.selectedStageId ||
                previousEndpointId != state.selectedEndpointId ||
                previousConnectionId != state.selectedConnectionId)
            {
                SyncActiveLayerToSelection(true);
            }

            canvas?.RequestRepaint();
            RebuildTopBar();
            RebuildLayerBar();
            RebuildToolBar();
            inspector?.Rebuild();
            RebuildInfo();
            Repaint();
        }

        public WorldLayoutSO Layout => layout;

        public void SetHoverTile(JWorldTile? tile)
        {
            state.hoverTile = tile;
            RebuildInfo();
        }

        public void RequestRebuildInfo()
        {
            RebuildInfo();
        }

        public void RequestSelectionRebuild()
        {
            SyncActiveLayerToSelection(true);
            canvas?.RequestRepaint();
            RebuildLayerBar();
            inspector?.Rebuild();
            RebuildInfo();
            Repaint();
        }

        public void BeginStagePlacement(WorldStageKind stageKind)
        {
            if (state.pendingStagePlacementKind is null)
            {
                state.toolBeforeStagePlacement = state.activeTool;
            }

            state.pendingStagePlacementKind = stageKind;
            state.pendingAnchorEndpointId = null;
            canvas?.RequestRepaint();
            RebuildTopBar();
            RebuildToolBar();
            RebuildInfo();
            Repaint();
        }

        public void CancelStagePlacement()
        {
            if (state.pendingStagePlacementKind is null)
            {
                return;
            }

            state.pendingStagePlacementKind = null;
            state.activeTool = state.toolBeforeStagePlacement;
            canvas?.RequestRepaint();
            RebuildTopBar();
            RebuildToolBar();
            RebuildInfo();
            Repaint();
        }

        public void SelectStage(string stageId)
        {
            state.selectedStageId = stageId;
            state.selectedEndpointId = null;
            state.selectedConnectionId = null;
            RequestSelectionRebuild();
        }

        public void SelectEndpoint(string endpointId)
        {
            var endpoint = layout?.FindEndpoint(endpointId);
            if (endpoint is null)
            {
                return;
            }

            state.selectedStageId = endpoint.stageId;
            state.selectedEndpointId = endpoint.id;
            state.selectedConnectionId = null;
            RequestSelectionRebuild();
        }

        public void SelectConnection(string connectionId)
        {
            var connection = FindConnection(connectionId);
            if (connection is null)
            {
                return;
            }

            var endpointA = layout.FindEndpoint(connection.endpointAId);
            var endpointB = layout.FindEndpoint(connection.endpointBId);
            var endpoint = PickConnectionEndpointForCurrentLayer(endpointA, endpointB);
            state.selectedStageId = endpoint?.stageId;
            state.selectedEndpointId = null;
            state.selectedConnectionId = connection.id;
            RequestSelectionRebuild();
        }

        private void RebuildTopBar()
        {
            if (topBar is null)
            {
                return;
            }

            topBar.Clear();

            var layoutField = new ObjectField("World Layout")
            {
                objectType = typeof(WorldLayoutSO),
                value = layout,
                style =
                {
                    minWidth = 240
                }
            };
            layoutField.RegisterValueChangedCallback(evt => SetLayout(evt.newValue as WorldLayoutSO));
            topBar.Add(layoutField);

            topBar.Add(new Button(CreateNewLayoutAsset)
            {
                text = "New",
                style = { marginLeft = 6 }
            });

            if (layout is null)
            {
                return;
            }

            layout.EnsureDefaults();

            var graphField = new ObjectField("J Graph")
            {
                objectType = typeof(JGraphSO),
                value = graph,
                style =
                {
                    marginLeft = 8,
                    minWidth = 200
                }
            };
            graphField.RegisterValueChangedCallback(evt =>
            {
                graph = evt.newValue as JGraphSO;
                RebuildTopBar();
            });
            topBar.Add(graphField);

            topBar.Add(new Button(CreateNewGraphAsset)
            {
                text = "+ JGraphSO",
                style = { marginLeft = 4 }
            });

            topBar.Add(new Button(ValidateLayout)
            {
                text = "Validate",
                style = { marginLeft = 8 }
            });

            var commit = new Button(CommitJourney)
            {
                text = "Commit Journey",
                style = { marginLeft = 4 }
            };
            commit.SetEnabled(graph is not null);
            topBar.Add(commit);

            topBar.Add(new Button(SaveLayout)
            {
                text = "Save",
                style = { marginLeft = 8 }
            });
        }

        private void RebuildLayerBar()
        {
            if (layerBar is null)
            {
                return;
            }

            layerBar.Clear();

            if (layout is null)
            {
                layerBar.Add(new Label("Layers"));
                return;
            }

            layout.EnsureDefaults();
            state.activeLayer = layout.ClampLayer(state.activeLayer);

            var title = new Label("Layers")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold,
                    marginBottom = 4
                }
            };
            layerBar.Add(title);

            var topControls = new VisualElement
            {
                style =
                {
                    flexDirection = FlexDirection.Row,
                    marginBottom = 6
                }
            };
            var addTopLayer = new Button(delegate
            {
                MutateLayout("Add upper world layer", delegate
                {
                    layout.maxWorldLayer += 1;
                    state.activeLayer = layout.maxWorldLayer;
                });
            })
            {
                text = "+",
                style =
                {
                    flexGrow = 1f,
                    marginRight = 2,
                    height = 24
                }
            };
            topControls.Add(addTopLayer);

            var removeTopLayer = new Button(delegate
            {
                MutateLayout("Remove upper world layer", delegate
                {
                    if (layout.maxWorldLayer > layout.minWorldLayer && !LayerHasStages(layout.maxWorldLayer))
                    {
                        layout.maxWorldLayer -= 1;
                        state.activeLayer = layout.ClampLayer(state.activeLayer);
                    }
                });
            })
            {
                text = "-",
                style =
                {
                    flexGrow = 1f,
                    marginLeft = 2,
                    height = 24
                }
            };
            removeTopLayer.SetEnabled(layout.maxWorldLayer > layout.minWorldLayer && !LayerHasStages(layout.maxWorldLayer));
            topControls.Add(removeTopLayer);
            layerBar.Add(topControls);

            for (var layer = layout.maxWorldLayer; layer >= layout.minWorldLayer; layer--)
            {
                var capturedLayer = layer;
                var button = new Button(delegate
                {
                    state.activeLayer = capturedLayer;
                    canvas?.RequestRepaint();
                    RebuildLayerBar();
                    inspector?.Rebuild();
                    RebuildInfo();
                })
                {
                    text = FormatLayer(capturedLayer),
                    style =
                    {
                        marginBottom = 3,
                        height = 26
                    }
                };

                if (capturedLayer == state.activeLayer)
                {
                    button.style.backgroundColor = new Color(1f, 0.86f, 0.32f, 0.45f);
                    button.style.color = Color.white;
                    button.style.unityFontStyleAndWeight = FontStyle.Bold;
                }

                layerBar.Add(button);
            }

            var bottomControls = new VisualElement
            {
                style =
                {
                    flexDirection = FlexDirection.Row,
                    marginTop = 6
                }
            };
            var addBottomLayer = new Button(delegate
            {
                MutateLayout("Add lower world layer", delegate
                {
                    layout.minWorldLayer -= 1;
                    state.activeLayer = layout.minWorldLayer;
                });
            })
            {
                text = "+",
                style =
                {
                    flexGrow = 1f,
                    marginRight = 2,
                    height = 24
                }
            };
            bottomControls.Add(addBottomLayer);

            var removeBottomLayer = new Button(delegate
            {
                MutateLayout("Remove lower world layer", delegate
                {
                    if (layout.minWorldLayer < layout.maxWorldLayer && !LayerHasStages(layout.minWorldLayer))
                    {
                        layout.minWorldLayer += 1;
                        state.activeLayer = layout.ClampLayer(state.activeLayer);
                    }
                });
            })
            {
                text = "-",
                style =
                {
                    flexGrow = 1f,
                    marginLeft = 2,
                    height = 24
                }
            };
            removeBottomLayer.SetEnabled(layout.minWorldLayer < layout.maxWorldLayer && !LayerHasStages(layout.minWorldLayer));
            bottomControls.Add(removeBottomLayer);
            layerBar.Add(bottomControls);
        }

        private bool LayerHasStages(int layer)
        {
            if (layout is null)
            {
                return false;
            }

            foreach (var stage in layout.stages)
            {
                if (stage != null && stage.worldLayer == layer)
                {
                    return true;
                }
            }

            return false;
        }

        private void RebuildToolBar()
        {
            if (toolBar is null)
            {
                return;
            }

            toolBar.Clear();
            toolBar.Add(new Label("Units")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold,
                    marginRight = 8
                }
            });

            AddUnitButton(WorldStageKind.Stage, "+ Stage", "Placing Stage");
            AddUnitButton(WorldStageKind.SavePoint, "+ Save Point", "Placing Save");

            var separator = new VisualElement
            {
                style =
                {
                    width = 1,
                    height = 24,
                    marginLeft = 8,
                    marginRight = 8,
                    backgroundColor = new Color(1f, 1f, 1f, 0.18f)
                }
            };
            toolBar.Add(separator);

            toolBar.Add(new Label("Tools")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold,
                    marginRight = 8
                }
            });

            AddToolButton(WorldEditorTool.Select, "Select");
            AddToolButton(WorldEditorTool.PaintStage, "Paint");
            AddToolButton(WorldEditorTool.EraseStage, "Erase");
            AddToolButton(WorldEditorTool.Socket, "Socket");
            AddToolButton(WorldEditorTool.Anchor, "Anchor");
            AddToolButton(WorldEditorTool.Lift, "Lift");
        }

        private void AddUnitButton(WorldStageKind stageKind, string label, string placingLabel)
        {
            var button = new Button(delegate { BeginStagePlacement(stageKind); })
            {
                text = state.pendingStagePlacementKind == stageKind ? placingLabel : label,
                style =
                {
                    marginRight = 4,
                    minWidth = stageKind == WorldStageKind.SavePoint ? 110 : 82,
                    height = 28
                }
            };

            if (state.pendingStagePlacementKind == stageKind)
            {
                button.style.backgroundColor = new Color(1f, 0.1f, 0.95f, 0.35f);
                button.style.color = Color.white;
                button.style.unityFontStyleAndWeight = FontStyle.Bold;
            }

            toolBar.Add(button);
        }

        private void AddToolButton(WorldEditorTool tool, string label)
        {
            var button = new Button(delegate
            {
                state.activeTool = tool;
                canvas?.RequestRepaint();
                RebuildToolBar();
                inspector?.Rebuild();
                RebuildInfo();
            })
            {
                text = label,
                style =
                {
                    marginRight = 4,
                    minWidth = 76,
                    height = 28
                }
            };

            if (state.activeTool == tool)
            {
                button.style.backgroundColor = new Color(1f, 0.86f, 0.32f, 0.45f);
                button.style.color = Color.white;
                button.style.unityFontStyleAndWeight = FontStyle.Bold;
            }

            toolBar.Add(button);
        }

        private static string FormatLayer(int layer)
        {
            if (layer > 0)
            {
                return "+" + layer;
            }

            return layer.ToString();
        }

        private void RebuildInfo()
        {
            if (infoLabel is null)
            {
                return;
            }

            if (layout is null)
            {
                infoLabel.text = "Select or create a WorldLayoutSO.";
                return;
            }

            var selected = layout.FindStage(state.selectedStageId);
            var selectedEndpoint = layout.FindEndpoint(state.selectedEndpointId);
            var selectedConnection = FindConnection(state.selectedConnectionId);
            var hoverText = state.hoverTile.HasValue
                ? " | Tile: (" + state.hoverTile.Value.x + ", " + state.hoverTile.Value.y + ")"
                : " | Tile: -";
            var anchorText = string.IsNullOrEmpty(state.pendingAnchorEndpointId)
                ? string.Empty
                : " | Anchor link armed";
            var placementText = state.pendingStagePlacementKind.HasValue
                ? " | Placing " + state.pendingStagePlacementKind.Value + " (click to deploy, Esc to cancel)"
                : string.Empty;
            var selectionText = "No selection.";
            if (selectedConnection is not null)
            {
                selectionText = "Selected connection: " + selectedConnection.id;
            }
            else if (selectedEndpoint is not null)
            {
                selectionText = "Selected " + selectedEndpoint.kind + ": " + selectedEndpoint.id;
            }
            else if (selected is not null)
            {
                selectionText = "Selected: " + selected.displayName + " (" + selected.id + ")";
            }

            infoLabel.text = selectionText + hoverText + anchorText + placementText;
        }

        private void SetLayout(WorldLayoutSO newLayout)
        {
            layout = newLayout;
            layout?.EnsureDefaults();
            if (layout is not null)
            {
                state.activeLayer = layout.ClampLayer(state.activeLayer);
            }

            if (layout?.FindStage(state.selectedStageId) is null)
            {
                state.selectedStageId = null;
            }

            if (layout?.FindEndpoint(state.selectedEndpointId) is null)
            {
                state.selectedEndpointId = null;
            }

            if (FindConnection(state.selectedConnectionId) is null)
            {
                state.selectedConnectionId = null;
            }

            canvas?.SetLayout(layout);
            inspector?.SetLayout(layout);
            RebuildTopBar();
            RebuildLayerBar();
            inspector?.Rebuild();
            RebuildInfo();
        }

        private void ClearInvalidSelection()
        {
            if (layout is null)
            {
                state.selectedStageId = null;
                state.selectedEndpointId = null;
                state.selectedConnectionId = null;
                return;
            }

            if (layout.FindStage(state.selectedStageId) is null)
            {
                state.selectedStageId = null;
            }

            if (layout.FindEndpoint(state.selectedEndpointId) is null)
            {
                state.selectedEndpointId = null;
            }

            if (FindConnection(state.selectedConnectionId) is null)
            {
                state.selectedConnectionId = null;
            }
        }

        private void SyncActiveLayerToSelection(bool frameWhenLayerChanges)
        {
            if (layout is null)
            {
                return;
            }

            layout.EnsureDefaults();
            var previousLayer = state.activeLayer;

            var selectedEndpoint = layout.FindEndpoint(state.selectedEndpointId);
            if (selectedEndpoint is not null)
            {
                var endpointStage = layout.FindStage(selectedEndpoint.stageId);
                if (endpointStage is not null)
                {
                    state.activeLayer = layout.ClampLayer(endpointStage.worldLayer);
                    FrameSelectionIfLayerChanged(previousLayer, frameWhenLayerChanges);
                    return;
                }
            }

            var selectedConnection = FindConnection(state.selectedConnectionId);
            if (selectedConnection is not null)
            {
                var endpointA = layout.FindEndpoint(selectedConnection.endpointAId);
                var endpointB = layout.FindEndpoint(selectedConnection.endpointBId);
                var endpoint = PickConnectionEndpointForSelectedStage(endpointA, endpointB) ??
                               PickConnectionEndpointForCurrentLayer(endpointA, endpointB);
                var endpointStage = endpoint is null ? null : layout.FindStage(endpoint.stageId);
                if (endpointStage is not null)
                {
                    state.activeLayer = layout.ClampLayer(endpointStage.worldLayer);
                    FrameSelectionIfLayerChanged(previousLayer, frameWhenLayerChanges);
                    return;
                }
            }

            var selectedStage = layout.FindStage(state.selectedStageId);
            if (selectedStage is not null)
            {
                state.activeLayer = layout.ClampLayer(selectedStage.worldLayer);
                FrameSelectionIfLayerChanged(previousLayer, frameWhenLayerChanges);
            }
        }

        private void FrameSelectionIfLayerChanged(int previousLayer, bool frameWhenLayerChanges)
        {
            if (frameWhenLayerChanges && previousLayer != state.activeLayer)
            {
                canvas?.FrameSelection();
            }
        }

        private WorldEndpointAuthoring PickConnectionEndpointForSelectedStage(
            WorldEndpointAuthoring endpointA,
            WorldEndpointAuthoring endpointB)
        {
            if (endpointA is not null && endpointA.stageId == state.selectedStageId)
            {
                return endpointA;
            }

            if (endpointB is not null && endpointB.stageId == state.selectedStageId)
            {
                return endpointB;
            }

            return null;
        }

        private WorldEndpointAuthoring PickConnectionEndpointForCurrentLayer(
            WorldEndpointAuthoring endpointA,
            WorldEndpointAuthoring endpointB)
        {
            var stageA = endpointA is null ? null : layout.FindStage(endpointA.stageId);
            if (stageA is not null && stageA.worldLayer == state.activeLayer)
            {
                return endpointA;
            }

            var stageB = endpointB is null ? null : layout.FindStage(endpointB.stageId);
            if (stageB is not null && stageB.worldLayer == state.activeLayer)
            {
                return endpointB;
            }

            return endpointA ?? endpointB;
        }

        private void CreateNewLayoutAsset()
        {
            var path = EditorUtility.SaveFilePanelInProject(
                "Create WorldLayoutSO",
                "WorldLayout",
                "asset",
                "Choose where to create the new WorldLayoutSO asset.");

            if (string.IsNullOrEmpty(path))
            {
                return;
            }

            var asset = CreateInstance<WorldLayoutSO>();
            asset.EnsureDefaults();
            AssetDatabase.CreateAsset(asset, path);
            AssetDatabase.SaveAssets();
            SetLayout(asset);
        }

        private void CreateNewGraphAsset()
        {
            var path = EditorUtility.SaveFilePanelInProject(
                "Create JGraphSO",
                "JGraph",
                "asset",
                "Choose where to create the published Journey graph asset.");

            if (string.IsNullOrEmpty(path))
            {
                return;
            }

            var asset = CreateInstance<JGraphSO>();
            AssetDatabase.CreateAsset(asset, path);
            AssetDatabase.SaveAssets();
            graph = asset;
            RebuildTopBar();
        }

        private void CreateStage()
        {
            BeginStagePlacement(WorldStageKind.Stage);
        }

        private void CreateSavePoint()
        {
            BeginStagePlacement(WorldStageKind.SavePoint);
        }

        private WorldConnectionAuthoring FindConnection(string connectionId)
        {
            if (layout is null || string.IsNullOrEmpty(connectionId))
            {
                return null;
            }

            layout.EnsureDefaults();
            foreach (var connection in layout.connections)
            {
                if (connection != null && connection.id == connectionId)
                {
                    return connection;
                }
            }

            return null;
        }

        private void SaveLayout()
        {
            if (layout is null)
            {
                return;
            }

            EditorUtility.SetDirty(layout);
            AssetDatabase.SaveAssets();
            Debug.Log("Saved WorldLayoutSO: " + AssetDatabase.GetAssetPath(layout));
        }

        private void ValidateLayout()
        {
            MutateLayout("Repair world layout before validation", delegate
            {
                layout.PruneEmptyStagesAndDanglingConnections();
            });
            ShowValidationMessages(WorldLayoutValidator.Validate(layout));
        }

        private void CommitJourney()
        {
            var messages = JourneyCommitter.Commit(layout, graph);
            ShowValidationMessages(messages);
            if (!WorldLayoutValidator.HasErrors(messages))
            {
                Debug.Log("Committed Journey graph: " + AssetDatabase.GetAssetPath(graph));
            }
        }

        private void ShowValidationMessages(List<WorldValidationMessage> messages)
        {
            if (messages is null || messages.Count == 0)
            {
                Debug.Log("World Editor validation passed.");
                return;
            }

            foreach (var message in messages)
            {
                if (message is null)
                {
                    continue;
                }

                LogValidationMessage(message);
            }
        }

        private static void LogValidationMessage(WorldValidationMessage message)
        {
            var text = "World Editor " + message.severity + ": " + message.message +
                       (string.IsNullOrEmpty(message.objectId) ? string.Empty : " [" + message.objectId + "]");

            if (message.severity == WorldValidationSeverity.Error)
            {
                Debug.LogError(text);
                return;
            }

            Debug.LogWarning(text);
        }
    }
}
