using System;
using System.Collections.Generic;
using System.IO;
using Editor.GON.StageEditor.Baker.Editor.GardensStageEditor;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

namespace Editor.GON.StageEditor
{
    public sealed class StageEditorWindow : EditorWindow
    {
        private PrefabTable prefabTable;
        private StageDefinition stage;
        private StageEditorState state;
        private List<StagePaletteEntry> palette;

        private VisualElement topBar;
        private StageMapCanvas canvas;
        private StageInspectorPanel inspector;
        private StagePalettePanel palettePanel;
        private Label mouseCellLabel;

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

        public static void Open(StageDefinition stageDefinition)
        {
            var window = GetWindow<StageEditorWindow>();
            window.titleContent = new GUIContent("Gardens Stage Editor");
            window.minSize = new Vector2(900, 600);
            window.Show();
            window.SetStage(stageDefinition);
        }

        public void CreateGUI()
        {
            state ??= new StageEditorState();
            state.ClearListeners();

            if (palette is null)
            {
                palette = StagePaletteEntry.CreateBuiltInPalette();
                if (state.activePaletteEntry is null && palette.Count > 0)
                {
                    state.activePaletteEntry = palette[0];
                    state.activeLayer = palette[0].layer;
                }
            }

            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);

            canvas = new StageMapCanvas(this, state);
            canvas.SetStage(stage);
            main.Add(canvas);

            inspector = new StageInspectorPanel(this, state);
            inspector.SetStage(stage);
            main.Add(inspector);

            palettePanel = new StagePalettePanel(state, palette);
            rootVisualElement.Add(palettePanel);

            state.Changed += delegate
            {
                canvas?.RequestRepaint();
                inspector?.Rebuild();
            };

            RebuildTopBar();
        }

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

            Undo.RecordObject(stage, undoName);
            mutation();
            stage.EnsureDefaults();
            EditorUtility.SetDirty(stage);

            canvas?.RequestRepaint();
            inspector?.Rebuild();

            RebuildTopBar();
            Repaint();
        }

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

            topBar.Clear();
            
            var prefabTableField = new ObjectField("Prefab Table")
            {
                objectType = typeof(PrefabTable),
                value = prefabTable,
                style =
                {
                    minWidth = 200
                }
            };
            prefabTableField.RegisterValueChangedCallback(evt =>
            {
                SetPrefabTable(evt.newValue as PrefabTable);
            });
            topBar.Add(prefabTableField);

            var stageField = new ObjectField("Stage")
            {
                objectType = typeof(StageDefinition),
                value = stage,
                style =
                {
                    minWidth = 200
                }
            };
            stageField.RegisterValueChangedCallback(evt =>
            {
                SetStage(evt.newValue as StageDefinition);
            });
            topBar.Add(stageField);

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

            if (stage is not null)
            {
                stage.EnsureDefaults();

                var floorNames = new List<string>();
                foreach (var stageFloorDefinition in stage.floors)
                {
                    floorNames.Add(stageFloorDefinition.floorId);
                }

                var floorPopup = new PopupField<string>("Floor", floorNames, Mathf.Clamp(stage.activeFloorIndex, 0, floorNames.Count - 1))
                {
                    style =
                    {
                        marginLeft = 10,
                        minWidth = 170
                    }
                };
                floorPopup.RegisterValueChangedCallback(evt =>
                {
                    var index = floorNames.IndexOf(evt.newValue);
                    if (index >= 0)
                    {
                        MutateStage("Change active floor", delegate { stage.activeFloorIndex = index; });
                    }
                });
                topBar.Add(floorPopup);

                var addFloor = new Button(AddFloor)
                {
                    text = "+ Floor",
                    style =
                    {
                        marginLeft = 4
                    }
                };
                topBar.Add(addFloor);

                var validate = new Button(ValidateStage)
                {
                    text = "Validate",
                    style =
                    {
                        marginLeft = 12
                    }
                };
                topBar.Add(validate);
                
                var save = new Button(delegate
                {
                    EditorUtility.SetDirty(stage);
                    AssetDatabase.SaveAssets();
                    Debug.Log("Saved StageDefinition: " + AssetDatabase.GetAssetPath(stage));
                })
                {
                    text = "Save Asset",
                    style =
                    {
                        marginLeft = 4
                    }
                };
                topBar.Add(save);
                
                var bake = new Button(BakeStage)
                {
                    text = "Bake",
                    style =
                    {
                        marginLeft = 4
                    }
                };
                topBar.Add(bake);
            }
            
            topBar.Add(AddSpacer());

            mouseCellLabel = new Label("Mouse: —")
            {
                style =
                {
                    opacity = 0.85f,
                    marginRight = 16f
                }
            };
            topBar.Add(mouseCellLabel);

            var hint = new Label("Infinite grid · LMB select/place/paint/move · selected resizable objects have a resize handle · RMB/MMB pan · Wheel zoom")
            {
                style =
                {
                    opacity = 0.75f
                }
            };
            topBar.Add(hint);
        }

        private static VisualElement AddSpacer()
        {
            return new VisualElement
            {
                style =
                {
                    flexGrow = 1f
                }
            };
        }


        public void SetMouseCell(Vector2Int? cell)
        {
            if (mouseCellLabel is null)
            {
                return;
            }

            mouseCellLabel.text = cell.HasValue
                ? "Mouse: X " + cell.Value.x + "  Y " + cell.Value.y
                : "Mouse: —";
        }

        private void SetStage(StageDefinition newStage)
        {
            stage = newStage;
            stage?.EnsureDefaults();
            state.SelectObject(null);
            canvas?.SetStage(stage);
            inspector?.SetStage(stage);

            RebuildTopBar();
        }
        
        private void SetPrefabTable(PrefabTable newPrefabTable)
        {
            prefabTable = newPrefabTable;
            
            RebuildTopBar();
        }

        private void CreateNewStageAsset()
        {
            var path = EditorUtility.SaveFilePanelInProject(
                "Create Stage Definition",
                "new-stage.stage-definition",
                "asset",
                "Choose where to create the new StageDefinition asset."
            );

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

            var asset = CreateInstance<StageDefinition>();
            asset.Id = Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
            asset.EnsureDefaults();

            AssetDatabase.CreateAsset(asset, path);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            SetStage(asset);
            Selection.activeObject = asset;
        }

        private void AddFloor()
        {
            if (stage is null)
            {
                return;
            }

            MutateStage("Add floor", delegate
            {
                var index = stage.floors.Count;
                var floor = new StageFloorDefinition
                {
                    floorId = "F" + index,
                    FloorHeight = stage.floors.Count == 0 ? 0.0f : stage.floors[^1].FloorHeight
                };

                stage.floors.Add(floor);
                stage.activeFloorIndex = stage.floors.Count - 1;
            });
        }

        private void ValidateStage()
        {
            var messages = StageValidator.Validate(stage);
            var report = StageValidator.Format(messages);
            Debug.Log(report);
            EditorUtility.DisplayDialog("Stage Validation", report, "OK");
        }

        private void BakeStage()
        {
            if (stage is null)
            {
                EditorUtility.DisplayDialog(
                    "Bake Stage",
                    "Select or create a StageDefinition before baking.",
                    "OK"
                );

                return;
            }
            
            if (stage.stageTag.IsNull())
            {
                EditorUtility.DisplayDialog(
                    "Bake Stage",
                    "Select StageTag to the stage before baking.",
                    "OK"
                );

                return;
            }

            if (prefabTable is null)
            {
                EditorUtility.DisplayDialog(
                    "Bake Stage",
                    "Select a PrefabTable before baking.",
                    "OK"
                );
                
                return;
            }

            StageBaker.Bake(stage, prefabTable);
        }
    }
}
