local _, ns = ...

------------------------------------------------------------
-- Interrupts Settings: persistent config + movable in-addon
-- settings window (the Blizzard panel isn't draggable, so
-- everything lives inside the addon itself).
------------------------------------------------------------

ns.InterruptsSettings = ns.InterruptsSettings or {}
local S = ns.InterruptsSettings

------------------------------------------------------------
-- Textures (built-in + common Blizzard assets, no SharedMedia)
------------------------------------------------------------

S.BAR_TEXTURES = {
    { key = "blizzard",   label = "Blizzard",    path = "Interface\\TargetingFrame\\UI-StatusBar" },
    { key = "raid",       label = "Raid Bar",    path = "Interface\\RaidFrame\\Raid-Bar-Hp-Fill" },
    { key = "flat",       label = "Flat",        path = "Interface\\Buttons\\WHITE8x8" },
    { key = "glaze",      label = "Glaze",       path = "Interface\\PaperDollInfoFrame\\UI-Character-Skills-Bar" },
    { key = "minimalist", label = "Minimalist",  path = "Interface\\TargetingFrame\\UI-StatusBar" },
}

function S:GetBarTexturePath(key)
    for _, t in ipairs(self.BAR_TEXTURES) do
        if t.key == key then return t.path end
    end
    return self.BAR_TEXTURES[1].path
end

------------------------------------------------------------
-- Defaults
------------------------------------------------------------

local DEFAULTS = {
    enabled         = true,
    mode            = "bar",
    barWidth        = 180,
    barHeight       = 22,
    barTexture      = "blizzard",
    iconSize        = 36,
    spacing         = 4,
    orientation     = "horizontal",
    growDirection   = "down",
    barStyle        = "classFill",
    classBorder     = false,
    showSpellIcon   = true,
    showName        = true,
    showTimer       = true,
    countDirection  = "down",
    posX            = 0,
    posY            = -160,
    anchor          = "CENTER",
    locked          = false,
    -- Legacy single-string field (kept for compatibility); new code uses
    -- the per-context flags below.
    visibility      = "group",
    visGroup        = true,
    visInstance     = false,
    visMythicPlus   = false,
    visRaid         = false,
    visPvp          = false,
    visAlways       = false,
    testMode        = false,
    showBackdrop    = true,
    backdropAlpha   = 0.85,
    framePadding    = 6,
}

local listeners = {}

-- Numeric fields are clamped to sane ranges so a corrupted SavedVariable
-- (stale schema, another addon stepping on YippYappHelperDB, hand-edit)
-- can't produce a 0-width bar or a million-pixel offset.
local NUMERIC_RANGES = {
    barWidth      = { min = 40,     max = 600  },
    barHeight     = { min = 6,      max = 80   },
    iconSize      = { min = 12,     max = 96   },
    spacing       = { min = 0,      max = 40   },
    posX          = { min = -8000,  max = 8000 },
    posY          = { min = -8000,  max = 8000 },
    backdropAlpha = { min = 0,      max = 1    },
    framePadding  = { min = 0,      max = 40   },
}

local function validateAndClamp(d)
    for k, defaultV in pairs(DEFAULTS) do
        local cur = d[k]
        if cur == nil or type(cur) ~= type(defaultV) then
            d[k] = defaultV
        elseif type(defaultV) == "number" then
            local r = NUMERIC_RANGES[k]
            if r then
                if cur < r.min then d[k] = r.min
                elseif cur > r.max then d[k] = r.max end
            end
        end
    end
end

local function db()
    YippYappHelperDB = YippYappHelperDB or {}
    if not YippYappHelperDB.interrupts then
        YippYappHelperDB.interrupts = {}
    end
    local d = YippYappHelperDB.interrupts
    validateAndClamp(d)
    return d
end

function S:Get(key)     return db()[key] end
function S:Set(key, v)
    db()[key] = v
    for _, cb in ipairs(listeners) do pcall(cb, key, v) end
end
function S:OnChanged(cb) table.insert(listeners, cb) end
function S:All()         return db() end
function S:Defaults()    return DEFAULTS end

------------------------------------------------------------
-- Visibility context
------------------------------------------------------------
function S:ShouldShow()
    local d = db()
    if not d.enabled then return false end
    if d.testMode then return true end

    if d.visAlways then return true end

    local inInstance, instanceType = IsInInstance()

    if d.visGroup and IsInGroup() then return true end
    if d.visInstance and inInstance and (instanceType == "party" or instanceType == "raid") then return true end
    if d.visRaid and inInstance and instanceType == "raid" then return true end
    if d.visPvp and inInstance and (instanceType == "pvp" or instanceType == "arena") then return true end
    if d.visMythicPlus and inInstance and instanceType == "party"
       and C_ChallengeMode and C_ChallengeMode.GetActiveKeystoneInfo then
        local lvl = select(1, C_ChallengeMode.GetActiveKeystoneInfo())
        if lvl and lvl > 0 then return true end
    end
    return false
end

------------------------------------------------------------
-- Settings window (movable, styled like the main app frame)
------------------------------------------------------------

local win

local function styleBackdrop(f, bgA, borderA)
    f:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 16,
        insets   = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    f:SetBackdropColor(0.05, 0.05, 0.05, bgA or 0.97)
    f:SetBackdropBorderColor(0.35, 0.35, 0.35, borderA or 1)
end

------------------------------------------------------------
-- Custom widgets (styled to match the addon, not WoW options)
------------------------------------------------------------

local BD_DARK = {
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
    insets = { left = 0, right = 0, top = 0, bottom = 0 },
}

local function applyBox(f, bgA)
    f:SetBackdrop(BD_DARK)
    f:SetBackdropColor(0.10, 0.10, 0.10, bgA or 0.9)
    f:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
end

local function hoverHook(f, onHover, onLeave)
    f:HookScript("OnEnter", function(self)
        self:SetBackdropBorderColor(0.0, 0.8, 1.0, 1)
        if onHover then onHover(self) end
    end)
    f:HookScript("OnLeave", function(self)
        self:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
        if onLeave then onLeave(self) end
    end)
end

local function makeCheckbox(parent, label, key)
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(260, 18)

    local box = CreateFrame("Button", nil, row, "BackdropTemplate")
    box:SetSize(16, 16)
    box:SetPoint("LEFT", row, "LEFT", 0, 0)
    applyBox(box)
    hoverHook(box)

    local tick = box:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    tick:SetPoint("CENTER", 0, 0)
    tick:SetText("x")
    tick:SetTextColor(0.2, 1.0, 0.4)
    tick:SetShown(S:Get(key) and true or false)

    local text = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    text:SetPoint("LEFT", box, "RIGHT", 6, 0)
    text:SetText(label)

    box:SetScript("OnClick", function()
        local v = not (S:Get(key) and true or false)
        S:Set(key, v)
        tick:SetShown(v)
    end)
    return row
end

local function makeSlider(parent, label, key, minV, maxV, step)
    local wrap = CreateFrame("Frame", nil, parent)
    wrap:SetSize(260, 40)

    local title = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    title:SetPoint("TOPLEFT", 0, 0)

    local valueText = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    valueText:SetPoint("TOPRIGHT", 0, 0)
    valueText:SetTextColor(0.0, 0.8, 1.0)

    local bar = CreateFrame("Frame", nil, wrap, "BackdropTemplate")
    bar:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
    bar:SetSize(240, 10)
    applyBox(bar, 0.85)

    local fill = bar:CreateTexture(nil, "ARTWORK")
    fill:SetPoint("TOPLEFT", bar, "TOPLEFT", 1, -1)
    fill:SetPoint("BOTTOMLEFT", bar, "BOTTOMLEFT", 1, 1)
    fill:SetColorTexture(0.0, 0.6, 1.0, 0.6)

    local thumb = CreateFrame("Frame", nil, bar, "BackdropTemplate")
    thumb:SetSize(8, 16)
    applyBox(thumb)
    thumb:SetBackdropColor(0.30, 0.30, 0.30, 1)
    thumb:SetBackdropBorderColor(0.85, 0.85, 0.85, 1)
    thumb:EnableMouse(true)
    thumb:SetFrameLevel(bar:GetFrameLevel() + 2)

    local function clamp(v) return math.max(minV, math.min(maxV, v)) end
    local function snap(v)
        if step and step > 0 then
            v = math.floor((v - minV) / step + 0.5) * step + minV
        end
        return clamp(v)
    end

    local function render(v)
        title:SetText(label)
        valueText:SetText(tostring(math.floor(v + 0.5)))
        local frac = (v - minV) / (maxV - minV)
        frac = math.max(0, math.min(1, frac))
        local width = bar:GetWidth() - 2
        fill:SetWidth(math.max(1, frac * width))
        thumb:ClearAllPoints()
        thumb:SetPoint("CENTER", bar, "LEFT", 1 + frac * width, 0)
    end

    local function setFromX(x)
        local left = bar:GetLeft()
        if not left then return end
        local width = bar:GetWidth() - 2
        local frac = math.max(0, math.min(1, (x - left - 1) / width))
        local v = snap(minV + frac * (maxV - minV))
        S:Set(key, v)
        render(v)
    end

    bar:EnableMouse(true)
    bar:SetScript("OnMouseDown", function(_, btn)
        if btn ~= "LeftButton" then return end
        local x = GetCursorPosition() / UIParent:GetEffectiveScale()
        setFromX(x)
    end)

    thumb:SetScript("OnMouseDown", function(self)
        self:SetScript("OnUpdate", function()
            local x = GetCursorPosition() / UIParent:GetEffectiveScale()
            setFromX(x)
        end)
    end)
    thumb:SetScript("OnMouseUp", function(self)
        self:SetScript("OnUpdate", nil)
    end)

    render(S:Get(key) or minV)
    return wrap
end

local function makeDropdown(parent, label, key, choices)
    local wrap = CreateFrame("Frame", nil, parent)
    wrap:SetSize(260, 40)

    local title = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    title:SetPoint("TOPLEFT", 0, 0)
    title:SetText(label)

    local btn = CreateFrame("Button", nil, wrap, "BackdropTemplate")
    btn:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
    btn:SetSize(240, 20)
    applyBox(btn)
    hoverHook(btn)

    local sel = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    sel:SetPoint("LEFT", 6, 0)

    local arrow = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    arrow:SetPoint("RIGHT", -6, 0)
    arrow:SetText("v")
    arrow:SetTextColor(0.6, 0.6, 0.6)

    local menu = CreateFrame("Frame", nil, btn, "BackdropTemplate")
    menu:SetFrameStrata("TOOLTIP")
    menu:SetPoint("TOPLEFT", btn, "BOTTOMLEFT", 0, -2)
    menu:SetWidth(btn:GetWidth())
    applyBox(menu, 0.97)
    menu:Hide()

    local function labelFor(val)
        for _, c in ipairs(choices) do
            if c[1] == val then return c[2] end
        end
        return ""
    end

    local function refresh() sel:SetText(labelFor(S:Get(key))) end
    refresh()

    local function rebuildMenu()
        for _, child in ipairs({ menu:GetChildren() }) do child:Hide(); child:SetParent(nil) end
        local h = 4
        for _, c in ipairs(choices) do
            local item = CreateFrame("Button", nil, menu)
            item:SetSize(btn:GetWidth() - 6, 18)
            item:SetPoint("TOPLEFT", 3, -h + 2)
            local t = item:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            t:SetPoint("LEFT", 4, 0)
            t:SetText(c[2])
            if S:Get(key) == c[1] then t:SetTextColor(0.0, 0.8, 1.0) end
            local hi = item:CreateTexture(nil, "BACKGROUND")
            hi:SetAllPoints()
            hi:SetColorTexture(0.0, 0.5, 1.0, 0)
            item:SetScript("OnEnter", function() hi:SetColorTexture(0.0, 0.5, 1.0, 0.25) end)
            item:SetScript("OnLeave", function() hi:SetColorTexture(0.0, 0.5, 1.0, 0) end)
            item:SetScript("OnClick", function()
                S:Set(key, c[1]); refresh(); menu:Hide()
            end)
            h = h + 18
        end
        menu:SetHeight(h + 2)
    end

    btn:SetScript("OnClick", function()
        if menu:IsShown() then menu:Hide() else rebuildMenu(); menu:Show() end
    end)

    return wrap
end

------------------------------------------------------------
-- Tabs + panels (fills entire page)
------------------------------------------------------------

local INTERRUPT_SECTIONS = {
    { id = "general",    label = "General" },
    { id = "frame",      label = "Frame" },
    { id = "layout",     label = "Layout" },
    { id = "appearance", label = "Appearance" },
}

-- Top-level tabs. Interrupt Tracker has many settings, so it gets sub-tabs
-- of its own. The other lightweight toggles share one "Other" tab.
local FEATURE_TABS = {
    { id = "general",    label = "General" },
    { id = "interrupts", label = "Interrupt Tracker" },
}

local function barTextureChoices()
    local out = {}
    for _, t in ipairs(S.BAR_TEXTURES) do
        table.insert(out, { t.key, t.label })
    end
    return out
end

------------------------------------------------------------
-- Per-module enable flags (shared across features)
------------------------------------------------------------

function ns.ModuleEnabled(name)
    YippYappHelperDB = YippYappHelperDB or {}
    YippYappHelperDB.modules = YippYappHelperDB.modules or {}
    local v = YippYappHelperDB.modules[name]
    if v == nil then return true end
    return v and true or false
end

function ns.SetModuleEnabled(name, v)
    YippYappHelperDB = YippYappHelperDB or {}
    YippYappHelperDB.modules = YippYappHelperDB.modules or {}
    YippYappHelperDB.modules[name] = v and true or false
end

-- Small styled button
local function makeButton(parent, label, onClick)
    local b = CreateFrame("Button", nil, parent, "BackdropTemplate")
    b:SetSize(120, 20)
    applyBox(b, 0.9)
    local t = b:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    t:SetPoint("CENTER")
    t:SetText(label)
    t:SetTextColor(0.85, 0.85, 0.85)
    b:SetScript("OnEnter", function(self)
        self:SetBackdropBorderColor(0, 0.8, 1, 1); t:SetTextColor(1, 1, 1)
    end)
    b:SetScript("OnLeave", function(self)
        self:SetBackdropBorderColor(0.35, 0.35, 0.35, 1); t:SetTextColor(0.85, 0.85, 0.85)
    end)
    b:SetScript("OnClick", onClick)
    return b
end

-- Checkbox bound to arbitrary getter/setter (not YippYappHelperDB.interrupts)
local function makeCustomCheckbox(parent, label, getFn, setFn)
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(260, 18)
    local box = CreateFrame("Button", nil, row, "BackdropTemplate")
    box:SetSize(16, 16)
    box:SetPoint("LEFT", row, "LEFT", 0, 0)
    applyBox(box)
    hoverHook(box)
    local tick = box:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    tick:SetPoint("CENTER", 0, 0)
    tick:SetText("x")
    tick:SetTextColor(0.2, 1.0, 0.4)
    tick:SetShown(getFn() and true or false)
    local text = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    text:SetPoint("LEFT", box, "RIGHT", 6, 0)
    text:SetText(label)
    box:SetScript("OnClick", function()
        local v = not (getFn() and true or false)
        setFn(v)
        tick:SetShown(v)
    end)
    return row
end

-- Row combining a checkbox and a "Preview" button for a feature.
local function makeFeatureRow(parent, label, moduleKey, onPreview)
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(420, 22)
    local cb = makeCustomCheckbox(row, label,
        function() return ns.ModuleEnabled(moduleKey) end,
        function(v) ns.SetModuleEnabled(moduleKey, v) end)
    cb:SetPoint("LEFT", row, "LEFT", 0, 0)
    if onPreview then
        local btn = makeButton(row, "Preview", onPreview)
        btn:SetPoint("LEFT", cb, "RIGHT", 180, 0)
    end
    return row
end

local function fillPanel(panel, items, startY)
    local y = startY or -4
    for _, item in ipairs(items) do
        if item.section then
            y = y - 6
            local fs = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
            fs:SetPoint("TOPLEFT", panel, "TOPLEFT", 10, y)
            fs:SetText(item.section)
            fs:SetTextColor(1, 0.82, 0)
            y = y - 24
        else
            local w = item.build(panel)
            w:ClearAllPoints()
            w:SetPoint("TOPLEFT", panel, "TOPLEFT", 18, y)
            y = y - (item.h + 10)
        end
    end
    return y
end

local PANEL_CONTENT = {
    general = {
        { h = 42, build = function(p)
            local f = CreateFrame("Frame", nil, p)
            f:SetSize(260, 40)
            local t = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            t:SetPoint("TOPLEFT", 0, 0)
            t:SetPoint("TOPRIGHT", 0, 0)
            t:SetJustifyH("LEFT")
            t:SetWordWrap(true)
            t:SetText("|cffff8800BETA|r Interrupt bars are in\nactive development and may change.")
            return f
        end },
        { h = 24, build = function(p) return makeCheckbox(p, "Enable interrupt tracker", "enabled") end },
        { h = 24, build = function(p) return makeCheckbox(p, "Test mode (show dummy entries)", "testMode") end },
        { h = 24, build = function(p) return makeCheckbox(p, "Lock frame position", "locked") end },
        { h = 18, build = function(p)
            local fs = p:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            local holder = CreateFrame("Frame", nil, p); holder:SetSize(260, 12)
            fs:SetParent(holder); fs:SetPoint("LEFT", 0, 0)
            fs:SetText("Show in (any selected):")
            fs:SetTextColor(1, 0.82, 0)
            return holder
        end },
        { h = 20, build = function(p) return makeCheckbox(p, "Always",                 "visAlways")     end },
        { h = 20, build = function(p) return makeCheckbox(p, "When in a group",        "visGroup")      end },
        { h = 20, build = function(p) return makeCheckbox(p, "Dungeons & raids (any)", "visInstance")   end },
        { h = 20, build = function(p) return makeCheckbox(p, "Mythic+ active key",     "visMythicPlus") end },
        { h = 20, build = function(p) return makeCheckbox(p, "Raid instances only",    "visRaid")       end },
        { h = 20, build = function(p) return makeCheckbox(p, "PvP / arenas",           "visPvp")        end },
    },
    frame = {
        { h = 24, build = function(p) return makeCheckbox(p, "Show frame background", "showBackdrop") end },
        { h = 46, build = function(p) return makeSlider(p, "Background alpha (x 100)", "backdropAlpha", 0, 100, 5) end },
        { h = 46, build = function(p) return makeSlider(p, "Frame padding", "framePadding", 0, 30, 1) end },
    },
    layout = {
        { h = 42, build = function(p) return makeDropdown(p, "Display mode", "mode", {
            { "bar", "Status bars" }, { "icon", "Icons" } }) end },
        { h = 42, build = function(p) return makeDropdown(p, "Orientation", "orientation", {
            { "horizontal", "Horizontal" }, { "vertical", "Vertical" } }) end },
        { h = 42, build = function(p) return makeDropdown(p, "Grow direction", "growDirection", {
            { "right", "Right" }, { "left", "Left" },
            { "up", "Up" },       { "down", "Down" } }) end },
        { h = 46, build = function(p) return makeSlider(p, "Bar width",  "barWidth",  40, 400, 2) end },
        { h = 46, build = function(p) return makeSlider(p, "Bar height", "barHeight", 8,  64,  1) end },
        { h = 46, build = function(p) return makeSlider(p, "Icon size",  "iconSize",  16, 96,  1) end },
        { h = 46, build = function(p) return makeSlider(p, "Spacing",    "spacing",   0,  40,  1) end },
    },
    appearance = {
        { h = 42, build = function(p) return makeDropdown(p, "Bar texture", "barTexture", barTextureChoices()) end },
        { h = 42, build = function(p) return makeDropdown(p, "Bar style", "barStyle", {
            { "classFill", "Class-colored bar" },
            { "classText", "Black bar, class-colored text" } }) end },
        { h = 24, build = function(p) return makeCheckbox(p, "Class-colored border", "classBorder") end },
        { h = 24, build = function(p) return makeCheckbox(p, "Show interrupt spell icon", "showSpellIcon") end },
        { h = 24, build = function(p) return makeCheckbox(p, "Show player name", "showName") end },
        { h = 24, build = function(p) return makeCheckbox(p, "Show timer text", "showTimer") end },
        { h = 42, build = function(p) return makeDropdown(p, "Timer counts", "countDirection", {
            { "down", "Down (time remaining)" },
            { "up",   "Up (time elapsed)" } }) end },
    },
}

local function makeTabButton(parent, label, onClick)
    local b = CreateFrame("Button", nil, parent, "BackdropTemplate")
    b:SetSize(110, 26)
    applyBox(b)
    local t = b:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    t:SetPoint("CENTER")
    t:SetText(label)
    t:SetTextColor(0.85, 0.85, 0.85)
    b._text = t
    b:SetScript("OnEnter", function(self)
        self:SetBackdropBorderColor(0.0, 0.8, 1.0, 1)
        t:SetTextColor(1, 1, 1)
    end)
    b:SetScript("OnLeave", function(self)
        if not self._active then
            self:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
            t:SetTextColor(0.85, 0.85, 0.85)
        end
    end)
    b:SetScript("OnClick", onClick)
    function b:SetActive(active)
        self._active = active
        if active then
            self:SetBackdropColor(0.18, 0.18, 0.18, 1)
            self:SetBackdropBorderColor(0.0, 0.8, 1.0, 1)
            t:SetTextColor(0.0, 0.9, 1.0)
        else
            self:SetBackdropColor(0.10, 0.10, 0.10, 0.9)
            self:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
            t:SetTextColor(0.85, 0.85, 0.85)
        end
    end
    return b
end

------------------------------------------------------------
-- Per-feature panel builders
------------------------------------------------------------

local function buildInterruptsPanel(panel)
    -- Two-column layout: General+Layout on left, Frame+Appearance on right
    local LEFT  = { "general", "layout" }
    local RIGHT = { "frame",   "appearance" }

    local leftCol = CreateFrame("Frame", nil, panel)
    leftCol:SetPoint("TOPLEFT", panel, "TOPLEFT", 4, -4)
    leftCol:SetWidth(280)
    leftCol:SetHeight(10)

    local rightCol = CreateFrame("Frame", nil, panel)
    rightCol:SetPoint("TOPLEFT", leftCol, "TOPRIGHT", 12, 0)
    rightCol:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -4, -4)
    rightCol:SetHeight(10)

    local function buildColumn(col, sectionIds)
        local items = {}
        for _, sid in ipairs(sectionIds) do
            local section
            for _, s in ipairs(INTERRUPT_SECTIONS) do
                if s.id == sid then section = s; break end
            end
            if section then
                table.insert(items, { section = section.label })
                for _, def in ipairs(PANEL_CONTENT[sid]) do
                    table.insert(items, def)
                end
            end
        end
        return fillPanel(col, items, -4)
    end

    local endLeft  = buildColumn(leftCol, LEFT)
    local endRight = buildColumn(rightCol, RIGHT)
    local h = math.max(-endLeft, -endRight) + 20
    leftCol:SetHeight(h)
    rightCol:SetHeight(h)
    panel._contentH = h + 16
end


local function buildOtherPanel(panel)
    local endY = fillPanel(panel, {
        { section = "Mythic+" },
        { h = 24, build = function(p)
            return makeFeatureRow(p, "Mythic+ completion popup |cffff8800(BETA)|r", "mplusCompletion",
                function()
                    if ns.ShowCompletionPopupTest then ns.ShowCompletionPopupTest() end
                end)
        end },
        { h = 24, build = function(p)
            return makeFeatureRow(p, "Utility advisor (mythic entry) |cffff8800(BETA)|r", "utilityAdvisor",
                function()
                    if ns.UtilityAdvisor and ns.UtilityAdvisor.ShowTest then
                        ns.UtilityAdvisor:ShowTest()
                    end
                end)
        end },
        { section = "Ready Check" },
        { h = 24, build = function(p)
            return makeCustomCheckbox(p, "Show ready-check overview window",
                function() return ns.ReadyCheck and ns.ReadyCheck.IsEnabled and ns.ReadyCheck.IsEnabled() end,
                function(v) if ns.ReadyCheck and ns.ReadyCheck.SetEnabled then ns.ReadyCheck.SetEnabled(v) end end)
        end },
        { section = "Release Spirit Blocker" },
        { h = 24, build = function(p)
            return makeCustomCheckbox(p, "Block the Release Spirit popup in guild raids",
                function() return ns.IsReleaseBlockerEnabled and ns:IsReleaseBlockerEnabled() end,
                function(v)
                    YippYappHelperDB = YippYappHelperDB or {}
                    YippYappHelperDB.releaseBlocker = v and true or false
                    if ns.RefreshReleaseBlockerButton then ns.RefreshReleaseBlockerButton() end
                end)
        end },
        { section = "Minimap Button" },
        { h = 24, build = function(p)
            return makeCustomCheckbox(p, "Show minimap icon",
                function()
                    YippYappHelperDB = YippYappHelperDB or {}
                    YippYappHelperDB.libDBIcon = YippYappHelperDB.libDBIcon or {}
                    return not YippYappHelperDB.libDBIcon.hide
                end,
                function(v)
                    YippYappHelperDB = YippYappHelperDB or {}
                    YippYappHelperDB.libDBIcon = YippYappHelperDB.libDBIcon or {}
                    YippYappHelperDB.libDBIcon.hide = not v
                    local LDBIcon = LibStub and LibStub("LibDBIcon-1.0", true)
                    if LDBIcon then
                        if v then LDBIcon:Show("YippYappHelper") else LDBIcon:Hide("YippYappHelper") end
                    end
                end)
        end },
        { section = "|cffff69b4Fun|r" },
        { h = 32, build = function(p)
            local f = CreateFrame("Frame", nil, p)
            f:SetSize(420, 30)
            local t = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            t:SetPoint("TOPLEFT", 0, 0)
            t:SetPoint("TOPRIGHT", 0, 0)
            t:SetJustifyH("LEFT")
            t:SetWordWrap(true)
            if ns.Xaltoes and ns.Xaltoes:IsDeferred() then
                t:SetText("|cffff8800Standalone Xaltoes addon detected|r — these controls are disabled and the toes are owned by that addon. Remove Xaltoes from your AddOns to re-enable in-app controls.")
                t:SetTextColor(0.85, 0.7, 0.4)
            else
                t:SetText("A solemn tribute to the toes of the Harbinger. Disable any time.")
                t:SetTextColor(0.7, 0.7, 0.7)
            end
            return f
        end },
        { h = 24, build = function(p)
            local row = CreateFrame("Frame", nil, p)
            row:SetSize(260, 18)
            local box = CreateFrame("Button", nil, row, "BackdropTemplate")
            box:SetSize(16, 16); box:SetPoint("LEFT", row, "LEFT", 0, 0)
            applyBox(box); hoverHook(box)
            local tick = box:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            tick:SetPoint("CENTER", 0, 0); tick:SetText("x"); tick:SetTextColor(0.2, 1.0, 0.4)
            local text = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            text:SetPoint("LEFT", box, "RIGHT", 6, 0)
            text:SetText("Show Xal'atath's toes on screen")
            local function render() tick:SetShown(ns.Xaltoes and ns.Xaltoes:IsEnabled() or false) end
            render()
            box:SetScript("OnClick", function()
                if ns.Xaltoes then ns.Xaltoes:SetEnabled(not ns.Xaltoes:IsEnabled()) end
            end)
            if ns.Xaltoes then ns.Xaltoes:OnChanged(render) end
            return row
        end },
        { h = 24, build = function(p)
            local row = CreateFrame("Frame", nil, p)
            row:SetSize(260, 18)
            local box = CreateFrame("Button", nil, row, "BackdropTemplate")
            box:SetSize(16, 16); box:SetPoint("LEFT", row, "LEFT", 0, 0)
            applyBox(box); hoverHook(box)
            local tick = box:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            tick:SetPoint("CENTER", 0, 0); tick:SetText("x"); tick:SetTextColor(0.2, 1.0, 0.4)
            local text = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            text:SetPoint("LEFT", box, "RIGHT", 6, 0)
            text:SetText("Unlock toes mover (left-drag to reposition)")
            local function render() tick:SetShown(ns.Xaltoes and ns.Xaltoes:IsMoverEnabled() or false) end
            render()
            box:SetScript("OnClick", function()
                if ns.Xaltoes then ns.Xaltoes:SetMoverEnabled(not ns.Xaltoes:IsMoverEnabled()) end
            end)
            if ns.Xaltoes then ns.Xaltoes:OnChanged(render) end
            return row
        end },
        { h = 46, build = function(p)
            local wrap = CreateFrame("Frame", nil, p)
            wrap:SetSize(260, 40)
            local title = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            title:SetPoint("TOPLEFT", 0, 0)
            local valueText = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            valueText:SetPoint("TOPRIGHT", 0, 0)
            valueText:SetTextColor(0.0, 0.8, 1.0)

            local bar = CreateFrame("Frame", nil, wrap, "BackdropTemplate")
            bar:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
            bar:SetSize(240, 10)
            applyBox(bar, 0.85)
            local fill = bar:CreateTexture(nil, "ARTWORK")
            fill:SetPoint("TOPLEFT", bar, "TOPLEFT", 1, -1)
            fill:SetPoint("BOTTOMLEFT", bar, "BOTTOMLEFT", 1, 1)
            fill:SetColorTexture(0.0, 0.6, 1.0, 0.6)
            local thumb = CreateFrame("Frame", nil, bar, "BackdropTemplate")
            thumb:SetSize(8, 16)
            applyBox(thumb)
            thumb:SetBackdropColor(0.30, 0.30, 0.30, 1)
            thumb:SetBackdropBorderColor(0.85, 0.85, 0.85, 1)
            thumb:EnableMouse(true)
            thumb:SetFrameLevel(bar:GetFrameLevel() + 2)

            local minV, maxV = 0, 359
            local function render(v)
                title:SetText("Toes rotation")
                valueText:SetText(math.floor(v + 0.5) .. " deg")
                local frac = (v - minV) / (maxV - minV)
                frac = math.max(0, math.min(1, frac))
                local width = bar:GetWidth() - 2
                fill:SetWidth(math.max(1, frac * width))
                thumb:ClearAllPoints()
                thumb:SetPoint("CENTER", bar, "LEFT", 1 + frac * width, 0)
            end
            local function setFromX(x)
                local left = bar:GetLeft()
                if not left then return end
                local width = bar:GetWidth() - 2
                local frac = math.max(0, math.min(1, (x - left - 1) / width))
                local v = math.floor(minV + frac * (maxV - minV) + 0.5)
                if ns.Xaltoes then ns.Xaltoes:SetRotation(v) end
                render(v)
            end
            bar:EnableMouse(true)
            bar:SetScript("OnMouseDown", function(_, btn)
                if btn ~= "LeftButton" then return end
                setFromX(GetCursorPosition() / UIParent:GetEffectiveScale())
            end)
            thumb:SetScript("OnMouseDown", function(self)
                self:SetScript("OnUpdate", function()
                    setFromX(GetCursorPosition() / UIParent:GetEffectiveScale())
                end)
            end)
            thumb:SetScript("OnMouseUp", function(self)
                self:SetScript("OnUpdate", nil)
            end)
            render((ns.Xaltoes and ns.Xaltoes:GetRotation()) or 0)
            if ns.Xaltoes then
                ns.Xaltoes:OnChanged(function()
                    render(ns.Xaltoes:GetRotation() or 0)
                end)
            end
            return wrap
        end },
        { h = 46, build = function(p)
            local wrap = CreateFrame("Frame", nil, p)
            wrap:SetSize(260, 40)
            local title = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            title:SetPoint("TOPLEFT", 0, 0)
            local valueText = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            valueText:SetPoint("TOPRIGHT", 0, 0)
            valueText:SetTextColor(0.0, 0.8, 1.0)
            local bar = CreateFrame("Frame", nil, wrap, "BackdropTemplate")
            bar:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
            bar:SetSize(240, 10)
            applyBox(bar, 0.85)
            local fill = bar:CreateTexture(nil, "ARTWORK")
            fill:SetPoint("TOPLEFT", bar, "TOPLEFT", 1, -1)
            fill:SetPoint("BOTTOMLEFT", bar, "BOTTOMLEFT", 1, 1)
            fill:SetColorTexture(0.0, 0.6, 1.0, 0.6)
            local thumb = CreateFrame("Frame", nil, bar, "BackdropTemplate")
            thumb:SetSize(8, 16)
            applyBox(thumb)
            thumb:SetBackdropColor(0.30, 0.30, 0.30, 1)
            thumb:SetBackdropBorderColor(0.85, 0.85, 0.85, 1)
            thumb:EnableMouse(true)
            thumb:SetFrameLevel(bar:GetFrameLevel() + 2)
            local minV, maxV = 50, 200
            local function pct() return math.floor(((ns.Xaltoes and ns.Xaltoes:GetScale()) or 1) * 100 + 0.5) end
            local function render(v)
                title:SetText("Toes size")
                valueText:SetText(math.floor(v + 0.5) .. "%")
                local frac = (v - minV) / (maxV - minV)
                frac = math.max(0, math.min(1, frac))
                local width = bar:GetWidth() - 2
                fill:SetWidth(math.max(1, frac * width))
                thumb:ClearAllPoints()
                thumb:SetPoint("CENTER", bar, "LEFT", 1 + frac * width, 0)
            end
            local function setFromX(x)
                local left = bar:GetLeft()
                if not left then return end
                local width = bar:GetWidth() - 2
                local frac = math.max(0, math.min(1, (x - left - 1) / width))
                local v = math.floor(minV + frac * (maxV - minV) + 0.5)
                if ns.Xaltoes then ns.Xaltoes:SetScale(v / 100) end
                render(v)
            end
            bar:EnableMouse(true)
            bar:SetScript("OnMouseDown", function(_, btn)
                if btn ~= "LeftButton" then return end
                setFromX(GetCursorPosition() / UIParent:GetEffectiveScale())
            end)
            thumb:SetScript("OnMouseDown", function(self)
                self:SetScript("OnUpdate", function()
                    setFromX(GetCursorPosition() / UIParent:GetEffectiveScale())
                end)
            end)
            thumb:SetScript("OnMouseUp", function(self)
                self:SetScript("OnUpdate", nil)
            end)
            render(pct())
            if ns.Xaltoes then
                ns.Xaltoes:OnChanged(function() render(pct()) end)
            end
            return wrap
        end },
        { h = 26, build = function(p)
            local row = CreateFrame("Frame", nil, p)
            row:SetSize(420, 24)
            local flip = makeButton(row, "Flip 180 deg", function()
                if ns.Xaltoes then ns.Xaltoes:Flip() end
            end)
            flip:SetPoint("LEFT", row, "LEFT", 0, 0)
            local resetPos = makeButton(row, "Reset position", function()
                if ns.Xaltoes then ns.Xaltoes:ResetPosition() end
            end)
            resetPos:SetPoint("LEFT", flip, "RIGHT", 8, 0)
            local resetAll = makeButton(row, "Reset all", function()
                if ns.Xaltoes then ns.Xaltoes:ResetAll() end
            end)
            resetAll:SetPoint("LEFT", resetPos, "RIGHT", 8, 0)
            return row
        end },
        { section = "Slash commands" },
        { h = 108, build = function(p)
            local wrap = CreateFrame("Frame", nil, p)
            wrap:SetSize(420, 108)
            local cmds = {
                { "/yh",             "Open the main window" },
                { "/keys",           "Open the Mythic+ page (also /yhkeys)" },
                { "/yyhrc",          "Preview the ready-check window" },
                { "/yyhopts",        "Open this settings window (also /yyhsettings)" },
                { "/yyhinterrupts",  "Open the interrupt-tracker settings" },
                { "/yh toes off",    "Hide Xal'atath's toes" },
                { "/yh toes mover",  "Unlock the toes mover to drag-position them" },
            }
            local ROW_H = 14
            for i, entry in ipairs(cmds) do
                local y = -(i - 1) * ROW_H
                local cmd = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                cmd:SetPoint("TOPLEFT", 4, y)
                cmd:SetWidth(160)
                cmd:SetJustifyH("LEFT")
                cmd:SetText("|cff00ff00" .. entry[1] .. "|r")

                local dsc = wrap:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                dsc:SetPoint("TOPLEFT", cmd, "TOPRIGHT", 10, 0)
                dsc:SetPoint("RIGHT", wrap, "RIGHT", -4, 0)
                dsc:SetJustifyH("LEFT")
                dsc:SetWordWrap(false)
                dsc:SetTextColor(0.72, 0.72, 0.72)
                dsc:SetText(entry[2])
            end
            return wrap
        end },
    }, -4)
    panel._contentH = -endY + 20
end

local FEATURE_BUILDERS = {
    interrupts = buildInterruptsPanel,
    general    = buildOtherPanel,
}

local function boxBorder(f, bgA)
    f:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 },
    })
    f:SetBackdropColor(0.04, 0.04, 0.04, bgA or 0.95)
    f:SetBackdropBorderColor(0.35, 0.35, 0.35, 1)
end

function S:BuildInto(parent)
    if parent._yyhInterruptsBuilt then return end

    -- The page already has an inner bordered backdrop (from MakePage in
    -- AppFrame.lua), so we just lay tabs + content directly on the page.
    local host = parent.inner or parent

    -- Left-side tab column (no border, just the tab buttons stacked)
    local tabBar = CreateFrame("Frame", nil, host)
    tabBar:SetPoint("TOPLEFT", host, "TOPLEFT", 8, -8)
    tabBar:SetPoint("BOTTOMLEFT", host, "BOTTOMLEFT", 8, 8)
    tabBar:SetWidth(150)

    -- Thin vertical divider line between tabs and content
    local divider = host:CreateTexture(nil, "OVERLAY")
    divider:SetColorTexture(0.35, 0.35, 0.35, 1)
    divider:SetPoint("TOPLEFT", tabBar, "TOPRIGHT", 4, 0)
    divider:SetPoint("BOTTOMLEFT", tabBar, "BOTTOMRIGHT", 4, 0)
    divider:SetWidth(1)

    -- Right-side scrollable content area (no inner border)
    local scroll = CreateFrame("ScrollFrame", nil, host, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", divider, "TOPRIGHT", 8, 0)
    scroll:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", -24, 8)
    -- Smaller mouse-wheel step than the template default (which scrolls by
    -- a full page). 30px per tick feels similar to native WoW panels.
    scroll:SetScript("OnMouseWheel", function(self, delta)
        local step = 30
        local newVal = self:GetVerticalScroll() - delta * step
        local maxVal = self:GetVerticalScrollRange()
        if newVal < 0 then newVal = 0 end
        if newVal > maxVal then newVal = maxVal end
        self:SetVerticalScroll(newVal)
    end)

    local contentArea = CreateFrame("Frame", nil, scroll)
    contentArea:SetSize(500, 700)
    scroll:SetScrollChild(contentArea)

    local panels = {}
    local tabButtons = {}
    local function switchTo(id)
        for tid, p in pairs(panels) do p:SetShown(tid == id) end
        for tid, btn in pairs(tabButtons) do btn:SetActive(tid == id) end
        -- Resize the scroll child to the active panel's height
        local panel = panels[id]
        if panel and panel._contentH then
            contentArea:SetHeight(panel._contentH)
        end
        scroll:UpdateScrollChildRect()
    end

    local y = -8
    for _, tab in ipairs(FEATURE_TABS) do
        local b = makeTabButton(tabBar, tab.label, function() switchTo(tab.id) end)
        b:SetSize(134, 26)
        b:SetPoint("TOPLEFT", tabBar, "TOPLEFT", 8, y)
        tabButtons[tab.id] = b
        y = y - 30

        local panel = CreateFrame("Frame", nil, contentArea)
        panel:SetPoint("TOPLEFT", contentArea, "TOPLEFT", 0, 0)
        panel:SetPoint("TOPRIGHT", contentArea, "TOPRIGHT", 0, 0)
        panel:SetHeight(700)
        panel:Hide()
        local builder = FEATURE_BUILDERS[tab.id]
        if builder then builder(panel) end
        panels[tab.id] = panel
    end

    -- Macro drag affordance pinned to the bottom of the tab sidebar. Lets
    -- users grab the /yh macro onto their action bar without leaving the
    -- settings window, and stays out of the tab content itself.
    local quickAccess = CreateFrame("Frame", nil, tabBar)
    quickAccess:SetPoint("BOTTOMLEFT", tabBar, "BOTTOMLEFT", 8, 8)
    quickAccess:SetPoint("BOTTOMRIGHT", tabBar, "BOTTOMRIGHT", -4, 8)
    quickAccess:SetHeight(66)

    local qaBg = quickAccess:CreateTexture(nil, "BACKGROUND")
    qaBg:SetAllPoints()
    qaBg:SetColorTexture(0.08, 0.08, 0.08, 0.6)

    local qaLabel = quickAccess:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    qaLabel:SetPoint("TOPLEFT", 6, -4)
    qaLabel:SetTextColor(0.55, 0.55, 0.55)
    qaLabel:SetText("Quick access")

    local dragBtn = CreateFrame("Button", nil, quickAccess)
    dragBtn:SetSize(36, 36)
    dragBtn:SetPoint("LEFT", quickAccess, "LEFT", 8, -6)
    dragBtn:RegisterForDrag("LeftButton")

    local dragIcon = dragBtn:CreateTexture(nil, "ARTWORK")
    dragIcon:SetAllPoints(dragBtn)
    dragIcon:SetTexture(1998635)
    dragIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    local dragBorder = dragBtn:CreateTexture(nil, "OVERLAY")
    dragBorder:SetSize(60, 60)
    dragBorder:SetPoint("CENTER")
    dragBorder:SetTexture("Interface\\Buttons\\UI-Quickslot2")

    dragBtn:SetScript("OnDragStart", function()
        if InCombatLockdown() then return end
        local idx = GetMacroIndexByName("YippYapp")
        if idx and idx > 0 then PickupMacro(idx) end
    end)
    dragBtn:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText("|cff00ff00YippYapp Helper|r")
        GameTooltip:AddLine("Drag to your action bar", 1, 1, 1)
        GameTooltip:AddLine("Runs /yh when clicked", 0.5, 0.5, 0.5)
        GameTooltip:Show()
    end)
    dragBtn:SetScript("OnLeave", GameTooltip_Hide)

    local hint = quickAccess:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    hint:SetPoint("TOPLEFT", dragBtn, "TOPRIGHT", 8, -2)
    hint:SetPoint("RIGHT", quickAccess, "RIGHT", -4, 0)
    hint:SetJustifyH("LEFT")
    hint:SetWordWrap(true)
    hint:SetTextColor(0.72, 0.72, 0.72)
    hint:SetText("Drag to\nyour bar, or\ntype |cff00ff00/yh|r")

    parent._yyhInterruptsBuilt = true
    switchTo(FEATURE_TABS[1].id)
end

function S:Open()
    if ns.ShowAppPage and ns.AppFrame then
        if not ns.AppFrame:IsShown() then ns.AppFrame:Show() end
        ns:ShowAppPage("settings")
    end
end
function S:Toggle() S:Open() end

SLASH_YYHINTERRUPTS1 = "/yyhinterrupts"
SlashCmdList.YYHINTERRUPTS = function() S:Toggle() end

-- Legacy slash commands that used to open Blizzard's AddOn settings panel.
-- That integration has been removed — redirect to the in-addon settings page.
SLASH_YYHSETTINGS1 = "/yyhopts"
SLASH_YYHSETTINGS2 = "/yyhsettings"
SlashCmdList.YYHSETTINGS = function() S:Toggle() end
ns.OpenSettings = function() S:Open() end
