local NS = rawget(_G, "MCDVoiceCooldownNS") or {}
_G.MCDVoiceCooldownNS = NS

NS.UI = NS.UI or {}
NS.UI.EditorFrame = NS.UI.EditorFrame or {}

local EditorFrame = NS.UI.EditorFrame
local Widgets = NS.UI.Widgets
local Skin = NS.UI.Skin
local L = NS.L or function(key, ...) if select("#", ...) > 0 then return string.format(tostring(key), ...) end return tostring(key) end

local function SetEnabled(frame, enabled)
    if frame.SetEnabled then
        frame:SetEnabled(enabled)
    end
    if frame.Enable and frame.Disable then
        if enabled then
            frame:Enable()
        else
            frame:Disable()
        end
    end
    if frame.SetAlpha then
        frame:SetAlpha(enabled and 1 or 0.55)
    end

    local label = frame.mcdLabel or frame.Text or frame.text
    if not label then
        return
    end

    if frame.GetObjectType and frame:GetObjectType() == "CheckButton" then
        if enabled then
            label:SetTextColor(1.00, 0.82, 0.18, 1)
        else
            label:SetTextColor(0.50, 0.50, 0.50, 1)
        end
    elseif enabled then
        label:SetTextColor(0.92, 0.92, 0.92, 1)
    else
        label:SetTextColor(0.50, 0.50, 0.50, 1)
    end
end

local function SetTabVisual(button, active)
    if not button then
        return
    end
    if Skin and Skin.SetTabSelected then
        Skin:SetTabSelected(button, active)
        return
    end
    button:SetButtonState(active and "PUSHED" or "NORMAL")
end

local function BuildDropdownItems(values)
    local items = {}
    for value, text in pairs(values or {}) do
        items[#items + 1] = {
            value = value,
            text = text,
        }
    end
    table.sort(items, function(a, b)
        if tonumber(a.value) == 0 and tonumber(b.value) ~= 0 then
            return true
        end
        if tonumber(b.value) == 0 and tonumber(a.value) ~= 0 then
            return false
        end
        return tostring(a.text or "") < tostring(b.text or "")
    end)
    return items
end

local function BuildBuiltinSoundItems()
    local items = {}
    for path, name in pairs(NS.AceOptions:GetBuiltinSoundList()) do
        items[#items + 1] = {
            value = path,
            text = name,
        }
    end
    table.sort(items, function(a, b)
        return tostring(a.text or "") < tostring(b.text or "")
    end)
    return items
end

local function CreateFieldLabel(parent, text, x, y)
    local label = Widgets:CreateLabel(parent, text, "GameFontNormal")
    label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    if Skin then
        Skin:StyleFont(label, "accent")
    end
    return label
end

local function PlaceSection(parent, title, y)
    local section = Widgets:CreateSection(parent, title)
    section:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y)
    section:SetPoint("RIGHT", parent, "RIGHT", 0, 0)
    return section
end

local function SetManyShown(items, shown)
    for _, item in ipairs(items or {}) do
        if item and item.SetShown then
            item:SetShown(shown)
        end
    end
end

local function CreateRateSlider(parent)
    local slider = CreateFrame("Slider", "MCDVoiceCooldownEditorRateSlider", parent, "BackdropTemplate")
    slider:SetMinMaxValues(-10, 10)
    slider:SetValueStep(1)
    slider:SetObeyStepOnDrag(true)
    slider:SetWidth(170)
    slider:SetHeight(18)
    slider:SetOrientation("HORIZONTAL")
    slider:SetThumbTexture("Interface\\Buttons\\WHITE8x8")

    local thumb = slider:GetThumbTexture()
    if thumb then
        thumb:SetSize(10, 20)
    end

    if Skin and Skin.SkinSlider then
        Skin:SkinSlider(slider)
    end

    slider.valueText = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    slider.valueText:SetPoint("LEFT", slider, "RIGHT", 10, 0)
    if Skin then
        Skin:StyleFont(slider.valueText, "accent")
    end
    slider.valueText:SetText("+0")
    return slider
end

local function InstallSpellIdAutofill(editor, editBox)
    local function autofillFromInput()
        editor:PullFromWidgets()
        NS.AceOptions:AutofillFromSpellId()
        editor:PushToWidgets()
    end

    editBox:SetScript("OnEnterPressed", function(self)
        self:ClearFocus()
        autofillFromInput()
    end)

    editBox:SetScript("OnEditFocusLost", function()
        autofillFromInput()
    end)

    editBox:SetScript("OnEscapePressed", function(self)
        self:ClearFocus()
    end)
end


local function InstallTalentIdAutofill(editor, editBox)
    local function autofillFromInput()
        editor:PullFromWidgets()
        if NS.AceOptions and type(NS.AceOptions.AutofillFromTalentId) == "function" then
            NS.AceOptions:AutofillFromTalentId()
        end
        editor:PushToWidgets()
    end

    editBox:SetScript("OnEnterPressed", function(self)
        self:ClearFocus()
        autofillFromInput()
    end)

    editBox:SetScript("OnEditFocusLost", function()
        autofillFromInput()
    end)

    editBox:SetScript("OnEscapePressed", function(self)
        self:ClearFocus()
    end)
end

function EditorFrame:EnsureFrame()
    if self.frame then
        return self.frame
    end

    local frame = CreateFrame("Frame", "MCDVoiceCooldownEditorFrame", UIParent, "BackdropTemplate")
    frame:SetSize(700, 720)
    frame:SetPoint("CENTER", UIParent, "CENTER", 100, 0)
    frame:SetFrameStrata("FULLSCREEN_DIALOG")
    frame:SetToplevel(true)
    frame:EnableMouse(true)
    frame:SetMovable(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
    frame:SetClampedToScreen(true)
    frame:SetScript("OnShow", function(selfFrame)
        selfFrame:Raise()
    end)
    frame:Hide()
    Widgets:ApplyPanelChrome(frame, { footerHeight = 54 })
    tinsert(UISpecialFrames, frame:GetName())

    local title = Widgets:CreateLabel(frame, L("TITLE_EDIT_CONFIG"), "GameFontNormalLarge")
    title:SetPoint("TOP", frame, "TOP", 0, -15)
    title:SetWidth(520)
    title:SetJustifyH("CENTER")
    if Skin then
        Skin:StyleFont(title, "title")
    end
    frame.title = title

    local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
    close:SetSize(28, 28)
    close:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -10, -10)
    if Skin and Skin.SkinCloseButton then
        Skin:SkinCloseButton(close)
    end
    close:SetScript("OnClick", function()
        self:Close()
    end)

    local tabCooldown = Widgets:CreateButton(frame, L("TAB_COOLDOWN"), 112, 32)
    tabCooldown:SetPoint("TOPLEFT", frame, "TOPLEFT", 22, -56)

    local tabCast = Widgets:CreateButton(frame, L("TAB_CAST"), 120, 32)
    tabCast:SetPoint("LEFT", tabCooldown, "RIGHT", 8, 0)

    local tabBloodlust = Widgets:CreateButton(frame, L("TAB_BLOODLUST"), 120, 32)
    tabBloodlust:SetPoint("LEFT", tabCast, "RIGHT", 8, 0)

    local content = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    content:SetPoint("TOPLEFT", frame, "TOPLEFT", 22, -100)
    content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -22, 66)
    Widgets:ApplyPanelChrome(content, { inset = true, noFooter = true, topHeight = 16, noTopLine = true })

    -- 统一网格布局常量：后续编辑页、嗜血页都使用同一套左右边距和列宽。
    -- 这样窗口收窄时不会出现某个输入框单独超出、重叠或右侧空白过大的问题。
    local LEFT = 20
    local R = 616
    local ID_X, ID_W = LEFT, 106
    local NAME_X, NAME_W = 146, 206
    local CD_X, CD_W = 376, 106
    local CHECK_X, CHECK_W = 512, 104
    local DROP_CLASS_X, DROP_CLASS_W = 70, 222
    local SPEC_LABEL_X, DROP_SPEC_X, DROP_SPEC_W = 312, 362, 222
    local BUILTIN_X, BUILTIN_W = ID_X, ID_W
    local CUSTOM_X, CUSTOM_W = NAME_X, R - NAME_X
    local TTS_X, TTS_W = ID_X, 230
    local SLIDER_LABEL_X, SLIDER_X = CD_X, CD_X
    local BLOODLUST_W = R - LEFT

    local actionSave = Widgets:CreateButton(frame, L("BTN_SAVE"), 120, 32)
    actionSave:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 22, 16)

    local actionTest = Widgets:CreateButton(frame, L("BTN_TEST"), 120, 32)
    actionTest:SetPoint("LEFT", actionSave, "RIGHT", 8, 0)

    -- 普通配置页：职业 / 专精
    local classSection = PlaceSection(content, L("SECTION_CLASS_SPEC"), -14)
    local classLabel = CreateFieldLabel(content, L("LABEL_CLASS"), LEFT, -50)
    local specLabel = CreateFieldLabel(content, L("LABEL_SPEC"), SPEC_LABEL_X, -50)

    local classDrop = Widgets:CreateDropdown(content, "MCDVoiceCooldownEditorClassDropDown", DROP_CLASS_W)
    classDrop:ClearAllPoints()
    classDrop:SetPoint("TOPLEFT", content, "TOPLEFT", DROP_CLASS_X, -43)

    local specDrop = Widgets:CreateDropdown(content, "MCDVoiceCooldownEditorSpecDropDown", DROP_SPEC_W)
    specDrop:ClearAllPoints()
    specDrop:SetPoint("TOPLEFT", content, "TOPLEFT", DROP_SPEC_X, -43)

    -- 普通配置页：技能参数
    local spellSection = PlaceSection(content, L("SECTION_SPELL_PARAMS"), -96)
    local spellIdLabel = CreateFieldLabel(content, L("LABEL_SPELL_ID"), ID_X, -130)
    local spellNameLabel = CreateFieldLabel(content, L("LABEL_SPELL_NAME"), NAME_X, -130)
    local baseCDLabel = CreateFieldLabel(content, L("LABEL_FIXED_CD_SEC"), CD_X, -130)

    local spellId = Widgets:CreateEditBox(content, ID_W, 30, true)
    spellId:SetPoint("TOPLEFT", content, "TOPLEFT", ID_X, -154)

    local spellName = Widgets:CreateEditBox(content, NAME_W, 30, false)
    spellName:SetPoint("TOPLEFT", content, "TOPLEFT", NAME_X, -154)

    local baseCD = Widgets:CreateEditBox(content, CD_W, 30, false)
    baseCD:SetPoint("TOPLEFT", content, "TOPLEFT", CD_X, -154)

    local checkTalent = Widgets:CreateCheckButton(content, L("LABEL_CHECK_TALENT"))
    checkTalent:SetPoint("TOPLEFT", content, "TOPLEFT", CHECK_X, -156)
    checkTalent:SetWidth(CHECK_W)

    -- CD提示专用：与上方三列严格对齐；未勾选“检查天赋”时全部禁用变灰。
    local talentIdLabel = CreateFieldLabel(content, L("LABEL_TALENT_ID"), ID_X, -202)
    local talentNameLabel = CreateFieldLabel(content, L("LABEL_TALENT_NAME"), NAME_X, -202)
    local talentCDLabel = CreateFieldLabel(content, L("LABEL_TALENT_CD_SEC"), CD_X, -202)

    local talentId = Widgets:CreateEditBox(content, ID_W, 30, true)
    talentId:SetPoint("TOPLEFT", content, "TOPLEFT", ID_X, -226)

    local talentName = Widgets:CreateEditBox(content, NAME_W, 30, false)
    talentName:SetPoint("TOPLEFT", content, "TOPLEFT", NAME_X, -226)

    local talentCD = Widgets:CreateEditBox(content, CD_W, 30, false)
    talentCD:SetPoint("TOPLEFT", content, "TOPLEFT", CD_X, -226)

    -- 施法成功专用：可选延时播放。与“检查天赋”所在区域复用空间，两个标签页互斥显示。
    local delayEnabled = Widgets:CreateCheckButton(content, L("LABEL_DELAY_CAST_SUCCESS"))
    delayEnabled:SetPoint("TOPLEFT", content, "TOPLEFT", ID_X, -226)
    delayEnabled:SetWidth(180)

    local delaySecondsLabel = CreateFieldLabel(content, L("LABEL_DELAY_SECONDS"), CD_X, -202)
    local delaySeconds = Widgets:CreateEditBox(content, CD_W, 30, false)
    delaySeconds:SetPoint("TOPLEFT", content, "TOPLEFT", CD_X, -226)

    -- 普通配置页：通知方式
    local notifySection = PlaceSection(content, L("SECTION_NOTIFY"), -286)
    local builtinLabel = CreateFieldLabel(content, L("LABEL_BUILTIN_SOUND"), BUILTIN_X, -316)

    local builtinDrop = Widgets:CreateDropdown(content, "MCDVoiceCooldownEditorBuiltinDropDown", BUILTIN_W)
    builtinDrop:ClearAllPoints()
    builtinDrop:SetPoint("TOPLEFT", content, "TOPLEFT", BUILTIN_X, -340)

    local useSound = Widgets:CreateCheckButton(content, L("LABEL_USE_CUSTOM_SOUND"))
    useSound:SetPoint("TOPLEFT", content, "TOPLEFT", CUSTOM_X, -316)
    useSound:SetWidth(190)

    local soundPath = Widgets:CreateEditBox(content, CUSTOM_W, 30, false)
    soundPath:SetPoint("TOPLEFT", content, "TOPLEFT", CUSTOM_X, -340)

    local useTTS = Widgets:CreateCheckButton(content, L("LABEL_USE_TTS"))
    useTTS:SetPoint("TOPLEFT", content, "TOPLEFT", TTS_X, -392)
    useTTS:SetWidth(170)

    local ttsText = Widgets:CreateEditBox(content, TTS_W, 30, false)
    ttsText:SetPoint("TOPLEFT", content, "TOPLEFT", TTS_X, -424)

    local rateLabel = CreateFieldLabel(content, L("LABEL_TTS_RATE"), SLIDER_LABEL_X, -399)
    local ttsRateSlider = CreateRateSlider(content)
    -- 与左侧 TTS 文本输入框的输入区域居中对齐，而不是贴着标题行。
    ttsRateSlider:SetPoint("TOPLEFT", content, "TOPLEFT", SLIDER_X, -430)

    local editorHint = Widgets:CreateLabel(content, L("EDITOR_HINT"), "GameFontHighlightSmall")
    editorHint:SetPoint("TOPLEFT", content, "TOPLEFT", LEFT, -488)
    editorHint:SetPoint("RIGHT", content, "RIGHT", -18, 0)
    editorHint:SetJustifyH("LEFT")
    if Skin then
        Skin:StyleFont(editorHint, "muted")
    end

    -- 嗜血配置页：单独布局，避免大字体时文本和输入框重叠
    local bloodlustSection = PlaceSection(content, L("SECTION_BLOODLUST_SOUND"), -16)
    local bloodlustSummary = Widgets:CreateLabel(content, "", "GameFontHighlightSmall")
    bloodlustSummary:SetPoint("TOPLEFT", content, "TOPLEFT", LEFT, -52)
    bloodlustSummary:SetPoint("RIGHT", content, "RIGHT", -18, 0)
    bloodlustSummary:SetJustifyH("LEFT")
    if Skin then
        Skin:StyleFont(bloodlustSummary, "muted")
    end

    local bloodlustLabels = {}
    local bloodlustFields = {}
    for i = 1, 5 do
        local y = -94 - ((i - 1) * 58)
        local label = CreateFieldLabel(content, L("LABEL_SOUND_PATH_N", i), LEFT, y)
        local box = Widgets:CreateEditBox(content, BLOODLUST_W, 30, false)
        box:SetPoint("TOPLEFT", content, "TOPLEFT", LEFT, y - 26)
        bloodlustLabels[i] = label
        bloodlustFields[i] = box
    end

    local bloodlustHint = Widgets:CreateLabel(content, L("BLOODLUST_HINT"), "GameFontHighlightSmall")
    bloodlustHint:SetPoint("TOPLEFT", content, "TOPLEFT", LEFT, -426)
    bloodlustHint:SetPoint("RIGHT", content, "RIGHT", -18, 0)
    bloodlustHint:SetJustifyH("LEFT")
    if Skin then
        Skin:StyleFont(bloodlustHint, "muted")
    end

    frame.widgets = {
        tabCooldown = tabCooldown,
        tabCast = tabCast,
        tabBloodlust = tabBloodlust,
        classSection = classSection,
        spellSection = spellSection,
        notifySection = notifySection,
        classLabel = classLabel,
        specLabel = specLabel,
        spellIdLabel = spellIdLabel,
        spellNameLabel = spellNameLabel,
        talentIdLabel = talentIdLabel,
        talentNameLabel = talentNameLabel,
        talentCDLabel = talentCDLabel,
        classDrop = classDrop,
        specDrop = specDrop,
        spellId = spellId,
        spellName = spellName,
        checkTalent = checkTalent,
        talentId = talentId,
        talentName = talentName,
        talentCDLabel = talentCDLabel,
        talentCD = talentCD,
        delayEnabled = delayEnabled,
        delaySecondsLabel = delaySecondsLabel,
        delaySeconds = delaySeconds,
        baseCDLabel = baseCDLabel,
        baseCD = baseCD,
        builtinLabel = builtinLabel,
        builtinDrop = builtinDrop,
        useTTS = useTTS,
        rateLabel = rateLabel,
        ttsText = ttsText,
        ttsRateSlider = ttsRateSlider,
        useSound = useSound,
        soundPath = soundPath,
        actionSave = actionSave,
        actionTest = actionTest,
        editorHint = editorHint,
        bloodlustSection = bloodlustSection,
        bloodlustSummary = bloodlustSummary,
        bloodlustLabels = bloodlustLabels,
        bloodlustFields = bloodlustFields,
        bloodlustHint = bloodlustHint,
    }

    InstallSpellIdAutofill(self, spellId)
    InstallTalentIdAutofill(self, talentId)

    tabCooldown:SetScript("OnClick", function()
        NS.AceOptions:GetState().entryType = "cooldown"
        self:Refresh()
    end)

    tabCast:SetScript("OnClick", function()
        NS.AceOptions:GetState().entryType = "cast"
        self:Refresh()
    end)

    tabBloodlust:SetScript("OnClick", function()
        NS.AceOptions:LoadBloodlustConfig()
        self:Refresh()
    end)

    classDrop.mcdOnValueChanged = function(value)
        local state = NS.AceOptions:GetState()
        state.classID = tonumber(value) or 0
        -- 不能设为 nil；GetState() 看到 specID=nil 会自动同步回当前玩家职业，
        -- 导致无法切到其他职业或“全职业”。切职业时默认落到“全专精”。
        state.specID = 0
        NS.AceOptions:EnsureValidScope()
        self:Refresh()
    end

    specDrop.mcdOnValueChanged = function(value)
        local state = NS.AceOptions:GetState()
        if (tonumber(state.classID) or 0) == 0 then
            state.specID = 0
        else
            state.specID = tonumber(value) or 0
        end
        self:Refresh()
    end

    builtinDrop.mcdOnValueChanged = function(value)
        local state = NS.AceOptions:GetState()
        state.builtinSoundPath = NS.AceOptions:NormalizeSoundPath(value or "")
        if NS.AceOptions.TrimText(state.customSoundPath or "") == "" then
            state.soundPath = state.builtinSoundPath
        end

        local _, modeSound = NS.API.GetModes()
        if state.notifyMode == modeSound and not (state.useCustomSound == true) and tostring(value or "") ~= "" then
            NS.API.PlayReadyNotification({
                notifyMode = modeSound,
                soundPath = tostring(value),
            })
        end
        self:Refresh()
    end

    actionSave:SetScript("OnClick", function()
        self:PullFromWidgets()
        local state = NS.AceOptions:GetState()
        if tostring(state.entryType or "") == "bloodlust" then
            if NS.AceOptions:SaveBloodlustConfig() then
                self:Close()
            end
            return
        end

        if NS.AceOptions:SaveEntry() then
            self:Close()
        end
    end)

    actionTest:SetScript("OnClick", function()
        self:PullFromWidgets()
        local state = NS.AceOptions:GetState()
        if tostring(state.entryType or "") == "bloodlust" then
            NS.AceOptions:TestBloodlustConfig()
            return
        end
        NS.AceOptions:TestCurrent()
    end)

    useTTS:SetScript("OnClick", function(button)
        local state = NS.AceOptions:GetState()
        local modeTts, modeSound = NS.API.GetModes()
        if button:GetChecked() then
            state.notifyMode = modeTts
            frame.widgets.useSound:SetChecked(false)
            state.useCustomSound = false
            state.customSoundPath = ""
        else
            state.notifyMode = modeSound
        end
        self:Refresh()
    end)

    useSound:SetScript("OnClick", function(button)
        local state = NS.AceOptions:GetState()
        local _, modeSound = NS.API.GetModes()
        if not button:IsEnabled() then
            button:SetChecked(false)
            return
        end

        state.notifyMode = modeSound
        state.useCustomSound = button:GetChecked() == true
        if button:GetChecked() then
            frame.widgets.useTTS:SetChecked(false)
        end
        self:Refresh()
    end)

    checkTalent:SetScript("OnClick", function(button)
        local state = NS.AceOptions:GetState()
        state.checkTalent = button:GetChecked() == true
        if not state.checkTalent then
            state.talentId = 0
            state.talentName = ""
            state.talentCD = 0
        end
        self:Refresh()
    end)

    delayEnabled:SetScript("OnClick", function(button)
        local state = NS.AceOptions:GetState()
        state.delayEnabled = button:GetChecked() == true
        self:Refresh()
    end)

    self.frame = frame
    return frame
end

function EditorFrame:PullFromWidgets()
    if not self.frame or not self.frame:IsShown() then
        return
    end

    local widgets = self.frame.widgets
    local state = NS.AceOptions:GetState()
    local modeTts, modeSound = NS.API.GetModes()

    if tostring(state.entryType or "") == "bloodlust" then
        state.bloodlustPaths = state.bloodlustPaths or {}
        for i = 1, 5 do
            state.bloodlustPaths[i] = NS.AceOptions:NormalizeSoundPath((widgets.bloodlustFields[i] and widgets.bloodlustFields[i]:GetText()) or "")
        end
        return
    end

    state.classID = tonumber(Widgets:GetDropdownValue(widgets.classDrop)) or 0
    if state.classID == 0 then
        state.specID = 0
    else
        state.specID = tonumber(Widgets:GetDropdownValue(widgets.specDrop)) or 0
    end
    state.spellId = tonumber(widgets.spellId:GetText() or "") or 0
    state.spellName = tostring(widgets.spellName:GetText() or "")
    state.baseCD = tonumber(widgets.baseCD:GetText() or "") or 0
    state.fixedCD = true
    state.checkTalent = widgets.checkTalent and widgets.checkTalent:GetChecked() == true or false
    state.talentId = tonumber((widgets.talentId and widgets.talentId:GetText()) or "") or 0
    state.talentName = tostring((widgets.talentName and widgets.talentName:GetText()) or "")
    state.talentCD = tonumber((widgets.talentCD and widgets.talentCD:GetText()) or "") or 0
    state.delayEnabled = widgets.delayEnabled and widgets.delayEnabled:GetChecked() == true or false
    state.delaySeconds = math.max(0, tonumber((widgets.delaySeconds and widgets.delaySeconds:GetText()) or "") or 0)
    state.ttsText = tostring(widgets.ttsText:GetText() or "")
    state.ttsRate = math.max(-10, math.min(10, tonumber(widgets.ttsRateSlider:GetValue()) or 0))
    state.customSoundPath = NS.AceOptions:NormalizeSoundPath(widgets.soundPath:GetText() or "")
    state.useCustomSound = widgets.useSound:GetChecked() == true
    state.builtinSoundPath = NS.AceOptions:NormalizeSoundPath(Widgets:GetDropdownValue(widgets.builtinDrop) or NS.AceOptions:GetDefaultBuiltinSoundPath())

    if widgets.useTTS:GetChecked() then
        state.notifyMode = modeTts
        state.soundPath = state.builtinSoundPath
    elseif widgets.useSound:GetChecked() and NS.AceOptions.TrimText(state.customSoundPath) ~= "" then
        state.notifyMode = modeSound
        state.soundPath = state.customSoundPath
    elseif widgets.useSound:GetChecked() then
        state.notifyMode = modeSound
        state.soundPath = ""
    else
        state.notifyMode = modeSound
        state.soundPath = state.builtinSoundPath
    end
end

function EditorFrame:PushToWidgets()
    local frame = self:EnsureFrame()
    local widgets = frame.widgets
    local state = NS.AceOptions:GetState()
    local modeTts, _ = NS.API.GetModes()
    local isCooldown = tostring(state.entryType or "cooldown") == "cooldown"
    local isBloodlust = tostring(state.entryType or "") == "bloodlust"
    local isCast = (not isCooldown) and (not isBloodlust)
    local isTts = tostring(state.notifyMode or "") == tostring(modeTts)

    local normalControls = {
        widgets.classSection,
        widgets.spellSection,
        widgets.notifySection,
        widgets.classLabel,
        widgets.specLabel,
        widgets.spellIdLabel,
        widgets.spellNameLabel,
        widgets.talentIdLabel,
        widgets.talentNameLabel,
        widgets.talentCDLabel,
        widgets.baseCDLabel,
        widgets.builtinLabel,
        widgets.rateLabel,
        widgets.classDrop,
        widgets.specDrop,
        widgets.spellId,
        widgets.spellName,
        widgets.checkTalent,
        widgets.talentId,
        widgets.talentName,
        widgets.talentCD,
        widgets.delayEnabled,
        widgets.delaySecondsLabel,
        widgets.delaySeconds,
        widgets.baseCD,
        widgets.builtinDrop,
        widgets.useTTS,
        widgets.ttsText,
        widgets.ttsRateSlider,
        widgets.ttsRateSlider and widgets.ttsRateSlider.valueText,
        widgets.useSound,
        widgets.soundPath,
        widgets.editorHint,
    }
    local bloodlustControls = {
        widgets.bloodlustSection,
        widgets.bloodlustSummary,
        widgets.bloodlustHint,
    }

    for _, item in ipairs(widgets.bloodlustLabels or {}) do
        bloodlustControls[#bloodlustControls + 1] = item
    end
    for _, item in ipairs(widgets.bloodlustFields or {}) do
        bloodlustControls[#bloodlustControls + 1] = item
    end

    SetManyShown(normalControls, not isBloodlust)
    SetManyShown(bloodlustControls, isBloodlust)

    if isBloodlust then
        frame.title:SetText(L("TITLE_BLOODLUST_SETTINGS"))
        if widgets.bloodlustSummary then
            widgets.bloodlustSummary:SetText(NS.AceOptions:GetBloodlustSummary())
        end
        state.bloodlustPaths = state.bloodlustPaths or { "", "", "", "", "" }
        for i = 1, 5 do
            if widgets.bloodlustFields[i] then
                widgets.bloodlustFields[i]:SetText(tostring(state.bloodlustPaths[i] or ""))
            end
        end
        SetTabVisual(widgets.tabCooldown, false)
        SetTabVisual(widgets.tabCast, false)
        SetTabVisual(widgets.tabBloodlust, true)
        return
    end

    frame.title:SetText(self.mode == "new" and L("TITLE_NEW_CONFIG") or L("TITLE_EDIT_CONFIG"))

    if state.useCustomSound == nil then
        state.useCustomSound = tostring(state.notifyMode or "") ~= tostring(modeTts) and
            NS.AceOptions.TrimText(state.customSoundPath or "") ~= ""
    end

    local isCustomSound = (state.useCustomSound == true) and not isTts

    Widgets:SetDropdownItems(widgets.classDrop, BuildDropdownItems(NS.AceOptions:GetClassValues()))
    Widgets:SetDropdownValue(widgets.classDrop, state.classID, L("PLACEHOLDER_SELECT_CLASS"))

    if (tonumber(state.classID) or 0) == 0 then
        state.specID = 0
    end
    local specDropdownEnabled = (tonumber(state.classID) or 0) ~= 0
    Widgets:SetDropdownItems(widgets.specDrop, BuildDropdownItems(NS.AceOptions:GetSpecValues(state.classID)))
    Widgets:SetDropdownValue(widgets.specDrop, state.specID, L("PLACEHOLDER_SELECT_SPEC"))
    Widgets:SetDropdownEnabled(widgets.specDrop, specDropdownEnabled)
    if widgets.specLabel then
        widgets.specLabel:SetTextColor(specDropdownEnabled and 1.00 or 0.50, specDropdownEnabled and 0.82 or 0.50, specDropdownEnabled and 0.18 or 0.50, 1)
    end

    Widgets:SetDropdownItems(widgets.builtinDrop, BuildBuiltinSoundItems())
    Widgets:SetDropdownValue(
        widgets.builtinDrop,
        NS.AceOptions.TrimText(state.builtinSoundPath) ~= "" and state.builtinSoundPath or NS.AceOptions:GetDefaultBuiltinSoundPath(),
        L("PLACEHOLDER_SELECT_BUILTIN_SOUND")
    )

    widgets.spellId:SetText((tonumber(state.spellId) or 0) > 0 and tostring(state.spellId) or "")
    widgets.spellName:SetText(tostring(state.spellName or ""))
    widgets.checkTalent:SetChecked(state.checkTalent == true)
    widgets.talentId:SetText((tonumber(state.talentId) or 0) > 0 and tostring(state.talentId) or "")
    widgets.talentName:SetText(tostring(state.talentName or ""))
    if widgets.talentCD then
        widgets.talentCD:SetText((tonumber(state.talentCD) or 0) > 0 and tostring(state.talentCD) or "")
    end
    if widgets.delayEnabled then
        widgets.delayEnabled:SetChecked(state.delayEnabled == true)
    end
    if widgets.delaySeconds then
        widgets.delaySeconds:SetText((tonumber(state.delaySeconds) or 0) > 0 and tostring(state.delaySeconds) or "")
    end
    widgets.baseCD:SetText((tonumber(state.baseCD) or 0) > 0 and tostring(state.baseCD) or "")
    state.fixedCD = true
    widgets.useTTS:SetChecked(isTts)
    widgets.useSound:SetChecked(isCustomSound)
    widgets.ttsText:SetText(tostring(state.ttsText or ""))
    widgets.ttsRateSlider:SetValue(math.max(-10, math.min(10, tonumber(state.ttsRate) or 0)))
    widgets.ttsRateSlider.valueText:SetText(string.format("%+d", math.floor(tonumber(widgets.ttsRateSlider:GetValue()) or 0)))
    widgets.soundPath:SetText(tostring(state.customSoundPath or ""))

    widgets.talentIdLabel:SetShown(isCooldown)
    widgets.talentNameLabel:SetShown(isCooldown)
    if widgets.talentCDLabel then
        widgets.talentCDLabel:SetShown(isCooldown)
    end
    widgets.baseCDLabel:SetShown(isCooldown)
    widgets.checkTalent:SetShown(isCooldown)
    widgets.talentId:SetShown(isCooldown)
    widgets.talentName:SetShown(isCooldown)
    if widgets.talentCD then
        widgets.talentCD:SetShown(isCooldown)
    end
    widgets.baseCD:SetShown(isCooldown)
    if widgets.delayEnabled then
        widgets.delayEnabled:SetShown(isCast)
    end
    if widgets.delaySecondsLabel then
        widgets.delaySecondsLabel:SetShown(isCast)
    end
    if widgets.delaySeconds then
        widgets.delaySeconds:SetShown(isCast)
    end

    local talentEnabled = isCooldown and (state.checkTalent == true)
    SetEnabled(widgets.checkTalent, isCooldown)
    SetEnabled(widgets.talentId, talentEnabled)
    SetEnabled(widgets.talentName, talentEnabled)
    if widgets.talentCD then
        SetEnabled(widgets.talentCD, talentEnabled)
    end
    if widgets.talentIdLabel then
        widgets.talentIdLabel:SetTextColor(talentEnabled and 1.00 or 0.50, talentEnabled and 0.82 or 0.50, talentEnabled and 0.18 or 0.50, 1)
    end
    if widgets.talentNameLabel then
        widgets.talentNameLabel:SetTextColor(talentEnabled and 1.00 or 0.50, talentEnabled and 0.82 or 0.50, talentEnabled and 0.18 or 0.50, 1)
    end
    if widgets.talentCDLabel then
        widgets.talentCDLabel:SetTextColor(talentEnabled and 1.00 or 0.50, talentEnabled and 0.82 or 0.50, talentEnabled and 0.18 or 0.50, 1)
    end

    local delayInputEnabled = isCast and (state.delayEnabled == true)
    if widgets.delayEnabled then
        SetEnabled(widgets.delayEnabled, isCast)
    end
    if widgets.delaySeconds then
        SetEnabled(widgets.delaySeconds, delayInputEnabled)
    end
    if widgets.delaySecondsLabel then
        widgets.delaySecondsLabel:SetTextColor(delayInputEnabled and 1.00 or 0.50, delayInputEnabled and 0.82 or 0.50, delayInputEnabled and 0.18 or 0.50, 1)
    end

    SetEnabled(widgets.baseCD, isCooldown)
    SetEnabled(widgets.useTTS, not isCustomSound)
    SetEnabled(widgets.ttsText, isTts)
    SetEnabled(widgets.ttsRateSlider, isTts and not isCustomSound)

    if widgets.rateLabel then
        if isTts and not isCustomSound then
            widgets.rateLabel:SetTextColor(1.00, 0.82, 0.18, 1)
        else
            widgets.rateLabel:SetTextColor(0.50, 0.50, 0.50, 1)
        end
    end

    SetEnabled(widgets.useSound, not isTts)
    SetEnabled(widgets.soundPath, (not isTts) and isCustomSound)

    if isTts then
        widgets.useSound:SetChecked(false)
        state.useCustomSound = false
    end
    if isCustomSound then
        widgets.useTTS:SetChecked(false)
    end

    Widgets:SetDropdownEnabled(widgets.builtinDrop, not isTts and not isCustomSound)

    if not widgets.ttsRateSlider.mcdHooked then
        widgets.ttsRateSlider:SetScript("OnValueChanged", function(slider, value)
            local normalized = math.max(-10, math.min(10, tonumber(value) or 0))
            slider.valueText:SetText(string.format("%+d", math.floor(normalized)))
            NS.AceOptions:GetState().ttsRate = normalized
        end)
        widgets.ttsRateSlider.mcdHooked = true
    end

    SetTabVisual(widgets.tabCooldown, isCooldown)
    SetTabVisual(widgets.tabCast, isCast)
    SetTabVisual(widgets.tabBloodlust, false)
end

local function SetLocaleText(target, text)
    if target and target.SetText then
        target:SetText(text)
    end
end

local function SetSectionLocaleText(section, text)
    if not section then
        return
    end
    if section.label and section.label.SetText then
        section.label:SetText(text)
    elseif section.mcdLabel and section.mcdLabel.SetText then
        section.mcdLabel:SetText(text)
    end
end

local function SetCheckButtonLocaleText(button, text)
    if not button then
        return
    end
    local label = button.mcdLabel or button.Text or button.text
    if label and label.SetText then
        label:SetText(text)
        if label.GetStringWidth and button.SetWidth then
            local currentWidth = button.GetWidth and (button:GetWidth() or 24) or 24
            button:SetWidth(math.max(currentWidth, math.ceil(label:GetStringWidth() or 0) + 34, 24))
        end
    end
end

-- 语言切换时仅刷新静态文案，不 PushToWidgets，避免覆盖编辑框里尚未保存的输入。
function EditorFrame:RefreshLocale()
    if not self.frame then
        return
    end

    local frame = self.frame
    local widgets = frame.widgets or {}
    local state = NS.AceOptions and NS.AceOptions.GetState and NS.AceOptions:GetState() or {}
    local entryType = tostring(state.entryType or "cooldown")

    if frame.title then
        if entryType == "bloodlust" then
            frame.title:SetText(L("TITLE_BLOODLUST_SETTINGS"))
        else
            frame.title:SetText(self.mode == "new" and L("TITLE_NEW_CONFIG") or L("TITLE_EDIT_CONFIG"))
        end
    end

    SetLocaleText(widgets.tabCooldown, L("TAB_COOLDOWN"))
    SetLocaleText(widgets.tabCast, L("TAB_CAST"))
    SetLocaleText(widgets.tabBloodlust, L("TAB_BLOODLUST"))
    SetLocaleText(widgets.actionSave, L("BTN_SAVE"))
    SetLocaleText(widgets.actionTest, L("BTN_TEST"))

    SetSectionLocaleText(widgets.classSection, L("SECTION_CLASS_SPEC"))
    SetSectionLocaleText(widgets.spellSection, L("SECTION_SPELL_PARAMS"))
    SetSectionLocaleText(widgets.notifySection, L("SECTION_NOTIFY"))
    SetSectionLocaleText(widgets.bloodlustSection, L("SECTION_BLOODLUST_SOUND"))

    SetLocaleText(widgets.classLabel, L("LABEL_CLASS"))
    SetLocaleText(widgets.specLabel, L("LABEL_SPEC"))
    SetLocaleText(widgets.spellIdLabel, L("LABEL_SPELL_ID"))
    SetLocaleText(widgets.spellNameLabel, L("LABEL_SPELL_NAME"))
    SetLocaleText(widgets.baseCDLabel, L("LABEL_FIXED_CD_SEC"))
    SetLocaleText(widgets.talentIdLabel, L("LABEL_TALENT_ID"))
    SetLocaleText(widgets.talentNameLabel, L("LABEL_TALENT_NAME"))
    SetLocaleText(widgets.talentCDLabel, L("LABEL_TALENT_CD_SEC"))
    SetLocaleText(widgets.delaySecondsLabel, L("LABEL_DELAY_SECONDS"))
    SetLocaleText(widgets.builtinLabel, L("LABEL_BUILTIN_SOUND"))
    SetLocaleText(widgets.rateLabel, L("LABEL_TTS_RATE"))
    SetLocaleText(widgets.editorHint, L("EDITOR_HINT"))
    SetLocaleText(widgets.bloodlustHint, L("BLOODLUST_HINT"))

    SetCheckButtonLocaleText(widgets.checkTalent, L("LABEL_CHECK_TALENT"))
    SetCheckButtonLocaleText(widgets.delayEnabled, L("LABEL_DELAY_CAST_SUCCESS"))
    SetCheckButtonLocaleText(widgets.useSound, L("LABEL_USE_CUSTOM_SOUND"))
    SetCheckButtonLocaleText(widgets.useTTS, L("LABEL_USE_TTS"))

    for i = 1, 5 do
        SetLocaleText(widgets.bloodlustLabels and widgets.bloodlustLabels[i], L("LABEL_SOUND_PATH_N", i))
    end

    if widgets.bloodlustSummary and NS.AceOptions and type(NS.AceOptions.GetBloodlustSummary) == "function" then
        widgets.bloodlustSummary:SetText(NS.AceOptions:GetBloodlustSummary())
    end

    if frame:IsShown() then
        frame:Raise()
    end
end

function EditorFrame:Refresh()
    if self.frame and self.frame:IsShown() then
        self:PushToWidgets()
        self.frame:Raise()
    end
end

function EditorFrame:OpenForNew()
    local state = NS.AceOptions:GetState()
    local selectedGroupKey = tostring(state.selectedCollectionKey or "")
    if selectedGroupKey == "" and NS.AceOptions and type(NS.AceOptions.IsGroupKey) == "function" and NS.AceOptions:IsGroupKey(state.selectedKey) then
        selectedGroupKey = tostring(state.selectedKey or "")
    end

    local info = nil
    if selectedGroupKey ~= "" and NS.AceOptions and type(NS.AceOptions.GetCollectionInfo) == "function" then
        info = NS.AceOptions:GetCollectionInfo(selectedGroupKey)
    end

    if type(info) == "table" then
        state.classID = tonumber(info.classID) or 0
        state.specID = tonumber(info.specID) or 0
        state.selectedCollectionKey = tostring(info.key or selectedGroupKey)
        state.selectedKey = tostring(info.key or selectedGroupKey)
    else
        NS.AceOptions:SyncScopeToCurrentSpec()
    end

    NS.AceOptions:EnsureValidScope()
    NS.AceOptions:ClearEditorFields()
    if type(info) == "table" then
        state.selectedCollectionKey = tostring(info.key or selectedGroupKey)
    end
    local frame = self:EnsureFrame()
    self.mode = "new"
    frame.title:SetText(L("TITLE_NEW_CONFIG"))
    frame:Show()
    frame:Raise()
    self:PushToWidgets()
end

function EditorFrame:OpenForEdit()
    local state = NS.AceOptions:GetState()
    if tostring(state.selectedKey or "") == "" then
        print("[MCD-CVA] " .. L("MSG_CHOOSE_CONFIG"))
        return
    end

    NS.AceOptions:LoadSelectedEntry()
    local frame = self:EnsureFrame()
    self.mode = "edit"
    frame.title:SetText(L("TITLE_EDIT_CONFIG"))
    frame:Show()
    frame:Raise()
    self:PushToWidgets()
end

function EditorFrame:Close()
    if self.frame then
        self.frame:Hide()
    end
end
