using System.Collections.Generic;
using Editor.GON.StageEditor;
using Intrepid.Gameplay.Journey;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

namespace Editor.GON.WorldEditor
{
    public sealed class WorldInspectorPanel : VisualElement
    {
        private readonly WorldEditorWindow owner;
        private readonly WorldEditorState state;
        private WorldLayoutSO worldLayout;

        public WorldInspectorPanel(WorldEditorWindow owner, WorldEditorState 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;
        }

        public void SetLayout(WorldLayoutSO newLayout)
        {
            worldLayout = newLayout;
            Rebuild();
        }

        public void Rebuild()
        {
            Clear();

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

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

            worldLayout.EnsureDefaults();

            var selectedConnection = FindConnection(state.selectedConnectionId);
            if (selectedConnection is not null)
            {
                BuildConnectionInspector(selectedConnection);
                return;
            }

            var selectedEndpoint = worldLayout.FindEndpoint(state.selectedEndpointId);
            if (selectedEndpoint is not null)
            {
                BuildEndpointInspector(selectedEndpoint);
                return;
            }

            var selectedStage = worldLayout.FindStage(state.selectedStageId);
            if (selectedStage is not null)
            {
                BuildStageInspector(selectedStage);
                return;
            }

            Add(new HelpBox("No world item selected.", HelpBoxMessageType.Info));
        }

        private void BuildStageInspector(WorldStageAuthoring stage)
        {
            AddSection("Stage");
            AddReadOnlyLine("Id", stage.id);
            AddReadOnlyLine("Kind", stage.stageKind.ToString());
            AddReadOnlyLine("Layer", FormatLayer(stage.worldLayer));
            AddReadOnlyLine("Tiles", stage.occupiedTiles.Count.ToString());
            AddReadOnlyLine("Endpoints", CountStageEndpoints(stage.id).ToString());

            var kind = new EnumField("Kind", stage.stageKind);
            kind.RegisterValueChangedCallback(evt => owner.MutateLayout("Change world stage kind", delegate
            {
                stage.stageKind = (WorldStageKind)evt.newValue;
                worldLayout.EnforceSavePointRules(stage);
            }));
            Add(kind);

            var name = new TextField("Name")
            {
                value = stage.displayName
            };
            name.RegisterValueChangedCallback(evt => owner.MutateLayout("Rename world stage", delegate
            {
                stage.displayName = string.IsNullOrWhiteSpace(evt.newValue) ? "Stage" : evt.newValue;
            }));
            Add(name);

            var color = new ColorField("Color")
            {
                value = stage.color
            };
            color.RegisterValueChangedCallback(evt => owner.MutateLayout("Change world stage color", delegate
            {
                stage.color = evt.newValue;
            }));
            Add(color);

            var definition = new ObjectField("Stage Definition")
            {
                objectType = typeof(StageDefinition),
                value = stage.stageDefinition
            };
            definition.RegisterValueChangedCallback(evt => owner.MutateLayout("Assign stage definition", delegate
            {
                stage.stageDefinition = evt.newValue as StageDefinition;
            }));
            Add(definition);

            var syncButton = new Button(delegate
            {
                owner.MutateLayout(
                    stage.stageDefinition is null ? "Create stage definition" : "Sync stage definition",
                    delegate
                    {
                        StageDefinitionSyncer.CreateOrSyncStageDefinition(worldLayout, stage);
                    });
            })
            {
                text = stage.stageDefinition is null ? "Create StageDefinition" : "Sync StageDefinition",
                style =
                {
                    marginTop = 8
                }
            };
            Add(syncButton);

            var openButton = new Button(delegate
            {
                Selection.activeObject = stage.stageDefinition;
                StageDefinitionSyncer.CreateOrSyncStageDefinition(worldLayout, stage);
                StageEditorWindow.Open(stage.stageDefinition);
            })
            {
                text = "Open Stage Editor",
                style =
                {
                    marginTop = 4
                }
            };
            openButton.SetEnabled(stage.stageDefinition is not null);
            Add(openButton);
        }

        private void BuildEndpointInspector(WorldEndpointAuthoring endpoint)
        {
            var stage = worldLayout.FindStage(endpoint.stageId);

            AddSection(endpoint.kind.ToString());
            AddReadOnlyLine("Id", endpoint.id);
            AddReadOnlyLine("Stage", stage is null ? endpoint.stageId : stage.displayName);
            AddReadOnlyLine("Layer", stage is null ? "-" : FormatLayer(stage.worldLayer));
            AddReadOnlyLine("Tile", "(" + endpoint.worldTile.x + ", " + endpoint.worldTile.y + ")");

            if (endpoint.kind == JEndpointKind.Socket)
            {
                AddReadOnlyLine("Face", endpoint.socketFace.ToString());
            }

            var required = new Toggle("Required")
            {
                value = endpoint.required
            };
            required.RegisterValueChangedCallback(evt => owner.MutateLayout("Toggle endpoint required", delegate
            {
                endpoint.required = evt.newValue;
            }));
            Add(required);

            var connections = FindConnectionsForEndpoint(endpoint.id);
            AddReadOnlyLine("Connections", connections.Count.ToString());
            foreach (var connection in connections)
            {
                AddConnectionButton(connection);
            }
        }

        private void BuildConnectionInspector(WorldConnectionAuthoring connection)
        {
            AddSection("JConnection");
            AddReadOnlyLine("Id", connection.id);

            var endpointA = worldLayout.FindEndpoint(connection.endpointAId);
            var endpointB = worldLayout.FindEndpoint(connection.endpointBId);
            AddEndpointButton("Endpoint A", endpointA, connection.endpointAId);
            AddEndpointButton("Endpoint B", endpointB, connection.endpointBId);

            var directionality = new EnumField("Direction", connection.directionality);
            directionality.RegisterValueChangedCallback(evt => owner.MutateLayout("Change connection direction", delegate
            {
                connection.directionality = (JConnectionDirectionality)evt.newValue;
            }));
            Add(directionality);
        }

        private void AddConnectionButton(WorldConnectionAuthoring connection)
        {
            var button = new Button(delegate
            {
                owner.SelectConnection(connection.id);
            })
            {
                text = "Connection: " + connection.id,
                style =
                {
                    marginTop = 4,
                    unityTextAlign = TextAnchor.MiddleLeft
                }
            };
            Add(button);
        }

        private void AddEndpointButton(string label, WorldEndpointAuthoring endpoint, string fallbackId)
        {
            var text = endpoint is null
                ? fallbackId + " (missing)"
                : endpoint.kind + " " + endpoint.id;
            var button = new Button(delegate
            {
                if (endpoint is null)
                {
                    return;
                }

                owner.SelectEndpoint(endpoint.id);
            })
            {
                text = label + ": " + text,
                style =
                {
                    marginTop = 4,
                    unityTextAlign = TextAnchor.MiddleLeft
                }
            };
            button.SetEnabled(endpoint is not null);
            Add(button);
        }

        private List<WorldConnectionAuthoring> FindConnectionsForEndpoint(string endpointId)
        {
            var result = new List<WorldConnectionAuthoring>();
            foreach (var connection in worldLayout.connections)
            {
                if (connection == null)
                {
                    continue;
                }

                if (connection.endpointAId == endpointId || connection.endpointBId == endpointId)
                {
                    result.Add(connection);
                }
            }

            return result;
        }

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

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

            return null;
        }

        private int CountStageEndpoints(string stageId)
        {
            var count = 0;
            foreach (var endpoint in worldLayout.endpoints)
            {
                if (endpoint != null && endpoint.stageId == stageId)
                {
                    count++;
                }
            }

            return count;
        }

        private void AddSection(string text)
        {
            Add(new Label(text)
            {
                style =
                {
                    unityFontStyleAndWeight = FontStyle.Bold,
                    marginTop = 4,
                    marginBottom = 6
                }
            });
        }

        private void AddReadOnlyLine(string label, string value)
        {
            Add(new Label(label + ": " + value)
            {
                style =
                {
                    marginBottom = 3,
                    opacity = 0.85f
                }
            });
        }

        private static string FormatLayer(int layer)
        {
            return layer > 0 ? "+" + layer : layer.ToString();
        }
    }
}
