// ---------------------------------------------------------------------------
// BT.Framework
// Log.cs — 카테고리·레벨 필터 지원 중앙 로거
// ---------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using UnityEngine;
using UnityDebug = UnityEngine.Debug;

namespace BurningTimes.Core.Util.Log
{
    /// <summary>
    /// BurningTimes 프레임워크의 중앙 로거.
    /// </summary>
    /// <remarks>
    /// <para>특징:</para>
    /// <list type="bullet">
    ///   <item><description>레벨 필터: <see cref="MinLevel"/> 미만은 출력하지 않는다.</description></item>
    ///   <item><description>카테고리 필터: <see cref="EnableCategory"/>/<see cref="DisableCategory"/> 로 제어.</description></item>
    ///   <item><description>외부 sink: <see cref="AddSink"/>로 파일·원격·크래시리포터 연동.</description></item>
    ///   <item><description>릴리즈 스트리핑: <see cref="Verbose"/>/<see cref="Info"/> 는
    ///     <c>UNITY_EDITOR</c>·<c>DEVELOPMENT_BUILD</c>·<c>NERDNAVIS_LOG_VERBOSE</c> 중
    ///     하나라도 정의돼야 호출부에 남는다(<see cref="ConditionalAttribute"/>).</description></item>
    /// </list>
    /// <para>설계 원칙(C11): 카테고리 문자열은 프로젝트 쪽에서 자유 정의하며
    /// 코어에는 특정 게임의 카테고리를 하드코딩하지 않는다.</para>
    /// </remarks>
    public static class Log
    {
        // -----------------------------------------------------------------
        // 상태
        // -----------------------------------------------------------------

        private static readonly object _gate = new object();
        private static readonly List<ILogSink> _sinks = new List<ILogSink>();
        private static readonly HashSet<string> _disabledCategories =
            new HashSet<string>(StringComparer.Ordinal);

        /// <summary>
        /// 출력되는 최소 레벨. 이 값 미만의 로그는 차단된다.
        /// 기본값은 에디터/개발빌드에서는 <see cref="LogLevel.Verbose"/>,
        /// 릴리즈에서는 <see cref="LogLevel.Warn"/>.
        /// </summary>
        public static LogLevel MinLevel { get; set; }
#if UNITY_EDITOR || DEVELOPMENT_BUILD
            = LogLevel.Verbose;
#else
            = LogLevel.Warn;
#endif

        /// <summary>
        /// 메시지 앞에 카테고리 태그를 붙일지 여부.
        /// </summary>
        public static bool PrefixCategory { get; set; } = true;

        // -----------------------------------------------------------------
        // Sink 등록
        // -----------------------------------------------------------------

        /// <summary>외부 sink를 등록한다.</summary>
        public static void AddSink(ILogSink sink)
        {
            if (sink == null) return;
            lock (_gate) { if (!_sinks.Contains(sink)) _sinks.Add(sink); }
        }

        /// <summary>등록된 외부 sink를 제거한다.</summary>
        public static void RemoveSink(ILogSink sink)
        {
            if (sink == null) return;
            lock (_gate) { _sinks.Remove(sink); }
        }

        /// <summary>모든 외부 sink를 제거한다(테스트 용도).</summary>
        public static void ClearSinks()
        {
            lock (_gate) { _sinks.Clear(); }
        }

        // -----------------------------------------------------------------
        // 카테고리 필터
        // -----------------------------------------------------------------

        /// <summary>특정 카테고리 로그 출력을 차단한다.</summary>
        public static void DisableCategory(string category)
        {
            if (string.IsNullOrEmpty(category)) return;
            lock (_gate) { _disabledCategories.Add(category); }
        }

        /// <summary>특정 카테고리 로그 출력을 허용한다(기본값).</summary>
        public static void EnableCategory(string category)
        {
            if (string.IsNullOrEmpty(category)) return;
            lock (_gate) { _disabledCategories.Remove(category); }
        }

        /// <summary>모든 카테고리 필터를 초기화한다.</summary>
        public static void ResetCategoryFilter()
        {
            lock (_gate) { _disabledCategories.Clear(); }
        }

        // -----------------------------------------------------------------
        // 공용 API — Conditional 스트리핑 대상
        // -----------------------------------------------------------------

        [Conditional("UNITY_EDITOR")]
        [Conditional("DEVELOPMENT_BUILD")]
        [Conditional("NERDNAVIS_LOG_VERBOSE")]
        public static void Verbose(string category, string message)
            => Emit(LogLevel.Verbose, category, message, null);

        [Conditional("UNITY_EDITOR")]
        [Conditional("DEVELOPMENT_BUILD")]
        [Conditional("NERDNAVIS_LOG_VERBOSE")]
        public static void Info(string category, string message)
            => Emit(LogLevel.Info, category, message, null);

        // -----------------------------------------------------------------
        // 공용 API — 릴리즈에서도 살아남는 레벨
        // -----------------------------------------------------------------

        public static void Warn(string category, string message)
            => Emit(LogLevel.Warn, category, message, null);

        public static void Error(string category, string message, Exception exception = null)
            => Emit(LogLevel.Error, category, message, exception);

        public static void Critical(string category, string message, Exception exception = null)
            => Emit(LogLevel.Critical, category, message, exception);

        // -----------------------------------------------------------------
        // 내부 공통 처리
        // -----------------------------------------------------------------

        private static void Emit(LogLevel level, string category, string message, Exception exception)
        {
            // 레벨·카테고리 필터
            if (level < MinLevel) return;
            lock (_gate)
            {
                if (category != null && _disabledCategories.Contains(category)) return;
            }

            var formatted = Format(category, message);

            // Unity Console 출력
            switch (level)
            {
                case LogLevel.Verbose:
                case LogLevel.Info:
                    UnityDebug.Log(formatted);
                    break;
                case LogLevel.Warn:
                    UnityDebug.LogWarning(formatted);
                    break;
                case LogLevel.Error:
                case LogLevel.Critical:
                    if (exception != null) UnityDebug.LogException(exception);
                    UnityDebug.LogError(formatted);
                    break;
            }

            // 외부 sink 전달 (등록된 sink 수만큼 스냅샷 순회)
            ILogSink[] snapshot;
            lock (_gate)
            {
                if (_sinks.Count == 0) return;
                snapshot = _sinks.ToArray();
            }
            for (int i = 0; i < snapshot.Length; i++)
            {
                try { snapshot[i].Emit(level, category, formatted, exception); }
                catch
                {
                    // sink 내부 예외는 무시한다(재귀 방지).
                }
            }
        }

        private static string Format(string category, string message)
        {
            if (!PrefixCategory || string.IsNullOrEmpty(category)) return message ?? string.Empty;
            var sb = new StringBuilder(
                (category?.Length ?? 0) + (message?.Length ?? 0) + 4);
            sb.Append('[').Append(category).Append("] ").Append(message);
            return sb.ToString();
        }
    }
}
