using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Dict;

namespace VOID.Generic.World
{
    public class ComplexArea : SerializedMonoBehaviour
    {
        [SerializeField] [ReadOnly] public List<Area> areas;

        [ReadOnly] [BoxGroup("variant", false)]
        public bool isVariant;

        [ReadOnly] [HideIf(nameof(isVariant))] [BoxGroup("variant", false)]
        public List<ComplexArea> variants = new();

        [ReadOnly] [ShowIf(nameof(isVariant))] [BoxGroup("variant", false)]
        public ComplexArea original;

#if UNITY_EDITOR
        private GameObject GetGameObjectForAreas()
        {
            const string areasGameObjectName = "_areas";
            var areasGameObject = gameObject.transform.Find(areasGameObjectName)?.gameObject;
            if (areasGameObject) return areasGameObject;
            areasGameObject = new GameObject(areasGameObjectName);
            areasGameObject.transform.position = Vector3.zero;
            areasGameObject.transform.parent = gameObject.transform;
            areasGameObject.transform.SetAsFirstSibling();
            return areasGameObject;
        }

        [Title("Generate")]
        [Button]
        private void GenerateAreas()
        {
            areas.ForEach(area => DestroyImmediate(area.gameObject));
            areas.Clear();
            GenerateMissingAreas();
        }

        [Button]
        private void GenerateMissingAreas()
        {
            // Bounds thats contains everything in this ComplexArea Prefab
            var complexAreaBounds = new Bounds();
            GetComponentsInChildren<Collider>()
                .ForEach(collider => complexAreaBounds.Encapsulate(collider.bounds));
            GetComponentsInChildren<MeshRenderer>()
                .ForEach(meshRenderer => complexAreaBounds.Encapsulate(meshRenderer.bounds));

            // To calculate position index we need only width (x) and length (z)
            var startIndex = new Vector2Int(
                Mathf.RoundToInt(complexAreaBounds.min.x / Area.Size),
                Mathf.RoundToInt(complexAreaBounds.min.z / Area.Size)
            );
            var endIndex = new Vector2Int(
                Mathf.RoundToInt(complexAreaBounds.max.x / Area.Size),
                Mathf.RoundToInt(complexAreaBounds.max.z / Area.Size)
            );

            var areasGameObject = GetGameObjectForAreas();

            // Create areas for everything inside bounds
            for (var x = startIndex.x; x <= endIndex.x; x++)
            {
                for (var y = startIndex.y; y <= endIndex.y; y++)
                {
                    if (areas.Any(area => area.position.x == x && area.position.y == y)) continue;

                    var areaGameObject = new GameObject();
                    areaGameObject.AddComponent<Area>();
                    areaGameObject.transform.parent = areasGameObject.transform;
                    areaGameObject.transform.position = new Vector3(x * Area.Size, 0, y * Area.Size);
                }
            }

            UpdateAll();
        }

        [Button]
        private void GenerateVariants()
        {
            Editor.EditorWindow.World.ComplexAreaRotateVariantGenerator.GenerateRotatedVariants(this);
        }

        [Title("Update")]
        [Button(ButtonSizes.Large)]
        public void UpdateAll()
        {
            UpdateAreaList();
            UpdatePositionIndexes();
            UpdateNames();
            UpdateConnections();
        }

        [ResponsiveButtonGroup("Update")]
        [Button]
        private void UpdateAreaList()
        {
            areas = gameObject.GetComponentsInChildren<Area>().ToList();
            areas.ForEach(area => area.complexArea = this);
        }

        [ResponsiveButtonGroup("Update")]
        [Button]
        private void UpdatePositionIndexes()
        {
            areas.ForEach(area =>
            {
                var position = area.transform.position;
                area.position.x = Mathf.RoundToInt(position.x / Area.Size);
                area.position.y = Mathf.RoundToInt(position.z / Area.Size);
            });
        }

        [ResponsiveButtonGroup("Update")]
        [Button]
        private void UpdateNames()
        {
            UpdatePositionIndexes();
            areas.ForEach(area => area.name = $"{nameof(Area)} ({area.position.x},{area.position.y})");
        }

        [ResponsiveButtonGroup("Update")]
        [Button]
        private void UpdateConnections()
        {
            UpdatePositionIndexes();
            areas.ForEach(area =>
            {
                var forwardArea = areas.Find(subArea => (area.position + Vector2Int.up).Equals(subArea.position));
                var rightArea = areas.Find(subArea => (area.position + Vector2Int.right).Equals(subArea.position));
                var backArea = areas.Find(subArea => (area.position + Vector2Int.down).Equals(subArea.position));
                var leftArea = areas.Find(subArea => (area.position + Vector2Int.left).Equals(subArea.position));

                area.forwardConnection.direction = Direction2D.Forward;
                area.rightConnection.direction = Direction2D.Right;
                area.backConnection.direction = Direction2D.Back;
                area.leftConnection.direction = Direction2D.Left;

                if (forwardArea)
                    area.forwardConnection.type = AreaConnectionType.Inner;
                else if (area.forwardConnection.type == AreaConnectionType.Inner)
                    area.forwardConnection.type = AreaConnectionType.None;

                if (rightArea)
                    area.rightConnection.type = AreaConnectionType.Inner;
                else if (area.rightConnection.type == AreaConnectionType.Inner)
                    area.rightConnection.type = AreaConnectionType.None;

                if (backArea)
                    area.backConnection.type = AreaConnectionType.Inner;
                else if (area.backConnection.type == AreaConnectionType.Inner)
                    area.backConnection.type = AreaConnectionType.None;

                if (leftArea)
                    area.leftConnection.type = AreaConnectionType.Inner;
                else if (area.leftConnection.type == AreaConnectionType.Inner)
                    area.leftConnection.type = AreaConnectionType.None;
            });
        }

        [Title("Auto-Fix")]
        [Button(ButtonSizes.Large)]
        public void AutoFixAll()
        {
            AutoFixAreaTransforms();
            AutoFixAreaHierarchy();
        }

        [ResponsiveButtonGroup("AutoFix")]
        [Button]
        private void AutoFixAreaTransforms()
        {
            areas.ForEach(area =>
            {
                var position = area.transform.localPosition;
                position.x = Mathf.RoundToInt(position.x / Area.Size) * (int)Area.Size;
                position.y = Mathf.RoundToInt(position.y / Area.Size) * (int)Area.Size;
                position.z = Mathf.RoundToInt(position.z / Area.Size) * (int)Area.Size;
                area.transform.localPosition = position;
            });
        }

        [ResponsiveButtonGroup("AutoFix")]
        [Button]
        private void AutoFixAreaHierarchy()
        {
            var areasGameObject = GetGameObjectForAreas();
            GetComponentsInChildren<Area>()
                .ForEach(area => area.gameObject.transform.parent = areasGameObject.transform);
        }
#endif
    }
}