// ---------------------------------------------------------------------------
// BT.Framework
// CoroutineRunner.cs — 일시정지·재개·키 중복방지를 지원하는 전역 코루틴 러너
// ---------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BurningTimes.Core.Util.Log;

namespace BurningTimes.Core.Coroutine
{
    /// <summary>
    /// 전역에서 사용 가능한 코루틴 러너.
    /// </summary>
    /// <remarks>
    /// <para>기존 BurningTimesCore의 <c>CoroutineHandler</c>와 <c>CoroutineRunner</c> 2종을 하나로 통합했다.
    /// 이전 프로젝트의 <c>MyCoroutine</c>도 이 러너로 흡수된다.</para>
    /// <para>호스트는 <see cref="CoroutineHost"/> 내부 <see cref="MonoBehaviour"/>로,
    /// <c>DontDestroyOnLoad</c> 처리된다. 첫 호출 시점에 지연 생성되며, 씬 전환과 무관하게 유지된다.</para>
    /// <para>주요 기능:</para>
    /// <list type="bullet">
    ///   <item><description>핸들 기반 중단 / 일시정지 / 재개</description></item>
    ///   <item><description>문자열 키 기반 중복 정책 (Replace/Ignore/Allow)</description></item>
    ///   <item><description>전체 중단 <see cref="StopAll"/></description></item>
    /// </list>
    /// </remarks>
    public static class CoroutineRunner
    {
        private const string LogCategory = "Coroutine";

        private static CoroutineHost _host;
        private static ulong _nextId = 1;
        private static readonly Dictionary<ulong, Entry> _entries = new Dictionary<ulong, Entry>();
        private static readonly Dictionary<string, ulong> _keyToId = new Dictionary<string, ulong>();

        // -----------------------------------------------------------------
        // 공개 API — 단순 시작
        // -----------------------------------------------------------------

        /// <summary>코루틴을 시작한다.</summary>
        /// <param name="routine">실행할 <see cref="IEnumerator"/>.</param>
        /// <returns>중단·제어에 사용하는 핸들. <paramref name="routine"/>이 null이면 <see cref="CoroutineHandle.None"/>.</returns>
        public static CoroutineHandle Start(IEnumerator routine)
            => StartInternal(routine, key: null);

        /// <summary>문자열 키로 코루틴을 시작한다. 동일 키 실행 중이면 <paramref name="policy"/>에 따라 처리.</summary>
        public static CoroutineHandle Start(string key, IEnumerator routine,
            DuplicatePolicy policy = DuplicatePolicy.Replace)
        {
            if (routine == null) return CoroutineHandle.None;
            if (string.IsNullOrEmpty(key)) return StartInternal(routine, key: null);

            if (_keyToId.TryGetValue(key, out var existingId))
            {
                switch (policy)
                {
                    case DuplicatePolicy.Ignore:
                        return new CoroutineHandle(existingId);
                    case DuplicatePolicy.Replace:
                        StopInternal(existingId);
                        break;
                    case DuplicatePolicy.Allow:
                        // 기존 유지, 새 항목은 키 등록하지 않음.
                        return StartInternal(routine, key: null);
                }
            }
            return StartInternal(routine, key);
        }

        // -----------------------------------------------------------------
        // 공개 API — 제어
        // -----------------------------------------------------------------

        /// <summary>핸들로 실행 중인 코루틴을 중단한다.</summary>
        public static void Stop(CoroutineHandle handle)
        {
            if (!handle.IsValid) return;
            StopInternal(handle.Id);
        }

        /// <summary>키로 실행 중인 코루틴을 중단한다.</summary>
        public static void StopByKey(string key)
        {
            if (string.IsNullOrEmpty(key)) return;
            if (_keyToId.TryGetValue(key, out var id)) StopInternal(id);
        }

        /// <summary>실행 중인 모든 코루틴을 중단한다.</summary>
        public static void StopAll()
        {
            if (_host == null) return;
            _host.StopAllCoroutines();
            _entries.Clear();
            _keyToId.Clear();
        }

        /// <summary>핸들로 일시정지. 재개 전까지 <c>yield return</c> 이 소비되지 않는다.</summary>
        public static void Pause(CoroutineHandle handle)
        {
            if (_entries.TryGetValue(handle.Id, out var e)) e.IsPaused = true;
        }

        /// <summary>핸들로 재개.</summary>
        public static void Resume(CoroutineHandle handle)
        {
            if (_entries.TryGetValue(handle.Id, out var e)) e.IsPaused = false;
        }

        /// <summary>핸들이 현재 실행 중인지 조회.</summary>
        public static bool IsRunning(CoroutineHandle handle)
            => handle.IsValid && _entries.ContainsKey(handle.Id);

        /// <summary>키가 현재 실행 중인지 조회.</summary>
        public static bool IsRunningByKey(string key)
            => !string.IsNullOrEmpty(key) && _keyToId.ContainsKey(key);

        /// <summary>핸들에 해당하는 코루틴의 일시정지 여부.</summary>
        public static bool IsPaused(CoroutineHandle handle)
            => _entries.TryGetValue(handle.Id, out var e) && e.IsPaused;

        // -----------------------------------------------------------------
        // 내부
        // -----------------------------------------------------------------

        private static CoroutineHandle StartInternal(IEnumerator routine, string key)
        {
            if (routine == null) return CoroutineHandle.None;

            EnsureHost();

            var id = _nextId++;
            if (_nextId == 0) _nextId = 1; // 0은 None 예약

            var entry = new Entry { Id = id, Source = routine, Key = key, IsPaused = false };
            _entries[id] = entry;
            if (!string.IsNullOrEmpty(key)) _keyToId[key] = id;

            entry.UnityCoroutine = _host.StartCoroutine(Wrap(entry));
            return new CoroutineHandle(id);
        }

        private static void StopInternal(ulong id)
        {
            if (!_entries.TryGetValue(id, out var entry)) return;
            if (_host != null && entry.UnityCoroutine != null)
                _host.StopCoroutine(entry.UnityCoroutine);

            _entries.Remove(id);
            if (entry.Key != null) _keyToId.Remove(entry.Key);
        }

        private static IEnumerator Wrap(Entry entry)
        {
            var source = entry.Source;
            while (true)
            {
                // 일시정지면 프레임 단위로 대기
                while (entry.IsPaused) yield return null;

                bool moved;
                try { moved = source.MoveNext(); }
                catch (System.Exception ex)
                {
                    Log.Error(LogCategory, $"coroutine threw (id={entry.Id}, key={entry.Key ?? "<none>"})", ex);
                    break;
                }
                if (!moved) break;
                yield return source.Current;
            }

            // 정상 종료·예외 종료 공통 정리
            _entries.Remove(entry.Id);
            if (entry.Key != null && _keyToId.TryGetValue(entry.Key, out var kid) && kid == entry.Id)
                _keyToId.Remove(entry.Key);
        }

        private static void EnsureHost()
        {
            if (_host != null) return;
            var go = new GameObject("[BurningTimes.CoroutineHost]");
            Object.DontDestroyOnLoad(go);
            go.hideFlags = HideFlags.HideAndDontSave;
            _host = go.AddComponent<CoroutineHost>();
        }

        /// <summary>테스트 전용: 내부 상태 초기화.</summary>
        internal static void ResetForTests()
        {
            if (_host != null)
            {
                _host.StopAllCoroutines();
                Object.Destroy(_host.gameObject);
                _host = null;
            }
            _entries.Clear();
            _keyToId.Clear();
            _nextId = 1;
        }

        // 내부 엔트리 — 클래스로 선언하여 Wrap 람다와 dictionary가 같은 참조를 공유하도록 함
        private sealed class Entry
        {
            public ulong Id;
            public IEnumerator Source;
            public string Key;
            public bool IsPaused;
            public UnityEngine.Coroutine UnityCoroutine;
        }

        /// <summary>전역 코루틴 호스트.</summary>
        private sealed class CoroutineHost : MonoBehaviour { }
    }
}
