using System.Collections.Generic;
using UnityEngine;

namespace JebAI
{
    /// <summary>
    /// Owns active SpeechBubble components and resolves anchors for Kerbals.
    /// </summary>
    public class SpeechBubbleManager : MonoBehaviour
    {
        public static SpeechBubbleManager Instance { get; private set; }

        private readonly Dictionary<string, SpeechBubble> _bubbles = new Dictionary<string, SpeechBubble>();

        void Awake()
        {
            if (Instance != null && Instance != this) { Destroy(gameObject); return; }
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }

        public void Show(ProtoCrewMember kerbal, string message, Part sourcePart = null)
        {
            if (kerbal == null) return;
            ShowInternal(kerbal.name, message, ResolveAnchor(kerbal.name, sourcePart));
        }

        public void Show(string kerbalName, string message)
        {
            if (string.IsNullOrEmpty(kerbalName)) return;
            ShowInternal(kerbalName, message, ResolveAnchor(kerbalName, null));
        }

        public void ClearAll()
        {
            foreach (SpeechBubble bubble in new List<SpeechBubble>(_bubbles.Values))
                if (bubble != null)
                    Destroy(bubble.gameObject);

            _bubbles.Clear();
        }

        internal void Release(SpeechBubble bubble)
        {
            if (bubble == null) return;

            if (_bubbles.TryGetValue(bubble.KerbalName, out SpeechBubble current) && current == bubble)
                _bubbles.Remove(bubble.KerbalName);
        }

        private void ShowInternal(string kerbalName, string message, Transform anchor)
        {
            if (string.IsNullOrWhiteSpace(message)) return;

            Vector3 fallback = anchor != null
                ? anchor.position
                : ResolveFallbackPosition(kerbalName);

            if (_bubbles.TryGetValue(kerbalName, out SpeechBubble existing) && existing != null)
            {
                existing.Refresh(message, anchor, fallback, JebAISettings.Instance.SpeechBubbleDuration);
                return;
            }

            var go = new GameObject($"JebAI_Bubble_{kerbalName}");
            go.transform.SetParent(transform, false);

            SpeechBubble bubble = go.AddComponent<SpeechBubble>();
            bubble.Initialize(
                this,
                kerbalName,
                message,
                anchor,
                fallback,
                JebAISettings.Instance.SpeechBubbleDuration);

            _bubbles[kerbalName] = bubble;
        }

        private static Transform ResolveAnchor(string kerbalName, Part preferredPart)
        {
            if (preferredPart != null)
                return ResolvePartAnchor(preferredPart);

            Vessel vessel = FindKerbalVessel(kerbalName);
            if (vessel == null)
                return ResolvePartAnchor(FlightGlobals.ActiveVessel?.rootPart);

            if (vessel.isEVA)
                return FindBestSpeechTransform(vessel.rootPart != null ? vessel.rootPart.transform : null);

            foreach (Part part in vessel.parts)
            {
                if (part?.protoModuleCrew == null) continue;
                foreach (ProtoCrewMember crew in part.protoModuleCrew)
                    if (crew != null && crew.name == kerbalName)
                        return ResolvePartAnchor(part);
            }

            return ResolvePartAnchor(vessel.rootPart);
        }

        private static Vector3 ResolveFallbackPosition(string kerbalName)
        {
            Transform anchor = ResolveAnchor(kerbalName, null);
            if (anchor != null)
                return anchor.position;

            if (FlightGlobals.ActiveVessel?.rootPart != null)
                return FlightGlobals.ActiveVessel.rootPart.transform.position;

            return Vector3.zero;
        }

        private static Vessel FindKerbalVessel(string kerbalName)
        {
            if (FlightGlobals.VesselsLoaded == null) return null;

            foreach (Vessel vessel in FlightGlobals.VesselsLoaded)
            {
                if (vessel == null) continue;

                List<ProtoCrewMember> crew = vessel.GetVesselCrew();
                if (crew == null) continue;

                foreach (ProtoCrewMember member in crew)
                    if (member != null && member.name == kerbalName)
                        return vessel;
            }

            return null;
        }

        private static Transform ResolvePartAnchor(Part part)
        {
            if (part == null) return null;
            return FindBestSpeechTransform(part.transform);
        }

        private static Transform FindBestSpeechTransform(Transform root)
        {
            if (root == null) return null;

            Transform named = FindNamedChild(root);
            if (named != null)
                return named;

            Renderer[] renderers = root.GetComponentsInChildren<Renderer>(true);
            Transform best = null;
            float bestY = float.MinValue;

            if (renderers != null)
            {
                for (int i = 0; i < renderers.Length; i++)
                {
                    Renderer renderer = renderers[i];
                    if (renderer == null) continue;

                    float candidateY = renderer.bounds.max.y;
                    if (candidateY > bestY)
                    {
                        bestY = candidateY;
                        best = renderer.transform;
                    }
                }
            }

            return best ?? root;
        }

        private static Transform FindNamedChild(Transform root)
        {
            Transform[] all = root.GetComponentsInChildren<Transform>(true);
            for (int i = 0; i < all.Length; i++)
            {
                Transform child = all[i];
                if (child == null) continue;

                string name = child.name;
                if (string.IsNullOrEmpty(name)) continue;

                string lower = name.ToLowerInvariant();
                if (lower.Contains("head") ||
                    lower.Contains("helmet") ||
                    lower.Contains("eye") ||
                    lower.Contains("visor"))
                {
                    return child;
                }
            }

            return null;
        }
    }
}
