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

namespace Editor.GON.WorldEditor
{
    public enum WorldStageKind
    {
        Stage = 0,
        SavePoint = 1
    }

    [CreateAssetMenu(menuName = "GON/Editor/World Layout", fileName = "WorldLayout")]
    public sealed class WorldLayoutSO : ScriptableObject
    {
        public string id;
        public float worldTileSize = 24f;
        public float worldLayerHeight = 24f;
        public const string SavePointTag = "SavePoint";
        public int minWorldLayer = -2;
        public int maxWorldLayer = 2;
        public List<WorldStageAuthoring> stages = new();
        public List<WorldEndpointAuthoring> endpoints = new();
        public List<WorldConnectionAuthoring> connections = new();

        public void EnsureDefaults()
        {
            if (string.IsNullOrWhiteSpace(id))
            {
                id = Guid.NewGuid().ToString("N");
            }

            if (worldTileSize <= 0f)
            {
                worldTileSize = 24f;
            }

            if (worldLayerHeight <= 0f)
            {
                worldLayerHeight = 24f;
            }

            if (minWorldLayer > maxWorldLayer)
            {
                (minWorldLayer, maxWorldLayer) = (maxWorldLayer, minWorldLayer);
            }

            stages ??= new List<WorldStageAuthoring>();
            endpoints ??= new List<WorldEndpointAuthoring>();
            connections ??= new List<WorldConnectionAuthoring>();

            foreach (var stage in stages)
            {
                stage?.EnsureDefaults();
            }
        }

        public int ClampLayer(int layer)
        {
            EnsureDefaults();
            return Mathf.Clamp(layer, minWorldLayer, maxWorldLayer);
        }

        public string CreateId(string prefix)
        {
            return prefix + "_" + Guid.NewGuid().ToString("N");
        }

        public WorldStageAuthoring FindStage(string stageId)
        {
            EnsureDefaults();
            foreach (var stage in stages)
            {
                if (stage != null && stage.id == stageId)
                {
                    return stage;
                }
            }

            return null;
        }

        public WorldStageAuthoring FindStageAt(int worldLayer, JWorldTile tile)
        {
            EnsureDefaults();
            foreach (var stage in stages)
            {
                if (stage != null && stage.worldLayer == worldLayer && stage.ContainsTile(tile))
                {
                    return stage;
                }
            }

            return null;
        }

        public WorldEndpointAuthoring FindEndpoint(string endpointId)
        {
            EnsureDefaults();
            foreach (var endpoint in endpoints)
            {
                if (endpoint != null && endpoint.id == endpointId)
                {
                    return endpoint;
                }
            }

            return null;
        }

        public bool IsTileOccupiedByOther(string stageId, int worldLayer, JWorldTile tile)
        {
            var occupyingStage = FindStageAt(worldLayer, tile);
            return occupyingStage != null && occupyingStage.id != stageId;
        }

        public WorldEndpointAuthoring FindEndpointAt(string stageId, JWorldTile tile, JEndpointKind kind)
        {
            EnsureDefaults();
            foreach (var endpoint in endpoints)
            {
                if (endpoint != null && endpoint.stageId == stageId && endpoint.kind == kind &&
                    endpoint.worldTile.x == tile.x && endpoint.worldTile.y == tile.y)
                {
                    return endpoint;
                }
            }

            return null;
        }

        public WorldEndpointAuthoring FindSocketAt(string stageId, JWorldTile tile, JSocketFace face)
        {
            EnsureDefaults();
            foreach (var endpoint in endpoints)
            {
                if (endpoint != null &&
                    endpoint.stageId == stageId &&
                    endpoint.kind == JEndpointKind.Socket &&
                    endpoint.socketFace == face &&
                    endpoint.worldTile.x == tile.x &&
                    endpoint.worldTile.y == tile.y)
                {
                    return endpoint;
                }
            }

            return null;
        }

        public WorldConnectionAuthoring FindConnectionBetween(string endpointAId, string endpointBId)
        {
            EnsureDefaults();
            foreach (var connection in connections)
            {
                if (connection == null)
                {
                    continue;
                }

                if ((connection.endpointAId == endpointAId && connection.endpointBId == endpointBId) ||
                    (connection.endpointAId == endpointBId && connection.endpointBId == endpointAId))
                {
                    return connection;
                }
            }

            return null;
        }

        public void RemoveEndpointAndConnections(string endpointId)
        {
            if (string.IsNullOrEmpty(endpointId))
            {
                return;
            }

            EnsureDefaults();
            for (var i = endpoints.Count - 1; i >= 0; i--)
            {
                if (endpoints[i] != null && endpoints[i].id == endpointId)
                {
                    endpoints.RemoveAt(i);
                }
            }

            for (var i = connections.Count - 1; i >= 0; i--)
            {
                var connection = connections[i];
                if (connection == null ||
                    connection.endpointAId == endpointId ||
                    connection.endpointBId == endpointId)
                {
                    connections.RemoveAt(i);
                }
            }
        }

        public void RemoveStageAndOwnedData(string stageId)
        {
            if (string.IsNullOrEmpty(stageId))
            {
                return;
            }

            EnsureDefaults();
            var endpointIds = new List<string>();
            foreach (var endpoint in endpoints)
            {
                if (endpoint != null && endpoint.stageId == stageId)
                {
                    endpointIds.Add(endpoint.id);
                }
            }

            foreach (var endpointId in endpointIds)
            {
                RemoveEndpointAndConnections(endpointId);
            }

            for (var i = stages.Count - 1; i >= 0; i--)
            {
                if (stages[i] != null && stages[i].id == stageId)
                {
                    stages.RemoveAt(i);
                }
            }
        }

        public void RemoveEndpointsAtTile(string stageId, JWorldTile tile)
        {
            if (string.IsNullOrEmpty(stageId))
            {
                return;
            }

            EnsureDefaults();
            var endpointIds = new List<string>();
            foreach (var endpoint in endpoints)
            {
                if (endpoint != null &&
                    endpoint.stageId == stageId &&
                    endpoint.worldTile.x == tile.x &&
                    endpoint.worldTile.y == tile.y)
                {
                    endpointIds.Add(endpoint.id);
                }
            }

            foreach (var endpointId in endpointIds)
            {
                RemoveEndpointAndConnections(endpointId);
            }
        }

        public bool PruneEmptyStagesAndDanglingConnections()
        {
            EnsureDefaults();
            var changed = false;
            for (var i = stages.Count - 1; i >= 0; i--)
            {
                var stage = stages[i];
                stage?.EnsureDefaults();
                if (stage == null || stage.occupiedTiles.Count > 0)
                {
                    continue;
                }

                RemoveStageAndOwnedData(stage.id);
                changed = true;
            }

            for (var i = endpoints.Count - 1; i >= 0; i--)
            {
                var endpoint = endpoints[i];
                var stage = endpoint == null ? null : FindStage(endpoint.stageId);
                if (endpoint == null || stage == null || !stage.ContainsTile(endpoint.worldTile))
                {
                    if (endpoint != null)
                    {
                        RemoveEndpointAndConnections(endpoint.id);
                    }
                    else
                    {
                        endpoints.RemoveAt(i);
                    }

                    changed = true;
                }
            }

            for (var i = connections.Count - 1; i >= 0; i--)
            {
                var connection = connections[i];
                if (connection == null ||
                    FindEndpoint(connection.endpointAId) == null ||
                    FindEndpoint(connection.endpointBId) == null)
                {
                    connections.RemoveAt(i);
                    changed = true;
                }
            }

            return changed;
        }

        public void EnforceSavePointRules(WorldStageAuthoring stage)
        {
            if (stage is null || stage.stageKind != WorldStageKind.SavePoint)
            {
                return;
            }

            stage.EnsureDefaults();
            if (stage.occupiedTiles.Count == 0)
            {
                stage.occupiedTiles.Add(new JWorldTile(0, 0));
            }

            var keptTile = stage.occupiedTiles[0];
            stage.occupiedTiles.Clear();
            stage.occupiedTiles.Add(keptTile);

            for (var i = endpoints.Count - 1; i >= 0; i--)
            {
                var endpoint = endpoints[i];
                if (endpoint == null || endpoint.stageId != stage.id)
                {
                    continue;
                }

                var outsideSaveTile = endpoint.worldTile.x != keptTile.x || endpoint.worldTile.y != keptTile.y;
                if (outsideSaveTile || endpoint.kind is JEndpointKind.Anchor or JEndpointKind.Lift)
                {
                    RemoveEndpointAndConnections(endpoint.id);
                }
            }
        }
    }

    [Serializable]
    public sealed class WorldStageAuthoring
    {
        public string id;
        public string displayName;
        public WorldStageKind stageKind;
        public int worldLayer;
        public Color color = new(0.25f, 0.62f, 0.95f, 0.7f);
        public StageDefinition stageDefinition;
        public List<JWorldTile> occupiedTiles = new();

        public void EnsureDefaults()
        {
            occupiedTiles ??= new List<JWorldTile>();
            if (string.IsNullOrWhiteSpace(displayName))
            {
                displayName = "Stage";
            }
        }

        public bool ContainsTile(JWorldTile tile)
        {
            EnsureDefaults();
            foreach (var occupiedTile in occupiedTiles)
            {
                if (occupiedTile.x == tile.x && occupiedTile.y == tile.y)
                {
                    return true;
                }
            }

            return false;
        }

        public void AddTile(JWorldTile tile)
        {
            EnsureDefaults();
            if (!ContainsTile(tile))
            {
                occupiedTiles.Add(tile);
            }
        }

        public void RemoveTile(JWorldTile tile)
        {
            EnsureDefaults();
            for (var i = occupiedTiles.Count - 1; i >= 0; i--)
            {
                if (occupiedTiles[i].x == tile.x && occupiedTiles[i].y == tile.y)
                {
                    occupiedTiles.RemoveAt(i);
                }
            }
        }
    }

    [Serializable]
    public sealed class WorldEndpointAuthoring
    {
        public string id;
        public string stageId;
        public JWorldTile worldTile;
        public JEndpointKind kind;
        public bool required = true;
        public JSocketFace socketFace;
    }

    [Serializable]
    public sealed class WorldConnectionAuthoring
    {
        public string id;
        public string endpointAId;
        public string endpointBId;
        public JConnectionDirectionality directionality = JConnectionDirectionality.Bidirectional;
    }
}
