using UnityEngine;
using UnityEngine.UIElements;

namespace Editor.GON.StageEditor
{
    public sealed class StageInspectorPanel : VisualElement
    {
        private readonly StageEditorWindow owner;
        private readonly StageEditorState state;
        private StageDefinition stage;

        public StageInspectorPanel(StageEditorWindow owner, StageEditorState state)
        {
            this.owner = owner;
            this.state = state;

            style.width = 300;
            style.minWidth = 260;
            style.maxWidth = 380;
            style.paddingLeft = 8;
            style.paddingRight = 8;
            style.paddingTop = 8;
            style.paddingBottom = 8;
            style.borderLeftWidth = 1;
            style.borderLeftColor = new Color(1f, 1f, 1f, 0.12f);
            style.flexShrink = 0;

            state.Changed += Rebuild;
        }

        public void SetStage(StageDefinition newStage)
        {
            stage = newStage;
            Rebuild();
        }

        public void Rebuild()
        {
            Clear();

            var title = new Label("Inspector")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold,
                    marginBottom = 8
                }
            };
            Add(title);

            if (stage is null)
            {
                Add(new HelpBox("Select or create a StageDefinition asset.", HelpBoxMessageType.Info));
                return;
            }

            stage.EnsureDefaults();

            if (state.selectedObject is not null && stage.objects.Contains(state.selectedObject))
            {
                BuildObjectInspector(state.selectedObject);
            }
            else
            {
                state.selectedObject = null;
                BuildStageInspector();
            }
        }

        private void BuildStageInspector()
        {
            var section = new Label("Stage")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold
                }
            };
            Add(section);
            
            var cellSize = new FloatField("Cell Size")
            {
                value = stage.cellSize
            };
            cellSize.RegisterValueChangedCallback(evt => owner.MutateStage("Change cell size", delegate { stage.cellSize = Mathf.Max(0.01f, evt.newValue); }));
            Add(cellSize);

            var floorHeight = new FloatField("Floor Height")
            {
                value = stage.floorHeight
            };
            floorHeight.RegisterValueChangedCallback(evt => owner.MutateStage("Change floor height", delegate { stage.floorHeight = Mathf.Max(0.01f, evt.newValue); }));
            Add(floorHeight);

            var spacer = new VisualElement
            {
                style =
                {
                    height = 10
                }
            };
            Add(spacer);

            var floor = stage.ActiveFloor;
            var floorSection = new Label("Active Floor")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold
                }
            };
            Add(floorSection);

            var floorId = new TextField("Floor Id")
            {
                value = floor.floorId
            };
            floorId.RegisterValueChangedCallback(evt => owner.MutateStage("Change floor id", delegate
            {
                var oldId = floor.floorId;
                floor.floorId = string.IsNullOrEmpty(evt.newValue) ? oldId : evt.newValue;
                foreach (var stageObjectDefinition in stage.objects)
                {
                    if (stageObjectDefinition is not null && stageObjectDefinition.floorId == oldId)
                    {
                        stageObjectDefinition.floorId = floor.floorId;
                    }
                }
            }));
            Add(floorId);

            Add(new Label("Structural Cells: " + floor.Cells.Count));

            Add(floor.TryGetUsedBounds(out var bounds)
                ? new Label("Used Bounds: x " + bounds.xMin + ".." + bounds.xMax + ", y " + bounds.yMin + ".." +
                            bounds.yMax)
                : new Label("Used Bounds: empty"));

            Add(new HelpBox("The grid is infinite/sparse. The stage size emerges from painted cells and logical objects.", HelpBoxMessageType.Info));
            Add(new HelpBox("Editor plane uses X/Y. Later, the stage baker can map editor Y to Unity Z and floor index to Unity Y.", HelpBoxMessageType.Info));
            BuildWorldSyncInspector();
        }

        private void BuildWorldSyncInspector()
        {
            var spacer = new VisualElement
            {
                style =
                {
                    height = 10
                }
            };
            Add(spacer);

            var section = new Label("World Sync")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold
                }
            };
            Add(section);

            var sync = stage.worldSync;
            if (sync is null || !sync.HasSync)
            {
                Add(new HelpBox("Not synced.", HelpBoxMessageType.Info));
                return;
            }

            sync.EnsureDefaults();
            var underlay = new Toggle("Show Underlay")
            {
                value = state.showWorldSyncUnderlay
            };
            underlay.RegisterValueChangedCallback(evt => state.SetWorldSyncUnderlayVisible(evt.newValue));
            Add(underlay);

            AddReadOnlyLine("World Stage", string.IsNullOrEmpty(sync.worldStageDisplayName)
                ? sync.worldStageId
                : sync.worldStageDisplayName);
            AddReadOnlyLine("Stage Id", sync.worldStageId);
            AddReadOnlyLine("Kind", sync.worldStageKind.ToString());
            AddReadOnlyLine("Layer", sync.worldLayer.ToString());
            AddReadOnlyLine("Synced At", string.IsNullOrEmpty(sync.syncedAt) ? "-" : sync.syncedAt);
            AddReadOnlyLine("Hash", ShortHash(sync.contractHash));
        }

        private void BuildObjectInspector(StageObjectDefinition obj)
        {
            var section = new Label("Selected Object")
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold
                }
            };
            Add(section);

            var id = new TextField("Id")
            {
                value = obj.id
            };
            id.RegisterValueChangedCallback(evt => owner.MutateStage("Change object id", delegate { obj.id = evt.newValue; }));
            Add(id);

            AddReadOnlyLine("Layer", obj.layer.ToString());
            AddReadOnlyLine("Kind", obj.kind.ToString());

            var typeId = new TextField("Type Id")
            {
                value = obj.typeId
            };
            typeId.RegisterValueChangedCallback(evt => owner.MutateStage("Change type id", delegate { obj.typeId = evt.newValue; }));
            Add(typeId);

            var prefabId = new TextField("Prefab Id")
            {
                value = obj.prefabId
            };
            prefabId.RegisterValueChangedCallback(evt => owner.MutateStage("Change prefab id", delegate { obj.prefabId = evt.newValue; }));
            Add(prefabId);

            var x = new IntegerField("X")
            {
                value = obj.x
            };
            x.RegisterValueChangedCallback(evt => owner.MutateStage("Move object x", delegate { obj.x = evt.newValue; }));
            Add(x);

            var y = new IntegerField("Y")
            {
                value = obj.y
            };
            y.RegisterValueChangedCallback(evt => owner.MutateStage("Move object y", delegate { obj.y = evt.newValue; }));
            Add(y);

            if (obj.IsResizable)
            {
                var min = obj.MinSize;
                var max = obj.MaxSize;
                Add(new Label("Resizable: yes  ·  Min " + min.x + "x" + min.y + "  ·  Max " + max.x + "x" + max.y)
                {
                    style =
                    {
                        opacity = 0.75f,
                        marginTop = 4,
                        marginBottom = 2
                    }
                });

                var width = new IntegerField("Width")
                {
                    value = obj.width
                };
                width.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object width", delegate
                {
                    obj.width = evt.newValue;
                    obj.NormalizeSize();
                }));
                Add(width);

                var height = new IntegerField("Height")
                {
                    value = obj.height
                };
                height.RegisterValueChangedCallback(evt => owner.MutateStage("Resize object height", delegate
                {
                    obj.height = evt.newValue;
                    obj.NormalizeSize();
                }));
                Add(height);
            }
            else
            {
                var fixedSize = obj.DefaultSize;
                Add(new Label("Size: " + fixedSize.x + "x" + fixedSize.y + " (fixed)")
                {
                    style =
                    {
                        opacity = 0.75f,
                        marginTop = 4,
                        marginBottom = 2
                    }
                });
                Add(new HelpBox("This object kind has a fixed size. It can be moved, but it cannot be resized from the inspector or the grid.", HelpBoxMessageType.Info));
            }

            var cutsGround = new Toggle("Cuts Ground")
            {
                value = obj.cutsGround
            };
            cutsGround.RegisterValueChangedCallback(evt => owner.MutateStage("Toggle cuts ground", delegate { obj.cutsGround = evt.newValue; }));
            Add(cutsGround);

            var blocksMovement = new Toggle("Blocks Movement")
            {
                value = obj.blocksMovement
            };
            blocksMovement.RegisterValueChangedCallback(evt => owner.MutateStage("Toggle blocks movement", delegate { obj.blocksMovement = evt.newValue; }));
            Add(blocksMovement);

            var notes = new TextField("Notes")
            {
                multiline = true,
                value = obj.notes
            };
            notes.RegisterValueChangedCallback(evt => owner.MutateStage("Change object notes", delegate { obj.notes = evt.newValue; }));
            Add(notes);

            var delete = new Button(delegate
            {
                owner.MutateStage("Delete object", delegate
                {
                    stage.objects.Remove(obj);
                    state.SelectObject(null);
                });
            })
            {
                text = "Delete Object",
                style =
                {
                    marginTop = 8
                }
            };
            Add(delete);
        }

        private void AddReadOnlyLine(string label, string value)
        {
            var row = new VisualElement
            {
                style =
                {
                    flexDirection = FlexDirection.Row,
                    marginTop = 2,
                    marginBottom = 2
                }
            };

            var left = new Label(label)
            {
                style =
                {
                    width = 90,
                    opacity = 0.75f
                }
            };
            row.Add(left);

            var right = new Label(value)
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold
                }
            };
            row.Add(right);

            Add(row);
        }

        private static string ShortHash(string hash)
        {
            if (string.IsNullOrEmpty(hash))
            {
                return "-";
            }

            return hash.Length <= 12 ? hash : hash[..12];
        }
    }
}
