using Cysharp.Threading.Tasks;
using System; // System.Type 사용을 위해 추가
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
using UVC.Log;

namespace UVC.UI.Modal
{
    /// <summary>
    /// ✨짠! 특별한 알림 상자를 보여주는 마법사, Modal 클래스예요! ✨
    /// 게임을 하다가 갑자기 "레벨 업!" 메시지가 뜨거나, "정말 게임을 끌 건가요?" 하고 물어보는 창 있죠?
    /// 그런 창들을 '모달'이라고 불러요. Modal 클래스는 이런 모달 창을 쉽게 만들고 보여줄 수 있게 도와준답니다.
    /// 마치 요술봉처럼, 필요할 때 "모달 나와라, 뚝딱!" 하고 명령하면 화면에 알림 상자를 뿅! 하고 나타나게 할 수 있어요.
    /// 그리고 다 봤으면 "모달 들어가라, 뿅!" 하고 사라지게 할 수도 있죠.
    /// </summary>
    public static class Modal
    {
        /// <summary>
        /// 📦 현재 열려있는 모달의 내용물(ModalContent)을 가리키는 포인터예요.
        /// 모달이 열릴 때 설정되고, 닫힐 때 null이 됩니다.
        /// OnClose 호출 시 사용됩니다.
        /// </summary>
        private static ModalContent currentContent;

        /// <summary>
        /// 🧙‍♂️ 현재 화면에 떠 있는 모달 창 자체를 가리키는 비밀 포인터예요.
        /// 모달이 열리면 여기에 그 모달 창이 저장되고, 닫히면 null(없음)이 돼요.
        /// 한 번에 하나의 모달만 보여주기 위해 이 포인터를 사용해요.
        /// </summary>
        private static GameObject currentModalInstance;

        /// <summary>
        /// 🛡️ 모달 창이 뜰 때 뒤에 있는 다른 버튼들을 누르지 못하게 막아주는 '투명 방패'예요.
        /// 이 방패도 모달이 열릴 때 나타났다가, 닫히면 사라져요.
        /// </summary>
        private static GameObject currentBlockerInstance;

        /// <summary>
        /// 📜 모달 창이 "네!" 또는 "아니오!" 같은 대답을 받을 때까지 기다리는 '약속 증서'예요.
        /// UniTaskCompletionSource의 줄임말인 tcs는 'Task Completion Source'의 약자랍니다.
        /// 모달이 열릴 때 이 약속 증서가 만들어지고, 사용자가 버튼을 누르면 여기에 결과가 적혀요.
        /// </summary>
        private static IUniTaskSource activeTcs;

        /// <summary>
        /// 🏷️ 모달이 돌려줄 대답의 종류(타입)를 기억하는 '이름표'예요.
        /// 예를 들어, '예/아니오' 질문이면 bool 타입(참/거짓)이라는 이름표가 붙어요.
        /// </summary>
        private static Type activeResultType;

        /// <summary>
        /// 🖼️ 모달 뒤에 깔릴 기본 '투명 방패' 디자인 파일이 어디 있는지 알려주는 '주소'예요.
        /// 특별히 다른 방패를 쓰고 싶다고 말하지 않으면 이 기본 방패를 사용해요.
        /// </summary>
        private const string DefaultBlockerPrefabPath = "Prefabs/UI/Modal/ModalBlocker";

        /// <summary>
        /// ✨ 모달아, 열려라! ✨
        /// 이 마법 주문을 외치면 화면에 새로운 알림 상자(모달)가 뿅! 하고 나타나요.
        /// 어떤 내용을 보여줄지, 버튼은 어떻게 할지 미리 정해서 알려줘야 해요.
        ///
        /// 예를 들어, "정말 게임을 종료할까요?" 라는 질문과 함께 [예], [아니오] 버튼이 있는 모달을 보여주고 싶다고 해봐요.
        /// 이럴 때 이 Open 마법을 사용하면 된답니다!
        ///
        /// 모달이 닫힐 때 어떤 대답을 했는지 (예: '예' 버튼을 눌렀는지, '아니오' 버튼을 눌렀는지) 알려줄 수도 있어요.
        /// 그 대답의 종류를 <typeparamref name="T"/> 여기에 적어주면 돼요. 예를 들어, <c>bool</c>이라고 적으면 '참' 또는 '거짓'으로 대답을 받을 수 있어요.
        /// </summary>
        /// <typeparam name="T">모달이 닫힐 때 돌려받을 대답의 종류예요. 예를 들어, '예'/'아니오' 선택은 bool 타입으로 받을 수 있어요.</typeparam>
        /// <param name="content">모달에 보여줄 제목, 메시지, 버튼 모양 등을 정한 '모달 내용물 꾸러미'예요.</param>
        /// <param name="blockerPrefabPath">모달이 뜰 때 뒤에 있는 화면을 살짝 가려주는 '가림막'의 디자인 파일 경로예요. 안 써주면 기본 가림막을 사용해요.</param>
        /// <returns>모달이 닫힐 때 사용자가 선택한 결과(대답)를 돌려줘요. 예를 들어, '예' 버튼을 누르면 true를 돌려줄 수 있어요.</returns>
        /// <example>
        /// <code>
        /// // "정말 게임을 종료할까요?" 모달을 열고, 사용자의 대답(true 또는 false)을 기다리는 예시예요.
        /// async UniTaskVoid ShowExitConfirmModal()
        /// {
        ///     // 1. 모달에 어떤 내용을 보여줄지 정해요.
        ///     // "MyConfirmModalPrefab" 부분에는 실제 만들어둔 모달 프리팹 파일의 경로를 적어주세요.
        ///     var myModalContent = new ModalContent("Prefabs/UI/MyConfirmModalPrefab") 
        ///     {
        ///         Title = "게임 종료",
        ///         Message = "정말로 게임을 종료하시겠어요? 🥺",
        ///         ConfirmButtonText = "네! 끌래요",
        ///         CancelButtonText = "아니요! 더 할래요"
        ///     };
        ///
        ///     // 2. Modal.Open 마법으로 모달을 열어요! 사용자가 버튼을 누를 때까지 기다렸다가, 그 결과를 알려줘요.
        ///     //    여기서는 사용자가 '네! 끌래요'를 누르면 true, '아니요! 더 할래요'나 닫기 버튼을 누르면 false를 돌려받기로 약속했어요(<bool>).
        ///     bool userSaidYes = await Modal.Open<bool>(myModalContent);
        ///
        ///     // 3. 사용자의 대답에 따라 다른 행동을 해요.
        ///     if (userSaidYes)
        ///     {
        ///         Debug.Log("흑흑, 게임을 종료합니다... 다음에 또 만나요! 👋");
        ///         // Application.Quit(); // 진짜로 게임을 끄는 코드
        ///     }
        ///     else
        ///     {
        ///         Debug.Log("야호! 게임을 계속합니다! 🥳");
        ///     }
        /// }
        /// </code>
        /// </example>
        public static async UniTask<T> Open<T>(ModalContent content, string blockerPrefabPath = DefaultBlockerPrefabPath)
        {
            // 📜 이야기: 모달을 열기 전에, 이미 다른 모달이 열려있는지 확인해요.
            // 만약 그렇다면, "앗! 이미 다른 모달이 열려있어요!"라고 알려주고 새 모달은 열지 않아요.
            // 한 번에 하나의 모달만 보여주는 것이 규칙이거든요!
            if (currentModalInstance != null)
            {
                ULog.Warning("[Modal] 다른 모달이 이미 열려있습니다. 새 모달을 열기 전에 기존 모달을 닫아주세요.");
                return default(T); // 기본값(예: bool이면 false, 숫자면 0)을 돌려주고 끝내요.
            }

            // 📜 이야기: 새 모달을 위한 '약속 증서(tcs)'를 만들어요.
            // 이 증서는 나중에 사용자가 버튼을 누르면 그 결과를 기록할 거예요.
            // 그리고 이 증서와 결과 타입을 마법사의 비밀 도구함에 잘 보관해요.
            var tcs = new UniTaskCompletionSource<T>();
            activeTcs = tcs;
            activeResultType = typeof(T);
            currentContent = content; // 현재 content 저장

            // --- 투명 방패(Blocker) 준비 ---
            CanvasGroup blockerCanvasGroup = null;
            // 📜 이야기: '투명 방패' 디자인 파일을 불러와요. Resources.LoadAsync는 비동기(기다리지 않고 다음 일 하기)로 파일을 불러오는 마법이에요.
            GameObject blockerPrefabObj = await Resources.LoadAsync<GameObject>(blockerPrefabPath) as GameObject;
            if (blockerPrefabObj != null)
            {
                // 화면에서 가장 큰 그림판(Canvas)을 찾아서 그 위에 방패를 놓을 거예요.
                Canvas mainCanvasForBlocker = UnityEngine.Object.FindFirstObjectByType<Canvas>();
                if (mainCanvasForBlocker != null)
                {
                    // 방패를 복제해서(Instantiate) 그림판 위에 놓고, 가장 위로 오도록 순서를 조정해요.
                    currentBlockerInstance = UnityEngine.Object.Instantiate(blockerPrefabObj, mainCanvasForBlocker.transform);
                    currentBlockerInstance.transform.SetAsLastSibling();

                    // 방패가 부드럽게 나타나도록 CanvasGroup 컴포넌트를 사용해요. 없으면 새로 추가!
                    blockerCanvasGroup = currentBlockerInstance.GetComponent<CanvasGroup>();
                    if (blockerCanvasGroup == null) blockerCanvasGroup = currentBlockerInstance.AddComponent<CanvasGroup>();
                    blockerCanvasGroup.alpha = 0f;// 처음엔 완전히 투명하게
                    _ = FadeUI(blockerCanvasGroup, 0.7f, 0.3f, true);// 0.3초 동안 서서히 나타나게 (투명도 70%)
                }
                else ULog.Error("[Modal] UIBlocker를 표시할 Canvas를 찾을 수 없습니다."); // 그림판을 못 찾으면 에러!
            }
            else ULog.Warning($"[Modal] UIBlocker 프리팹을 다음 경로에서 찾을 수 없습니다: {blockerPrefabPath}");

            // --- 모달 창(Modal) 준비 ---
            // 📜 이야기: 이제 진짜 모달 창 디자인 파일을 불러올 차례예요. 이것도 비동기로!
            GameObject modalPrefabObj = await Resources.LoadAsync<GameObject>(currentContent.PrefabPath) as GameObject;
            if (modalPrefabObj == null)
            {
                ULog.Error($"[Modal] 모달 프리팹을 다음 경로에서 찾을 수 없습니다: {currentContent.PrefabPath}");
                await CleanupCurrentModalResources(currentContent, null, false); // 열려던 것들(방패 등)을 깨끗이 치우고,
                tcs.TrySetResult(default(T)); // 약속 증서에는 "실패했어요" (기본값) 라고 적어요.
                return await tcs.Task; // 그리고 결과를 돌려주고 끝.
            }

            // 모달 창도 가장 큰 그림판 위에 놓을 거예요.
            Canvas mainCanvasForModal = UnityEngine.Object.FindFirstObjectByType<Canvas>();
            if (mainCanvasForModal == null) // 그림판을 못 찾으면,
            {
                ULog.Error("[Modal] 모달을 표시할 Canvas를 찾을 수 없습니다.");
                await CleanupCurrentModalResources(currentContent, null, false); // 마찬가지로 정리하고,
                tcs.TrySetResult(default(T)); // 실패 기록.
                return await tcs.Task; // 결과 돌려주고 끝.
            }

            // 모달 창을 복제해서 그림판 위에 놓고, 가장 위로 오도록 순서를 조정해요. (방패보다도 위!)
            currentModalInstance = UnityEngine.Object.Instantiate(modalPrefabObj, mainCanvasForModal.transform);
            currentModalInstance.transform.SetAsLastSibling();

            // 📜 이야기: 모달 창에는 'ModalView'라는 특별한 부품이 붙어있어야 해요.
            // 이 부품이 모달 창의 글자, 버튼 등을 관리하거든요.
            ModalView modalView = currentModalInstance.GetComponent<ModalView>();
            if (modalView == null) // ModalView 부품이 없다면,
            {
                ULog.Error($"[Modal] 모달 프리팹에 ModalView 컴포넌트가 없습니다: {currentContent.PrefabPath}");
                await CleanupCurrentModalResources(currentContent, null, false); // 정리하고,
                tcs.TrySetResult(default(T)); // 실패 기록.
                return await tcs.Task; // 결과 돌려주고 끝.
            }

            // 📜 이야기: 만약 모달 창이 (우리가 모르는 사이에) 갑자기 사라지면 어떻게 될까요?
            // 그런 상황에 대비해서, 모달 창이 파괴될 때 자동으로 "취소"된 것처럼 처리하도록 등록해둬요.
            // GetCancellationTokenOnDestroy()는 "이 게임 오브젝트가 파괴되면 알려줘!"라는 신호예요.
            var modalDestroyToken = currentModalInstance.GetCancellationTokenOnDestroy();
            modalDestroyToken.RegisterWithoutCaptureExecutionContext(async () => {
                // 이 코드는 모달 인스턴스가 파괴될 때 실행돼요.
                // 만약 우리가 아직 결과를 기다리고 있는(Pending) 모달이었다면,
                if (Modal.activeTcs == tcs && tcs.Task.Status == UniTaskStatus.Pending)
                {
                    ULog.Debug("[Modal] 활성 모달 인스턴스가 외부에서 파괴되어 취소로 처리합니다.");
                    // 파괴된 모달에서 ModalView를 가져오려고 시도해요 (없을 수도 있지만).
                    ModalView viewOnDestroy = currentModalInstance != null ? currentModalInstance.GetComponent<ModalView>() : null;
                    // 그리고 "외부에서 파괴됐으니 취소할게요" 라고 알리면서 정리해요.
                    await CleanupCurrentModalResources(currentContent, viewOnDestroy, false, true, tcs, typeof(T));
                }
            });

            // 📜 이야기: ModalView가 UI를 설정하도록 OnOpen 호출 전에 버튼 리스너만 설정합니다.
            // ModalView.OnOpen 내부에서 content를 기반으로 title, message, 버튼 텍스트/활성화 등을 설정합니다.

            // --- 버튼 리스너 설정 ---
            // ModalView의 버튼 객체에 Modal 시스템의 액션을 연결합니다.
            // 버튼의 텍스트나 활성화 상태는 ModalView.OnOpen에서 content를 기반으로 설정됩니다.
            SetupButtonClickListeners(modalView, currentContent, tcs);


            // 📜 이야기: 모달 창이 화면에 나타나기 직전에, ModalContent와 ModalView에게 "이제 열릴 거야!"라고 순서대로 알려줘요.
            if (content != null) await currentContent.OnOpen();
            await modalView.OnOpen(currentContent); // ModalView가 content를 기반으로 UI를 최종 구성합니다.

            // 📜 이야기: 모든 준비가 끝났어요! 이제 사용자가 버튼을 누를 때까지 기다려요.
            // tcs.Task는 '약속 증서'에 결과가 적힐 때까지 기다리는 마법이에요.
            return await tcs.Task;
        }

        /// <summary>
        /// ModalView의 버튼들에 클릭 리스너를 설정합니다.
        /// 버튼의 텍스트나 활성화 상태는 ModalView.OnOpen에서 처리됩니다.
        /// </summary>
        private static void SetupButtonClickListeners<T>(ModalView modalView, ModalContent content, UniTaskCompletionSource<T> tcs)
        {
            if (modalView.confirmButton != null)
            {
                // 기존 리스너 제거 후 새 리스너 추가
                modalView.confirmButton.onClick.RemoveAllListeners();
                // content.ShowConfirmButton 여부는 ModalView.OnOpen에서 버튼 자체의 활성화로 처리
                modalView.confirmButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, true, modalView));
            }

            if (modalView.cancelButton != null)
            {
                modalView.cancelButton.onClick.RemoveAllListeners();
                modalView.cancelButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, false, modalView));
            }

            if (modalView.closeButton != null)
            {
                modalView.closeButton.onClick.RemoveAllListeners();
                modalView.closeButton.onClick.AddListener(() => _ = HandleModalActionAsync(content, tcs, false, modalView));
            }
        }


        /// <summary>
        /// 🧑‍⚖️ 모달 버튼 클릭 판정 조수예요! 사용자가 모달의 버튼(확인, 취소, 닫기) 중 하나를 누르면 호출돼요.
        /// 어떤 버튼을 눌렀는지에 따라 모달을 닫고 결과를 처리해요.
        /// </summary>
        /// <typeparam name="T">모달이 돌려줄 결과의 타입이에요.</typeparam>
        /// <param name="tcs">결과를 기록할 '약속 증서'예요.</param>
        /// <param name="isConfirmAction">'확인' 버튼을 눌렀으면 true, 그 외 (취소, 닫기)는 false예요.</param>
        /// <param name="modalViewContext">현재 사용 중인 모달의 ModalView예요.</param>
        private static async UniTaskVoid HandleModalActionAsync<T>(
            ModalContent content,
            UniTaskCompletionSource<T> tcs,
            bool isConfirmAction,
            ModalView modalViewContext)
        {
            // 📜 이야기: 이 함수는 사용자가 버튼을 눌렀을 때 실행돼요.
            // 그런데 만약 이전에 처리하던 약속 증서(activeTcs)와 지금 받은 증서(tcs)가 다르거나,
            // 모달 창(currentModalInstance)이나 모달 뷰(modalViewContext)가 없다면, 뭔가 잘못된 상황이에요.
            // 이럴 때는 아무것도 하지 않고 조용히 돌아가요. (예: 모달이 이미 닫히고 있는 중일 수 있어요)
            if (tcs != activeTcs || currentModalInstance == null || modalViewContext == null)
            {
                return;
            }

            // 📜 이야기: 사용자가 버튼을 하나 눌렀으니, 다른 버튼들은 잠깐 못 누르게 막아요. (실수로 두 번 누르는 것 방지)
            modalViewContext.SetAllButtonsInteractable(false);

            // 📜 이야기: 이제 모달을 닫고 뒷정리를 할 시간이에요!
            // CleanupCurrentModalResources 조수에게 "이 모달 뷰를 사용했고, 사용자는 '확인'(또는 '취소')을 눌렀어요" 라고 알려주며 뒷정리를 부탁해요.
            // 이 뒷정리 과정에서 '약속 증서'에 최종 결과가 기록될 거예요.
            await CleanupCurrentModalResources(content, modalViewContext, isConfirmAction, false, tcs, typeof(T));
        }

        /// <summary>
        /// 🧹 모달 뒷정리 전문 조수예요! 모달 창과 투명 방패를 없애고, '약속 증서'에 결과를 적어줘요.
        /// 모달이 정상적으로 닫힐 때 (버튼 클릭) 또는 예기치 않게 파괴되었을 때 모두 이 조수가 마무리해요.
        /// </summary>
        /// <param name="content">닫으려는 모달의 내용물(ModalContent)이에요. OnClose 호출 및 결과 반환에 사용될 수 있어요.</param>
        /// <param name="modalViewToClose">닫으려는 모달의 ModalView예요. 결과값을 가져올 때 사용될 수 있어요.</param>
        /// <param name="confirmOrCancel">사용자가 '확인'을 눌렀으면 true, '취소'나 '닫기'를 눌렀으면 false예요.</param>
        /// <param name="wasExternallyDestroyed">모달이 버튼 클릭이 아니라 외부 요인으로 파괴되었으면 true예요.</param>
        /// <param name="tcsToResolve">결과를 기록할 '약속 증서'예요. 지정하지 않으면 현재 활성화된 증서(activeTcs)를 사용해요.</param>
        /// <param name="resultTypeForTcs">결과의 타입이에요. 지정하지 않으면 현재 활성화된 타입(activeResultType)을 사용해요.</param>
        private static async UniTask CleanupCurrentModalResources(
            ModalContent content,
            ModalView modalViewToClose, // ModalView 인스턴스 전달
            bool confirmOrCancel,
            bool wasExternallyDestroyed = false,
            IUniTaskSource tcsToResolve = null,
            Type resultTypeForTcs = null)
        {
            // 📜 이야기: 뒷정리를 시작하기 전에, 지금 없애야 할 모달 창과 방패를 기억해둬요.
            // 왜냐하면 뒷정리하는 동안 currentModalInstance 같은 전역 변수들이 null로 바뀔 거라서,
            // 미리 지역 변수(local variable)에 저장해두지 않으면 나중에 어떤 걸 없애야 할지 헷갈릴 수 있거든요.
            var blockerInstanceToDestroy = currentBlockerInstance;
            var modalInstanceToDestroy = currentModalInstance; // 이 시점의 currentModalInstance 사용

            // 📜 이야기: 만약 이 함수를 부를 때 '약속 증서'나 '결과 타입'을 따로 알려주지 않았다면,
            // 마법사의 비밀 도구함에 보관된 현재 활성화된 것들을 사용해요.
            if (tcsToResolve == null) tcsToResolve = activeTcs;
            if (resultTypeForTcs == null) resultTypeForTcs = activeResultType;

            // 📜 이야기: 뒷정리 시에는 현재 활성화된 content를 사용합니다.
            // Open 시 설정된 currentContent가 이 content와 동일해야 합니다.
            ModalContent contentToClose = content ?? currentContent;

            // 📜 이야기: 이제 이 모달은 끝났으니, 마법사의 비밀 도구함에서 관련 정보들을 모두 지워요.
            // 이렇게 해야 다음 모달을 열 때 깨끗한 상태에서 시작할 수 있어요.
            currentModalInstance = null;
            currentBlockerInstance = null;
            activeTcs = null;
            activeResultType = null;
            currentContent = null; // 현재 content 참조도 초기화

            // --- 투명 방패(Blocker) 제거 ---
            if (blockerInstanceToDestroy != null) // 없앨 방패가 있다면,
            {
                var blockerCG = blockerInstanceToDestroy.GetComponent<CanvasGroup>();
                if (blockerCG != null) await FadeUI(blockerCG, 0f, 0.3f, false);
                UnityEngine.Object.Destroy(blockerInstanceToDestroy);
            }

            // --- '약속 증서(TaskCompletionSource)'에 결과 기록 ---
            // 📜 이야기: 이제 '약속 증서'에 사용자의 선택을 기록할 시간이에요.
            // 이 증서에 결과가 적히면, Modal.Open 마법을 쓰고 기다리던 곳에서 그 결과를 받고 다음 일을 할 수 있게 돼요.
            if (tcsToResolve != null && tcsToResolve.GetStatus(0) == UniTaskStatus.Pending) // 증서가 있고, 아직 결과가 안 적혔다면,
            {

                // OnClose 호출을 TrySetResult 직전에 배치
                if (contentToClose != null) await contentToClose.OnClose();
                ModalView viewForOnClose = modalInstanceToDestroy?.GetComponent<ModalView>() ?? modalViewToClose;
                if (viewForOnClose != null)
                {
                    await viewForOnClose.OnClose(contentToClose);
                }


                // 만약 결과 타입이 bool (참/거짓) 이라면,
                if (resultTypeForTcs == typeof(bool))
                {
                    // 증서를 bool 타입용으로 바꿔서 confirmOrCancel 값을 직접 적어요.
                    if (tcsToResolve is UniTaskCompletionSource<bool> boolCompletionSource)
                    {
                        boolCompletionSource.TrySetResult(confirmOrCancel);
                    }
                }
                else // bool이 아닌 다른 타입의 경우 ModalView.GetResult() 사용
                {
                    object resultFromView = null;
                    // modalViewToClose는 파괴되었을 수도 있으므로, modalInstanceToDestroy에서 다시 가져오거나 null 체크 강화
                    ModalView viewForGetResult = modalInstanceToDestroy?.GetComponent<ModalView>() ?? modalViewToClose;
                    if (viewForGetResult != null)
                    {
                        resultFromView = viewForGetResult.GetResult();
                    }

                    // 📜 이야기: 이제 가져온 결과를 '약속 증서'에 적어야 하는데, 타입이 맞는지 잘 확인해야 해요.
                    // C#의 제네릭(Generic)이라는 기능을 사용해서 어떤 타입의 증서든 처리할 수 있게 만들어요.
                    // GetType().GetGenericTypeDefinition() 이 부분은 "이 증서가 UniTaskCompletionSource<T> 형태인가요?" 라고 묻는 거예요.
                    if (tcsToResolve.GetType().IsGenericType &&
                        tcsToResolve.GetType().GetGenericTypeDefinition() == typeof(UniTaskCompletionSource<>))
                    {
                        // 증서의 제네릭 타입(T)을 알아내요.
                        var genericArg = tcsToResolve.GetType().GetGenericArguments()[0];
                        if (genericArg == resultTypeForTcs) // 우리가 예상한 결과 타입과 같다면,
                        {
                            object resultToSet;
                            // ModalView에서 가져온 결과가 있고, 그 타입이 우리가 원하는 타입과 호환된다면,
                            if (resultFromView != null && resultTypeForTcs.IsAssignableFrom(resultFromView.GetType()))
                            {
                                resultToSet = resultFromView; // 그 결과를 사용해요.
                            }
                            else // 그렇지 않다면 (결과가 없거나, 타입이 안 맞으면),
                            {
                                if (resultFromView != null) // 타입은 안 맞지만 결과가 있긴 할 때 경고를 남겨요.
                                {
                                    ULog.Warning($"[Modal] GetResult() 반환 타입({resultFromView.GetType()})과 모달 결과 타입({resultTypeForTcs})이 일치하지 않습니다. 기본값을 사용합니다.");
                                }
                                resultToSet = GetDefault(resultTypeForTcs); // 해당 타입의 기본값을 사용해요.
                            }

                            // 📜 이야기: TrySetGenericResultHelper라는 또 다른 조수에게 "이 증서에 이 결과를 적어줘!" 라고 부탁해요.
                            // 이 조수는 어떤 타입(T)의 증서든 안전하게 결과를 적는 방법을 알고 있어요. (리플렉션 사용)
                            var trySetResultHelperMethod = typeof(Modal).GetMethod(nameof(TrySetGenericResultHelper), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
                                                                      .MakeGenericMethod(resultTypeForTcs);
                            trySetResultHelperMethod.Invoke(null, new object[] { tcsToResolve, resultToSet });
                        }
                    }
                }
            }

            // --- 모달 창(Modal Instance) 제거 ---
            if (modalInstanceToDestroy != null) // 없앨 모달 창이 있다면,
            {
                // 만약 모달이 외부 요인으로 파괴된 게 아니라면 (즉, 버튼 클릭 등으로 정상 종료된 거라면),
                // 게임 세상에서 완전히 없애요. (외부에서 이미 파괴됐다면 또 없앨 필요 없으니까요)
                if (!wasExternallyDestroyed)
                {
                    UnityEngine.Object.Destroy(modalInstanceToDestroy);
                }
            }
        }

        /// <summary>
        /// ✍️ 제네릭 '약속 증서'에 결과 적기 도우미예요.
        /// 어떤 타입(<typeparamref name="T"/>)의 UniTaskCompletionSource든 안전하게 결과를 설정해줘요.
        /// 혹시라도 타입이 안 맞아서 오류가 나면, 에러 메시지를 보여주고 기본값을 대신 넣어줘요.
        /// </summary>
        /// <typeparam name="T">결과의 타입이에요.</typeparam>
        /// <param name="tcs">결과를 적을 IUniTaskSource (UniTaskCompletionSource<T>여야 해요).</param>
        /// <param name="result">증서에 적을 실제 결과값이에요.</param>
        private static void TrySetGenericResultHelper<T>(IUniTaskSource tcs, object result)
        {
            if (tcs is UniTaskCompletionSource<T> genericTcs) // 받은 증서가 UniTaskCompletionSource<T> 타입이 맞는지 확인!
            {
                try
                {
                    // 결과를 T 타입으로 바꿔서(캐스팅) 증서에 적어요.
                    genericTcs.TrySetResult((T)result);
                }
                catch (InvalidCastException ex)
                {
                    // 에러를 기록하고, 대신 T 타입의 기본값을 넣어줘요. (프로그램이 멈추는 것 방지)
                    ULog.Error($"[Modal] 결과를 {typeof(T)}로 캐스팅하는데 실패했습니다: {ex.Message}. 기본값을 사용합니다.", ex);
                    genericTcs.TrySetResult(default(T));
                }
            }
        }

        /// <summary>
        /// ✨ 모달아, 사라져라! ✨
        /// 지금 화면에 떠 있는 모달 창을 닫고 싶을 때 이 마법 주문을 사용해요.
        /// 모달 창이 스르륵 사라질 거예요.
        ///
        /// 보통은 모달 창 안에 있는 [닫기] 버튼이나 [취소] 버튼을 누르면 자동으로 닫히지만,
        /// 특별한 경우에 코드로 직접 모달을 닫아야 할 때 사용할 수 있어요.
        /// </summary>
        /// <returns>모달이 성공적으로 닫히면 알려줘요 (특별한 값을 돌려주진 않아요).</returns>
        /// <example>
        /// <code>
        /// // 예를 들어, 5초 뒤에 자동으로 모달을 닫고 싶을 때
        /// async UniTask CloseModalAfter5Seconds()
        /// {
        ///     // (모달이 이미 열려있다고 가정해요)
        ///     await UniTask.Delay(TimeSpan.FromSeconds(5)); // 5초 기다리기
        ///     Debug.Log("시간이 다 되었어요! 모달을 닫습니다.");
        ///     await Modal.Close(); // 모달 닫기 마법!
        /// }
        /// </code>
        /// </example>
        public static async UniTask Close()
        {
            // 📜 이야기: 닫을 모달이 없거나, 이미 처리 중인 '약속 증서'가 없다면 할 일이 없어요.
            if (currentModalInstance == null && activeTcs == null) return;

            // 📜 이야기: 현재 활성화된 '약속 증서'와 '결과 타입', 그리고 모달 뷰를 가져와요.
            // 이 정보들은 모달을 안전하게 닫고 결과를 처리하는 데 필요해요.
            IUniTaskSource tcsToClose = activeTcs;
            Type resultTypeToClose = activeResultType;
            ModalView view = currentModalInstance?.GetComponent<ModalView>();
            ModalContent contentToClose = currentContent; // 현재 저장된 content 사용

            // 모달에 버튼들이 있다면, 실수로 또 누르지 못하게 잠시 비활성화해요.
            if (view != null) view.SetAllButtonsInteractable(false); // ModalView의 메서드 사용

            // 📜 이야기: 지금부터 모달을 본격적으로 닫을 거니까,
            // 현재 모달 인스턴스와 블로커 인스턴스를 지역 변수에 잠시 저장해둬요.
            // 그리고 전역 변수들은 null로 만들어서 "지금은 열린 모달 없음!" 상태로 만들어요.
            var localModalInstance = currentModalInstance;
            var localBlockerInstance = currentBlockerInstance;
            // currentContent는 CleanupCurrentModalResources에서 처리되므로 여기서는 local로 옮기지 않음

            currentModalInstance = null;
            currentBlockerInstance = null;
            activeTcs = null;
            activeResultType = null;
            currentContent = null; // 현재 content 참조도 초기화

            // 📜 이야기: 만약 닫으려는 '약속 증서'가 있고, 아직 결과가 정해지지 않았다면,
            // "취소"된 것으로 처리해요. Close() 마법은 항상 '취소'로 간주하거든요.
            if (tcsToClose != null && tcsToClose.GetStatus(0) == UniTaskStatus.Pending)
            {
                // OnClose 호출을 결과 설정 직전에 배치
                if (contentToClose != null) await contentToClose.OnClose();
                ModalView viewForOnClose = localModalInstance?.GetComponent<ModalView>(); // localModalInstance 사용
                if (viewForOnClose != null)
                {
                    await viewForOnClose.OnClose(contentToClose);
                }

                if (resultTypeToClose == typeof(bool) && tcsToClose is UniTaskCompletionSource<bool> boolTcs)
                {
                    // 결과 타입이 bool이면 false (취소)를 설정해요.
                    boolTcs.TrySetResult(false);
                }
                // 결과 타입이 bool이 아니고, 제네릭 UniTaskCompletionSource 형태라면,
                else if (resultTypeToClose != null && tcsToClose.GetType().IsGenericType && tcsToClose.GetType().GetGenericTypeDefinition() == typeof(UniTaskCompletionSource<>))
                {
                    // 해당 타입의 기본값으로 결과를 설정해요. (예: int면 0, string이면 null)
                    // TrySetDefaultResultHelper 조수에게 이 일을 맡겨요.
                    var genericArg = tcsToClose.GetType().GetGenericArguments()[0];
                    if (genericArg == resultTypeToClose)
                    {
                        var trySetDefaultResultMethod = typeof(Modal).GetMethod(nameof(TrySetDefaultResultHelper), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
                                                                  .MakeGenericMethod(genericArg);
                        trySetDefaultResultMethod.Invoke(null, new object[] { tcsToClose });
                    }
                }
            }

            // --- 투명 방패(Blocker)와 모달 창(Modal Instance) 제거 ---
            // 이 부분은 CleanupCurrentModalResources와 비슷하게 동작해요.
            if (localBlockerInstance != null)
            {
                var blockerCG = localBlockerInstance.GetComponent<CanvasGroup>();
                if (blockerCG != null) await FadeUI(blockerCG, 0f, 0.3f, false); // 부드럽게 사라지게
                UnityEngine.Object.Destroy(localBlockerInstance); // 완전히 제거
            }

            if (localModalInstance != null)
            {
                UnityEngine.Object.Destroy(localModalInstance); // 완전히 제거
            }
        }

        /// <summary>
        /// ✍️ 제네릭 '약속 증서'에 기본값 적기 도우미예요.
        /// 어떤 타입(<typeparamref name="T"/>)의 UniTaskCompletionSource든 해당 타입의 기본값(default)으로 결과를 설정해줘요.
        /// Close() 함수에서 모달을 강제로 닫을 때 사용돼요.
        /// </summary>
        /// <typeparam name="T">결과의 타입이에요.</typeparam>
        /// <param name="tcs">결과를 적을 IUniTaskSource (UniTaskCompletionSource<T>여야 해요).</param>
        private static void TrySetDefaultResultHelper<T>(IUniTaskSource tcs)
        {
            if (tcs is UniTaskCompletionSource<T> genericTcs) // 받은 증서가 UniTaskCompletionSource<T> 타입이 맞는지 확인!
            {
                genericTcs.TrySetResult(default(T)); // T 타입의 기본값 (예: bool은 false, int는 0, 클래스는 null)을 설정해요.
            }
        }

        /// <summary>
        /// ✨ UI를 부드럽게 나타나거나 사라지게 하는 마법이에요 (페이드 효과).
        /// CanvasGroup의 투명도(alpha)를 조절해서 천천히 보이거나 안 보이게 만들어요.
        /// </summary>
        /// <param name="canvasGroup">투명도를 조절할 CanvasGroup 컴포넌트예요.</param>
        /// <param name="targetAlpha">목표 투명도예요. (0.0 = 완전 투명, 1.0 = 완전 불투명)</param>
        /// <param name="duration">페이드 효과에 걸리는 시간(초)이에요.</param>
        /// <param name="fadeIn">true면 나타나게(페이드 인), false면 사라지게(페이드 아웃) 해요.</param>
        private static async UniTask FadeUI(CanvasGroup canvasGroup, float targetAlpha, float duration, bool fadeIn)
        {
            if (canvasGroup == null) return; // CanvasGroup이 없으면 아무것도 안 해요.
            // 📜 이야기: 이 CanvasGroup이 혹시라도 중간에 사라지면(파괴되면) 페이드 효과를 멈춰야 해요.
            // 그래서 GetCancellationTokenOnDestroy()로 "파괴되면 알려줘!" 신호를 받아둬요.
            CancellationToken cancellationToken;
            try
            {
                cancellationToken = canvasGroup.GetCancellationTokenOnDestroy();
            }
            catch (Exception ex) // 아주 드물게 이 과정에서 오류가 날 수도 있어서 예외 처리를 해줘요.
            {
                ULog.Warning($"[Modal] CanvasGroup에 대한 취소 토큰을 가져오는 중 오류 발생: {ex.Message}", ex);
                return;
            }
            float startAlpha = canvasGroup.alpha; // 현재 투명도에서 시작
            float time = 0; // 시간 기록 변수
            if (fadeIn) // 나타나는 효과(페이드 인)라면,
            {
                // 사용자가 이 UI와 상호작용(클릭 등)할 수 있게 하고, 마우스 클릭을 막지 않도록 설정해요.
                canvasGroup.interactable = true;
                canvasGroup.blocksRaycasts = true;
            }
            // 📜 이야기: duration(지정된 시간) 동안 투명도를 조금씩 바꿔요.
            while (time < duration)
            {
                // 만약 중간에 "파괴 신호"가 오거나 CanvasGroup이 사라지면 즉시 멈춰요.
                if (cancellationToken.IsCancellationRequested || canvasGroup == null) return;

                // Mathf.Lerp는 두 값 사이를 부드럽게 변화시키는 마법이에요.
                // time / duration은 현재 진행률 (0.0 ~ 1.0)을 나타내요.
                canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, time / duration);
                time += Time.deltaTime; // 지난 시간을 더해주고,
                // 다음 프레임까지 잠깐 기다렸다가 다시 실행해요. (UniTask.Yield)
                // SuppressCancellationThrow는 "파괴 신호"가 와도 오류를 내지 말고 조용히 멈추라는 뜻이에요.
                await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken).SuppressCancellationThrow();
            }

            // 시간이 다 되었거나 중간에 멈췄을 때, 최종적으로 목표 투명도로 설정해요.
            if (cancellationToken.IsCancellationRequested || canvasGroup == null) return;
            canvasGroup.alpha = targetAlpha;
            if (!fadeIn) // 사라지는 효과(페이드 아웃)였다면,
            {
                // 이제 이 UI는 보이지 않으니 상호작용도 막고, 마우스 클릭도 막아요.
                canvasGroup.interactable = false;
                canvasGroup.blocksRaycasts = false;
            }
        }

        /// <summary>
        /// 🎁 특정 타입의 '기본값'을 돌려주는 작은 도우미예요.
        /// 값 타입(struct, int, bool 등)이면 Activator.CreateInstance로 기본 인스턴스를 만들고,
        /// 참조 타입(class)이면 null을 돌려줘요.
        /// </summary>
        /// <param name="type">기본값을 알고 싶은 타입 정보예요.</param>
        /// <returns>해당 타입의 기본값이에요.</returns>
        private static object GetDefault(Type type)
        {
            if (type == null) return null; // 타입 정보가 없으면 null
            if (type.IsValueType) return Activator.CreateInstance(type); // 값 타입이면 기본 인스턴스 생성 (예: int는 0, bool은 false)
            return null; // 참조 타입이면 null
        }
    }
}