-- ui.lua

local addonName = ...

local function withCombatGuard(fn)
    if InCombatLockdown() then
        print("|cffffff00CCS:|r Cannot change settings during combat. Changes will apply after combat ends.")
        return
    end
    fn()
end

local function stripButtonBorder(btn)
    if btn.Left   then btn.Left:Hide()   end
    if btn.Middle then btn.Middle:Hide() end
    if btn.Right  then btn.Right:Hide()  end
end

local function stripCheckBorder(cb)
    cb:SetNormalTexture("")
    cb:SetPushedTexture("")
    cb:SetHighlightTexture("")
    cb:SetDisabledTexture("")
end

local function addTooltip(frame, title, body)
    frame:EnableMouse(true)
    frame:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:AddLine(title, 1, 1, 1)
        if body then GameTooltip:AddLine(body, 0.8, 0.8, 0.8, true) end
        GameTooltip:Show()
    end)
    frame:SetScript("OnLeave", function() GameTooltip:Hide() end)
end

-- Preview a sound by its value key (LSM name or "file:name" or CCS short name)
local function previewSound(value)
    if not value or value == "__default__" then return end
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    local basePath = "Interface\\AddOns\\" .. addonName .. "\\sounds\\"

    local ccsShortNames = {
        "break","breath","burn","dot","marked","move","pools","soak","spread","targeted",
    }
    local ccsSet = {}
    for _, n in ipairs(ccsShortNames) do ccsSet[n] = true end

    local path
    if value:sub(1, 5) == "file:" then
        path = basePath .. value:sub(6) .. ".ogg"
    elseif ccsSet[value] then
        path = basePath .. value .. ".ogg"
    elseif LSM then
        path = LSM:Fetch("sound", value)
    end
    if path then PlaySoundFile(path, "Master") end
end

local CATEGORY_NAME = "Custom Countdown Sounds"
local CCS_CategoryID

------------------------------------------------------------
local CCS_DropdownPopup

local function CCS_GetOrCreatePopup()
    if CCS_DropdownPopup then return CCS_DropdownPopup end

    local MAX_VISIBLE = 13
    local ROW_H       = 16
    local PAD         = 6
    local SCROLL_W    = 14

    local popup = CreateFrame("Frame", "CCS_DropdownPopup", UIParent, "BackdropTemplate")
    popup:SetFrameStrata("FULLSCREEN_DIALOG")
    popup:SetFrameLevel(200)
    popup:SetBackdrop({
        bgFile   = "Interface/Tooltips/UI-Tooltip-Background",
        edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
        edgeSize = 12,
        insets   = { left=3, right=3, top=3, bottom=3 },
    })
    popup:SetBackdropColor(0.1, 0.1, 0.1, 0.97)
    popup:SetBackdropBorderColor(0.5, 0.5, 0.5, 1)
    popup:Hide()
    popup._buttons = {}
    popup._owner   = nil
    popup._offset  = 0
    popup._DD_W    = 110  -- full-width fallback for non-wide dropdowns

    local SEARCH_H = 22

    -- Search bar (only shown for warn dropdowns with _widePreview)
    local searchBox = CreateFrame("EditBox", nil, popup, "BackdropTemplate")
    searchBox:SetHeight(SEARCH_H)
    searchBox:SetPoint("TOPLEFT",  popup, "TOPLEFT",  PAD, -PAD)
    searchBox:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -(PAD + SCROLL_W + 2), -PAD)
    searchBox:SetBackdrop({
        bgFile   = "Interface/Tooltips/UI-Tooltip-Background",
        edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
        edgeSize = 8,
        insets   = { left=3, right=3, top=3, bottom=3 },
    })
    searchBox:SetBackdropColor(0.05, 0.05, 0.05, 1)
    searchBox:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
    searchBox:SetFontObject("GameFontHighlightSmall")
    searchBox:SetTextInsets(6, 6, 0, 0)
    searchBox:SetAutoFocus(false)
    searchBox:SetMaxLetters(64)
    searchBox:Hide()
    popup._searchBox = searchBox

    local searchHint = searchBox:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    searchHint:SetPoint("LEFT",  searchBox, "LEFT",  6, 0)
    searchHint:SetPoint("RIGHT", searchBox, "RIGHT", -6, 0)
    searchHint:SetJustifyH("LEFT")
    searchHint:SetText("Type to search...")

    searchBox:SetScript("OnTextChanged", function(self)
        local txt = self:GetText()
        searchHint:SetShown(txt == "")
        -- Filter items and reset scroll
        local query = txt:lower()
        if query == "" then
            popup._items = popup._allItems
        else
            local filtered = {}
            for _, item in ipairs(popup._allItems) do
                if item.label:lower():find(query, 1, true) then
                    filtered[#filtered + 1] = item
                end
            end
            popup._items = filtered
        end
        popup._offset = 0
        popup._total  = #popup._items
        popup._refresh()
    end)

    searchBox:SetScript("OnEscapePressed", function()
        popup:Hide()
    end)

    local clipper = CreateFrame("Frame", nil, popup)
    clipper:SetPoint("TOPLEFT",     popup, "TOPLEFT",     PAD,               -PAD)
    clipper:SetPoint("BOTTOMRIGHT", popup, "BOTTOMRIGHT", -(PAD+SCROLL_W+2),  PAD)
    popup._clipper = clipper

    local track = CreateFrame("Frame", nil, popup, "BackdropTemplate")
    track:SetWidth(SCROLL_W)
    track:SetPoint("TOPRIGHT",    popup, "TOPRIGHT",    -PAD, -PAD)
    track:SetPoint("BOTTOMRIGHT", popup, "BOTTOMRIGHT", -PAD,  PAD)
    track:SetBackdrop({
        bgFile   = "Interface/Tooltips/UI-Tooltip-Background",
        edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
        edgeSize = 6,
        insets   = { left=2, right=2, top=2, bottom=2 },
    })
    track:SetBackdropColor(0.05, 0.05, 0.05, 1)
    track:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)

    local thumb = track:CreateTexture(nil, "OVERLAY")
    thumb:SetColorTexture(0.5, 0.5, 0.5, 0.8)
    popup._thumb = thumb

    local function UpdateThumb()
        local total   = popup._total or 0
        local visible = math.min(total, MAX_VISIBLE)
        if total <= visible then thumb:Hide(); return end
        thumb:Show()
        local trackH = track:GetHeight()
        local ratio  = visible / total
        local thumbH = math.max(16, trackH * ratio)
        local travel = trackH - thumbH
        local frac   = (popup._offset or 0) / math.max(1, total - visible)
        thumb:SetHeight(thumbH)
        thumb:ClearAllPoints()
        thumb:SetPoint("TOPLEFT",  track, "TOPLEFT",  2, -(travel * frac))
        thumb:SetPoint("TOPRIGHT", track, "TOPRIGHT", -2, -(travel * frac))
    end
    popup._updateThumb = UpdateThumb

    popup:EnableMouseWheel(true)
    popup:SetScript("OnMouseWheel", function(_, delta)
        local total   = popup._total or 0
        local visible = math.min(total, MAX_VISIBLE)
        popup._offset = math.max(0, math.min(popup._offset - delta, total - visible))
        if popup._refresh then popup._refresh() end
    end)

    track:EnableMouse(true)
    track:SetScript("OnMouseDown", function(_, btn)
        if btn ~= "LeftButton" then return end
        local total   = popup._total or 0
        local visible = math.min(total, MAX_VISIBLE)
        if total <= visible then return end
        track:SetScript("OnUpdate", function()
            local _, my = GetCursorPosition()
            local scale = track:GetEffectiveScale()
            local ty    = track:GetTop()
            local th    = track:GetHeight()
            local frac  = math.max(0, math.min(1, (ty - my/scale) / th))
            popup._offset = math.floor(frac * (total - visible) + 0.5)
            if popup._refresh then popup._refresh() end
        end)
    end)
    track:SetScript("OnMouseUp", function() track:SetScript("OnUpdate", nil) end)

    local PREV_W = 22  -- width reserved for preview button

    for i = 1, MAX_VISIBLE do
        local row = CreateFrame("Button", nil, clipper)
        row:SetHeight(ROW_H)

        row._check = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        row._check:SetPoint("LEFT", row, "LEFT", 6, 0)
        row._check:SetWidth(14)
        row._check:SetJustifyH("LEFT")

        row._text = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        row._text:SetPoint("LEFT",  row._check, "RIGHT", 2, 0)
        row._text:SetJustifyH("LEFT")

        local hl = row:CreateTexture(nil, "HIGHLIGHT")
        hl:SetAllPoints()
        hl:SetColorTexture(1, 1, 1, 0.1)

        -- Preview button: plays the sound without selecting or closing
        local prev = CreateFrame("Button", nil, row)
        prev:SetSize(PREV_W, ROW_H)
        prev:SetPoint("RIGHT", row, "RIGHT", -2, 0)

        local prevHL = prev:CreateTexture(nil, "HIGHLIGHT")
        prevHL:SetAllPoints()
        prevHL:SetColorTexture(0.6, 0.8, 1, 0.15)

        local prevTex = prev:CreateTexture(nil, "ARTWORK")
        prevTex:SetSize(PREV_W - 6, PREV_W - 6)
        prevTex:SetPoint("CENTER", prev, "CENTER", 0, 0)
        prevTex:SetAtlas("common-icon-sound")
        prevTex:SetVertexColor(0.6, 0.8, 1, 0.7)

        prev:SetScript("OnEnter", function() prevTex:SetVertexColor(0.8, 1, 1, 1) end)
        prev:SetScript("OnLeave", function() prevTex:SetVertexColor(0.6, 0.8, 1, 0.7) end)

        row._prev = prev
        row:Hide()
        popup._buttons[i] = row
    end

    local function Refresh()
        local items  = popup._items or {}
        local owner  = popup._owner
        local offset = popup._offset or 0
        for i = 1, MAX_VISIBLE do
            local row  = popup._buttons[i]
            local item = items[offset + i]
            if item then
                row._text:ClearAllPoints()
                row._text:SetPoint("LEFT", row._check, "RIGHT", 2, 0)
                if owner and owner._widePreview then
                    row._text:SetPoint("RIGHT", row, "RIGHT", -(PREV_W + 6), 0)
                    row._prev:Show()
                else
                    row._text:SetPoint("RIGHT", row, "RIGHT", -4, 0)
                    row._prev:Hide()
                end
                row._text:SetText(item.label)
                row._check:SetText(item.value == (owner and owner._value) and "|cff00ff00*|r" or "")
                row:ClearAllPoints()
                row:SetPoint("TOPLEFT",  clipper, "TOPLEFT",  0, -(i-1)*ROW_H)
                row:SetPoint("TOPRIGHT", clipper, "TOPRIGHT", 0, -(i-1)*ROW_H)
                row:Show()
                row:SetScript("OnClick", function()
                    if owner then
                        owner._value = item.value
                        owner._label:SetText(item.shortLabel or item.label)
                        if owner._onSelect then owner._onSelect(item.value, item.label) end
                    end
                    popup:Hide()
                end)
                -- Re-wire preview each Refresh so closure always has the correct item
                row._prev:SetScript("OnClick", function()
                    local val = item.value
                    if val == "__default__" then
                        val = popup._default
                    end
                    previewSound(val)
                end)
            else
                row:Hide()
            end
        end
        UpdateThumb()
    end
    popup._refresh = Refresh

    local catcher = CreateFrame("Frame", nil, UIParent)
    catcher:SetAllPoints(UIParent)
    catcher:SetFrameStrata("FULLSCREEN_DIALOG")
    catcher:SetFrameLevel(199)
    catcher:Hide()
    catcher:EnableMouse(true)
    catcher:SetScript("OnMouseDown", function() popup:Hide(); catcher:Hide() end)
    popup._catcher = catcher
    popup:SetScript("OnHide", function()
        catcher:Hide()
        popup._searchBox:SetText("")
        popup._searchBox:ClearFocus()
    end)

    CCS_DropdownPopup = popup
    return popup
end

local function CCS_CreateDropdown(parent, width, height)
    width  = width  or 120
    height = height or 22

    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(width, height)
    btn:SetBackdrop({
        bgFile   = "Interface/Tooltips/UI-Tooltip-Background",
        edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
        edgeSize = 10,
        insets   = { left=3, right=3, top=3, bottom=3 },
    })
    btn:SetBackdropColor(0.15, 0.15, 0.15, 1)
    btn:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)

    btn._label = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    btn._label:SetPoint("LEFT",  btn, "LEFT",  8,   0)
    btn._label:SetPoint("RIGHT", btn, "RIGHT", -18, 0)
    btn._label:SetJustifyH("LEFT")
    btn._label:SetText("--")

    btn._arrow = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    btn._arrow:SetPoint("RIGHT", btn, "RIGHT", -6, 0)
    btn._arrow:SetText("v")
    btn._arrow:SetTextColor(0.8, 0.8, 0.8)

    btn._items    = {}
    btn._value    = nil
    btn._onSelect = nil
    btn._enabled  = true

    btn:SetScript("OnEnter", function(self)
        if self._enabled then self:SetBackdropBorderColor(0.8, 0.8, 0.8, 1) end
    end)
    btn:SetScript("OnLeave", function(self)
        if self._enabled then self:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) end
    end)

    btn:SetScript("OnClick", function(self)
        if not self._enabled then return end
        local popup = CCS_GetOrCreatePopup()
        if popup:IsShown() and popup._owner == self then popup:Hide(); return end

        local MAX_VISIBLE = 13
        local ROW_H = 16
        local PAD   = 6
        local SCROLL_W = 14

        popup._owner   = self
        popup._allItems = self._items
        popup._items   = self._items
        popup._offset  = 0
        popup._total   = #self._items
        popup._default = self._defaultSound

        local SEARCH_H = 22
        local hasSearch = self._widePreview
        if hasSearch then
            popup._searchBox:Show()
            popup._searchBox:SetText("")
            popup._clipper:SetPoint("TOPLEFT", popup, "TOPLEFT", PAD, -(PAD + SEARCH_H + 4))
        else
            popup._searchBox:Hide()
            popup._clipper:SetPoint("TOPLEFT", popup, "TOPLEFT", PAD, -PAD)
        end

        local visible = math.min(#self._items, MAX_VISIBLE)
        local pw
        if self._widePreview then
            pw = math.max(width * 2, 260) + SCROLL_W + PAD
        else
            pw = (popup._DD_W or width) + SCROLL_W + PAD
        end
        local extraH = hasSearch and (SEARCH_H + 4) or 0
        popup:SetSize(pw, PAD*2 + visible*ROW_H + extraH)
        popup:ClearAllPoints()
        popup:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)
        popup._refresh()
        popup:Show()
        popup._catcher:Show()
        if hasSearch then
            popup._searchBox:SetFocus()
        end
    end)

    function btn:SetItems(items)  self._items = items or {} end
    function btn:GetValue()       return self._value        end
    function btn:SetOnSelect(fn)  self._onSelect = fn       end

    function btn:SetValue(value)
        self._value = value
        for _, item in ipairs(self._items) do
            if item.value == value then
                self._label:SetText(item.shortLabel or item.label)
                return
            end
        end
        self._label:SetText("--")
    end

    function btn:SetEnabled(enabled)
        self._enabled = enabled and true or false
        if self._enabled then
            self._label:SetTextColor(1,   1,   1)
            self._arrow:SetTextColor(0.8, 0.8, 0.8)
            self:SetBackdropColor(0.15, 0.15, 0.15, 1)
            self:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
        else
            self._label:SetTextColor(0.5, 0.5, 0.5)
            self._arrow:SetTextColor(0.4, 0.4, 0.4)
            self:SetBackdropColor(0.1, 0.1, 0.1, 1)
            self:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
        end
    end

    return btn
end

------------------------------------------------------------
-- Countdown helpers
------------------------------------------------------------

local COUNTDOWN_KEYS = { "file:ccs2s","file:ccs3s","file:ccs4s","file:ccs5s","file:ccs6s","file:ccs7s","file:ccs8s","file:ccs9s","file:ccs10s","file:ccs11s","file:ccs12s","file:ccs13s" }
local COUNTDOWN_LABELS = {
    ["file:ccs2s"]="2 Seconds",  ["file:ccs3s"]="3 Seconds",  ["file:ccs4s"]="4 Seconds",
    ["file:ccs5s"]="5 Seconds",  ["file:ccs6s"]="6 Seconds",  ["file:ccs7s"]="7 Seconds",
    ["file:ccs8s"]="8 Seconds",  ["file:ccs9s"]="9 Seconds",  ["file:ccs10s"]="10 Seconds",
    ["file:ccs11s"]="11 Seconds",["file:ccs12s"]="12 Seconds",["file:ccs13s"]="13 Seconds",
}

local function prettifyKey(key)
    if not key then return "none" end
    -- Strip file: prefix, replace underscores with spaces, title-case each word
    local s = key:gsub("^file:", ""):gsub("_", " ")
    return s:gsub("(%a)([%w]*)", function(first, rest)
        return first:upper() .. rest:lower()
    end)
end

local function getDefaultWarn(ability)
    local s = ability.soundH or ability.soundM
    if not s then return nil end
    return type(s) == "table" and s[1] or s
end

local cachedSoundItems = nil
local function getSoundItems()
    if cachedSoundItems then return cachedSoundItems end
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    local items = {}
    if LSM then
        local list = LSM:List("sound") or {}
        table.sort(list)
        for _, name in ipairs(list) do
            items[#items + 1] = { label = name, value = name }
        end
    end
    cachedSoundItems = items
    return items
end

local function buildWarnItems(defaultKey)
    local items = {}
    local short = prettifyKey(defaultKey)
    local defLabel = short .. " (default)"
    items[#items + 1] = { label = defLabel, shortLabel = short, value = "__default__" }
    for _, item in ipairs(getSoundItems()) do
        items[#items + 1] = { label = item.label, shortLabel = item.label, value = item.value }
    end
    return items
end

local function getDefaultCountdown(ability, diff)
    local s = diff == "M" and ability.soundM or ability.soundH
    return type(s) == "table" and s[2] or nil
end

local function hasCountdown(ability)
    return getDefaultCountdown(ability,"H") ~= nil or getDefaultCountdown(ability,"M") ~= nil
end

local function hasHeroic(ability)
    return ability.soundH ~= nil
end

local function hasMythic(ability)
    return ability.soundM ~= nil
end

local function getWarnSound(ability)
    local s = ability.soundH or ability.soundM
    if not s then return nil end
    return type(s) == "table" and s[1] or s
end

local function buildCountdownItems(defaultKey)
    local items = {}
    local shortDefault = defaultKey and (COUNTDOWN_LABELS[defaultKey] or prettifyKey(defaultKey)) or "off"
    local defLabel = shortDefault .. " (default)"
    items[#items+1] = { label = defLabel, shortLabel = shortDefault:match("^(%d+)") or shortDefault, value = "__default__" }
    for _, k in ipairs(COUNTDOWN_KEYS) do
        local full = COUNTDOWN_LABELS[k]       -- "8 Seconds"
        local num  = full:match("^(%d+)") or full  -- "8"
        items[#items+1] = { label = num, shortLabel = num, value = k }
    end
    return items
end

------------------------------------------------------------
-- Layout constants
------------------------------------------------------------

local ROW_HEIGHT  = 22
local HEADER_H    = 24
local INDENT      = 12
local CB_SIZE     = 22
local DD_W        = 110   -- warn dropdown button width
local CD_W        = 50    -- countdown dropdown button width (content is just a number)
local LEFT_FRAC   = 0.48

------------------------------------------------------------
-- Ability List
------------------------------------------------------------

local function BuildAbilityList(scrollChild, totalWidth)
    local leftW  = math.floor(totalWidth * LEFT_FRAC)
    local rightW = totalWidth - leftW
    local rPad   = 10
    local colHCB = rPad + 12
    local colHDD = colHCB + CB_SIZE + 2
    local colMCB = colHDD + CD_W + 12 + 12
    local colMDD = colMCB + CB_SIZE + 2

    local y = -8
    local allWarnCBs = {}
    local allWarnDDs = {}
    local defaultUCBs  = {}
    local noDefaultUCBs = {}

    local raidColors = {
        ["March on Quel'Danas"] = "|cff6fcf6f",  -- soft green
        ["The Dreamrift"]       = "|cff6aacdc",  -- soft blue
        ["The Voidspire"]       = "|cffc17de8",  -- soft purple
    }
    local lastRaid = nil

    -- Vertical divider — top anchor set after first raid header so it doesn't bleed above it
    local divider = scrollChild:CreateTexture(nil, "BACKGROUND")
    divider:SetColorTexture(0.3, 0.3, 0.3, 0.8)
    divider:SetWidth(1)
    divider:SetPoint("BOTTOMLEFT", scrollChild, "BOTTOMLEFT", leftW, 0)
    local dividerTopSet = false

    for _, entry in ipairs(CCS_Spells) do
        if entry.abilities then

            local justHadRaidHeader = false

            -- Raid header: rendered once when the raid name changes
            if entry.raid and entry.raid ~= lastRaid then
                lastRaid = entry.raid
                justHadRaidHeader = true
                if y < -8 then y = y - 8 end

                local RAID_H = 32

                -- Full-width separator line above (flush to bg top)
                local raidSep = scrollChild:CreateTexture(nil, "BACKGROUND")
                raidSep:SetColorTexture(0.4, 0.4, 0.4, 0.5)
                raidSep:SetHeight(1)
                raidSep:SetPoint("TOPLEFT",  scrollChild, "TOPLEFT", 0,          y)
                raidSep:SetPoint("TOPRIGHT", scrollChild, "TOPLEFT", totalWidth, y)
                y = y - 1

                -- Frame covers vertical divider
                local raidBg = CreateFrame("Frame", nil, scrollChild)
                raidBg:SetPoint("TOPLEFT",  scrollChild, "TOPLEFT", 0,          y)
                raidBg:SetPoint("TOPRIGHT", scrollChild, "TOPLEFT", totalWidth, y)
                raidBg:SetHeight(RAID_H)
                local raidBgTex = raidBg:CreateTexture(nil, "BACKGROUND")
                raidBgTex:SetAllPoints()
                raidBgTex:SetColorTexture(0.08, 0.08, 0.08, 1)

                local color = raidColors[entry.raid] or "|cffcccccc"
                local raidLbl = raidBg:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
                raidLbl:SetText(color .. entry.raid .. "|r")
                raidLbl:SetPoint("CENTER", raidBg, "CENTER", 0, 0)
                raidLbl:SetJustifyH("CENTER")

                y = y - RAID_H

                -- Separator line below (flush to bg bottom)
                local raidSepBot = scrollChild:CreateTexture(nil, "BACKGROUND")
                raidSepBot:SetColorTexture(0.4, 0.4, 0.4, 0.5)
                raidSepBot:SetHeight(1)
                raidSepBot:SetPoint("TOPLEFT",  scrollChild, "TOPLEFT", 0,          y)
                raidSepBot:SetPoint("TOPRIGHT", scrollChild, "TOPLEFT", totalWidth, y)
                if not dividerTopSet then
                    dividerTopSet = true
                    divider:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", leftW, y)
                end
                y = y - 8
            end

            -- Thin separator above boss header — skip immediately after a raid header
            if not justHadRaidHeader and y < -8 then
                local sep = scrollChild:CreateTexture(nil, "BACKGROUND")
                sep:SetColorTexture(0.25, 0.25, 0.25, 0.6)
                sep:SetHeight(1)
                sep:SetPoint("TOPLEFT",  scrollChild, "TOPLEFT", 0,          y)
                sep:SetPoint("TOPRIGHT", scrollChild, "TOPLEFT", totalWidth, y)
                y = y - 6
            end

            -- Boss header
            local header = scrollChild:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
            header:SetPoint("TOPLEFT", INDENT, y)
            header:SetWidth(totalWidth - INDENT * 2)
            header:SetJustifyH("LEFT")
            header:SetText(entry.section or entry.boss)
            y = y - HEADER_H

            for _, ability in ipairs(entry.abilities) do

                -- Left cell
                local leftCell = CreateFrame("Frame", nil, scrollChild)
                leftCell:SetSize(leftW, ROW_HEIGHT)
                leftCell:SetPoint("TOPLEFT", 0, y)

                -- Right cell
                local rightCell = CreateFrame("Frame", nil, scrollChild)
                rightCell:SetSize(rightW, ROW_HEIGHT)
                rightCell:SetPoint("TOPLEFT", leftW, y)

                -- Warn sound dropdown + toggle (right-aligned in left cell)
                local warnDD = CCS_CreateDropdown(leftCell, DD_W, ROW_HEIGHT - 4)
                warnDD:SetPoint("RIGHT", leftCell, "RIGHT", -8, 0)
                local defaultWarn = getDefaultWarn(ability)
                warnDD:SetItems(buildWarnItems(defaultWarn))
                warnDD._defaultWarn  = defaultWarn
                warnDD._defaultSound = defaultWarn
                warnDD._widePreview  = true  -- wide popup with preview button
                addTooltip(warnDD, "Warning Sound", "Choose which sound plays when this aura is applied.")
                local warnOverride = CCS.GetWarnOverride(ability.key)
                warnDD:SetValue(warnOverride or "__default__")
                warnDD:SetEnabled(CCS.isWarnEnabled(ability.key))
                warnDD:SetOnSelect(function(value)
                    withCombatGuard(function()
                        CCS.SetWarnOverride(ability.key, value ~= "__default__" and value or nil)
                        CCS.RefreshAbility(ability.key, ability)
                    end)
                end)

                local warnCB = CreateFrame("CheckButton", nil, leftCell, "UICheckButtonTemplate")
                warnCB:SetSize(CB_SIZE, CB_SIZE)
                warnCB:SetPoint("RIGHT", warnDD, "LEFT", -4, 0)
                warnCB:SetChecked(CCS.isWarnEnabled(ability.key))
                stripCheckBorder(warnCB)
                addTooltip(warnCB, "Warning Sound", "Play a sound when this ability's aura is applied to you.")

                -- Spell icon
                local ICON_SIZE = 18
                local icon = leftCell:CreateTexture(nil, "ARTWORK")
                icon:SetSize(ICON_SIZE, ICON_SIZE)
                icon:SetPoint("LEFT", leftCell, "LEFT", INDENT, 0)
                local texID = C_Spell.GetSpellTexture(ability.privateID)
                if texID then
                    icon:SetTexture(texID)
                    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) -- trim default icon border
                else
                    icon:SetColorTexture(0.2, 0.2, 0.2, 0.5) -- grey placeholder if not found
                end

                -- Ability name — tooltip frame, truncates before warn checkbox
                local lblFrame = CreateFrame("Frame", nil, leftCell)
                lblFrame:SetPoint("LEFT",  leftCell, "LEFT", INDENT + ICON_SIZE + 4, 0)
                lblFrame:SetPoint("RIGHT", warnCB,   "LEFT", -6, 0)
                lblFrame:SetHeight(ROW_HEIGHT)

                local lbl = lblFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
                lbl:SetAllPoints()
                lbl:SetJustifyH("LEFT")
                lbl:SetJustifyV("MIDDLE")
                lbl:SetText(ability.label)

                lblFrame:EnableMouse(true)
                lblFrame:SetScript("OnEnter", function(self)
                    if not ability.privateID then return end
                    GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                    GameTooltip:SetSpellByID(ability.privateID)
                    GameTooltip:AddLine(" ")
                    GameTooltip:AddLine("|cffaaaaaa SpellID: " .. ability.privateID .. "|r")
                    GameTooltip:Show()
                end)
                lblFrame:SetScript("OnLeave", function()
                    GameTooltip:Hide()
                end)

                warnCB:SetScript("OnClick", function(self)
                    withCombatGuard(function()
                        local en = self:GetChecked()
                        CCS.SetWarnEnabled(ability.key, en)
                        CCS.RefreshAbility(ability.key, ability)
                        warnDD:SetEnabled(en)
                    end)
                end)

                -- ── Countdown controls ────────────────────────────────────────────────
                local hDefaultCD = hasHeroic(ability) and getDefaultCountdown(ability, "H") or nil
                local mDefaultCD = hasMythic(ability)  and getDefaultCountdown(ability, "M") or nil

                local hOver = CCS.GetCountdownOverride(ability.key, "H")
                local mOver = CCS.GetCountdownOverride(ability.key, "M")

                -- HC row
                local hLbl = rightCell:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
                hLbl:SetText("|cffaaaaaa HC|r")
                hLbl:SetPoint("LEFT", rightCell, "LEFT", colHCB - 20, 0)

                local hCB = CreateFrame("CheckButton", nil, rightCell, "UICheckButtonTemplate")
                hCB:SetSize(CB_SIZE, CB_SIZE)
                hCB:SetPoint("LEFT", rightCell, "LEFT", colHCB, 0)
                hCB:SetChecked(CCS.IsCDEnabled(ability.key, "H"))
                stripCheckBorder(hCB)
                addTooltip(hCB, "Heroic Countdown", "Play a countdown sound before this ability fires on Heroic.")

                local hDD = CCS_CreateDropdown(rightCell, CD_W, ROW_HEIGHT - 4)
                hDD:SetPoint("LEFT", rightCell, "LEFT", colHDD, 0)
                hDD:SetItems(buildCountdownItems(hDefaultCD))
                hDD._defaultSound = hDefaultCD
                hDD:SetValue(hOver or "__default__")
                addTooltip(hDD, "Heroic Countdown Sound", "Choose the countdown timer sound for Heroic.")

                local hNoDefaultLbl = rightCell:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
                hNoDefaultLbl:SetText("|cff555555No default|r")
                hNoDefaultLbl:SetPoint("LEFT", rightCell, "LEFT", colHDD, 0)

                -- M row
                local mLbl = rightCell:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
                mLbl:SetText("|cffaaaaaa M|r")
                mLbl:SetPoint("LEFT", rightCell, "LEFT", colMCB - 14, 0)

                local mCB = CreateFrame("CheckButton", nil, rightCell, "UICheckButtonTemplate")
                mCB:SetSize(CB_SIZE, CB_SIZE)
                mCB:SetPoint("LEFT", rightCell, "LEFT", colMCB, 0)
                mCB:SetChecked(CCS.IsCDEnabled(ability.key, "M"))
                stripCheckBorder(mCB)
                addTooltip(mCB, "Mythic Countdown", "Play a countdown sound before this ability fires on Mythic.")

                local mDD = CCS_CreateDropdown(rightCell, CD_W, ROW_HEIGHT - 4)
                mDD:SetPoint("LEFT", rightCell, "LEFT", colMDD, 0)
                mDD:SetItems(buildCountdownItems(mDefaultCD))
                mDD._defaultSound = mDefaultCD
                mDD:SetValue(mOver or "__default__")
                addTooltip(mDD, "Mythic Countdown Sound", "Choose the countdown timer sound for Mythic.")

                local mNoDefaultLbl = rightCell:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
                mNoDefaultLbl:SetText("|cff555555No default|r")
                mNoDefaultLbl:SetPoint("LEFT", rightCell, "LEFT", colMDD, 0)

                -- ── Refresh helpers ───────────────────────────────────────────────────
                -- A diff's dropdown is shown when: has a default OR has an override OR is enabled.
                -- When shown but disabled, it's greyed out (SetEnabled(false)).
                -- When there's no default and no override and not enabled: show "No default".
                local function refreshHDD()
                    if not hasHeroic(ability) then
                        hLbl:Hide(); hCB:Hide(); hDD:Hide(); hNoDefaultLbl:Hide(); return
                    end
                    local en = hCB:GetChecked()
                    local hasContent = hDefaultCD ~= nil or hOver ~= nil or CCS.GetCountdownOverride(ability.key, "H") ~= nil
                    if en or hasContent then
                        hDD:Show(); hDD:SetEnabled(en); hNoDefaultLbl:Hide()
                    else
                        hDD:Hide(); hNoDefaultLbl:Show()
                    end
                end

                local function refreshMDD()
                    if not hasMythic(ability) then
                        mLbl:Hide(); mCB:Hide(); mDD:Hide(); mNoDefaultLbl:Hide(); return
                    end
                    local en = mCB:GetChecked()
                    local hasContent = mDefaultCD ~= nil or mOver ~= nil or CCS.GetCountdownOverride(ability.key, "M") ~= nil
                    if en or hasContent then
                        mDD:Show(); mDD:SetEnabled(en); mNoDefaultLbl:Hide()
                    else
                        mDD:Hide(); mNoDefaultLbl:Show()
                    end
                end

                -- ── Callbacks ─────────────────────────────────────────────────────────
                hCB:SetScript("OnClick", function(self)
                    withCombatGuard(function()
                        CCS.SetCDEnabled(ability.key, "H", self:GetChecked())
                        CCS.RefreshAbility(ability.key, ability)
                        refreshHDD()
                    end)
                end)
                hDD:SetOnSelect(function(value)
                    withCombatGuard(function()
                        local v = value ~= "__default__" and value or nil
                        hOver = v  -- update local so refreshHDD sees it
                        CCS.SetCountdownOverride(ability.key, "H", v)
                        CCS.RefreshAbility(ability.key, ability)
                        refreshHDD()
                    end)
                end)

                mCB:SetScript("OnClick", function(self)
                    withCombatGuard(function()
                        CCS.SetCDEnabled(ability.key, "M", self:GetChecked())
                        CCS.RefreshAbility(ability.key, ability)
                        refreshMDD()
                    end)
                end)
                mDD:SetOnSelect(function(value)
                    withCombatGuard(function()
                        local v = value ~= "__default__" and value or nil
                        mOver = v  -- update local so refreshMDD sees it
                        CCS.SetCountdownOverride(ability.key, "M", v)
                        CCS.RefreshAbility(ability.key, ability)
                        refreshMDD()
                    end)
                end)

                -- ── Initial display ───────────────────────────────────────────────────
                refreshHDD()
                refreshMDD()

                -- ── Bulk tracking ─────────────────────────────────────────────────────
                allWarnCBs[#allWarnCBs + 1] = warnCB
                allWarnDDs[#allWarnDDs + 1] = warnDD

                -- ctrl.hasDefault: used by bulk enable to decide whether to enable
                -- ctrl.syncFromDB: refreshes UI checkboxes + dropdowns from DB
                -- ctrl.setEnabled: writes en to DB for all applicable diffs
                local function makeCtrl(cb, diff, defCD, refreshFn)
                    return {
                        cb         = cb,
                        diff       = diff,
                        abilityKey = ability.key,
                        hasDefault = defCD ~= nil,
                        syncFromDB = function()
                            cb:SetChecked(CCS.IsCDEnabled(ability.key, diff))
                            refreshFn()
                        end,
                        setEnabled = function(en)
                            CCS.SetCDEnabled(ability.key, diff, en)
                            cb:SetChecked(en)
                            CCS.RefreshAbility(ability.key, ability)
                            refreshFn()
                        end,
                    }
                end

                if hasHeroic(ability) then
                    local ctrl = makeCtrl(hCB, "H", hDefaultCD, refreshHDD)
                    if hDefaultCD then
                        defaultUCBs[#defaultUCBs + 1] = ctrl
                    else
                        noDefaultUCBs[#noDefaultUCBs + 1] = ctrl
                    end
                end
                if hasMythic(ability) then
                    local ctrl = makeCtrl(mCB, "M", mDefaultCD, refreshMDD)
                    if mDefaultCD then
                        defaultUCBs[#defaultUCBs + 1] = ctrl
                    else
                        noDefaultUCBs[#noDefaultUCBs + 1] = ctrl
                    end
                end

                y = y - ROW_HEIGHT - 1
            end
            y = y - 8
        end
    end

    divider:SetHeight(math.abs(y) + 16)
    scrollChild:SetHeight(math.abs(y) + 16)

    return allWarnCBs, allWarnDDs, defaultUCBs, noDefaultUCBs, {
        leftW    = leftW,
        colHCB   = colHCB,
        colHDD   = colHDD,
        colMCB   = colMCB,
        colMDD   = colMDD,
        -- Center of warn DD in left cell: DD is right-aligned with 8px pad, width DD_W
        warnDDCx = leftW - 8 - DD_W / 2,
        -- Center of countdown controls: midpoint from HC label to end of M dropdown
        cdCx     = ((colHCB - 14) + (colMDD + CD_W)) / 2,
    }
end

------------------------------------------------------------
-- Options Panel
------------------------------------------------------------

local function CreateCCSOptionsPanel()
    local panel = CreateFrame("Frame")
    panel.name = CATEGORY_NAME

    -- Top block: dark background containing title, description, buttons and column headers
    local topBlock = CreateFrame("Frame", nil, panel)
    topBlock:SetPoint("TOPLEFT",  panel, "TOPLEFT",   6, -6)
    topBlock:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -36, -6)
    local topBlockBg = topBlock:CreateTexture(nil, "BACKGROUND")
    topBlockBg:SetAllPoints()
    topBlockBg:SetColorTexture(0.08, 0.08, 0.08, 1)

    local title = topBlock:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -12)
    title:SetText(CATEGORY_NAME)

    -- Load Defaults button on the title line, right-aligned
    local loadDefaultsBtn = CreateFrame("Button", nil, topBlock, "UIPanelButtonTemplate")
    loadDefaultsBtn:SetSize(70, 20)
    loadDefaultsBtn:SetPoint("RIGHT", topBlock, "TOPRIGHT", -12, -14)
    loadDefaultsBtn:SetText("Defaults")
    stripButtonBorder(loadDefaultsBtn)
    addTooltip(loadDefaultsBtn, "Load Default Configuration", "Resets all warning and countdown sounds to their built-in defaults.\nThis will remove all your custom sound choices.")

    -- Confirm dialog
    local confirmDialog = CreateFrame("Frame", nil, topBlock, "BackdropTemplate")
    confirmDialog:SetSize(260, 70)
    confirmDialog:SetPoint("TOPRIGHT", topBlock, "TOPRIGHT", -12, -36)
    confirmDialog:SetFrameStrata("DIALOG")
    confirmDialog:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10,
        insets   = { left=3, right=3, top=3, bottom=3 },
    })
    confirmDialog:SetBackdropColor(0.08, 0.08, 0.08, 1)
    confirmDialog:SetBackdropBorderColor(0.8, 0.6, 0.1, 1)
    confirmDialog:Hide()

    local confirmText = confirmDialog:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    confirmText:SetPoint("TOP", confirmDialog, "TOP", 0, -10)
    confirmText:SetText("|cffffff00This will remove your custom settings\nand load the built-in defaults.|r")
    confirmText:SetJustifyH("CENTER")

    local yesBtn = CreateFrame("Button", nil, confirmDialog, "UIPanelButtonTemplate")
    yesBtn:SetSize(60, 20)
    yesBtn:SetPoint("BOTTOMLEFT", confirmDialog, "BOTTOMLEFT", 14, 8)
    yesBtn:SetText("Yes")

    local noBtn = CreateFrame("Button", nil, confirmDialog, "UIPanelButtonTemplate")
    noBtn:SetSize(60, 20)
    noBtn:SetPoint("BOTTOMRIGHT", confirmDialog, "BOTTOMRIGHT", -14, 8)
    noBtn:SetText("No")

    noBtn:SetScript("OnClick", function() confirmDialog:Hide() end)
    loadDefaultsBtn:SetScript("OnClick", function()
        if confirmDialog:IsShown() then confirmDialog:Hide() else confirmDialog:Show() end
    end)

    local inst = topBlock:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    inst:SetPoint("TOPLEFT",  title, "BOTTOMLEFT", 0, -6)
    inst:SetPoint("TOPRIGHT", topBlock, "TOPRIGHT", -16, 0)
    inst:SetJustifyH("LEFT")
    inst:SetWordWrap(true)
    inst:SetText(
        "Choose sounds that play automatically when a private aura lands on you in Heroic or Mythic raid.\n" ..
        "Warning sounds (left side) and countdown sounds (right side) are toggled independently."
    )

    local function makeGroupBox(parent, anchorFrame, anchorPoint, xOff, yOff)
        local box = CreateFrame("Frame", nil, parent, "BackdropTemplate")
        box:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Buttons\\WHITE8x8",
            edgeSize = 1,
        })
        box:SetBackdropColor(0, 0, 0, 0)
        box:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.8)
        box:SetPoint("TOPLEFT", anchorFrame, anchorPoint, xOff, yOff)
        return box
    end

    -- Warn group box
    local warnBox = makeGroupBox(topBlock, inst, "BOTTOMLEFT", 0, -8)

    local warnLabel = warnBox:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
    warnLabel:SetText("|cffcccccc All Spell Warnings|r")
    warnLabel:SetPoint("TOPLEFT", warnBox, "TOPLEFT", 14, -7)

    local enableWarnBtn = CreateFrame("Button", nil, warnBox, "UIPanelButtonTemplate")
    enableWarnBtn:SetSize(60, 20)
    enableWarnBtn:SetPoint("LEFT", warnLabel, "RIGHT", 8, 0)
    enableWarnBtn:SetText("Enable")
    stripButtonBorder(enableWarnBtn)
    addTooltip(enableWarnBtn, "Enable All Warnings", "Turn on warning sounds for every ability.")

    local disableWarnBtn = CreateFrame("Button", nil, warnBox, "UIPanelButtonTemplate")
    disableWarnBtn:SetSize(60, 20)
    disableWarnBtn:SetPoint("LEFT", enableWarnBtn, "RIGHT", 2, 0)
    disableWarnBtn:SetText("Disable")
    stripButtonBorder(disableWarnBtn)
    addTooltip(disableWarnBtn, "Disable All Warnings", "Turn off warning sounds for every ability.")

    warnBox:SetSize(220, 24)  -- initial size, corrected in OnShow

    -- CD group box — anchored 60px right of warnBox (50px gap increase)
    local cdBox = makeGroupBox(topBlock, warnBox, "TOPRIGHT", 60, 0)

    local cdLabel = cdBox:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
    cdLabel:SetText("|cffcccccc All Countdowns|r")
    cdLabel:SetPoint("TOPLEFT", cdBox, "TOPLEFT", 14, -7)

    local enableCDBtn = CreateFrame("Button", nil, cdBox, "UIPanelButtonTemplate")
    enableCDBtn:SetSize(60, 20)
    enableCDBtn:SetPoint("LEFT", cdLabel, "RIGHT", 8, 0)
    enableCDBtn:SetText("Enable")
    stripButtonBorder(enableCDBtn)
    addTooltip(enableCDBtn, "Enable All Countdowns", "Turn on countdown sounds for every ability that has a default countdown set.")

    local disableCDBtn = CreateFrame("Button", nil, cdBox, "UIPanelButtonTemplate")
    disableCDBtn:SetSize(60, 20)
    disableCDBtn:SetPoint("LEFT", enableCDBtn, "RIGHT", 2, 0)
    disableCDBtn:SetText("Disable")
    stripButtonBorder(disableCDBtn)
    addTooltip(disableCDBtn, "Disable All Countdowns", "Turn off countdown sounds for every ability that has a default countdown set.")

    cdBox:SetSize(205, 24)  -- initial size, corrected in OnShow

    -- Resize boxes once fonts are resolved — symmetric padding: 14 left, 8 right
    topBlock:HookScript("OnShow", function()
        local warnPad = 24 + 8 + 8 + 60 + 2 + 60 + 0
        warnBox:SetWidth(warnLabel:GetStringWidth() + warnPad)
        local cdPad = 14 + 8 + 8 + 60 + 2 + 60 + 8
        cdBox:SetWidth(cdLabel:GetStringWidth() + cdPad)
    end)

    -- topBlock height: title(20) + gap(6) + inst(~30) + gap(12) + buttons(24) + bottom padding
    topBlock:SetHeight(96)

    -- Grey divider line below the top block
    local topDivider = panel:CreateTexture(nil, "ARTWORK")
    topDivider:SetColorTexture(0.35, 0.35, 0.35, 0.8)
    topDivider:SetHeight(1)
    topDivider:SetPoint("TOPLEFT",  topBlock, "BOTTOMLEFT",  0, 0)
    topDivider:SetPoint("TOPRIGHT", topBlock, "BOTTOMRIGHT", 0, 0)

    -- Column headers sit below the top block, outside it
    local headerBar = CreateFrame("Frame", nil, panel)
    headerBar:SetHeight(42)
    headerBar:SetPoint("TOPLEFT",  topBlock, "BOTTOMLEFT",  0, -6)
    headerBar:SetPoint("TOPRIGHT", topBlock, "BOTTOMRIGHT", 0, -6)

    -- Left header: Warning Sound
    local leftHdrTitle = headerBar:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    leftHdrTitle:SetText("|cffFFD100Warning Sound|r")

    -- Right header: Countdown Timer + sub
    local rightHdrTitle = headerBar:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    rightHdrTitle:SetText("|cffFFD100Countdown Timer|r")

    local rightHdrSub = headerBar:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
    rightHdrSub:SetText("|cffaaaaaa HC: Heroic          M: Mythic|r")

    -- Vertical divider in header — hidden, columns are self-evident from labels
    local hdrDiv = headerBar:CreateTexture(nil, "ARTWORK")
    hdrDiv:Hide()

    -- Scroll frame
    local scroll = CreateFrame("ScrollFrame", nil, panel, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT",     headerBar, "BOTTOMLEFT",  0,   -2)
    scroll:SetPoint("BOTTOMRIGHT", panel,     "BOTTOMRIGHT", -30, 10)

    if scroll.ScrollBar then
        scroll.ScrollBar:ClearAllPoints()
        scroll.ScrollBar:SetPoint("TOPRIGHT",    scroll, "TOPRIGHT",    0, -16)
        scroll.ScrollBar:SetPoint("BOTTOMRIGHT", scroll, "BOTTOMRIGHT", 0,  16)
    end

    scroll:EnableMouseWheel(true)
    scroll:SetScript("OnMouseWheel", function(self, delta)
        self:SetVerticalScroll(math.max(self:GetVerticalScroll() - delta * 30, 0))
    end)

    local scrollChild = CreateFrame("Frame")
    scrollChild:SetWidth(1)
    scrollChild:SetHeight(1)
    scroll:SetScrollChild(scrollChild)

    local built = false
    local warnDDsForRefresh = nil  -- populated after first build, refreshed on each show

    panel:SetScript("OnShow", function()
        cachedSoundItems = nil  -- invalidate LSM cache so new sounds appear

        if not built then
            built = true
            local w = scroll:GetWidth()
            scrollChild:SetWidth(w)

            local allWarnCBs, allWarnDDs,
                  defaultUCBs, noDefaultUCBs, cols = BuildAbilityList(scrollChild, w)
            local leftW = cols.leftW

            -- store warn dropdowns so we can refresh their items on subsequent opens
            warnDDsForRefresh = {}
            for i, dd in ipairs(allWarnDDs) do
                warnDDsForRefresh[i] = dd
            end

            -- Position header labels
            leftHdrTitle:ClearAllPoints()
            leftHdrTitle:SetPoint("CENTER", headerBar, "LEFT", cols.warnDDCx - 20, 0)

            rightHdrSub:ClearAllPoints()
            rightHdrSub:SetPoint("CENTER", headerBar, "LEFT", leftW + cols.cdCx, -14)
            rightHdrTitle:ClearAllPoints()
            rightHdrTitle:SetPoint("BOTTOM", rightHdrSub, "TOP", 0, 2)

            enableWarnBtn:SetScript("OnClick", function()
                withCombatGuard(function()
                    CCS.SetAllWarn(true)
                    CCS.RefreshAll()
                    for _, cb in ipairs(allWarnCBs) do cb:SetChecked(true) end
                    for _, dd in ipairs(allWarnDDs) do dd:SetEnabled(true) end
                end)
            end)
            disableWarnBtn:SetScript("OnClick", function()
                withCombatGuard(function()
                    CCS.SetAllWarn(false)
                    CCS.RefreshAll()
                    for _, cb in ipairs(allWarnCBs) do cb:SetChecked(false) end
                    for _, dd in ipairs(allWarnDDs) do dd:SetEnabled(false) end
                end)
            end)

            -- Bulk CD enable: enable all rows with a default; restore saved state for no-default rows
            enableCDBtn:SetScript("OnClick", function()
                withCombatGuard(function()
                    CCS.SetAllCD(true)
                    CCS.RefreshAll()
                    for _, ctrl in ipairs(defaultUCBs) do
                        ctrl.setEnabled(true)
                    end
                    for _, ctrl in ipairs(noDefaultUCBs) do
                        -- Only restore if user had previously enabled it
                        if ctrl.wasEnabled then
                            ctrl.setEnabled(true)
                            ctrl.wasEnabled = nil
                        else
                            ctrl.syncFromDB()
                        end
                    end
                end)
            end)

            -- Bulk CD disable: disable all rows with a default; save state for no-default rows
            disableCDBtn:SetScript("OnClick", function()
                withCombatGuard(function()
                    CCS.SetAllCD(false)
                    CCS.RefreshAll()
                    for _, ctrl in ipairs(defaultUCBs) do
                        ctrl.setEnabled(false)
                    end
                    for _, ctrl in ipairs(noDefaultUCBs) do
                        -- Only worth restoring if the user had an actual override set;
                        -- enabled=true with no override just means "off (default)" — nothing to restore
                        local hasOverride = CCS.GetCountdownOverride(ctrl.abilityKey, ctrl.diff) ~= nil
                        ctrl.wasEnabled = ctrl.cb:GetChecked() and hasOverride
                        if ctrl.cb:GetChecked() then ctrl.setEnabled(false) end
                        ctrl.syncFromDB()
                    end
                end)
            end)

            yesBtn:SetScript("OnClick", function()
                confirmDialog:Hide()
                withCombatGuard(function()
                    -- Wipe all overrides and enabled states
                    CCS_DB.warnEnabled       = {}
                    CCS_DB.warnOverride      = {}
                    CCS_DB.countdownEnabled  = {}
                    CCS_DB.countdownOverride = {}
                    CCS.RefreshAll()
                    -- Rebuild the scroll list from scratch
                    for _, child in ipairs({scrollChild:GetChildren()}) do
                        child:Hide()
                        child:SetParent(nil)
                    end
                    built = false
                    panel:Hide()
                    panel:Show()
                end)
            end)

        else
            -- Panel already built — just refresh the warn dropdown item lists
            -- so newly installed addon sounds appear without a /reload
            if warnDDsForRefresh then
                for _, dd in ipairs(warnDDsForRefresh) do
                    local currentVal = dd:GetValue()
                    dd:SetItems(buildWarnItems(dd._defaultWarn))
                    dd:SetValue(currentVal)
                end
            end
        end
    end)

    return panel
end

------------------------------------------------------------
-- Minimap Button
------------------------------------------------------------

local function CreateMinimapButton()
    local radius = 80  -- distance from minimap center
    local button = CreateFrame("Button", "CCSMinimapButton", Minimap)
    button:SetSize(28, 28)
    button:SetFrameStrata("MEDIUM")
    button:SetFrameLevel(8)

    local icon = button:CreateTexture(nil, "BACKGROUND")
    icon:SetSize(22, 22)
    icon:SetPoint("CENTER", button, "CENTER", 0, 0)
    icon:SetTexture("Interface\\AddOns\\" .. addonName .. "\\icon.tga")

    -- Circular mask applied directly to the icon texture
    local mask = button:CreateMaskTexture()
    mask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    mask:SetAllPoints(icon)
    icon:AddMaskTexture(mask)

    -- Border sized to match the button — offset slightly to align ring with icon
    local border = button:CreateTexture(nil, "OVERLAY")
    border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
    border:SetSize(56, 56)
    border:SetPoint("CENTER", button, "CENTER", 11, -11)

    -- Drag to reposition around minimap
    button:RegisterForDrag("LeftButton")
    button:SetMovable(true)

    local function updatePosition()
        local angle = math.rad(CCS_DB.minimapAngle or 220)
        button:ClearAllPoints()
        button:SetPoint("CENTER", Minimap, "CENTER",
            radius * math.cos(angle),
            radius * math.sin(angle))
    end

    button:SetScript("OnDragStart", function(self)
        self:SetScript("OnUpdate", function()
            local mx, my = Minimap:GetCenter()
            local cx, cy = GetCursorPosition()
            local scale  = Minimap:GetEffectiveScale()
            local angle  = math.deg(math.atan2((cy / scale - my), (cx / scale - mx)))
            CCS_DB.minimapAngle = angle
            updatePosition()
        end)
    end)
    button:SetScript("OnDragStop", function(self)
        self:SetScript("OnUpdate", nil)
    end)

    button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    button:SetScript("OnClick", function(self, btn)
        if InCombatLockdown() then
            print("|cffffff00CCS:|r Cannot open settings during combat.")
            return
        end
        if btn == "RightButton" and IsShiftKeyDown() then
            CCS_DB.minimapHidden = true
            button:Hide()
            print("|cffffff00CCS:|r Minimap button hidden. Use |cffffffff/ccs minimap|r to restore.")
            return
        end
        if CCS_CategoryID then
            Settings.OpenToCategory(CCS_CategoryID)
        end
    end)

    addTooltip(button, "Custom Countdown Sounds", "Click to open settings.\nDrag to reposition.\nShift+Right-click to hide.")

    updatePosition()
    return button
end

------------------------------------------------------------
-- Register, slash, init
------------------------------------------------------------

local function RegisterOptionsPanel(panel)
    local category = Settings.RegisterCanvasLayoutCategory(panel, CATEGORY_NAME)
    Settings.RegisterAddOnCategory(category)
    if type(category) == "table" then
        if type(category.GetID) == "function" then
            CCS_CategoryID = category:GetID()
        elseif type(category.ID) == "number" then
            CCS_CategoryID = category.ID
        end
    end
end

SLASH_CCS1 = "/ccs"
SlashCmdList["CCS"] = function(msg)
    local arg = msg and msg:match("^%s*(%S+)") or ""
    if arg:lower() == "minimap" then
        if CCS_DB.minimapHidden then
            CCS_DB.minimapHidden = false
            local btn = CCSMinimapButton
            if btn then btn:Show() end
            print("|cffffff00CCS:|r Minimap button shown.")
        else
            CCS_DB.minimapHidden = true
            local btn = CCSMinimapButton
            if btn then btn:Hide() end
            print("|cffffff00CCS:|r Minimap button hidden. Use |cffffffff/ccs minimap|r to restore.")
        end
        return
    end
    if InCombatLockdown() then
        print("|cffffff00CCS:|r Cannot open settings during combat.")
        return
    end
    if not CCS_CategoryID then
        print("|cffff5555CCS:|r Options not registered yet.")
        return
    end
    Settings.OpenToCategory(CCS_CategoryID)
end

local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("ADDON_LOADED")
initFrame:SetScript("OnEvent", function(self, _, name)
    if name ~= addonName then return end
    RegisterOptionsPanel(CreateCCSOptionsPanel())
    local btn = CreateMinimapButton()
    if CCS_DB and CCS_DB.minimapHidden then btn:Hide() end
    self:UnregisterEvent("ADDON_LOADED")
end)