-- =============================================================================
-- Config.lua  —  RNTools Settings window + profile management
-- Loaded after all other modules (references RNToolsAddon.db from RNTools_Core)
-- =============================================================================

local AceGUI = LibStub("AceGUI-3.0")

-- ── Local color palette ───────────────────────────────────────────────────────
local CC = {
    bg   = {0.08, 0.08, 0.08, 0.97},
    bord = {0.20, 0.20, 0.20, 1.0},
    gold = {1.00, 0.82, 0.00, 1.0},
}

local CFG_BACKDROP = {
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
    insets   = {left=1, right=1, top=1, bottom=1},
}

local function CFSetBG(frame, r, g, b, a)
    frame:SetBackdrop(CFG_BACKDROP)
    frame:SetBackdropColor(r, g, b, a or 1)
    frame:SetBackdropBorderColor(CC.bord[1], CC.bord[2], CC.bord[3], CC.bord[4])
end

-- ── DB accessor ───────────────────────────────────────────────────────────────
local function P()
    if _G.RNToolsAddon and _G.RNToolsAddon.db then
        return _G.RNToolsAddon.db.profile
    end
    return {}
end

local function DB()
    return _G.RNToolsAddon and _G.RNToolsAddon.db
end

-- ── Config serializer (flat key=value, handles string/number/boolean) ─────────
-- String values: escape | -> \p  = -> \e  \ -> \\
local SERIAL_KEYS = {
    "addonScale",
    "rm_cd_default","rm_roll_type","rm_spec_enforce","rm_channel",
    "rm_open_tmpl","rm_winner_tmpl","rm_closed_tmpl","rm_mini_auto_show",
    "rb_enable_login","rb_expiry_min",
    "rb_chan_world","rb_chan_trade","rb_chan_lfg","rb_chan_yell",
    "bar_lock","bar_scale","bar_click_toggle",
    "bar_pill_BT","bar_pill_SPAM","bar_pill_AR","bar_pill_ML",
    "bar_pill_AI","bar_pill_MS","bar_pill_RB",
    "spec_auto_gather","spec_expiry_days","spec_show_rolls","spec_enforce",
    "minimap_show","minimap_left_click",
    "ms_auto_gather","ms_announce_fmt",
    "ai_login_enabled","ai_response_text",
    "qa_open_login","qa_lock",
    "qa_sec_toggles","qa_sec_announce","qa_sec_rolls","qa_sec_spec",
    "spam_active_preset",
    "spam_interval_general","spam_interval_trade","spam_interval_world","spam_interval_yell",
}

local function SerializeConfig(profile)
    local parts = {}
    for _, k in ipairs(SERIAL_KEYS) do
        local v = profile[k]
        if v ~= nil then
            local t = type(v)
            if t == "boolean" then
                table.insert(parts, k .. "=" .. (v and "T" or "F"))
            elseif t == "number" then
                table.insert(parts, k .. "=" .. tostring(v))
            elseif t == "string" then
                local s = v:gsub("\\", "\\\\"):gsub("|", "\\p"):gsub("=", "\\e")
                table.insert(parts, k .. "=" .. s)
            end
        end
    end
    return "RNTV1:" .. table.concat(parts, "|")
end

local function DeserializeConfig(str)
    if type(str) ~= "string" or str:sub(1,6) ~= "RNTV1:" then
        return nil, "Not a valid RNTools config string (must start with RNTV1:)"
    end
    local t = {}
    local body = str:sub(7)
    -- Iterate pipe-separated pairs; end sentinel ensures last pair is captured
    for pair in (body .. "|"):gmatch("([^|]+)|") do
        local k, v = pair:match("^([^=]+)=(.*)")
        if k and v ~= nil then
            if v == "T" then
                t[k] = true
            elseif v == "F" then
                t[k] = false
            elseif tonumber(v) then
                t[k] = tonumber(v)
            else
                t[k] = v:gsub("\\e","="):gsub("\\p","|"):gsub("\\\\","\\")
            end
        end
    end
    return t
end

-- ── StaticPopups ──────────────────────────────────────────────────────────────
StaticPopupDialogs["RNTCFG_RELOAD"] = {
    text        = "|cffFFD100RNTools Settings|r\n\nSome settings require a UI reload to take effect.\nReload now?",
    button1     = "Reload UI",
    button2     = "Not Now",
    OnAccept    = function() ReloadUI() end,
    timeout = 0, whileDead = 1, hideOnEscape = 1,
}

StaticPopupDialogs["RNTCFG_NEW_PROFILE"] = {
    text        = "Enter new profile name:",
    button1     = "Create",
    button2     = "Cancel",
    hasEditBox  = true,
    maxLetters  = 64,
    OnAccept    = function(self)
        local name = self.editBox:GetText():match("^%s*(.-)%s*$")
        if name and name ~= "" then
            local db = DB()
            if db then db:SetProfile(name) end
            if _G.RNTools_RefreshConfigProfiles then _G.RNTools_RefreshConfigProfiles() end
        end
    end,
    OnShow      = function(self) self.editBox:SetText("") end,
    timeout = 0, whileDead = 1, hideOnEscape = 1,
}

StaticPopupDialogs["RNTCFG_COPY_PROFILE"] = {
    text        = "Copy current profile to (enter target name):",
    button1     = "Copy",
    button2     = "Cancel",
    hasEditBox  = true,
    maxLetters  = 64,
    OnAccept    = function(self)
        local name = self.editBox:GetText():match("^%s*(.-)%s*$")
        if name and name ~= "" then
            local db = DB()
            if db then db:CopyProfile(name) end
            if _G.RNTools_RefreshConfigProfiles then _G.RNTools_RefreshConfigProfiles() end
        end
    end,
    OnShow      = function(self) self.editBox:SetText("") end,
    timeout = 0, whileDead = 1, hideOnEscape = 1,
}

StaticPopupDialogs["RNTCFG_DELETE_PROFILE"] = {
    text        = 'Delete profile "%s"? This cannot be undone.',
    button1     = "Delete",
    button2     = "Cancel",
    OnAccept    = function(self, data)
        local db = DB()
        if db and data then db:DeleteProfile(data, true) end
        if _G.RNTools_RefreshConfigProfiles then _G.RNTools_RefreshConfigProfiles() end
    end,
    timeout = 0, whileDead = 1, hideOnEscape = 1,
}

-- ── Large profile string popup (ElvUI-style) ─────────────────────────────────
-- One frame, shared for both Export and Import.
local PROF_POPUP = nil

local POPUP_BACKDROP = {
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
    insets   = {left=1, right=1, top=1, bottom=1},
}
local EB_BACKDROP = {
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
    insets   = {left=1, right=1, top=1, bottom=1},
}

local function GetOrCreateProfPopup()
    if PROF_POPUP then return PROF_POPUP end

    local PW, PH = 680, 420
    local f = CreateFrame("Frame", "RNToolsProfPopup", UIParent)
    f:SetWidth(PW)
    f:SetHeight(PH)
    f:SetPoint("CENTER")
    f:SetFrameStrata("TOOLTIP")
    f:SetToplevel(true)
    f:EnableMouse(true)
    f:SetMovable(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop",  f.StopMovingOrSizing)
    f:SetScript("OnKeyDown", function(self, key)
        if key == "ESCAPE" then self:Hide() end
    end)
    f:SetBackdrop(POPUP_BACKDROP)
    f:SetBackdropColor(0.06, 0.06, 0.06, 0.98)
    f:SetBackdropBorderColor(0.30, 0.30, 0.30, 1)

    -- Title
    local titleFS = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    titleFS:SetPoint("TOP", f, "TOP", 0, -16)
    f.titleFS = titleFS

    -- Hint
    local hintFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hintFS:SetPoint("TOP", titleFS, "BOTTOM", 0, -4)
    hintFS:SetTextColor(0.8, 0.8, 0.8, 1)
    f.hintFS = hintFS

    -- EditBox container
    local ebHolder = CreateFrame("Frame", nil, f)
    ebHolder:SetPoint("TOPLEFT",     f, "TOPLEFT",     18, -70)
    ebHolder:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -18,  50)
    ebHolder:SetBackdrop(EB_BACKDROP)
    ebHolder:SetBackdropColor(0.05, 0.05, 0.05, 1)
    ebHolder:SetBackdropBorderColor(0.30, 0.30, 0.30, 1)

    local ebSF = CreateFrame("ScrollFrame", "RNToolsProfPopupScroll", ebHolder, "UIPanelScrollFrameTemplate")
    ebSF:SetPoint("TOPLEFT",     ebHolder, "TOPLEFT",      6, -6)
    ebSF:SetPoint("BOTTOMRIGHT", ebHolder, "BOTTOMRIGHT", -26,  6)

    local EB_W = PW - 18 - 18 - 6 - 26 - 6   -- ~606
    local eb = CreateFrame("EditBox", nil, ebSF)
    eb:SetMultiLine(true)
    eb:SetAutoFocus(false)
    eb:SetFontObject(ChatFontNormal)
    eb:SetWidth(EB_W)
    eb:SetScript("OnEscapePressed", function() f:Hide() end)
    ebSF:SetScrollChild(eb)
    f.eb = eb

    -- AceGUI buttons so ElvUI skins them the same as all other buttons in the addon
    -- actBtn shows "Import" or "Close"; size for the longer label ("Import" = 6*9+30 = 84)
    local actBtn = AceGUI:Create("Button")
    actBtn:SetWidth(math.max(68, string.len("Import")*9+30))  -- 84
    actBtn.frame:SetParent(f)
    actBtn.frame:ClearAllPoints()
    actBtn.frame:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -20, 16)
    actBtn.frame:SetHeight(22)
    actBtn.frame:Show()
    f.actBtn = actBtn

    local cancelBtn = AceGUI:Create("Button")
    cancelBtn:SetText("Cancel")
    cancelBtn:SetWidth(math.max(68, string.len("Cancel")*9+30))  -- 84
    cancelBtn.frame:SetParent(f)
    cancelBtn.frame:ClearAllPoints()
    cancelBtn.frame:SetPoint("RIGHT", actBtn.frame, "LEFT", -8, 0)
    cancelBtn.frame:SetHeight(22)
    cancelBtn.frame:Show()
    cancelBtn:SetCallback("OnClick", function() f:Hide() end)
    f.cancelBtn = cancelBtn

    f:Hide()
    PROF_POPUP = f
    return f
end

local function ShowExportPopup(str)
    local f = GetOrCreateProfPopup()
    f.titleFS:SetText("|cffFFD100Export Profile|r")
    f.hintFS:SetText("Press Ctrl+A to select all, then Ctrl+C to copy.")
    f.eb:SetText(str or "")
    f.actBtn:SetText("Close")
    f.actBtn:SetCallback("OnClick", function() f:Hide() end)
    f.cancelBtn.frame:Hide()
    f:Show()
    f:Raise()
    f.eb:SetFocus()
    f.eb:HighlightText()
end

local function ShowImportPopup()
    local f = GetOrCreateProfPopup()
    f.titleFS:SetText("|cffFFD100Import Profile|r")
    f.hintFS:SetText("Paste your exported config string below, then click Import.")
    f.eb:SetText("")
    f.actBtn:SetText("Import")
    f.actBtn:SetCallback("OnClick", function()
        local str = (f.eb:GetText() or ""):match("^%s*(.-)%s*$")
        f:Hide()
        local t, err = DeserializeConfig(str)
        if not t then
            print("|cffff4d4d[RNTools Config]|r Import failed: " .. (err or "unknown"))
            return
        end
        local p = P()
        for k, v in pairs(t) do p[k] = v end
        print("|cff20ff60[RNTools Config]|r Settings imported. Some changes apply on next /reload.")
        if _G.RNTools_RefreshConfigWindow then _G.RNTools_RefreshConfigWindow() end
    end)
    f.cancelBtn.frame:Show()
    f:Show()
    f:Raise()
    f.eb:SetFocus()
end

-- ── Config window ─────────────────────────────────────────────────────────────
local CONF_WIN   = nil
local CONF_W     = 700
-- Content width: leave 20px margin each side inside the AceGUI chrome + scrollbar
local CONTENT_W  = CONF_W - 34 - 30 - 40   -- 596

-- Cached widget map for RefreshConfigWindow
local CW = {}

local function OpenConfigWindow()
    -- Build the AceGUI Frame
    local win = AceGUI:Create("Frame")
    CONF_WIN = win
    win:SetTitle("RNTools — Settings")
    win:SetWidth(CONF_W)
    win:SetHeight(580)
    win:EnableResize(false)
    win:SetLayout("Fill")
    win:SetCallback("OnClose", function(w)
        -- Save position, then just hide — never Release().
        -- Releasing returns the AceGUI Frame to the pool but leaves all our
        -- manually-parented child frames (sf, cnt, widget frames) still attached
        -- to the recycled content frame.  Next open reuses the same pool frame,
        -- giving two overlapping sf/cnt pairs; the old ones intercept all
        -- mouse/keyboard events causing the "frozen" symptom.
        local db = DB()
        if db then
            local p, _, rp, x, y = w.frame:GetPoint(1)
            if p then db.profile.confPos = {point=p, rPoint=rp, x=x, y=y} end
        end
        w:Hide()
        -- CONF_WIN stays set; RNTools_ToggleConfig will Show() it on next call.
    end)

    -- Set initial scale from saved profile (AceDB is the source of truth)
    local cfgScale = P().addonScale
    if type(cfgScale) ~= "number" or cfgScale <= 0 then cfgScale = 1.0 end
    win.frame:SetScale(cfgScale)
    win.frame:SetBackdropColor(0, 0, 0, 0)
    local _cfgBg = win.frame:CreateTexture(nil, "BACKGROUND")
    _cfgBg:SetTexture("Interface\\AddOns\\RNTools\\Textures\\Backdrop")
    _cfgBg:SetBlendMode("DISABLE")
    _cfgBg:SetAllPoints(win.frame)
    -- Register so future RNTools_ApplyAddonScale calls also scale this window
    if RNTools_RegisterScalableFrame then RNTools_RegisterScalableFrame(win.frame) end

    -- Restore position
    local db = DB()
    if db and db.profile.confPos then
        local cp = db.profile.confPos
        win.frame:ClearAllPoints()
        win.frame:SetPoint(cp.point or "CENTER", UIParent, cp.rPoint or "CENTER", cp.x or 0, cp.y or 0)
    end

    -- Scroll frame inside win.content
    -- Anchored to avoid scrollbar overflow (SCROLL_INSET=30 already in CONTENT_W)
    local sf = CreateFrame("ScrollFrame", "RNToolsConfScrollFrame", win.content, "UIPanelScrollFrameTemplate")
    sf:SetPoint("TOP",    win.content, "TOP",    0,  0)
    sf:SetPoint("BOTTOM", win.content, "BOTTOM", 0,  0)
    sf:SetWidth(CONTENT_W + 30)

    local cnt = CreateFrame("Frame", nil, sf)
    cnt:SetWidth(CONTENT_W)
    sf:SetScrollChild(cnt)

    local yOff = 6  -- top padding

    -- PlaceWidget: anchors widget.frame into cnt at current yOff
    local function PW(widget, h, spacing)
        widget.frame:SetParent(cnt)
        widget.frame:ClearAllPoints()
        widget.frame:SetPoint("TOPLEFT",  cnt, "TOPLEFT",  0, -yOff)
        widget.frame:SetPoint("TOPRIGHT", cnt, "TOPRIGHT", 0, -yOff)
        widget.frame:SetHeight(h)
        widget.frame:Show()
        yOff = yOff + h + (spacing or 4)
    end

    -- Heading helper
    local function Hdr(txt)
        local h = AceGUI:Create("Heading")
        h:SetText(txt)
        h:SetFullWidth(true)
        PW(h, 18, 8)
    end

    -- NewRow: create a SimpleGroup Flow row, size it, place it, return it
    -- Children added after this call use row.content.width = CONTENT_W
    local function NewRow(h, spacing)
        local row = AceGUI:Create("SimpleGroup")
        row:SetLayout("Flow")
        row.noAutoHeight = true
        row:SetWidth(CONTENT_W)   -- fires OnWidthSet -> row.content.width = CONTENT_W
        PW(row, h, spacing or 4)
        return row
    end

    -- Checkbox helper. afterFn (optional) called after writing to P().
    local function MakeCB(lbl, key, w, afterFn)
        local cb = AceGUI:Create("CheckBox")
        cb:SetLabel(lbl)
        cb:SetValue(P()[key] == true)
        if w then cb:SetWidth(w) else cb:SetRelativeWidth(0.33) end
        cb:SetCallback("OnValueChanged", function(_, _, val)
            P()[key] = val
            if afterFn then afterFn(val) end
        end)
        CW[key] = cb
        return cb
    end

    local function ApplyBar() if RNTools_ApplyStatusBarSettings then RNTools_ApplyStatusBarSettings() end end

    -- EditBox helper (fixed width, numeric flag auto-detected by key)
    local NUM_KEYS = {
        rb_expiry_min=true, spec_expiry_days=true,
        spam_interval_general=true, spam_interval_trade=true,
        spam_interval_world=true, spam_interval_yell=true,
        bar_scale=true, addonScale=true,
    }
    local function MakeEB(lbl, key, w)
        local eb = AceGUI:Create("EditBox")
        eb:SetLabel(lbl)
        eb:SetText(tostring(P()[key] or ""))
        eb:DisableButton(true)
        eb:SetWidth(w or 200)
        if NUM_KEYS[key] then
            eb:SetCallback("OnTextChanged", function(_, _, val)
                P()[key] = tonumber(val) or P()[key]
            end)
        else
            eb:SetCallback("OnTextChanged", function(_, _, val)
                P()[key] = val
            end)
        end
        CW[key] = eb
        return eb
    end

    -- ── PROFILES ─────────────────────────────────────────────────────────────
    Hdr("Profiles")

    -- Row 1: dropdown + action buttons
    local profRow = NewRow(50, 4)

    local profDD = AceGUI:Create("Dropdown")
    profDD:SetLabel("Active Profile")
    profDD:SetWidth(220)
    CW.profDD = profDD

    local function RefreshProfDD()
        local adb = DB()
        if not adb then return end
        local names = {}
        adb:GetProfiles(names)
        local list, order = {}, {}
        for _, n in ipairs(names) do
            list[n] = n
            table.insert(order, n)
        end
        profDD:SetList(list, order)
        profDD:SetValue(adb:GetCurrentProfile())
    end
    RefreshProfDD()
    profDD:SetCallback("OnValueChanged", function(_, _, val)
        local adb = DB()
        if adb then adb:SetProfile(val) end
    end)
    profRow:AddChild(profDD)

    local newBtn = AceGUI:Create("Button")
    newBtn:SetText("New")
    newBtn:SetWidth(68)
    newBtn:SetCallback("OnClick", function() StaticPopup_Show("RNTCFG_NEW_PROFILE") end)
    profRow:AddChild(newBtn)

    local copyBtn = AceGUI:Create("Button")
    copyBtn:SetText("Copy")
    copyBtn:SetWidth(68)
    copyBtn:SetCallback("OnClick", function() StaticPopup_Show("RNTCFG_COPY_PROFILE") end)
    profRow:AddChild(copyBtn)

    local delBtn = AceGUI:Create("Button")
    delBtn:SetText("Delete")
    delBtn:SetWidth(80)
    delBtn:SetCallback("OnClick", function()
        local adb = DB()
        if adb then StaticPopup_Show("RNTCFG_DELETE_PROFILE", adb:GetCurrentProfile()) end
    end)
    profRow:AddChild(delBtn)

    local resetBtn = AceGUI:Create("Button")
    resetBtn:SetText("Reset")
    resetBtn:SetWidth(68)
    resetBtn:SetCallback("OnClick", function()
        local adb = DB()
        if adb then
            adb:ResetProfile()
            if _G.RNTools_RefreshConfigWindow then _G.RNTools_RefreshConfigWindow() end
        end
    end)
    profRow:AddChild(resetBtn)

    -- Row 2: export / import
    local ioRow = NewRow(28, 10)

    local expBtn = AceGUI:Create("Button")
    expBtn:SetText("Export Profile")
    expBtn:SetWidth(math.max(68, string.len("Export Profile")*9+30))
    expBtn:SetCallback("OnClick", function()
        ShowExportPopup(SerializeConfig(P()))
    end)
    ioRow:AddChild(expBtn)

    local impBtn = AceGUI:Create("Button")
    impBtn:SetText("Import Profile")
    impBtn:SetWidth(math.max(68, string.len("Import Profile")*9+30))
    impBtn:SetCallback("OnClick", function()
        ShowImportPopup()
    end)
    ioRow:AddChild(impBtn)

    -- ── SETUP WIZARD ────────────────────────────────────────────────────────
    Hdr("Setup Wizard")

    local wizRow = NewRow(28, 10)
    local rerunBtn = AceGUI:Create("Button")
    rerunBtn:SetText("Re-run Setup Wizard")
    rerunBtn:SetRelativeWidth(0.45)
    rerunBtn:SetCallback("OnClick", function()
        if RNTools_OpenSetupWizard then RNTools_OpenSetupWizard() end
    end)
    wizRow:AddChild(rerunBtn)
    local roleBtn = AceGUI:Create("Button")
    roleBtn:SetText("Change Role")
    roleBtn:SetRelativeWidth(0.45)
    roleBtn:SetCallback("OnClick", function()
        if RNTools_OpenSetupWizard then
            RNTools_OpenSetupWizard()
            C_Timer_fake(0.05, function()
                if RNTools_SetupWizardPage then RNTools_SetupWizardPage(2) end
            end)
        end
    end)
    wizRow:AddChild(roleBtn)

    -- ── GENERAL ──────────────────────────────────────────────────────────────
    Hdr("General")

    local scaleRow = NewRow(52, 10)

    local _rawScale = tonumber(P().addonScale) or 0
    if _rawScale <= 0 then _rawScale = 1.0 end
    local initPct = math.floor(_rawScale * 100 + 0.5)
    local scaleSlider = AceGUI:Create("Slider")
    scaleSlider:SetLabel("UI Scale: " .. initPct .. "%")
    scaleSlider:SetSliderValues(50, 200, 5)
    scaleSlider:SetValue(initPct)
    scaleSlider:SetWidth(300)
    -- While dragging: update the label preview only — do NOT apply scale yet.
    scaleSlider:SetCallback("OnValueChanged", function(_, _, val)
        local snapped = math.floor(val / 5 + 0.5) * 5
        snapped = math.max(50, math.min(200, snapped))
        scaleSlider:SetLabel("UI Scale: " .. snapped .. "%")
    end)
    -- On mouse release: commit scale to all windows via the global applicator.
    scaleSlider.slider:HookScript("OnMouseUp", function()
        local val = scaleSlider:GetValue()
        local snapped = math.floor(val / 5 + 0.5) * 5
        snapped = math.max(50, math.min(200, snapped))
        local scaleVal = snapped / 100
        P().addonScale = scaleVal
        if RNTools_ApplyAddonScale then RNTools_ApplyAddonScale(scaleVal) end
    end)
    CW.addonScale = scaleSlider
    scaleRow:AddChild(scaleSlider)

    local scaleReset = AceGUI:Create("Button")
    scaleReset:SetText("Reset (100%)")
    scaleReset:SetWidth(120)
    scaleReset:SetCallback("OnClick", function()
        P().addonScale = 1.0
        scaleSlider:SetValue(100)
        scaleSlider:SetLabel("UI Scale: 100%")
        if RNTools_ApplyAddonScale then RNTools_ApplyAddonScale(1.0) end
    end)
    scaleRow:AddChild(scaleReset)

    -- ── ROLL MANAGER ─────────────────────────────────────────────────────────
    Hdr("Roll Manager")

    local rmRow1 = NewRow(52, 4)

    -- CD default: same values as Mini RM dropdown {0,10,15,20,25,30}
    local cdDD = AceGUI:Create("Dropdown")
    cdDD:SetLabel("Default CD Timer")
    cdDD:SetWidth(180)
    do
        local list  = {["0"]="(none)",["10"]="10 sec",["15"]="15 sec",["20"]="20 sec",["25"]="25 sec",["30"]="30 sec"}
        local order = {"0","10","15","20","25","30"}
        cdDD:SetList(list, order)
        cdDD:SetValue(tostring(P().rm_cd_default or 20))
    end
    cdDD:SetCallback("OnValueChanged", function(_, _, val)
        local n = tonumber(val) or 20
        P().rm_cd_default = n
        -- Propagate live: update global RM state + SVar + button labels
        _G.RM_cdDuration = n
        if type(_G.RTRollManagerSave) == "table" then _G.RTRollManagerSave.cdDuration = n end
        if _G.RNTools_SyncCDLabel then _G.RNTools_SyncCDLabel() end
    end)
    CW.rm_cd_default = cdDD
    rmRow1:AddChild(cdDD)

    local rtDD = AceGUI:Create("Dropdown")
    rtDD:SetLabel("Default Roll Type")
    rtDD:SetWidth(180)
    do
        local list = {MS="MS", OS="OS", OFFSPEC="Offspec", OPEN="Open", FREE="Free"}
        local order = {"MS","OS","OFFSPEC","OPEN","FREE"}
        rtDD:SetList(list, order)
        rtDD:SetValue(P().rm_roll_type or "MS")
    end
    rtDD:SetCallback("OnValueChanged", function(_, _, val) P().rm_roll_type = val end)
    CW.rm_roll_type = rtDD
    rmRow1:AddChild(rtDD)

    local specEnfCB = AceGUI:Create("CheckBox")
    specEnfCB:SetLabel("Spec Enforcement")
    specEnfCB:SetValue(P().rm_spec_enforce ~= false)
    specEnfCB:SetWidth(150)
    specEnfCB:SetCallback("OnValueChanged", function(_, _, val) P().rm_spec_enforce = val end)
    CW.rm_spec_enforce = specEnfCB
    rmRow1:AddChild(specEnfCB)

    -- Migrate old wrong default templates to match actual hardcoded behavior
    do
        local p = P()
        if p.rm_open_tmpl == "Rolling for {item} - /roll {max}" then
            p.rm_open_tmpl = "{item} >> {label} - Drop your rolls! /roll 1-100"
        end
        if p.rm_winner_tmpl == "{name} wins {item} with a roll of {roll}!" then
            p.rm_winner_tmpl = "{item} >> Winner: {name} ({roll} - {label})"
        end
    end

    local rmRow2 = NewRow(52, 4)

    local chanDD = AceGUI:Create("Dropdown")
    chanDD:SetLabel("Announce Channel")
    chanDD:SetWidth(200)
    do
        local list  = {RAID_WARNING="Raid Warning", RAID="Raid", SAY="Say", PARTY="Party"}
        local order = {"RAID_WARNING","RAID","SAY","PARTY"}
        chanDD:SetList(list, order)
        chanDD:SetValue(P().rm_channel or "RAID_WARNING")
    end
    chanDD:SetCallback("OnValueChanged", function(_, _, val) P().rm_channel = val end)
    CW.rm_channel = chanDD
    rmRow2:AddChild(chanDD)
    rmRow2:AddChild(MakeEB("Roll Open Msg  ({item}, {label})", "rm_open_tmpl", 380))

    local rmRow3 = NewRow(52, 10)
    rmRow3:AddChild(MakeEB("Winner Msg  ({item}, {name}, {roll}, {label})", "rm_winner_tmpl", CONTENT_W - 8))

    local rmRow4 = NewRow(28, 4)
    local qualLbl = AceGUI:Create("Label")
    qualLbl:SetText("Track Loot Quality:")
    qualLbl:SetWidth(130)
    rmRow4:AddChild(qualLbl)
    rmRow4:AddChild(MakeCB("|cff1eff00Uncommon|r", "rm_qual_uncommon", 120))
    rmRow4:AddChild(MakeCB("|cff0070ddRare|r",     "rm_qual_rare",     100))
    rmRow4:AddChild(MakeCB("|cffa335eeEpic|r",     "rm_qual_epic",     100))
    rmRow4:AddChild(MakeCB("|cffff8000Legendary|r","rm_qual_legendary", 110))

    local rmRow5 = NewRow(28, 4)
    rmRow5:AddChild(MakeCB("Auto-show Mini Roll Manager on roll detect", "rm_mini_auto_show", CONTENT_W - 8, function(val)
        _G.RM_MiniAutoShow = val ~= false
    end))

    -- ── SPEC DATABASE ─────────────────────────────────────────────────────────
    Hdr("Spec Database")

    local specRow1 = NewRow(52, 10)
    specRow1:AddChild(MakeCB("Auto-gather specs on raid join", "spec_auto_gather", 240))
    specRow1:AddChild(MakeCB("Global spec enforcement", "spec_enforce", 200, function(val)
        RNTools_SpecEnforce = val ~= false
    end))
    specRow1:AddChild(MakeCB("Show spec in roll list", "spec_show_rolls", 180, function(val)
        RNTools_MiniTrackSpec = val ~= false
        if RNTools_MiniTrackSpecCB then RNTools_MiniTrackSpecCB:SetValue(RNTools_MiniTrackSpec) end
    end))

    local specRow2 = NewRow(52, 10)
    specRow2:AddChild(MakeEB("Spec expiry (days, 0=never)", "spec_expiry_days", 200))

    -- ── RAID BROWSER ──────────────────────────────────────────────────────────
    Hdr("Raid Browser")

    local rbRow1 = NewRow(52, 8)
    rbRow1:AddChild(MakeCB("Enable on login", "rb_enable_login", 160))  -- login-only, /reload required

    -- Expiry: also propagate live to RB.expiry so existing listings respect the new value
    local rbExpEB = AceGUI:Create("EditBox")
    rbExpEB:SetLabel("Expiry (min)")
    rbExpEB:SetText(tostring(P().rb_expiry_min or 10))
    rbExpEB:DisableButton(true)
    rbExpEB:SetWidth(160)
    rbExpEB:SetCallback("OnTextChanged", function(_, _, val)
        local n = tonumber(val)
        if n then
            P().rb_expiry_min = n
            if RNTools_SetRBExpiry then RNTools_SetRBExpiry(n) end
        end
    end)
    CW.rb_expiry_min = rbExpEB
    rbRow1:AddChild(rbExpEB)

    local rbRow2 = NewRow(28, 10)
    local rbChanLbl = AceGUI:Create("Label")
    rbChanLbl:SetText("|cffFFD100Scan channels:|r  ")
    rbChanLbl:SetWidth(120)
    rbRow2:AddChild(rbChanLbl)
    rbRow2:AddChild(MakeCB("World/Global", "rb_chan_world", 120))
    rbRow2:AddChild(MakeCB("Trade",        "rb_chan_trade", 80))
    rbRow2:AddChild(MakeCB("LFG",          "rb_chan_lfg",   68))
    rbRow2:AddChild(MakeCB("Yell",         "rb_chan_yell",  68))

    -- ── STATUS BAR ────────────────────────────────────────────────────────────
    Hdr("Status Bar")

    local barRow1 = NewRow(52, 4)

    local barInitPct = math.floor((P().bar_scale or 1.0) * 100 + 0.5)
    local barScaleSlider = AceGUI:Create("Slider")
    barScaleSlider:SetLabel("Bar Scale: " .. barInitPct .. "%")
    barScaleSlider:SetSliderValues(50, 200, 5)
    barScaleSlider:SetValue(barInitPct)
    barScaleSlider:SetWidth(250)
    barScaleSlider:SetCallback("OnValueChanged", function(_, _, val)
        local snapped = math.floor(val / 5 + 0.5) * 5
        barScaleSlider:SetLabel("Bar Scale: " .. snapped .. "%")
    end)
    barScaleSlider.slider:HookScript("OnMouseUp", function()
        local val = barScaleSlider:GetValue()
        local snapped = math.floor(val / 5 + 0.5) * 5
        local scaleVal = snapped / 100
        P().bar_scale = scaleVal
        ApplyBar()
    end)
    CW.bar_scale = barScaleSlider
    barRow1:AddChild(barScaleSlider)

    barRow1:AddChild(MakeCB("Lock position",       "bar_lock",          130, ApplyBar))
    barRow1:AddChild(MakeCB("Click = toggle",      "bar_click_toggle",  150))

    local barRow2 = NewRow(28, 10)
    local barLbl = AceGUI:Create("Label")
    barLbl:SetText("|cffFFD100Visible pills:|r  ")
    barLbl:SetWidth(110)
    barRow2:AddChild(barLbl)
    for _, pair in ipairs({
        {"bar_pill_BT","BT"},{"bar_pill_SPAM","Spam"},{"bar_pill_AR","AR"},
        {"bar_pill_ML","ML"},{"bar_pill_AI","AI"},{"bar_pill_MS","MS"},{"bar_pill_RB","RB"},
    }) do
        barRow2:AddChild(MakeCB(pair[2], pair[1], 72, ApplyBar))
    end

    -- ── MINIMAP ───────────────────────────────────────────────────────────────
    Hdr("Minimap")

    local function ApplyMinimap() if RNTools_ApplyMinimapSettings then RNTools_ApplyMinimapSettings() end end

    local mmRow = NewRow(52, 10)
    mmRow:AddChild(MakeCB("Show minimap button", "minimap_show", 180, ApplyMinimap))

    local mmClickDD = AceGUI:Create("Dropdown")
    mmClickDD:SetLabel("Left-click action")
    mmClickDD:SetWidth(220)
    do
        local list  = {["main"]="Open Main Window", ["qa"]="Open Quick Access", ["bar"]="Toggle Status Bar"}
        local order = {"main","qa","bar"}
        mmClickDD:SetList(list, order)
        mmClickDD:SetValue(P().minimap_left_click or "main")
    end
    mmClickDD:SetCallback("OnValueChanged", function(_, _, val) P().minimap_left_click = val end)
    CW.minimap_left_click = mmClickDD
    mmRow:AddChild(mmClickDD)

    -- ── MS+BUFFS ──────────────────────────────────────────────────────────────
    Hdr("MS+Buffs")

    local msRow = NewRow(52, 10)
    msRow:AddChild(MakeCB("Auto-gather specs on raid join", "ms_auto_gather", 240))
    msRow:AddChild(MakeEB("Announce format ({name}, {spec})", "ms_announce_fmt", 340))

    -- ── AUTO INVITE ───────────────────────────────────────────────────────────
    Hdr("Auto Invite")

    local aiRow = NewRow(52, 10)
    aiRow:AddChild(MakeCB("Enable on login/reload", "ai_login_enabled", 200))  -- login-only

    -- "Default response text" also propagates live to AutoInvite's own SVar
    local aiRespEB = AceGUI:Create("EditBox")
    aiRespEB:SetLabel("Default response text")
    aiRespEB:SetText(P().ai_response_text or "")
    aiRespEB:DisableButton(true)
    aiRespEB:SetWidth(380)
    aiRespEB:SetCallback("OnTextChanged", function(_, _, val)
        P().ai_response_text = val
        -- AutoInvite reads from its own SVar, so propagate there too
        if type(RNToolsAutoInviteSave) == "table" then
            RNToolsAutoInviteSave.responseText = val
        end
    end)
    CW.ai_response_text = aiRespEB
    aiRow:AddChild(aiRespEB)

    -- ── QUICK ACCESS ──────────────────────────────────────────────────────────
    Hdr("Quick Access")

    local function ApplyQA() if RNTools_ApplyQASettings then RNTools_ApplyQASettings() end end

    local qaRow1 = NewRow(28, 4)
    qaRow1:AddChild(MakeCB("Open on login", "qa_open_login", 150))         -- applies at next login, no live action
    qaRow1:AddChild(MakeCB("Lock position", "qa_lock",       150, ApplyQA))

    -- Visible sections — split across 2 rows so nothing overflows
    local qaVisLbl = AceGUI:Create("Label")
    qaVisLbl:SetText("|cffFFD100Visible sections in Quick Access:|r")
    qaVisLbl:SetWidth(CONTENT_W)
    PW(qaVisLbl, 16, 3)

    local qaRow2 = NewRow(28, 4)
    qaRow2:AddChild(MakeCB("Module Toggles", "qa_sec_toggles", 180, ApplyQA))
    qaRow2:AddChild(MakeCB("Quick Announce", "qa_sec_announce",180, ApplyQA))
    qaRow2:AddChild(MakeCB("Roll Manager",   "qa_sec_rolls",   180, ApplyQA))

    local qaRow3 = NewRow(28, 10)
    qaRow3:AddChild(MakeCB("Spec Database",  "qa_sec_spec",    180, ApplyQA))

    -- ── SPAMMER ───────────────────────────────────────────────────────────────
    Hdr("Spammer")

    local spamRow = NewRow(52, 4)
    spamRow:AddChild(MakeEB("General channel (sec)", "spam_interval_general", 160))
    spamRow:AddChild(MakeEB("Trade channel (sec)",   "spam_interval_trade",   160))

    local spamRow2 = NewRow(52, 10)
    spamRow2:AddChild(MakeEB("World/Global channel (sec)", "spam_interval_world", 160))
    spamRow2:AddChild(MakeEB("Yell channel (sec)",         "spam_interval_yell",  160))

    -- ── Finish ────────────────────────────────────────────────────────────────
    yOff = yOff + 16
    cnt:SetHeight(yOff)

    -- ── Footer Save button — same row as AceGUI's built-in Close button ────────
    -- AceGUI Frame places its Close button at BOTTOMRIGHT -27,17 (100x20px) and a
    -- statusbg Button frame at BOTTOMLEFT 15,15 / BOTTOMRIGHT -132,15.
    -- The statusbg is a Button (EnableMouse=true) and blocks clicks on anything
    -- placed inside its x-range.  The Close button works because it's anchored
    -- in the 5px gap to the RIGHT of statusbg (frame_right-127 > frame_right-132).
    -- Solution: hide statusbg (we never use the status text) so the whole footer
    -- area is clear, then place Save to the left of Close with no blocker.
    win.statustext:GetParent():Hide()

    -- Save button: AceGUI:Create("Button") so ElvUI skins it identically to the
    -- built-in Close button (which also goes through AceGUI's RegisterAsWidget hook).
    -- Manually anchored at BOTTOMRIGHT -135,17 — the 8px gap left of Close
    -- (Close is 100px wide at BOTTOMRIGHT -27,17; 27+100+8 = 135).
    local saveFooterBtn = AceGUI:Create("Button")
    saveFooterBtn:SetText("Save Settings")
    saveFooterBtn:SetWidth(math.max(68, string.len("Save Settings")*9+30))  -- 147
    saveFooterBtn.frame:SetParent(win.frame)
    saveFooterBtn.frame:ClearAllPoints()
    -- Close button: 100px at BOTTOMRIGHT -27,17. Gap 8px → Save right edge at -(27+100+8) = -135.
    saveFooterBtn.frame:SetPoint("BOTTOMRIGHT", win.frame, "BOTTOMRIGHT", -135, 17)
    saveFooterBtn.frame:SetHeight(22)
    saveFooterBtn.frame:Show()
    saveFooterBtn:SetCallback("OnClick", function()
        -- Commit slider values to P() — in case OnMouseUp didn't fire
        do
            local raw = scaleSlider:GetValue()
            local snapped = math.floor(raw / 5 + 0.5) * 5
            local sv = math.max(0.5, math.min(2.0, snapped / 100))
            P().addonScale = sv
            if RNTools_ApplyAddonScale then RNTools_ApplyAddonScale(sv) end
        end
        do
            local raw = barScaleSlider:GetValue()
            local snapped = math.floor(raw / 5 + 0.5) * 5
            local sv = math.max(0.5, math.min(2.0, snapped / 100))
            P().bar_scale = sv
        end
        if RNTools_ApplyStatusBarSettings then RNTools_ApplyStatusBarSettings() end
        if RNTools_ApplyMinimapSettings   then RNTools_ApplyMinimapSettings()   end
        StaticPopup_Show("RNTCFG_RELOAD")
    end)

    -- Expose refresh helpers as globals (cleaned up on window close)
    _G.RNTools_RefreshConfigWindow = function()
        local p = P()
        for k, w in pairs(CW) do
            if k == "profDD" then
                -- handled by RefreshProfDD
            elseif k == "addonScale" then
                local pct = math.floor(((tonumber(p.addonScale) or 1.0) * 100) + 0.5)
                pcall(function() w:SetValue(pct) end)
                pcall(function() w:SetLabel("UI Scale: " .. pct .. "%") end)
            elseif k == "bar_scale" then
                local pct = math.floor(((tonumber(p.bar_scale) or 1.0) * 100) + 0.5)
                pcall(function() w:SetValue(pct) end)
                pcall(function() w:SetLabel("Bar Scale: " .. pct .. "%") end)
            elseif k == "rm_cd_default" then
                -- Dropdown stores string keys "10"/"15"/"20"/"25"
                pcall(function() w:SetValue(tostring(p.rm_cd_default or 20)) end)
            elseif w.SetValue ~= nil then
                local v = p[k]
                if v ~= nil then
                    if type(v) == "boolean" or type(v) == "number" then
                        pcall(function() w:SetValue(v) end)
                    end
                end
            elseif w.SetText ~= nil then
                local v = p[k]
                if type(v) == "string" then
                    pcall(function() w:SetText(v) end)
                elseif type(v) == "number" then
                    pcall(function() w:SetText(tostring(v)) end)
                end
            end
        end
        RefreshProfDD()
    end

    _G.RNTools_RefreshConfigProfiles = RefreshProfDD
end

-- ── Public toggle ─────────────────────────────────────────────────────────────
function RNTools_ToggleConfig()
    if CONF_WIN then
        if CONF_WIN.frame:IsShown() then
            CONF_WIN:Hide()
        else
            CONF_WIN:Show()
        end
        return
    end
    OpenConfigWindow()
end

-- ── Config tab placeholder ────────────────────────────────────────────────────
-- The tab acts as a trigger: OnShow opens the floating Config window.
function CreateConfigTabContent(parent, onClose)
    local f = CreateFrame("Frame", nil, parent)
    f:SetAllPoints(parent)
    f:Hide()
    CFSetBG(f, CC.bg[1], CC.bg[2], CC.bg[3], CC.bg[4])

    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("CENTER", f, "CENTER", 0, 40)
    title:SetTextColor(CC.gold[1], CC.gold[2], CC.gold[3])
    title:SetText("RNTools — Settings")

    local sub = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    sub:SetPoint("CENTER", f, "CENTER", 0, 10)
    sub:SetTextColor(0.75, 0.75, 0.75, 1)
    sub:SetText("The config window opens separately so you can keep it open while raiding.")

    local hint = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hint:SetPoint("CENTER", f, "CENTER", 0, -10)
    hint:SetTextColor(0.55, 0.55, 0.55, 1)
    hint:SetText("You can also open it anytime with:  /rnt config")

    f:SetScript("OnShow", function()
        RNTools_ToggleConfig()
    end)

    return f
end
