#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

/// <summary>
/// Tools → VRBeatSaber → 전체 자동 설정
/// 씬 생성, 프리팹 생성, Build Settings 등록까지 한 번에 처리
/// </summary>
public static class VRBeatSaberSceneBuilder
{
    private static GameObject s_cardPrefab;

    [MenuItem("Tools/VRBeatSaber/전체 자동 설정 (한 번만 실행)")]
    public static void SetupAll()
    {
        if (!EditorUtility.DisplayDialog(
            "VRBeatSaber 자동 설정",
            "Intro / SongSelect / SongCreator 씬과\nSongCard 프리팹, Build Settings를 자동으로 설정합니다.\n\n현재 씬이 저장되지 않으면 경고가 뜹니다.",
            "실행", "취소")) return;

        if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;

        EnsureFolder("Assets/Scenes");
        EnsureFolder("Assets/Prefab");

        // 1. SongCard 프리팹
        s_cardPrefab = CreateSongCardPrefab();

        // 2. SongSelect 씬 생성
        BuildSongSelectScene();

        // 3. SongCreator 씬 생성
        BuildSongCreatorScene();

        // 4. Intro 씬에 UI 추가
        ModifyIntroScene();

        // 5. Game 씬 AudioClip 제거
        FixGameScene();

        // 6. Build Settings 등록
        RegisterBuildSettings();

        AssetDatabase.Refresh();

        EditorUtility.DisplayDialog(
            "완료",
            "자동 설정 완료!\n\n" +
            "[ 직접 해야 할 것 (2가지) ]\n\n" +
            "1. SongCreator 씬 → [CreatorManager]\n" +
            "   → NasPublisher → Nas Password\n" +
            "   (DSM 비밀번호 입력)\n\n" +
            "2. Quest에 MP3 파일 넣기\n" +
            "   adb push 파일.mp3\n" +
            "   /sdcard/Android/data/{패키지명}/files/input/",
            "확인");
    }

    // ══════════════════════════════════════════════════════════
    // 1. SongCard 프리팹
    // ══════════════════════════════════════════════════════════

    private static GameObject CreateSongCardPrefab()
    {
        var root = new GameObject("SongCard");
        root.AddComponent<Image>().color = new Color(0.12f, 0.12f, 0.12f);
        var btn = root.AddComponent<Button>();
        var rt  = root.GetComponent<RectTransform>();
        rt.sizeDelta = new Vector2(430, 90);

        // 다운로드 뱃지 (우측 녹색 원)
        var badge    = new GameObject("DownloadedBadge");
        badge.transform.SetParent(root.transform, false);
        badge.AddComponent<Image>().color = new Color(0.2f, 0.9f, 0.4f);
        var bRt = badge.GetComponent<RectTransform>();
        bRt.anchorMin        = new Vector2(1, 0.5f);
        bRt.anchorMax        = new Vector2(1, 0.5f);
        bRt.pivot            = new Vector2(1, 0.5f);
        bRt.anchoredPosition = new Vector2(-12, 0);
        bRt.sizeDelta        = new Vector2(20, 20);

        // 제목
        var titleTMP = MakeTMP("TitleText", root.transform, "곡 제목",
            new Vector2(-10, 16), new Vector2(370, 30), 20);

        // 아티스트
        var artistTMP = MakeTMP("ArtistText", root.transform, "아티스트",
            new Vector2(-30, -16), new Vector2(260, 24), 15);
        artistTMP.color = new Color(0.7f, 0.7f, 0.7f);

        // 길이
        var durTMP = MakeTMP("DurationText", root.transform, "0:00",
            new Vector2(-45, -16), new Vector2(120, 24), 15);
        durTMP.color     = new Color(0.6f, 0.6f, 0.6f);
        durTMP.alignment = TextAlignmentOptions.MidlineRight;

        // SongCard 컴포넌트 바인딩
        var card = root.AddComponent<SongCard>();
        var so   = new SerializedObject(card);
        Bind(so, "titleText",       titleTMP);
        Bind(so, "artistText",      artistTMP);
        Bind(so, "durationText",    durTMP);
        Bind(so, "downloadedBadge", badge);
        Bind(so, "button",          btn);
        so.ApplyModifiedProperties();

        var prefab = PrefabUtility.SaveAsPrefabAsset(root, "Assets/Prefab/SongCard.prefab");
        Object.DestroyImmediate(root);
        Debug.Log("[SceneBuilder] SongCard 프리팹 생성 완료");
        return prefab;
    }

    // ══════════════════════════════════════════════════════════
    // 2. SongSelect 씬
    // ══════════════════════════════════════════════════════════

    private static void BuildSongSelectScene()
    {
        var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
        MakeCamera(new Vector3(0, 1.6f, 0));
        MakeEventSystem();

        // SongLibrary는 DontDestroyOnLoad를 사용하므로 단독 오브젝트에 배치
        // (GameManager와 같이 두면 DownloadManager까지 DontDestroyOnLoad로 이동해 버림)
        var slGO = new GameObject("[SongLibrary]");
        slGO.AddComponent<SongLibrary>();

        // GameManager: 씬마다 새로 생성되는 매니저들
        var gm    = new GameObject("[GameManager]");
        gm.AddComponent<CacheManager>();
        var dlMgr = gm.AddComponent<DownloadManager>();

        var canvas = MakeCanvas(new Vector2(1200, 700), new Vector3(0, 1.5f, 2f));

        // 탭
        var tabPanel    = MakePanel("TabPanel",    canvas.transform, new Vector2(0, 310),   new Vector2(1180, 60));
        var tabAllBtn   = MakeButton("TabAllBtn",   tabPanel.transform, "전체",   new Vector2(-290, 0), new Vector2(200, 50));
        var tabOwnedBtn = MakeButton("TabOwnedBtn", tabPanel.transform, "보유중", new Vector2(-80,  0), new Vector2(200, 50));

        // 목록 (좌측)
        var listPanel = MakePanel("ListPanel", canvas.transform, new Vector2(-355, -30), new Vector2(460, 600));
        var (_, cardContainer) = MakeScrollRect("SongScroll", listPanel.transform, Vector2.zero, new Vector2(450, 590));

        // 상세 패널 (우측)
        var detailGO = MakePanel("DetailPanel", canvas.transform, new Vector2(250, -30), new Vector2(680, 600));
        var detail   = detailGO.AddComponent<SongDetailPanel>();

        var titleTxt  = MakeTMP("TitleText",  detailGO.transform, "곡 제목",    new Vector2(0, 250),  new Vector2(640, 50), 28);
        var artistTxt = MakeTMP("ArtistText", detailGO.transform, "아티스트",   new Vector2(0, 200),  new Vector2(640, 36), 20);
        var infoTxt   = MakeTMP("InfoText",   detailGO.transform, "BPM | 길이", new Vector2(0, 160),  new Vector2(640, 30), 18);

        var diffPanel = MakePanel("DiffPanel", detailGO.transform, new Vector2(0, 90), new Vector2(640, 60));
        var btnEasy   = MakeButton("EasyBtn",   diffPanel.transform, "Easy",   new Vector2(-240, 0), new Vector2(140, 50));
        var btnNorm   = MakeButton("NormalBtn", diffPanel.transform, "Normal", new Vector2(-80,  0), new Vector2(140, 50));
        var btnHard   = MakeButton("HardBtn",   diffPanel.transform, "Hard",   new Vector2(80,   0), new Vector2(140, 50));
        var btnExp    = MakeButton("ExpertBtn", diffPanel.transform, "Expert", new Vector2(240,  0), new Vector2(140, 50));

        var actPanel = MakePanel("ActionPanel", detailGO.transform, new Vector2(0, 15), new Vector2(640, 60));
        var dlBtn    = MakeButton("DownloadBtn", actPanel.transform, "▼ 다운로드", new Vector2(-165, 0), new Vector2(290, 50));
        var delBtn   = MakeButton("DeleteBtn",   actPanel.transform, "삭제",       new Vector2(165,  0), new Vector2(290, 50));

        var progGroup  = MakePanel("ProgressGroup",  detailGO.transform, new Vector2(0, -50), new Vector2(640, 40));
        var progSlider = MakeSlider("ProgressSlider", progGroup.transform, new Vector2(-70, 0), new Vector2(480, 28));
        var progText   = MakeTMP("ProgressText", progGroup.transform, "0%", new Vector2(275, 0), new Vector2(100, 30), 16);

        var playBtn = MakeButton("PlayButton", detailGO.transform, "▶  플레이", new Vector2(0, -140), new Vector2(300, 65));

        var loadingOvr = MakePanel("LoadingOverlay", canvas.transform, Vector2.zero, new Vector2(1200, 700));
        loadingOvr.GetComponent<Image>().color = new Color(0, 0, 0, 0.75f);
        MakeTMP("LoadingText", loadingOvr.transform, "불러오는 중...", Vector2.zero, new Vector2(400, 60), 26);

        var errorOvr = MakePanel("ErrorOverlay", canvas.transform, Vector2.zero, new Vector2(1200, 700));
        errorOvr.GetComponent<Image>().color = new Color(0, 0, 0, 0.75f);
        var errorTxt = MakeTMP("ErrorText", errorOvr.transform, "", Vector2.zero, new Vector2(700, 120), 22);

        loadingOvr.SetActive(false);
        errorOvr.SetActive(false);
        detailGO.SetActive(false);

        // SongDetailPanel 바인딩
        var dso = new SerializedObject(detail);
        Bind(dso, "titleText",      titleTxt);
        Bind(dso, "artistText",     artistTxt);
        Bind(dso, "infoText",       infoTxt);
        Bind(dso, "btnEasy",        btnEasy.GetComponent<Button>());
        Bind(dso, "btnNormal",      btnNorm.GetComponent<Button>());
        Bind(dso, "btnHard",        btnHard.GetComponent<Button>());
        Bind(dso, "btnExpert",      btnExp.GetComponent<Button>());
        Bind(dso, "downloadButton", dlBtn.GetComponent<Button>());
        Bind(dso, "deleteButton",   delBtn.GetComponent<Button>());
        Bind(dso, "playButton",     playBtn.GetComponent<Button>());
        Bind(dso, "progressGroup",  progGroup);
        Bind(dso, "progressSlider", progSlider);
        Bind(dso, "progressText",   progText);
        dso.ApplyModifiedProperties();

        // SongSelectManager 바인딩
        var mgr = canvas.AddComponent<SongSelectManager>();
        var mso = new SerializedObject(mgr);
        Bind(mso, "tabAllBtn",       tabAllBtn.GetComponent<Button>());
        Bind(mso, "tabOwnedBtn",     tabOwnedBtn.GetComponent<Button>());
        Bind(mso, "cardContainer",   cardContainer);
        Bind(mso, "songCardPrefab",  s_cardPrefab);
        Bind(mso, "detailPanel",     detail);
        Bind(mso, "downloadManager", dlMgr);
        Bind(mso, "loadingOverlay",  loadingOvr);
        Bind(mso, "errorOverlay",    errorOvr);
        Bind(mso, "errorText",       errorTxt);
        mso.ApplyModifiedProperties();

        EditorSceneManager.SaveScene(scene, "Assets/Scenes/SongSelect.unity");
        Debug.Log("[SceneBuilder] SongSelect 씬 완료");
    }

    // ══════════════════════════════════════════════════════════
    // 3. SongCreator 씬
    // ══════════════════════════════════════════════════════════

    private static void BuildSongCreatorScene()
    {
        var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
        MakeCamera(new Vector3(0, 1.6f, 0));
        MakeEventSystem();

        // SongLibrary 싱글턴 — SongCreator에서 직접 쓰진 않지만 DontDestroyOnLoad 체계 유지
        new GameObject("[SongLibrary]").AddComponent<SongLibrary>();

        var gm        = new GameObject("[CreatorManager]");
        var uploader  = gm.AddComponent<BeatSageUploader>();
        var publisher = gm.AddComponent<NasPublisher>();
        var scMgr     = gm.AddComponent<SongCreatorManager>();

        var canvas = MakeCanvas(new Vector2(900, 760), new Vector3(0, 1.5f, 2f));

        MakeTMP("TitleLabel", canvas.transform, "노래 만들기", new Vector2(0, 345), new Vector2(860, 55), 32);

        var audioPanel = MakePanel("AudioPanel", canvas.transform, new Vector2(0, 250), new Vector2(860, 90));
        var audioDd    = MakeDropdown("AudioDropdown", audioPanel.transform, new Vector2(-120, 0), new Vector2(540, 60));
        var refreshBtn = MakeButton("RefreshBtn", audioPanel.transform, "새로고침", new Vector2(255, 0), new Vector2(160, 58));
        var pathHint   = MakeTMP("PathHint", canvas.transform, "", new Vector2(0, 195), new Vector2(860, 28), 13);

        var metaPanel   = MakePanel("MetaPanel",   canvas.transform, new Vector2(0, 105), new Vector2(860, 110));
        var titleInput  = MakeInputField("TitleInput",  metaPanel.transform, "곡 제목",  new Vector2(-280, 0), new Vector2(255, 55));
        var artistInput = MakeInputField("ArtistInput", metaPanel.transform, "아티스트", new Vector2(0,    0), new Vector2(255, 55));
        var bpmInput    = MakeInputField("BpmInput",    metaPanel.transform, "BPM",      new Vector2(280,  0), new Vector2(255, 55));

        var diffPanel  = MakePanel("DiffPanel", canvas.transform, new Vector2(0, -15), new Vector2(860, 65));
        var togEasy    = MakeToggle("EasyToggle",   diffPanel.transform, "Easy",   new Vector2(-310, 0));
        var togNormal  = MakeToggle("NormalToggle", diffPanel.transform, "Normal", new Vector2(-155, 0));
        var togHard    = MakeToggle("HardToggle",   diffPanel.transform, "Hard",   new Vector2(0,    0));
        var togExpert  = MakeToggle("ExpertToggle", diffPanel.transform, "Expert", new Vector2(155,  0));
        togHard.isOn   = true;
        togExpert.isOn = true;

        var actPanel  = MakePanel("ActionPanel", canvas.transform, new Vector2(0, -125), new Vector2(860, 80));
        var genBtn    = MakeButton("GenerateButton", actPanel.transform, "AI 생성 시작",  new Vector2(-175, 0), new Vector2(440, 65));
        var manualBtn = MakeButton("ManualButton",   actPanel.transform, "직접 만들기 →", new Vector2(245,  0), new Vector2(185, 42));

        var progGroup  = MakePanel("ProgressGroup",  canvas.transform, new Vector2(0, -250), new Vector2(860, 95));
        var statusTxt  = MakeTMP("StatusText",    progGroup.transform, "", new Vector2(0, 28), new Vector2(840, 36), 18);
        var progSlider = MakeSlider("ProgressSlider", progGroup.transform, new Vector2(0, -15), new Vector2(820, 28));
        progGroup.SetActive(false);

        var sso = new SerializedObject(scMgr);
        Bind(sso, "audioDropdown",      audioDd);
        Bind(sso, "refreshBtn",         refreshBtn.GetComponent<Button>());
        Bind(sso, "inputPathHint",      pathHint);
        Bind(sso, "titleInput",         titleInput);
        Bind(sso, "artistInput",        artistInput);
        Bind(sso, "bpmInput",           bpmInput);
        Bind(sso, "toggleEasy",         togEasy);
        Bind(sso, "toggleNormal",       togNormal);
        Bind(sso, "toggleHard",         togHard);
        Bind(sso, "toggleExpert",       togExpert);
        Bind(sso, "generateButton",     genBtn.GetComponent<Button>());
        Bind(sso, "manualEditorButton", manualBtn.GetComponent<Button>());
        Bind(sso, "progressGroup",      progGroup);
        Bind(sso, "statusText",         statusTxt);
        Bind(sso, "progressSlider",     progSlider);
        Bind(sso, "beatSageUploader",   uploader);
        Bind(sso, "nasPublisher",       publisher);
        sso.ApplyModifiedProperties();

        EditorSceneManager.SaveScene(scene, "Assets/Scenes/SongCreator.unity");
        Debug.Log("[SceneBuilder] SongCreator 씬 완료");
    }

    // ══════════════════════════════════════════════════════════
    // 4. Intro 씬 수정
    // ══════════════════════════════════════════════════════════

    private static void ModifyIntroScene()
    {
        const string path = "Assets/Scenes/Intro.unity";
        if (!File.Exists(path)) { Debug.LogWarning("[SceneBuilder] Intro.unity 없음 — 건너뜀"); return; }

        var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);

        // 이미 IntroManager가 있으면 건너뜀
        if (Object.FindObjectOfType<IntroManager>() != null)
        {
            Debug.Log("[SceneBuilder] Intro 씬 IntroManager 이미 존재 — 건너뜀");
            return;
        }

        var canvas  = MakeCanvas(new Vector2(600, 420), new Vector3(0, 1.5f, 2f));
        MakeTMP("TitleText", canvas.transform, "VR BEAT SABER", new Vector2(0, 145), new Vector2(560, 70), 38);
        var playBtn   = MakeButton("PlayButton",   canvas.transform, "게임하기",   new Vector2(0, 30),  new Vector2(420, 85));
        var createBtn = MakeButton("CreateButton", canvas.transform, "노래만들기", new Vector2(0, -80), new Vector2(420, 85));

        var introGO = new GameObject("[IntroManager]");
        var mgr     = introGO.AddComponent<IntroManager>();
        var iso     = new SerializedObject(mgr);
        Bind(iso, "playButton",   playBtn.GetComponent<Button>());
        Bind(iso, "createButton", createBtn.GetComponent<Button>());
        iso.ApplyModifiedProperties();

        EditorSceneManager.SaveScene(scene);
        Debug.Log("[SceneBuilder] Intro 씬 수정 완료");
    }

    // ══════════════════════════════════════════════════════════
    // 5. Game 씬 — AudioClip 참조 제거
    // ══════════════════════════════════════════════════════════

    private static void FixGameScene()
    {
        const string path = "Assets/Scenes/Game.unity";
        if (!File.Exists(path)) { Debug.LogWarning("[SceneBuilder] Game.unity 없음 — 건너뜀"); return; }

        var scene   = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
        var spawner = Object.FindObjectOfType<Spawner>();

        if (spawner == null) { Debug.LogWarning("[SceneBuilder] Spawner 없음"); return; }

        // Spawner가 참조하는 AudioSource의 AudioClip 제거 (런타임에 로컬 파일에서 로드)
        var sso     = new SerializedObject(spawner);
        var asProp  = sso.FindProperty("audioSource");
        if (asProp?.objectReferenceValue is AudioSource audioSrc)
        {
            var asObj  = new SerializedObject(audioSrc);
            var clipProp = asObj.FindProperty("m_audioClip");
            if (clipProp != null)
            {
                clipProp.objectReferenceValue = null;
                asObj.ApplyModifiedProperties();
                Debug.Log("[SceneBuilder] AudioClip 참조 제거 완료");
            }
        }

        EditorSceneManager.SaveScene(scene);
        Debug.Log("[SceneBuilder] Game 씬 정리 완료");
    }

    // ══════════════════════════════════════════════════════════
    // 6. Build Settings
    // ══════════════════════════════════════════════════════════

    private static void RegisterBuildSettings()
    {
        var paths = new[]
        {
            "Assets/Scenes/Intro.unity",
            "Assets/Scenes/SongSelect.unity",
            "Assets/Scenes/SongCreator.unity",
            "Assets/Scenes/Game.unity",
            "Assets/Scenes/MapEditorScene.unity",
        };

        var list = new List<EditorBuildSettingsScene>();
        foreach (var p in paths)
        {
            if (File.Exists(p))
                list.Add(new EditorBuildSettingsScene(p, true));
            else
                Debug.LogWarning($"[SceneBuilder] 씬 파일 없음: {p}");
        }

        EditorBuildSettings.scenes = list.ToArray();
        Debug.Log($"[SceneBuilder] Build Settings 등록 완료: {list.Count}개");
    }

    // ══════════════════════════════════════════════════════════
    // UI 헬퍼
    // ══════════════════════════════════════════════════════════

    private static void MakeCamera(Vector3 pos)
    {
        var go = new GameObject("Main Camera");
        go.AddComponent<Camera>();
        go.AddComponent<AudioListener>();
        go.tag = "MainCamera";
        go.transform.position = pos;
    }

    private static void MakeEventSystem()
    {
        var go = new GameObject("EventSystem");
        go.AddComponent<EventSystem>();
        // New Input System 사용 시 StandaloneInputModule 대신 InputSystemUIInputModule 필요
        // 없으면 StandaloneInputModule로 폴백 (Player Settings에서 Both로 설정 시 동작)
        var inputModuleType = System.Type.GetType(
            "UnityEngine.InputSystem.UI.InputSystemUIInputModule, Unity.InputSystem");
        if (inputModuleType != null)
            go.AddComponent(inputModuleType);
        else
            go.AddComponent<StandaloneInputModule>();
    }

    private static GameObject MakeCanvas(Vector2 size, Vector3 worldPos)
    {
        var go = new GameObject("Canvas");
        var c  = go.AddComponent<Canvas>();
        c.renderMode = RenderMode.WorldSpace;
        go.AddComponent<CanvasScaler>();

        // VR(XR Interaction Toolkit)에서는 TrackedDeviceGraphicRaycaster 필요
        var trackedRaycasterType = System.Type.GetType(
            "UnityEngine.XR.Interaction.Toolkit.UI.TrackedDeviceGraphicRaycaster, Unity.XR.Interaction.Toolkit");
        if (trackedRaycasterType != null)
            go.AddComponent(trackedRaycasterType);
        else
            go.AddComponent<GraphicRaycaster>();
        var rt = go.GetComponent<RectTransform>();
        rt.sizeDelta        = size;
        go.transform.position   = worldPos;
        go.transform.localScale = Vector3.one * 0.002f;
        return go;
    }

    private static GameObject MakePanel(string name, Transform parent, Vector2 pos, Vector2 size)
    {
        var go  = new GameObject(name);
        go.transform.SetParent(parent, false);
        go.AddComponent<Image>().color = new Color(0.08f, 0.08f, 0.08f, 0.88f);
        var rt  = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos;
        rt.sizeDelta        = size;
        return go;
    }

    private static GameObject MakeButton(string name, Transform parent, string label, Vector2 pos, Vector2 size)
    {
        var go = new GameObject(name);
        go.transform.SetParent(parent, false);
        go.AddComponent<Image>().color = new Color(0.22f, 0.22f, 0.22f);
        go.AddComponent<Button>();
        var rt = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos;
        rt.sizeDelta        = size;

        var tgo = new GameObject("Text");
        tgo.transform.SetParent(go.transform, false);
        var tmp = tgo.AddComponent<TextMeshProUGUI>();
        tmp.text      = label;
        tmp.fontSize  = 18;
        tmp.alignment = TextAlignmentOptions.Center;
        FullStretch(tgo.GetComponent<RectTransform>());
        return go;
    }

    private static TMP_Text MakeTMP(string name, Transform parent, string text, Vector2 pos, Vector2 size, int fs)
    {
        var go  = new GameObject(name);
        go.transform.SetParent(parent, false);
        var tmp = go.AddComponent<TextMeshProUGUI>();
        tmp.text      = text;
        tmp.fontSize  = fs;
        tmp.alignment = TextAlignmentOptions.MidlineLeft;
        var rt  = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos;
        rt.sizeDelta        = size;
        return tmp;
    }

    private static TMP_InputField MakeInputField(string name, Transform parent, string ph, Vector2 pos, Vector2 size)
    {
        var go    = new GameObject(name);
        go.transform.SetParent(parent, false);
        go.AddComponent<Image>().color = new Color(0.15f, 0.15f, 0.15f);
        var field = go.AddComponent<TMP_InputField>();
        var rt    = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos;
        rt.sizeDelta        = size;

        var area   = new GameObject("Text Area");
        area.transform.SetParent(go.transform, false);
        var aRt    = area.AddComponent<RectTransform>();
        aRt.anchorMin = Vector2.zero; aRt.anchorMax = Vector2.one;
        aRt.sizeDelta = new Vector2(-16, -8);

        var phGO   = new GameObject("Placeholder");
        phGO.transform.SetParent(area.transform, false);
        var phTMP  = phGO.AddComponent<TextMeshProUGUI>();
        phTMP.text = ph; phTMP.color = new Color(0.5f, 0.5f, 0.5f); phTMP.fontSize = 16;
        FullStretch(phGO.GetComponent<RectTransform>());

        var inGO   = new GameObject("Text");
        inGO.transform.SetParent(area.transform, false);
        var inTMP  = inGO.AddComponent<TextMeshProUGUI>();
        inTMP.fontSize = 16;
        FullStretch(inGO.GetComponent<RectTransform>());

        field.textViewport  = aRt;
        field.placeholder   = phTMP;
        field.textComponent = inTMP;
        return field;
    }

    private static TMP_Dropdown MakeDropdown(string name, Transform parent, Vector2 pos, Vector2 size)
    {
        var go = new GameObject(name);
        go.transform.SetParent(parent, false);
        go.AddComponent<Image>().color = new Color(0.15f, 0.15f, 0.15f);
        var dd = go.AddComponent<TMP_Dropdown>();
        var rt = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos;
        rt.sizeDelta        = size;

        // Caption label (선택된 항목 표시)
        var labelGO  = new GameObject("Label");
        labelGO.transform.SetParent(go.transform, false);
        var labelTMP = labelGO.AddComponent<TextMeshProUGUI>();
        labelTMP.text     = "";
        labelTMP.fontSize = 16;
        var labelRt  = labelGO.GetComponent<RectTransform>();
        labelRt.anchorMin = Vector2.zero; labelRt.anchorMax = Vector2.one;
        labelRt.offsetMin = new Vector2(10, 2); labelRt.offsetMax = new Vector2(-28, -2);

        // Arrow indicator
        var arrowGO = new GameObject("Arrow");
        arrowGO.transform.SetParent(go.transform, false);
        arrowGO.AddComponent<Image>().color = Color.white;
        var arrowRt = arrowGO.GetComponent<RectTransform>();
        arrowRt.anchorMin        = new Vector2(1, 0.5f);
        arrowRt.anchorMax        = new Vector2(1, 0.5f);
        arrowRt.pivot            = new Vector2(1, 0.5f);
        arrowRt.anchoredPosition = new Vector2(-6, 0);
        arrowRt.sizeDelta        = new Vector2(20, 20);

        // Template (드롭다운 목록, 비활성 상태로 시작)
        var tplGO = new GameObject("Template");
        tplGO.transform.SetParent(go.transform, false);
        tplGO.AddComponent<Image>().color = new Color(0.12f, 0.12f, 0.12f);
        var tplSr  = tplGO.AddComponent<ScrollRect>();
        var tplRt  = tplGO.GetComponent<RectTransform>();
        tplRt.anchorMin = new Vector2(0, 0); tplRt.anchorMax = new Vector2(1, 0);
        tplRt.pivot     = new Vector2(0.5f, 1);
        tplRt.anchoredPosition = new Vector2(0, 2);
        tplRt.sizeDelta        = new Vector2(0, 150);

        var vpGO = new GameObject("Viewport");
        vpGO.transform.SetParent(tplGO.transform, false);
        vpGO.AddComponent<Image>();
        vpGO.AddComponent<Mask>().showMaskGraphic = false;
        var vpRt = vpGO.GetComponent<RectTransform>();
        FullStretch(vpRt);

        var contentGO = new GameObject("Content");
        contentGO.transform.SetParent(vpGO.transform, false);
        var contentRt = contentGO.AddComponent<RectTransform>();
        contentRt.anchorMin = new Vector2(0, 1); contentRt.anchorMax = Vector2.one;
        contentRt.pivot     = new Vector2(0.5f, 1);
        contentRt.anchoredPosition = Vector2.zero; contentRt.sizeDelta = Vector2.zero;

        tplSr.viewport   = vpRt;
        tplSr.content    = contentRt;
        tplSr.horizontal = false;

        // Item 템플릿 (Content 안, 드롭다운이 복제해서 사용)
        var itemGO  = new GameObject("Item");
        itemGO.transform.SetParent(contentGO.transform, false);
        var itemTgl = itemGO.AddComponent<Toggle>();
        var itemRt  = itemGO.GetComponent<RectTransform>();
        itemRt.anchorMin = new Vector2(0, 0.5f); itemRt.anchorMax = new Vector2(1, 0.5f);
        itemRt.sizeDelta = new Vector2(0, 30);

        var iBgGO  = new GameObject("Item Background");
        iBgGO.transform.SetParent(itemGO.transform, false);
        var iBgImg = iBgGO.AddComponent<Image>();
        iBgImg.color = new Color(0.15f, 0.15f, 0.15f);
        FullStretch(iBgGO.GetComponent<RectTransform>());

        var iCkGO  = new GameObject("Item Checkmark");
        iCkGO.transform.SetParent(itemGO.transform, false);
        var iCkImg = iCkGO.AddComponent<Image>();
        iCkImg.color = new Color(0.2f, 0.9f, 0.4f);
        var iCkRt  = iCkGO.GetComponent<RectTransform>();
        iCkRt.anchorMin = new Vector2(0, 0.5f); iCkRt.anchorMax = new Vector2(0, 0.5f);
        iCkRt.sizeDelta = new Vector2(18, 18); iCkRt.anchoredPosition = new Vector2(12, 0);

        var iLblGO  = new GameObject("Item Label");
        iLblGO.transform.SetParent(itemGO.transform, false);
        var iLblTMP = iLblGO.AddComponent<TextMeshProUGUI>();
        iLblTMP.text = "Option"; iLblTMP.fontSize = 14;
        var iLblRt  = iLblGO.GetComponent<RectTransform>();
        iLblRt.anchorMin = Vector2.zero; iLblRt.anchorMax = Vector2.one;
        iLblRt.offsetMin = new Vector2(28, 1); iLblRt.offsetMax = new Vector2(-8, -2);

        itemTgl.targetGraphic = iBgImg;
        itemTgl.graphic       = iCkImg;

        dd.template    = tplRt;
        dd.captionText = labelTMP;
        dd.itemText    = iLblTMP;

        tplGO.SetActive(false);
        return dd;
    }

    private static Toggle MakeToggle(string name, Transform parent, string label, Vector2 pos)
    {
        var go     = new GameObject(name);
        go.transform.SetParent(parent, false);
        var toggle = go.AddComponent<Toggle>();
        var rt     = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos;
        rt.sizeDelta        = new Vector2(140, 40);

        var bg    = new GameObject("Background");
        bg.transform.SetParent(go.transform, false);
        bg.AddComponent<Image>().color = new Color(0.25f, 0.25f, 0.25f);
        var bRt   = bg.GetComponent<RectTransform>();
        bRt.anchoredPosition = new Vector2(-45, 0);
        bRt.sizeDelta        = new Vector2(28, 28);

        var ck    = new GameObject("Checkmark");
        ck.transform.SetParent(bg.transform, false);
        var ckImg = ck.AddComponent<Image>();
        ckImg.color = new Color(0.2f, 0.9f, 0.4f);
        FullStretch(ck.GetComponent<RectTransform>());

        var lGO   = new GameObject("Label");
        lGO.transform.SetParent(go.transform, false);
        var lTMP  = lGO.AddComponent<TextMeshProUGUI>();
        lTMP.text = label; lTMP.fontSize = 16;
        var lRt   = lGO.GetComponent<RectTransform>();
        lRt.anchoredPosition = new Vector2(20, 0);
        lRt.sizeDelta        = new Vector2(95, 30);

        toggle.targetGraphic = bg.GetComponent<Image>();
        toggle.graphic       = ckImg;
        toggle.isOn          = false;
        return toggle;
    }

    private static Slider MakeSlider(string name, Transform parent, Vector2 pos, Vector2 size)
    {
        var go     = new GameObject(name);
        go.transform.SetParent(parent, false);
        var slider = go.AddComponent<Slider>();
        var rt     = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos;
        rt.sizeDelta        = size;

        var bg = new GameObject("Background");
        bg.transform.SetParent(go.transform, false);
        bg.AddComponent<Image>().color = new Color(0.25f, 0.25f, 0.25f);
        var bRt = bg.GetComponent<RectTransform>();
        bRt.anchorMin = new Vector2(0, 0.25f); bRt.anchorMax = new Vector2(1, 0.75f);
        bRt.sizeDelta = Vector2.zero;

        var fa   = new GameObject("Fill Area");
        fa.transform.SetParent(go.transform, false);
        var faRt = fa.AddComponent<RectTransform>();
        faRt.anchorMin = new Vector2(0, 0.25f); faRt.anchorMax = new Vector2(1, 0.75f);
        faRt.sizeDelta = new Vector2(-20, 0);

        var fill   = new GameObject("Fill");
        fill.transform.SetParent(fa.transform, false);
        fill.AddComponent<Image>().color = new Color(0.2f, 0.7f, 1f);
        var fRt    = fill.GetComponent<RectTransform>();
        fRt.anchorMin = Vector2.zero; fRt.anchorMax = new Vector2(0, 1);
        fRt.sizeDelta = new Vector2(10, 0);

        slider.fillRect = fRt;
        slider.value    = 0f;
        return slider;
    }

    private static (ScrollRect, Transform) MakeScrollRect(string name, Transform parent, Vector2 pos, Vector2 size)
    {
        var go = new GameObject(name);
        go.transform.SetParent(parent, false);
        go.AddComponent<Image>().color = new Color(0, 0, 0, 0.2f);
        var sr = go.AddComponent<ScrollRect>();
        var rt = go.GetComponent<RectTransform>();
        rt.anchoredPosition = pos; rt.sizeDelta = size;

        var vp   = new GameObject("Viewport");
        vp.transform.SetParent(go.transform, false);
        vp.AddComponent<Image>();
        vp.AddComponent<Mask>().showMaskGraphic = false;
        FullStretch(vp.GetComponent<RectTransform>());

        var content = new GameObject("CardContainer");
        content.transform.SetParent(vp.transform, false);
        var vlg = content.AddComponent<VerticalLayoutGroup>();
        vlg.spacing = 8; vlg.childForceExpandWidth = true; vlg.childForceExpandHeight = false;
        vlg.padding = new RectOffset(8, 8, 8, 8);
        content.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
        var cRt = content.GetComponent<RectTransform>();
        cRt.anchorMin = new Vector2(0, 1); cRt.anchorMax = Vector2.one;
        cRt.pivot = new Vector2(0.5f, 1); cRt.sizeDelta = Vector2.zero;

        sr.viewport = vp.GetComponent<RectTransform>();
        sr.content  = cRt;
        sr.horizontal = false;
        return (sr, content.transform);
    }

    private static void FullStretch(RectTransform rt)
    {
        rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
        rt.anchoredPosition = Vector2.zero; rt.sizeDelta = Vector2.zero;
    }

    private static void Bind(SerializedObject so, string field, Object value)
    {
        var prop = so.FindProperty(field);
        if (prop != null) prop.objectReferenceValue = value;
        else Debug.LogWarning($"[SceneBuilder] 필드 없음: {field}");
    }

    private static void EnsureFolder(string path)
    {
        if (!Directory.Exists(path))
        {
            var parts = path.Split('/');
            AssetDatabase.CreateFolder(string.Join("/", parts[..^1]), parts[^1]);
        }
    }
}
#endif
