using System;
using System.Collections.Generic;
using UnityEngine;

namespace YachtDice.Dice
{
    public class Dice : MonoBehaviour
    {
        [Serializable]
        public struct Entry : IEquatable<Entry>
        {
            [field: SerializeField] public int Value { get; private set; }
            [field: SerializeField] public Transform Point { get; private set; }

            public bool Equals(Entry other) => Point == other.Point;
            public override bool Equals(object obj) => obj is Entry other && Equals(other);
            public override int GetHashCode() => Point != null ? Point.GetHashCode() : 0;
        }

        [SerializeField] private List<Entry> entries = new();

        private HashSet<Entry> entrySet;

        private void Awake() => RebuildSet();
        private void OnValidate() => RebuildSet();

        public void Test()
        {
            if (!TryGetTopValue(out var top) || !TryGetBottomValue(out var bottom))
                return;

            Debug.Log($"Top Value: {top}, Bottom Value: {bottom}");

            AlignToTopByLocalAngles();
        }

        private void RebuildSet()
        {
            entrySet = new HashSet<Entry>();
            if (entries == null) return;
            for (int i = 0; i < entries.Count; i++) entrySet.Add(entries[i]);
        }

        public bool TryGetTopValue(out int value) => TryGetExtremeValue(isTop: true, out value);
        public bool TryGetBottomValue(out int value) => TryGetExtremeValue(isTop: false, out value);

        public int AlignToTopByLocalAngles()
        {
            var e = GetExtremeEntryByWorldY(isTop: true);
            AlignByFaceLocalAngles(e.Point);
            return e.Value;
        }

        public int AlignToBottomByLocalAngles()
        {
            var e = GetExtremeEntryByWorldY(isTop: false);
            AlignByFaceLocalAngles(e.Point);
            return e.Value;
        }

        private bool TryGetExtremeValue(bool isTop, out int value)
        {
            value = default;

            if (entrySet == null || entrySet.Count == 0)
                return false;

            bool found = false;
            float bestY = isTop ? float.NegativeInfinity : float.PositiveInfinity;
            int best = default;

            foreach (var e in entrySet)
            {
                var p = e.Point;
                if (!p) continue;

                float y = p.position.y;
                if (!found || (isTop ? y > bestY : y < bestY))
                {
                    found = true;
                    bestY = y;
                    best = e.Value;
                }
            }

            if (!found) return false;

            value = best;
            return true;
        }

        private Entry GetExtremeEntryByWorldY(bool isTop)
        {
            if (entrySet == null || entrySet.Count == 0)
                throw new InvalidOperationException("Dice: коллекция пуста.");

            bool found = false;
            float bestY = isTop ? float.NegativeInfinity : float.PositiveInfinity;
            Entry best = default;

            foreach (var e in entrySet)
            {
                var p = e.Point;
                if (!p) continue;

                float y = p.position.y;
                if (!found || (isTop ? y > bestY : y < bestY))
                {
                    found = true;
                    bestY = y;
                    best = e;
                }
            }

            if (!found)
                throw new InvalidOperationException("Dice: все Transform == null.");

            return best;
        }

        private static float Norm180(float a)
        {
            a %= 360f;
            return a > 180f ? a - 360f : (a < -180f ? a + 360f : a);
        }

        private void AlignByFaceLocalAngles(Transform facePoint)
        {
            if (!facePoint) throw new InvalidOperationException("Dice: facePoint == null.");

            transform.localEulerAngles = Vector3.Scale(facePoint.localEulerAngles, new Vector3(-1f, -1f, -1f));

            var e = transform.localEulerAngles;
            transform.localEulerAngles = new Vector3(Norm180(e.x), Norm180(e.y), Norm180(e.z));
        }
    }
}
