local addonName, privateDB = ...

-- ============================================================================
-- CONFIG TAB MODULE
-- Settings UI and contributor credits
-- ============================================================================

local CONFIG = privateDB.CONFIG
local L = HDG_L or {}  -- Localization
local HDG_ConfigTab = {}
privateDB.HDG_ConfigTab = HDG_ConfigTab

local mainFrame = nil

-- ============================================================================
-- THEME HELPERS (delegating to ThemeEngine canonical helpers)
-- ============================================================================

local HDG = _G.HousingDecorGuide
local function GetScheme() return HDG.Theme.GetCurrentScheme() end
local function RegisterWidget(w, t) HDG.Theme.RegisterWidget(w, t) end
local ApplyFont = function(fs, s, ft) HDG.Theme.ApplyFont(fs, s, ft) end
-- AddSectionStyle removed (unused after card-based rewrite)


-- ============================================================================
-- STORE HELPERS
-- ============================================================================

local function GetStore()
    local HDG = _G.HousingDecorGuide
    return HDG and HDG.Store
end

local function GetState()
    local store = GetStore()
    return store and store:GetState()
end

local function Dispatch(action, payload)
    local store = GetStore()
    if store then
        store:Dispatch(action, payload)
    end
end

local function GetConfig(key)
    local state = GetState()
    return state and state.config and state.config[key]
end

local function GetUIState(key)
    local state = GetState()
    return state and state.ui and state.ui[key]
end

-- ============================================================================
-- UI HELPERS
-- ============================================================================

-- Apply theme text color to a font string (inline color codes don't work reliably)
local function SetThemedText(fontString, text, scheme, colorKey)
    fontString:SetText(text)
    local s = scheme or GetScheme()
    if s then
        local color = s[colorKey or "text"]
        if color then
            fontString:SetTextColor(color.r, color.g, color.b, color.a or 1)
        end
    end
end

-- ============================================================================
-- RESET CONFIG POPUP
-- Wipes HDG_DB.config (preserves shoppingList, characters, lumberCoords,
-- houseTab, customStyles, etc). Used to clear stale appearance settings that
-- can't be reached via the normal UI controls (e.g. fontScale stuck non-zero
-- from old builds, legacy keys leaked in via the HDG_Settings proxy).
-- ============================================================================

StaticPopupDialogs["HDG_RESET_CONFIG"] = {
    text = "Reset Housing Decor Guide configuration?\n\n"
        .. "This wipes appearance + behaviour settings (font, scale, theme, filters, etc.) and reloads from defaults.\n\n"
        .. "|cFF00FF00Preserved:|r shopping lists, characters, lumber map, custom styles, house tab layout.\n\n"
        .. "Clicking Reset will trigger a /reload. If anything looks off afterwards, a full relog usually clears it.",
    button1 = "Reset",
    button2 = "Cancel",
    OnAccept = function()
        if HDG_DB then HDG_DB.config = nil end
        ReloadUI()
    end,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

function HDG_ConfigTab.Init(frame)
    mainFrame = frame
end

-- ============================================================================
-- CREATE PANEL
-- ============================================================================

function HDG_ConfigTab.Create()
    if not mainFrame then return end

    local layout = HDG.UI:TabPanel(mainFrame, {
        title = L["CONFIG_TITLE"] or "Configuration",
        hasHeaderBar = true,
        hasScroll = true,
    })

    local configFrame = layout.frame
    configFrame:Hide()
    mainFrame.configFrame = configFrame

    local scrollChild = layout.scrollChild
    scrollChild:SetWidth(CONFIG.MAIN_FRAME_WIDTH - 60)
    scrollChild:SetHeight(CONFIG.SCROLL_CHILD_HEIGHT)

    -- Track active dropdown menu for closing (shared by all dropdowns)
    local activeFontMenu = nil

    -- Track card positions for scroll height
    local lastCardBottom = -8  -- initial top margin

    -- ========================================================================
    -- FACTORY: MakeCard(title) -> card table
    -- ========================================================================
    local function MakeCard(title)
        local scheme = GetScheme()
        local card = CreateFrame("Frame", nil, scrollChild, "BackdropTemplate")
        card:SetPoint("TOPLEFT", 6, lastCardBottom)
        card:SetPoint("RIGHT", scrollChild, "RIGHT", -6, 0)
        card:SetBackdrop({
            bgFile = CONFIG.TEXTURES.WHITE_BG,
            edgeFile = CONFIG.TEXTURES.WHITE_BG,
            edgeSize = 1,
        })
        if scheme then
            card:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.4)
            card:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 0.3)
        end
        RegisterWidget(card, "Panel")

        -- Header text
        local header = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        header:SetPoint("TOPLEFT", 12, -10)
        ApplyFont(header, scheme, "body")
        if scheme then
            header:SetTextColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 1)
        end
        header:SetText(title)
        RegisterWidget(header, "SectionTitle")

        -- Subtle underline below header
        local underline = card:CreateTexture(nil, "ARTWORK")
        underline:SetHeight(1)
        underline:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -4)
        underline:SetPoint("RIGHT", card, "RIGHT", -12, 0)
        if scheme then
            underline:SetColorTexture(scheme.accent.r, scheme.accent.g, scheme.accent.b, 0.2)
        end

        return {
            frame = card,
            lastAnchor = underline,
            lastIsTop = false,
            bottomY = -28, -- tracks running content height (header + underline + gap)
        }
    end

    local function FinishCard(card, extraPad)
        local height = -card.bottomY + (extraPad or 12)
        card.frame:SetHeight(height)
        lastCardBottom = lastCardBottom - height - 10 -- 10px gap between cards
    end

    -- ========================================================================
    -- FACTORY: MakeCheckbox(card, label, hint, opts)
    -- ========================================================================
    local function MakeCheckbox(card, label, hint, opts)
        opts = opts or {}
        local scheme = GetScheme()
        local xOff = 10 + (opts.indent or 0)

        local cb = CreateFrame("CheckButton", nil, card.frame, "UICheckButtonTemplate")
        cb:SetSize(26, 26)
        cb:SetPoint("LEFT", card.frame, "LEFT", xOff, 0)
        cb:SetPoint("TOP", card.lastAnchor, "BOTTOM", 0, -6)

        -- Initial checked state
        if opts.getValue then
            cb:SetChecked(opts.getValue())
        elseif opts.configKey then
            local val = GetConfig(opts.configKey)
            if val == nil then val = opts.default or false end
            cb:SetChecked(val)
        end

        local cbLabel = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        cbLabel:SetPoint("LEFT", cb, "RIGHT", 4, 0)
        cbLabel:SetPoint("RIGHT", card.frame, "RIGHT", -12, 0)
        ApplyFont(cbLabel, scheme, "body")
        SetThemedText(cbLabel, label, scheme, "text")
        cbLabel:SetWordWrap(false)
        cbLabel:SetJustifyH("LEFT")

        card.bottomY = card.bottomY - 30

        if hint then
            local hintLabel = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            hintLabel:SetPoint("TOPLEFT", cbLabel, "BOTTOMLEFT", 0, -1)
            hintLabel:SetPoint("RIGHT", card.frame, "RIGHT", -12, 0)
            ApplyFont(hintLabel, scheme, "small")
            SetThemedText(hintLabel, hint, scheme, "dim")
            hintLabel:SetWordWrap(true)
            hintLabel:SetJustifyH("LEFT")
            card.lastAnchor = hintLabel
            card.bottomY = card.bottomY - 4
        else
            card.lastAnchor = cbLabel
        end

        -- OnClick handler
        cb:SetScript("OnClick", function(self)
            if opts.onClick then
                opts.onClick(self)
            elseif opts.configKey then
                Dispatch("SET_CONFIG", { key = opts.configKey, value = self:GetChecked() })
            end
        end)

        -- Conditional disable
        if opts.disabled then
            cb:Disable()
            cb:SetAlpha(0.5)
            cbLabel:SetAlpha(0.5)
        end

        return cb
    end

    -- ========================================================================
    -- FACTORY: MakeSlider(card, label, hint, opts)
    -- ========================================================================
    local function MakeSlider(card, label, hint, opts)
        opts = opts or {}
        local scheme = GetScheme()
        local xOff = 10 + (opts.indent or 0)

        local sliderLabel = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        sliderLabel:SetPoint("LEFT", card.frame, "LEFT", xOff, 0)
        sliderLabel:SetPoint("TOP", card.lastAnchor, "BOTTOM", 0, -8)
        ApplyFont(sliderLabel, scheme, "body")
        SetThemedText(sliderLabel, label, scheme, "text")

        local slider = CreateFrame("Slider", nil, card.frame, "OptionsSliderTemplate")
        slider:SetPoint("LEFT", sliderLabel, "RIGHT", 10, 0)
        slider:SetSize(100, 16)
        slider:SetMinMaxValues(opts.min or 0, opts.max or 100)
        slider:SetValueStep(opts.step or 1)
        slider:SetObeyStepOnDrag(true)
        slider:SetValue(GetConfig(opts.configKey) or opts.default or opts.min or 0)
        slider.Low:SetText("")
        slider.High:SetText("")
        slider.Text:SetText("")

        -- Atlas-based track + thumb
        local bg = slider:GetThumbTexture()
        if bg then bg:SetAtlas("common-slider-thumb"); bg:SetSize(20, 20) end
        local track = slider:CreateTexture(nil, "BACKGROUND")
        track:SetAtlas("common-slider-track")
        track:SetAllPoints()
        -- Hide default OptionsSliderTemplate border textures
        for _, region in ipairs({slider:GetRegions()}) do
            if region:IsObjectType("Texture") and region ~= bg and region ~= track then
                region:SetAlpha(0)
            end
        end

        local valueText = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        valueText:SetPoint("LEFT", slider, "RIGHT", 8, 0)
        ApplyFont(valueText, scheme, "body")

        local formatter = opts.formatter or function(v) return tostring(v) end
        local function UpdateValue(value)
            valueText:SetText(formatter(value))
            local s = GetScheme()
            local colorKey = opts.valueColorKey or "success"
            if s and s[colorKey] then
                valueText:SetTextColor(s[colorKey].r, s[colorKey].g, s[colorKey].b, s[colorKey].a or 1)
            end
        end
        UpdateValue(GetConfig(opts.configKey) or opts.default or opts.min or 0)

        card.bottomY = card.bottomY - 28

        if hint then
            local hintLabel = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            hintLabel:SetPoint("TOPLEFT", sliderLabel, "BOTTOMLEFT", 0, -1)
            hintLabel:SetPoint("RIGHT", card.frame, "RIGHT", -12, 0)
            ApplyFont(hintLabel, scheme, "small")
            SetThemedText(hintLabel, hint, scheme, "dim")
            hintLabel:SetJustifyH("LEFT")
            card.lastAnchor = hintLabel
            card.bottomY = card.bottomY - 4
        else
            card.lastAnchor = sliderLabel
        end

        slider:SetScript("OnValueChanged", function(self, value)
            if opts.round then
                value = math.floor(value * opts.round + 0.5) / opts.round
            else
                value = math.floor(value + 0.5)
            end
            Dispatch("SET_CONFIG", { key = opts.configKey, value = value })
            UpdateValue(value)
            if opts.onValueChanged then opts.onValueChanged(value) end
        end)

        return slider, valueText, UpdateValue
    end

    -- ========================================================================
    -- FACTORY: MakeDropdown(card, label, hint, opts)
    -- ========================================================================
    local function MakeDropdown(card, label, hint, opts)
        opts = opts or {}
        local scheme = GetScheme()

        local ddLabel = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        ddLabel:SetPoint("LEFT", card.frame, "LEFT", 10, 0)
        ddLabel:SetPoint("TOP", card.lastAnchor, "BOTTOM", 0, -8)
        ApplyFont(ddLabel, scheme, "body")
        SetThemedText(ddLabel, label, scheme, "text")

        local ddWidth = opts.width or 140
        local dd = CreateFrame("Button", nil, card.frame, "BackdropTemplate")
        dd:SetSize(ddWidth, 22)
        dd:SetPoint("LEFT", ddLabel, "RIGHT", 8, 0)
        dd:SetBackdrop({
            bgFile = CONFIG.TEXTURES.WHITE_BG,
            edgeFile = CONFIG.TEXTURES.WHITE_BG,
            edgeSize = 1,
        })
        if scheme then
            dd:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
            dd:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 1)
        end
        RegisterWidget(dd, "Panel")

        dd.text = dd:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        dd.text:SetPoint("LEFT", 8, 0)
        dd.text:SetPoint("RIGHT", -20, 0)
        dd.text:SetJustifyH("LEFT")
        ApplyFont(dd.text, scheme, "small")

        dd.arrow = dd:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        dd.arrow:SetPoint("RIGHT", -8, 0)
        ApplyFont(dd.arrow, scheme, "small")
        SetThemedText(dd.arrow, "v", scheme, "dim")

        -- Set initial display text
        local currentVal = opts.currentValue or (opts.configKey and GetConfig(opts.configKey)) or ""
        local displayMap = opts.displayMap or {}
        dd.text:SetText(displayMap[currentVal] or currentVal)

        card.bottomY = card.bottomY - 30

        if hint then
            local hintLabel = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            hintLabel:SetPoint("TOPLEFT", ddLabel, "BOTTOMLEFT", 0, -1)
            hintLabel:SetPoint("RIGHT", card.frame, "RIGHT", -12, 0)
            ApplyFont(hintLabel, scheme, "small")
            SetThemedText(hintLabel, hint, scheme, "dim")
            hintLabel:SetJustifyH("LEFT")
            card.lastAnchor = hintLabel
            card.bottomY = card.bottomY - 4
        else
            card.lastAnchor = ddLabel
        end

        dd:SetScript("OnClick", function(self)
            if activeFontMenu then activeFontMenu:Hide(); activeFontMenu = nil; return end

            -- Reuse cached menu if available
            if self._cachedMenu then
                self._cachedMenu:Show()
                activeFontMenu = self._cachedMenu
                return
            end

            local mScheme = GetScheme()
            local options = opts.options or {}
            local rowH = opts.fontPreview and 22 or 20
            local menu = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
            self._cachedMenu = menu
            menu:SetSize(self:GetWidth(), #options * rowH + 4)
            menu:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)
            menu:SetBackdrop({
                bgFile = CONFIG.TEXTURES.WHITE_BG,
                edgeFile = CONFIG.TEXTURES.WHITE_BG,
                edgeSize = 1,
            })
            if mScheme then
                menu:SetBackdropColor(mScheme.bg.r, mScheme.bg.g, mScheme.bg.b, 0.98)
                menu:SetBackdropBorderColor(mScheme.border.r, mScheme.border.g, mScheme.border.b, 1)
            end
            menu:SetFrameStrata("FULLSCREEN_DIALOG")
            activeFontMenu = menu

            for i, opt in ipairs(options) do
                local btn = CreateFrame("Button", nil, menu, "BackdropTemplate")
                btn:SetSize(menu:GetWidth() - 4, rowH)
                btn:SetPoint("TOPLEFT", 2, -2 - (i - 1) * rowH)
                btn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8" })
                btn:SetBackdropColor(0, 0, 0, 0)

                btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                btn.text:SetPoint("LEFT", 8, 0)
                if opts.fontPreview and opt.fontPath then
                    btn.text:SetFont(opt.fontPath, 11, "")
                else
                    ApplyFont(btn.text, mScheme, "small")
                end
                btn.text:SetText(opt.label)
                if mScheme then btn.text:SetTextColor(mScheme.text.r, mScheme.text.g, mScheme.text.b, 1) end

                btn:SetScript("OnEnter", function(b)
                    if mScheme then b:SetBackdropColor(mScheme.accent.r, mScheme.accent.g, mScheme.accent.b, 0.3) end
                end)
                btn:SetScript("OnLeave", function(b) b:SetBackdropColor(0, 0, 0, 0) end)
                btn:SetScript("OnClick", function()
                    if opts.configKey then
                        Dispatch("SET_CONFIG", { key = opts.configKey, value = opt.key })
                    end
                    self.text:SetText(opt.label)
                    menu:Hide()
                    activeFontMenu = nil
                    if opts.onSelect then opts.onSelect(opt.key, opt.label) end
                end)
            end

            menu:SetScript("OnHide", function() activeFontMenu = nil end)
        end)

        return dd
    end

    -- ========================================================================
    -- FACTORY: MakeScaleControl(card, label, opts)
    -- ========================================================================
    local function MakeScaleControl(card, label, opts)
        opts = opts or {}
        local scheme = GetScheme()

        local scaleLabel = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        scaleLabel:SetPoint("LEFT", card.frame, "LEFT", 10, 0)
        scaleLabel:SetPoint("TOP", card.lastAnchor, "BOTTOM", 0, -8)
        ApplyFont(scaleLabel, scheme, "body")
        SetThemedText(scaleLabel, label, scheme, "text")

        -- Helper to create +/- button
        local function MakeAdjBtn(lbl)
            local btn = CreateFrame("Button", nil, card.frame, "BackdropTemplate")
            btn:SetSize(24, 22)
            btn:SetBackdrop({
                bgFile = CONFIG.TEXTURES.WHITE_BG,
                edgeFile = CONFIG.TEXTURES.WHITE_BG,
                edgeSize = 1,
            })
            if scheme then
                btn:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
                btn:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 1)
            end
            btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            btn.text:SetPoint("CENTER", 0, 0)
            ApplyFont(btn.text, scheme, "body")
            SetThemedText(btn.text, lbl, scheme, "text")
            btn:SetScript("OnEnter", function(self)
                if scheme then
                    self:SetBackdropColor(scheme.button_hover.r, scheme.button_hover.g, scheme.button_hover.b, 0.9)
                    self.text:SetTextColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 1)
                end
            end)
            btn:SetScript("OnLeave", function(self)
                if scheme then
                    self:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
                    self.text:SetTextColor(scheme.text.r, scheme.text.g, scheme.text.b, 1)
                end
            end)
            RegisterWidget(btn, "Button")
            return btn
        end

        local downBtn = MakeAdjBtn("-")
        downBtn:SetPoint("LEFT", scaleLabel, "RIGHT", 8, 0)

        local valueText = card.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        valueText:SetPoint("LEFT", downBtn, "RIGHT", 6, 0)
        ApplyFont(valueText, scheme, "body")
        if scheme then
            valueText:SetTextColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 1)
        end

        local upBtn = MakeAdjBtn("+")
        upBtn:SetPoint("LEFT", valueText, "RIGHT", 6, 0)

        local formatter = opts.formatter or function(v) return tostring(v) end
        local function UpdateDisplay(value)
            valueText:SetText(formatter(value))
        end
        UpdateDisplay(GetConfig(opts.configKey) or opts.default or 1.0)

        downBtn:SetScript("OnClick", function()
            local current = GetConfig(opts.configKey) or opts.default or 1.0
            local newVal = math.max(current - (opts.step or 0.1), opts.min or 0.8)
            newVal = math.floor(newVal * 10 + 0.5) / 10
            Dispatch("SET_CONFIG", { key = opts.configKey, value = newVal })
            UpdateDisplay(newVal)
            if opts.onChanged then opts.onChanged(newVal) end
        end)

        upBtn:SetScript("OnClick", function()
            local current = GetConfig(opts.configKey) or opts.default or 1.0
            local newVal = math.min(current + (opts.step or 0.1), opts.max or 1.5)
            newVal = math.floor(newVal * 10 + 0.5) / 10
            Dispatch("SET_CONFIG", { key = opts.configKey, value = newVal })
            UpdateDisplay(newVal)
            if opts.onChanged then opts.onChanged(newVal) end
        end)

        card.lastAnchor = scaleLabel
        card.bottomY = card.bottomY - 30
        return valueText
    end

    -- ========================================================================
    -- CARD: Community (Discord)
    -- ========================================================================
    local cardCommunity = MakeCard(L["CONFIG_COMMUNITY"] or "Community")
    local discordScheme = GetScheme()
    local DISCORD_INVITE = "https://discord.gg/RWZaxJaHFP"

    local translateNote = cardCommunity.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    translateNote:SetPoint("TOPLEFT", cardCommunity.lastAnchor, "BOTTOMLEFT", 0, -6)
    ApplyFont(translateNote, discordScheme, "small")
    SetThemedText(translateNote, L["TRANSLATE_NOTE"] or "Share feedback, bugs, and translations on Discord.", discordScheme, "dim")

    local discordIcon = cardCommunity.frame:CreateTexture(nil, "ARTWORK")
    discordIcon:SetSize(24, 24)
    discordIcon:SetPoint("TOPLEFT", translateNote, "BOTTOMLEFT", 0, -8)
    discordIcon:SetTexture(CONFIG.ADDON_TEXTURES.DISCORD)

    local discordEditBox = CreateFrame("EditBox", nil, cardCommunity.frame, "BackdropTemplate")
    discordEditBox:SetSize(250, 24)
    discordEditBox:SetPoint("LEFT", discordIcon, "RIGHT", 10, 0)
    discordEditBox:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    if discordScheme then
        discordEditBox:SetBackdropColor(discordScheme.bg.r, discordScheme.bg.g, discordScheme.bg.b, 1)
    end
    discordEditBox:SetBackdropBorderColor(0.35, 0.40, 0.98, 0.6)  -- Discord blurple border
    ApplyFont(discordEditBox, discordScheme, "body")
    discordEditBox:SetText(DISCORD_INVITE)
    discordEditBox:SetAutoFocus(false)
    discordEditBox:SetTextInsets(8, 8, 0, 0)
    discordEditBox:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
    discordEditBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    discordEditBox:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)

    local discordHint = cardCommunity.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    discordHint:SetPoint("LEFT", discordEditBox, "RIGHT", 8, 0)
    ApplyFont(discordHint, discordScheme, "small")
    SetThemedText(discordHint, "Ctrl+C to copy", discordScheme, "dim")

    -- Buy Me a Coffee link
    local COFFEE_URL = "https://buymeacoffee.com/vamoose"

    local coffeeIcon = cardCommunity.frame:CreateTexture(nil, "ARTWORK")
    coffeeIcon:SetSize(24, 24)
    coffeeIcon:SetPoint("TOPLEFT", discordIcon, "BOTTOMLEFT", 0, -8)
    coffeeIcon:SetAtlas("auctionhouse-icon-coin")

    local coffeeEditBox = CreateFrame("EditBox", nil, cardCommunity.frame, "BackdropTemplate")
    coffeeEditBox:SetSize(250, 24)
    coffeeEditBox:SetPoint("LEFT", coffeeIcon, "RIGHT", 10, 0)
    coffeeEditBox:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    if discordScheme then
        coffeeEditBox:SetBackdropColor(discordScheme.bg.r, discordScheme.bg.g, discordScheme.bg.b, 1)
    end
    coffeeEditBox:SetBackdropBorderColor(1, 0.82, 0.2, 0.6) -- gold border
    ApplyFont(coffeeEditBox, discordScheme, "body")
    coffeeEditBox:SetText(COFFEE_URL)
    coffeeEditBox:SetAutoFocus(false)
    coffeeEditBox:SetTextInsets(8, 8, 0, 0)
    coffeeEditBox:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
    coffeeEditBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    coffeeEditBox:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)

    local coffeeHint = cardCommunity.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    coffeeHint:SetPoint("LEFT", coffeeEditBox, "RIGHT", 8, 0)
    ApplyFont(coffeeHint, discordScheme, "small")
    SetThemedText(coffeeHint, "Ctrl+C to copy", discordScheme, "dim")

    cardCommunity.bottomY = -112
    FinishCard(cardCommunity)

    -- ========================================================================
    -- CARD 1: Appearance
    -- ========================================================================
    local cardAppearance = MakeCard(L["CONFIG_APPEARANCE"] or "Appearance")

    -- Horizontal row: Scale | Font | Waypoints
    do
        local scheme = GetScheme()
        local rowAnchor = cardAppearance.lastAnchor
        local cardFrame = cardAppearance.frame

        -- 1) UI Scale (leftmost)
        local scaleLabel = cardFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        scaleLabel:SetPoint("LEFT", cardFrame, "LEFT", 10, 0)
        scaleLabel:SetPoint("TOP", rowAnchor, "BOTTOM", 0, -8)
        ApplyFont(scaleLabel, scheme, "body")
        SetThemedText(scaleLabel, "Scale:", scheme, "text")

        local function MakeAdjBtn(parent, lbl)
            local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
            btn:SetSize(24, 22)
            btn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, edgeFile = CONFIG.TEXTURES.WHITE_BG, edgeSize = 1 })
            if scheme then
                btn:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
                btn:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 1)
            end
            btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            btn.text:SetPoint("CENTER", 0, 0)
            ApplyFont(btn.text, scheme, "body")
            SetThemedText(btn.text, lbl, scheme, "text")
            btn:SetScript("OnEnter", function(self)
                if scheme then self:SetBackdropColor(scheme.button_hover.r, scheme.button_hover.g, scheme.button_hover.b, 0.9); self.text:SetTextColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 1) end
            end)
            btn:SetScript("OnLeave", function(self)
                if scheme then self:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9); self.text:SetTextColor(scheme.text.r, scheme.text.g, scheme.text.b, 1) end
            end)
            RegisterWidget(btn, "Button")
            return btn
        end

        local downBtn = MakeAdjBtn(cardFrame, "-")
        downBtn:SetPoint("LEFT", scaleLabel, "RIGHT", 6, 0)
        local scaleValue = cardFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        scaleValue:SetPoint("LEFT", downBtn, "RIGHT", 4, 0)
        ApplyFont(scaleValue, scheme, "body")
        if scheme then scaleValue:SetTextColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 1) end
        local upBtn = MakeAdjBtn(cardFrame, "+")
        upBtn:SetPoint("LEFT", scaleValue, "RIGHT", 4, 0)

        local function UpdateScaleDisplay(v) scaleValue:SetText(string.format("%.0f%%", v * 100)) end
        UpdateScaleDisplay(GetConfig("scale") or 1.0)

        downBtn:SetScript("OnClick", function()
            local cur = GetConfig("scale") or 1.0
            local nv = math.max(math.floor((cur - 0.1) * 10 + 0.5) / 10, 0.8)
            Dispatch("SET_CONFIG", { key = "scale", value = nv })
            UpdateScaleDisplay(nv)
            if mainFrame then mainFrame:SetScale(nv) end
        end)
        upBtn:SetScript("OnClick", function()
            local cur = GetConfig("scale") or 1.0
            local nv = math.min(math.floor((cur + 0.1) * 10 + 0.5) / 10, 1.5)
            Dispatch("SET_CONFIG", { key = "scale", value = nv })
            UpdateScaleDisplay(nv)
            if mainFrame then mainFrame:SetScale(nv) end
        end)

        -- 2) Font dropdown (middle)
        local fontLabel = cardFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        fontLabel:SetPoint("LEFT", upBtn, "RIGHT", 20, 0)
        ApplyFont(fontLabel, scheme, "body")
        SetThemedText(fontLabel, "Font:", scheme, "text")

        local fontOptions = {}
        for _, fontKey in ipairs(HDG.Colors.FontOrder) do
            table.insert(fontOptions, {
                key = fontKey,
                label = HDG.Colors.FontDisplayNames[fontKey] or fontKey,
                fontPath = HDG.Colors.FontFiles[fontKey],
            })
        end

        -- Inline dropdown for font
        local fontDD = CreateFrame("Button", nil, cardFrame, "BackdropTemplate")
        fontDD:SetSize(130, 22)
        fontDD:SetPoint("LEFT", fontLabel, "RIGHT", 6, 0)
        fontDD:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, edgeFile = CONFIG.TEXTURES.WHITE_BG, edgeSize = 1 })
        if scheme then
            fontDD:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
            fontDD:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 1)
        end
        RegisterWidget(fontDD, "Panel")
        fontDD.text = fontDD:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        fontDD.text:SetPoint("LEFT", 8, 0); fontDD.text:SetPoint("RIGHT", -20, 0); fontDD.text:SetJustifyH("LEFT")
        ApplyFont(fontDD.text, scheme, "small")
        fontDD.text:SetText(HDG.Colors.FontDisplayNames[GetConfig("fontFamily") or "Expressway"] or "Expressway")
        fontDD.arrow = fontDD:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        fontDD.arrow:SetPoint("RIGHT", -8, 0)
        ApplyFont(fontDD.arrow, scheme, "small")
        SetThemedText(fontDD.arrow, "v", scheme, "dim")

        fontDD:SetScript("OnClick", function(self)
            if activeFontMenu then activeFontMenu:Hide(); activeFontMenu = nil; return end
            if self._cachedMenu then
                self._cachedMenu:Show()
                activeFontMenu = self._cachedMenu
                return
            end
            local mScheme = GetScheme()
            local rowH = 22
            local menu = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
            self._cachedMenu = menu
            menu:SetSize(self:GetWidth(), #fontOptions * rowH + 4)
            menu:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)
            menu:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, edgeFile = CONFIG.TEXTURES.WHITE_BG, edgeSize = 1 })
            if mScheme then
                menu:SetBackdropColor(mScheme.bg.r, mScheme.bg.g, mScheme.bg.b, 0.98)
                menu:SetBackdropBorderColor(mScheme.border.r, mScheme.border.g, mScheme.border.b, 1)
            end
            menu:SetFrameStrata("FULLSCREEN_DIALOG")
            activeFontMenu = menu
            for i, opt in ipairs(fontOptions) do
                local btn = CreateFrame("Button", nil, menu, "BackdropTemplate")
                btn:SetSize(menu:GetWidth() - 4, rowH)
                btn:SetPoint("TOPLEFT", 2, -2 - (i - 1) * rowH)
                btn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8" })
                btn:SetBackdropColor(0, 0, 0, 0)
                btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                btn.text:SetPoint("LEFT", 8, 0)
                if opt.fontPath then btn.text:SetFont(opt.fontPath, 11, "") else ApplyFont(btn.text, mScheme, "small") end
                btn.text:SetText(opt.label)
                if mScheme then btn.text:SetTextColor(mScheme.text.r, mScheme.text.g, mScheme.text.b, 1) end
                btn:SetScript("OnEnter", function(b) if mScheme then b:SetBackdropColor(mScheme.accent.r, mScheme.accent.g, mScheme.accent.b, 0.3) end end)
                btn:SetScript("OnLeave", function(b) b:SetBackdropColor(0, 0, 0, 0) end)
                btn:SetScript("OnClick", function()
                    Dispatch("SET_CONFIG", { key = "fontFamily", value = opt.key })
                    self.text:SetText(opt.label)
                    menu:Hide(); activeFontMenu = nil
                    if HDG.EventBus then HDG.EventBus:Trigger("HDG_THEME_UPDATE", {}) end
                    print(HDG.Theme.C_Accent() .. "[HDG]|r Font changed to " .. opt.label)
                end)
            end
            menu:SetScript("OnHide", function() activeFontMenu = nil end)
        end)

        -- 3) Waypoint dropdown (rightmost)
        local wpLabel = cardFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        wpLabel:SetPoint("LEFT", fontDD, "RIGHT", 20, 0)
        ApplyFont(wpLabel, scheme, "body")
        SetThemedText(wpLabel, "Waypoints:", scheme, "text")

        local wpAutoLabel = L["CFG_WAYPOINT_AUTO"] or "Auto"
        local wpNativeLabel = L["CFG_WAYPOINT_NATIVE"] or "Blizzard Native"
        local wpTomTomLabel = L["CFG_WAYPOINT_TOMTOM"] or "TomTom Only"
        local wpOptions = {
            { key = "auto",   label = wpAutoLabel },
            { key = "native", label = wpNativeLabel },
            { key = "tomtom", label = wpTomTomLabel },
        }
        local wpDisplayMap = { auto = wpAutoLabel, native = wpNativeLabel, tomtom = wpTomTomLabel }

        local wpDD = CreateFrame("Button", nil, cardFrame, "BackdropTemplate")
        wpDD:SetSize(130, 22)
        wpDD:SetPoint("LEFT", wpLabel, "RIGHT", 6, 0)
        wpDD:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, edgeFile = CONFIG.TEXTURES.WHITE_BG, edgeSize = 1 })
        if scheme then
            wpDD:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
            wpDD:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 1)
        end
        RegisterWidget(wpDD, "Panel")
        wpDD.text = wpDD:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        wpDD.text:SetPoint("LEFT", 8, 0); wpDD.text:SetPoint("RIGHT", -20, 0); wpDD.text:SetJustifyH("LEFT")
        ApplyFont(wpDD.text, scheme, "small")
        wpDD.text:SetText(wpDisplayMap[GetConfig("waypointProvider") or "auto"] or "Auto")
        wpDD.arrow = wpDD:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        wpDD.arrow:SetPoint("RIGHT", -8, 0)
        ApplyFont(wpDD.arrow, scheme, "small")
        SetThemedText(wpDD.arrow, "v", scheme, "dim")

        wpDD:SetScript("OnClick", function(self)
            if activeFontMenu then activeFontMenu:Hide(); activeFontMenu = nil; return end
            if self._cachedMenu then
                self._cachedMenu:Show()
                activeFontMenu = self._cachedMenu
                return
            end
            local mScheme = GetScheme()
            local rowH = 20
            local menu = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
            self._cachedMenu = menu
            menu:SetSize(self:GetWidth(), #wpOptions * rowH + 4)
            menu:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -2)
            menu:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, edgeFile = CONFIG.TEXTURES.WHITE_BG, edgeSize = 1 })
            if mScheme then
                menu:SetBackdropColor(mScheme.bg.r, mScheme.bg.g, mScheme.bg.b, 0.98)
                menu:SetBackdropBorderColor(mScheme.border.r, mScheme.border.g, mScheme.border.b, 1)
            end
            menu:SetFrameStrata("FULLSCREEN_DIALOG")
            activeFontMenu = menu
            for i, opt in ipairs(wpOptions) do
                local btn = CreateFrame("Button", nil, menu, "BackdropTemplate")
                btn:SetSize(menu:GetWidth() - 4, rowH)
                btn:SetPoint("TOPLEFT", 2, -2 - (i - 1) * rowH)
                btn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8" })
                btn:SetBackdropColor(0, 0, 0, 0)
                btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                btn.text:SetPoint("LEFT", 8, 0)
                ApplyFont(btn.text, mScheme, "small")
                btn.text:SetText(opt.label)
                if mScheme then btn.text:SetTextColor(mScheme.text.r, mScheme.text.g, mScheme.text.b, 1) end
                btn:SetScript("OnEnter", function(b) if mScheme then b:SetBackdropColor(mScheme.accent.r, mScheme.accent.g, mScheme.accent.b, 0.3) end end)
                btn:SetScript("OnLeave", function(b) b:SetBackdropColor(0, 0, 0, 0) end)
                btn:SetScript("OnClick", function()
                    Dispatch("SET_CONFIG", { key = "waypointProvider", value = opt.key })
                    self.text:SetText(opt.label)
                    menu:Hide(); activeFontMenu = nil
                end)
            end
            menu:SetScript("OnHide", function() activeFontMenu = nil end)
        end)

        -- 4) Font Size +/- (second row)
        local fsLabel = cardFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        fsLabel:SetPoint("TOPLEFT", scaleLabel, "BOTTOMLEFT", 0, -14)
        ApplyFont(fsLabel, scheme, "body")
        SetThemedText(fsLabel, "Font Size:", scheme, "text")

        local currentScale = GetConfig("fontScale") or 0
        local fsValue = cardFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        ApplyFont(fsValue, scheme, "body")
        local function UpdateFSLabel(val)
            local actual = 10 + val  -- base "small" size + offset
            local display = val == 0 and ("Default (" .. actual .. "pt)") or ((val > 0 and ("+" .. val) or tostring(val)) .. " (" .. actual .. "pt)")
            SetThemedText(fsValue, display, scheme, "accent")
        end
        UpdateFSLabel(currentScale)

        local function MakeFSButton(label, anchor, anchorPt, xOff, delta)
            local btn = CreateFrame("Button", nil, cardFrame, "BackdropTemplate")
            btn:SetSize(22, 22)
            btn:SetPoint("LEFT", anchor, anchorPt, xOff, 0)
            btn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, edgeFile = CONFIG.TEXTURES.WHITE_BG, edgeSize = 1 })
            if scheme then
                btn:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
                btn:SetBackdropBorderColor(scheme.border.r, scheme.border.g, scheme.border.b, 1)
            end
            RegisterWidget(btn, "Panel")
            btn.text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            btn.text:SetPoint("CENTER", 0, 0)
            ApplyFont(btn.text, scheme, "body")
            SetThemedText(btn.text, label, scheme, "text")
            btn:SetScript("OnEnter", function(b) if scheme then b:SetBackdropColor(scheme.accent.r, scheme.accent.g, scheme.accent.b, 0.3) end end)
            btn:SetScript("OnLeave", function(b) if scheme then b:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9) end end)
            btn:SetScript("OnClick", function()
                local cur = GetConfig("fontScale") or 0
                local newVal = math.max(-4, math.min(4, cur + delta))
                Dispatch("SET_CONFIG", { key = "fontScale", value = newVal })
                UpdateFSLabel(newVal)
                if HDG.EventBus then HDG.EventBus:Trigger("HDG_THEME_UPDATE", {}) end
            end)
            return btn
        end

        local fsMinus = MakeFSButton("-", fsLabel, "RIGHT", 6, -1)
        local fsPlus = MakeFSButton("+", fsMinus, "RIGHT", 4, 1)
        fsValue:SetPoint("LEFT", fsPlus, "RIGHT", 6, 0)

        -- Reset Config button — wipes HDG_DB.config to clear stale settings.
        -- Anchored to the right of fsValue so it lives on the Font Size row.
        local resetBtn = CreateFrame("Button", nil, cardFrame, "BackdropTemplate")
        resetBtn:SetSize(110, 22)
        resetBtn:SetPoint("RIGHT", cardFrame, "RIGHT", -10, 0)
        resetBtn:SetPoint("TOP", fsLabel, "TOP", 0, 2)
        resetBtn:SetBackdrop({ bgFile = CONFIG.TEXTURES.WHITE_BG, edgeFile = CONFIG.TEXTURES.WHITE_BG, edgeSize = 1 })
        if scheme then
            resetBtn:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9)
            local err = scheme.error or scheme.danger or scheme.accent
            resetBtn:SetBackdropBorderColor(err.r, err.g, err.b, 1)
        end
        RegisterWidget(resetBtn, "Panel")
        resetBtn.text = resetBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        resetBtn.text:SetPoint("CENTER", 0, 0)
        ApplyFont(resetBtn.text, scheme, "body")
        if scheme then
            local err = scheme.error or scheme.danger or scheme.accent
            resetBtn.text:SetText("Reset Config")
            resetBtn.text:SetTextColor(err.r, err.g, err.b, 1)
        else
            resetBtn.text:SetText("Reset Config")
        end
        resetBtn:SetScript("OnEnter", function(self)
            if scheme then
                local err = scheme.error or scheme.danger or scheme.accent
                self:SetBackdropColor(err.r, err.g, err.b, 0.25)
            end
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            GameTooltip:SetText("Reset Configuration", 1, 1, 1)
            GameTooltip:AddLine("Clears stale appearance + behaviour settings.", 0.8, 0.8, 0.8, true)
            GameTooltip:AddLine("Shopping lists, characters, and house tab layout are preserved.", 0.8, 0.8, 0.8, true)
            GameTooltip:AddLine("Triggers a /reload. Full relog only if anything looks off after.", 1, 0.84, 0, true)
            GameTooltip:Show()
        end)
        resetBtn:SetScript("OnLeave", function(self)
            if scheme then self:SetBackdropColor(scheme.panel.r, scheme.panel.g, scheme.panel.b, 0.9) end
            GameTooltip:Hide()
        end)
        resetBtn:SetScript("OnClick", function()
            StaticPopup_Show("HDG_RESET_CONFIG")
        end)

        cardAppearance.lastAnchor = scaleLabel
        cardAppearance.bottomY = cardAppearance.bottomY - 55
    end

    FinishCard(cardAppearance)

    -- ========================================================================
    -- CARD 2: Interface
    -- ========================================================================
    local cardInterface = MakeCard(L["CONFIG_INTERFACE"] or "Interface")

    local showMinimap = HDG_Settings.minimap and HDG_Settings.minimap.showMinimapButton
    if showMinimap == nil then showMinimap = true end
    MakeCheckbox(cardInterface, L["CFG_SHOW_MINIMAP"] or "Show Minimap Button", L["CFG_HINT_MINIMAP"] or "(Draggable icon around the minimap)", {
        getValue = function() return showMinimap end,
        onClick = function(self)
            local minimap = privateDB.HDG_Minimap
            if minimap and minimap.SetShowMinimapButton then
                minimap.SetShowMinimapButton(self:GetChecked())
            end
        end,
    })

    local minimap = privateDB.HDG_Minimap
    local compartmentAvailable = minimap and minimap.IsCompartmentAvailable and minimap.IsCompartmentAvailable()
    local showCompartment = HDG_Settings.minimap and HDG_Settings.minimap.showInCompartment
    if showCompartment == nil then showCompartment = true end
    MakeCheckbox(cardInterface, L["CFG_SHOW_COMPARTMENT"] or "Show in Addon Compartment", L["CFG_HINT_COMPARTMENT"] or "(Minimap drawer menu - requires /reload)", {
        getValue = function() return showCompartment end,
        disabled = not compartmentAvailable,
        onClick = function(self)
            if HDG_Settings.minimap then
                HDG_Settings.minimap.showInCompartment = self:GetChecked()
            end
            print(HDG.Theme.C_Warning() .. "[HDG]|r " .. (L["CFG_COMPARTMENT_CHANGED"] or "Addon compartment setting changed. Use /reload to apply."))
        end,
    })

    MakeCheckbox(cardInterface, L["CFG_SHOW_PROF_BUTTONS"] or "Show Profession Window Buttons", L["CFG_HINT_PROF_BUTTONS"] or "(Decor Guide & Filter Decor buttons)", {
        configKey = "showProfessionButtons", default = true,
        onClick = function(self)
            Dispatch("SET_CONFIG", { key = "showProfessionButtons", value = self:GetChecked() })
            if HDG_UI and HDG_UI.UpdateProfessionButtonVisibility then
                HDG_UI.UpdateProfessionButtonVisibility()
            end
        end,
    })

    MakeCheckbox(cardInterface, L["CFG_TOOLTIP_REAGENT_TAG"], L["CFG_HINT_REAGENT_TAG"], {
        configKey = "tooltipDecorTag", default = true,
        onClick = function(self)
            Dispatch("SET_CONFIG", { key = "tooltipDecorTag", value = self:GetChecked() })
        end,
    })

    HDG_ConfigTab.lumberPinCheckbox = MakeCheckbox(cardInterface, L["CFG_PIN_LUMBER"] or "Pin Lumber Tracker", L["CFG_HINT_PIN_LUMBER"] or "(Floating widget - also via pin button)", {
        getValue = function() return GetUIState("lumberPinned") or false end,
        onClick = function(self)
            if self:GetChecked() then
                if HDG_LumberTracker and HDG_LumberTracker.ShowPinned then
                    HDG_LumberTracker.ShowPinned()
                end
            else
                Dispatch("SET_LUMBER_PINNED", { pinned = false })
                if HDG_LumberTracker and HDG_LumberTracker.HidePinned then
                    HDG_LumberTracker.HidePinned()
                end
                if HDG_UI and HDG_UI.UpdateLumberLayout then
                    HDG_UI.UpdateLumberLayout()
                end
            end
        end,
    })

    FinishCard(cardInterface)

    -- ========================================================================
    -- CARD 3: Lumber Radar
    -- ========================================================================
    local cardRadar = MakeCard(L["CONFIG_LUMBER_RADAR"] or "Lumber Radar")

    MakeCheckbox(cardRadar, L["CFG_AUTO_LUMBER_TRACKER"] or "Auto-Enable Lumber Tracker", L["CFG_HINT_AUTO_LUMBER"] or "(Auto-show tracker when collecting lumber)", {
        configKey = "lumberAutoShow", default = false,
    })

    MakeCheckbox(cardRadar, L["CFG_AUTO_LUMBER_RADAR"] or "Auto-Show Lumber Radar", L["CFG_HINT_AUTO_RADAR"] or "(/lumberhud to toggle manually)", {
        configKey = "lumberHUDEnabled", default = false,
        onClick = function(self)
            Dispatch("SET_CONFIG", { key = "lumberHUDEnabled", value = self:GetChecked() })
            if not self:GetChecked() and privateDB.HDG_LumberHUD then
                privateDB.HDG_LumberHUD.Hide()
            end
        end,
    })

    MakeSlider(cardRadar, "Radar Scale:", L["CFG_HINT_RADAR_SCALE"] or "(0.5x to 2.5x - resize the radar)", {
        configKey = "lumberHUDScale", default = 1.0, min = 0.5, max = 2.5, step = 0.1, round = 10,
        formatter = function(v) return string.format("%.1fx", v) end,
        onValueChanged = function(v)
            if privateDB.HDG_LumberHUD and privateDB.HDG_LumberHUD.UpdateScale then
                privateDB.HDG_LumberHUD.UpdateScale(v)
            end
        end,
    })

    MakeSlider(cardRadar, "Radar FPS:", L["CFG_HINT_RADAR_FPS"] or "(10-30 fps - smoothness vs CPU)", {
        configKey = "lumberHUDFPS", default = 20, min = 10, max = 30, step = 5,
        formatter = function(v) return string.format("%d", v) end,
        onValueChanged = function(v)
            if privateDB.HDG_LumberHUD and privateDB.HDG_LumberHUD.UpdateFramerate then
                privateDB.HDG_LumberHUD.UpdateFramerate(v)
            end
        end,
    })

    FinishCard(cardRadar)

    -- ========================================================================
    -- CARD 4: Quotes
    -- ========================================================================
    local cardQuotes = MakeCard(L["CONFIG_QUOTES"] or "Farming & Crafting Quotes")

    local quotesCheckbox = MakeCheckbox(cardQuotes, L["CFG_ENABLE_QUOTES"] or "Enable Quotes", L["CFG_HINT_QUOTES"] or "(Talking head popups and chat messages while farming/crafting)", {
        configKey = "quotesEnabled", default = true,
    })

    local quotesOnlyChatCheckbox = MakeCheckbox(cardQuotes, L["CFG_QUOTES_CHAT_ONLY"] or "Only print quotes to chat", L["CFG_HINT_CHAT_ONLY"] or "(No talking head display)", {
        configKey = "quotesOnlyChat", default = false, indent = 20,
    })

    local lumberQuotesCheckbox = MakeCheckbox(cardQuotes, L["CFG_LUMBER_QUOTES"] or "Lumber Quotes", L["CFG_HINT_LUMBER_QUOTES"] or "(Vamoose, Squirrel, Gazlowe while lumber farming)", {
        configKey = "lumberQuotesEnabled", default = true, indent = 20,
    })

    local vamooseSlider, vamooseValueText = MakeSlider(cardQuotes, "Chattiness:", L["CFG_HINT_CHATTINESS"] or "(0=Off, or quote after 1 to N lumber)", {
        configKey = "vamooseChattiness", default = 30, min = 0, max = 99, step = 1, indent = 41,
        formatter = function(v)
            if v == 0 then return "Off" else return string.format("%d", v) end
        end,
        valueColorKey = "success",
    })
    -- Override value color for "Off" state
    local origVamooseOnChanged = vamooseSlider:GetScript("OnValueChanged")
    vamooseSlider:SetScript("OnValueChanged", function(self, value)
        origVamooseOnChanged(self, value)
        value = math.floor(value + 0.5)
        if value == 0 then
            local s = GetScheme()
            if s and s.error then
                vamooseValueText:SetTextColor(s.error.r, s.error.g, s.error.b, s.error.a or 1)
            end
        end
    end)
    -- Set initial color
    if (GetConfig("vamooseChattiness") or 30) == 0 then
        local s = GetScheme()
        if s and s.error then
            vamooseValueText:SetTextColor(s.error.r, s.error.g, s.error.b, s.error.a or 1)
        end
    end

    local craftQuotesCheckbox = MakeCheckbox(cardQuotes, L["CFG_CRAFT_QUOTES"] or "Craft Quotes", L["CFG_HINT_CRAFT_QUOTES"] or "(When crafting/acquiring decor)", {
        configKey = "craftQuotesEnabled", default = true, indent = 20,
    })

    -- Master toggle <-> sub-option sync
    local function UpdateLumberQuotesSubOptions()
        local enabled = quotesCheckbox:GetChecked() and lumberQuotesCheckbox:GetChecked()
        local alpha = enabled and 1.0 or 0.5
        if vamooseSlider then
            if enabled then vamooseSlider:Enable() else vamooseSlider:Disable() end
            vamooseSlider:SetAlpha(alpha)
        end
        if vamooseValueText then vamooseValueText:SetAlpha(alpha) end
    end

    local function UpdateQuotesSubOptions()
        local masterEnabled = quotesCheckbox:GetChecked()
        local subAlpha = masterEnabled and 1.0 or 0.5

        if masterEnabled then quotesOnlyChatCheckbox:Enable() else quotesOnlyChatCheckbox:Disable() end
        quotesOnlyChatCheckbox:SetAlpha(subAlpha)

        if masterEnabled then lumberQuotesCheckbox:Enable() else lumberQuotesCheckbox:Disable() end
        lumberQuotesCheckbox:SetAlpha(subAlpha)

        if masterEnabled then craftQuotesCheckbox:Enable() else craftQuotesCheckbox:Disable() end
        craftQuotesCheckbox:SetAlpha(subAlpha)

        UpdateLumberQuotesSubOptions()
    end

    local function SyncMasterQuotesCheckbox()
        if not lumberQuotesCheckbox:GetChecked() and not craftQuotesCheckbox:GetChecked() then
            quotesCheckbox:SetChecked(false)
            Dispatch("SET_CONFIG", { key = "quotesEnabled", value = false })
        end
        UpdateQuotesSubOptions()
    end

    -- Wire OnClick handlers with sync logic
    quotesCheckbox:SetScript("OnClick", function(self)
        Dispatch("SET_CONFIG", { key = "quotesEnabled", value = self:GetChecked() })
        UpdateQuotesSubOptions()
    end)
    lumberQuotesCheckbox:SetScript("OnClick", function(self)
        Dispatch("SET_CONFIG", { key = "lumberQuotesEnabled", value = self:GetChecked() })
        SyncMasterQuotesCheckbox()
    end)
    craftQuotesCheckbox:SetScript("OnClick", function(self)
        Dispatch("SET_CONFIG", { key = "craftQuotesEnabled", value = self:GetChecked() })
        SyncMasterQuotesCheckbox()
    end)
    UpdateQuotesSubOptions()

    FinishCard(cardQuotes)

    -- ========================================================================
    -- CARD 5: Language
    -- ========================================================================
    local cardLang = MakeCard(L["CONFIG_LANGUAGE"] or "Language")

    local locScheme = GetScheme()

    -- Locale dropdown (complex: dynamic locale list, persistent menu)
    local locLabel = cardLang.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    locLabel:SetPoint("LEFT", cardLang.frame, "LEFT", 10, 0)
    locLabel:SetPoint("TOP", cardLang.lastAnchor, "BOTTOM", 0, -8)
    ApplyFont(locLabel, locScheme, "body")
    SetThemedText(locLabel, (L["LOCALE_OVERRIDE"] or "Language Override") .. ":", locScheme, "text")

    local localeDropdown = CreateFrame("Frame", "HDG_LocaleDropdown", cardLang.frame, "BackdropTemplate")
    localeDropdown:SetSize(150, 24)
    localeDropdown:SetPoint("LEFT", locLabel, "RIGHT", 10, 0)
    localeDropdown:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    if locScheme then
        localeDropdown:SetBackdropColor(locScheme.panel.r, locScheme.panel.g, locScheme.panel.b, 1)
        localeDropdown:SetBackdropBorderColor(locScheme.border.r, locScheme.border.g, locScheme.border.b, locScheme.border.a)
    end
    RegisterWidget(localeDropdown, "Panel")

    local localeText = localeDropdown:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    localeText:SetPoint("LEFT", 8, 0)
    localeText:SetPoint("RIGHT", -20, 0)
    localeText:SetJustifyH("LEFT")
    ApplyFont(localeText, locScheme, "body")
    localeDropdown.text = localeText

    local localeArrow = localeDropdown:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    localeArrow:SetPoint("RIGHT", -6, 0)
    localeArrow:SetText("v")
    ApplyFont(localeArrow, locScheme, "body")
    if locScheme then
        localeArrow:SetTextColor(locScheme.text_dim.r, locScheme.text_dim.g, locScheme.text_dim.b)
    end

    local availableLocales = HDG_L:GetAvailableLocales()
    local currentLocale = HDG_Settings.localeOverride or "auto"
    local function GetLocaleDisplayName(code)
        for _, loc in ipairs(availableLocales) do
            if loc.code == code then return loc.name end
        end
        return code
    end
    localeText:SetText(GetLocaleDisplayName(currentLocale))

    -- Locale menu
    local localeMenu = CreateFrame("Frame", "HDG_LocaleMenu", localeDropdown, "BackdropTemplate")
    localeMenu:SetPoint("TOPLEFT", localeDropdown, "BOTTOMLEFT", 0, -2)
    localeMenu:SetWidth(150)
    localeMenu:SetBackdrop({
        bgFile = CONFIG.TEXTURES.WHITE_BG,
        edgeFile = CONFIG.TEXTURES.WHITE_BG,
        edgeSize = 1,
    })
    if locScheme then
        localeMenu:SetBackdropColor(locScheme.panel.r, locScheme.panel.g, locScheme.panel.b, 0.98)
        localeMenu:SetBackdropBorderColor(locScheme.border.r, locScheme.border.g, locScheme.border.b, locScheme.border.a)
    end
    RegisterWidget(localeMenu, "Panel")
    localeMenu:SetFrameStrata("DIALOG")
    localeMenu:Hide()

    local menuYOff = -4
    for _, loc in ipairs(availableLocales) do
        local item = CreateFrame("Button", nil, localeMenu)
        item:SetHeight(18)
        item:SetPoint("TOPLEFT", 4, menuYOff)
        item:SetPoint("TOPRIGHT", -4, menuYOff)
        local itemText = item:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        itemText:SetPoint("LEFT", 4, 0)
        ApplyFont(itemText, locScheme, "small")
        itemText:SetText(loc.name)
        if locScheme then itemText:SetTextColor(locScheme.text.r, locScheme.text.g, locScheme.text.b) end
        item.text = itemText
        item:SetScript("OnEnter", function(self)
            if locScheme then self.text:SetTextColor(locScheme.warning.r, locScheme.warning.g, locScheme.warning.b) end
        end)
        item:SetScript("OnLeave", function(self)
            if locScheme then self.text:SetTextColor(locScheme.text.r, locScheme.text.g, locScheme.text.b) end
        end)
        item:SetScript("OnClick", function()
            HDG_Settings.localeOverride = loc.code
            localeText:SetText(loc.name)
            localeMenu:Hide()
        end)
        menuYOff = menuYOff - 18
    end
    localeMenu:SetHeight(-menuYOff + 4)

    localeDropdown:EnableMouse(true)
    localeDropdown:SetScript("OnMouseDown", function()
        if localeMenu:IsShown() then localeMenu:Hide() else localeMenu:Show() end
    end)
    localeMenu:SetScript("OnShow", function() localeMenu:SetFrameLevel(100) end)

    -- Reload button
    local reloadBtn = HDG.UI:Button(cardLang.frame, L["BTN_RELOAD"], 60, 22, function() ReloadUI() end)
    reloadBtn:SetPoint("LEFT", localeDropdown, "RIGHT", 10, 0)

    -- Locale note
    local localeNote = cardLang.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    localeNote:SetPoint("TOPLEFT", locLabel, "BOTTOMLEFT", 0, -4)
    localeNote:SetPoint("RIGHT", cardLang.frame, "RIGHT", -12, 0)
    ApplyFont(localeNote, locScheme, "small")
    SetThemedText(localeNote, L["LOCALE_NOTE"] or "Only affects addon UI text, not item/recipe names.", locScheme, "dim")

    cardLang.lastAnchor = localeNote
    cardLang.bottomY = -70
    FinishCard(cardLang)

    -- ========================================================================
    -- CARD 7: How to Use
    -- ========================================================================
    local cardGuide = MakeCard(L["CONFIG_HOW_TO_USE"] or "How to Use")
    local guideScheme = GetScheme()

    local guideSections = {
        {
            title = "Recipes Tab",
            lines = {
                "Browse all craftable housing decor organized by profession",
                "Click a recipe to see required materials in the right panel",
                "Click '+' to add items to your crafting queue",
                "|cFFfcd34dGold text|r = known by you, |cFF888888Gray|r = not known by anyone",
                "Toggle 'Raw Mats' to see base gathering materials vs direct recipe mats",
                "Use search box to filter by recipe or material name",
                "Shift-click materials to search in Auctionator",
                "Right-click materials to add to Auctionator shopping list",
                "'Craft These First' section shows intermediate items to craft",
            }
        },
        {
            title = L["HEADER_CRAFTING_QUEUE"],
            lines = {
                "Items automatically grouped by which character can craft them",
                "Your current character's craftable items appear first",
                "'Not Known' section shows recipes none of your alts have",
                "Queue persists across characters - no need to recreate!",
                "Left-click to decrement quantity, right-click to remove",
            }
        },
        {
            title = "Preview Tab",
            lines = {
                "View 3D models of all housing decor items",
                "Drag to rotate models horizontally and vertically",
                "Use Expansion filter + tag to see all decor for that expansion",
                "Example: Filter 'Expansion' then 'Cata' to see all Cata decor",
                "Shift-click any item to add it to your crafting queue",
                "|cFFfcd34dBright Star|r = Known recipe, |cFF888888Dim Check|r = Learnable",
                "Toggle 'Collection' to show ALL decor, not just craftable",
                "Collection view shows live data of what you've collected",
            }
        },
        {
            title = "Alts Tab",
            lines = {
                "View profession skills across all your characters",
                "Colors show if skill meets decor recipe thresholds:",
                "  |cFF2dd4bfTeal|r = Can learn recipes, |cFFfcd34dYellow|r = Below threshold",
                "Hover over skills to see which characters have them",
                "Data updates automatically when you open profession windows",
                "Use the Refresh button to reload all character data",
            }
        },
        {
            title = "Goblin Tab",
            lines = {
                "Analyze profit margins on all housing decor items",
                "Toggle between TSM and Auctionator price sources",
                "Click column headers to sort by profit, margin, etc.",
                "Click a row to expand and see material cost breakdown",
                "Ctrl-click to add profitable items to your queue",
                "Red prices = unprofitable, Green prices = profitable",
            }
        },
        {
            title = "Trainers Tab",
            lines = {
                "Find profession trainers for any expansion",
                "Click the waypoint icon to set a destination",
                "Trainers listed by profession with coordinates",
                "Works with native waypoints or TomTom",
            }
        },
        {
            title = "Data Tab",
            lines = {
                "View your crafting and acquisition history",
                "Track lumber farming sessions with rate statistics",
                "Export data to CSV for spreadsheet analysis",
                "Click column headers to sort history entries",
                "Session history shows items per hour farming rates",
            }
        },
        {
            title = "Lumber Tracker",
            lines = {
                "Pin the lumber widget to track inventory while playing",
                "Farming sessions start automatically when you gather lumber",
                "Ctrl-click any material in recipes to add to tracking",
                "Focus mode hides other rows while actively farming an item",
                "'Needed' column shows quantity required for your queue",
                "'All Recipes' column shows quantity for all uncollected decor",
            }
        },
        {
            title = "Lumber Radar HUD",
            lines = {
                "A circular radar that tracks your lumber gathering locations",
                "and helps you plan efficient farming routes based on respawn timers.",
                "",
                "|cFFfcd34dHow It Works:|r",
                "  You are always at the center (green dot with direction line)",
                "  Blips show locations where you've gathered lumber",
                "  N/E/S/W labels rotate as you turn - always shows true north",
                "  Blips move around you as you move and rotate",
                "",
                "|cFFfcd34dBlip Colors (Respawn Tracking):|r",
                "  |cFFff3333Red|r = Just cut (0-5 min) - don't return yet",
                "  |cFFffff00Yellow|r = Respawning (5-9 min) - almost ready",
                "  |cFF00ff00Green (pulsing)|r = Ready! (9-10+ min) - safe to return",
                "  |cFF888888Grey (faded)|r = Old (1+ hour) - stale data",
                "",
                "|cFFfcd34dDistance Rings:|r",
                "  Inner ring = 150 yards",
                "  Outer ring = 300 yards",
                "  Maximum range = 450 yards",
                "",
                "|cFFfcd34dConfiguration (in Config tab):|r",
                "  Radar Scale: 0.5x to 2.5x - resize the display",
                "  Radar Framerate: 10-30 fps - balance smoothness vs CPU",
                "",
                "|cFFfcd34dUsage Tips:|r",
                "  Drag the radar to reposition it on screen",
                "  Fly toward green blips - they approach center as you get closer",
                "  Avoid red areas - those nodes are depleted",
                "  Plan circular routes: by the time you return, nodes respawn!",
                "  Routes can be smaller than you think - respawns are fast!",
                "  You can farm efficiently in a circle not much bigger than the radar",
                "",
                "|cFFfcd34dCommands:|r",
                "  |cFF2dd4bf/lumberhud|r or |cFF2dd4bf/hdg hud|r - Toggle radar",
            }
        },
        {
            title = "Example Workflow",
            lines = {
                "Want to craft all Cata decor? Here's how:",
                "1. Go to Preview tab, select 'Expansion' filter, then 'Cata' tag",
                "2. Shift-click each item to add to your crafting queue",
                "3. Switch back to Recipes tab - all items sorted by crafter!",
                "4. Queue persists across characters, no need to recreate",
                "",
                "Want to see ALL decor (not just craftable)?",
                "Toggle 'Collection' in Preview - shows live collection data",
            }
        },
        {
            title = "Tips & Tricks",
            lines = {
                "|cFFfcd34dProfession Window Buttons:|r Open any profession window and",
                "look for 'Filter Decor' and 'Decor Guide' buttons at the top!",
                "  - |cFF2dd4bfFilter Decor|r: Filters to only housing decor items",
                "  - |cFF2dd4bfDecor Guide|r: Opens this addon at that profession",
                "",
                "|cFFfcd34dLumber Auto-Show:|r The lumber tracker automatically appears",
                "when you start farming wood (can be disabled in settings above)",
            }
        },
    }

    local guideY = cardGuide.bottomY
    for _, section in ipairs(guideSections) do
        local sectionTitle = cardGuide.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        sectionTitle:SetPoint("TOPLEFT", cardGuide.frame, "TOPLEFT", 15, guideY)
        ApplyFont(sectionTitle, guideScheme, "header")
        SetThemedText(sectionTitle, section.title, guideScheme, "warning")
        guideY = guideY - 22

        for _, line in ipairs(section.lines) do
            local lineText = cardGuide.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            lineText:SetPoint("TOPLEFT", cardGuide.frame, "TOPLEFT", 30, guideY)
            lineText:SetWidth(CONFIG.MAIN_FRAME_WIDTH - 100)
            lineText:SetJustifyH("LEFT")
            ApplyFont(lineText, guideScheme, "body")
            SetThemedText(lineText, "- " .. line, guideScheme, "text")
            guideY = guideY - 18
        end
        guideY = guideY - 10
    end

    cardGuide.bottomY = guideY
    FinishCard(cardGuide, 8)

    -- ========================================================================
    -- CARD 8: Advanced
    -- ========================================================================
    local cardAdvanced = MakeCard(L["CONFIG_ADVANCED"] or "Advanced")

    MakeCheckbox(cardAdvanced, L["CFG_ENABLE_DEBUG"] or "Enable Debug Mode", L["CFG_HINT_DEBUG"] or "(Enables /hdgdebug slash commands for troubleshooting)", {
        configKey = "debugModeEnabled", default = false,
        onClick = function(self)
            local enabled = self:GetChecked()
            Dispatch("SET_CONFIG", { key = "debugModeEnabled", value = enabled })
            if privateDB.HDG_Debug and privateDB.HDG_Debug.SetUnlocked then
                privateDB.HDG_Debug.SetUnlocked(enabled)
            end
            if enabled then
                print("|cFF00FFFF[HDG]|r " .. (L["CFG_DEBUG_ENABLED"] or "Debug mode enabled - /hdgdebug commands now available"))
            else
                print("|cFF00FFFF[HDG]|r " .. (L["CFG_DEBUG_DISABLED"] or "Debug mode disabled"))
            end
        end,
    })

    FinishCard(cardAdvanced)

    -- ========================================================================
    -- CARD 9: Special Thanks
    -- ========================================================================
    local cardCredits = MakeCard(L["CONFIG_SPECIAL_THANKS"] or "Special Thanks")
    local contribScheme = GetScheme()

    local contributorsMsg = cardCredits.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    contributorsMsg:SetPoint("TOPLEFT", cardCredits.frame, "TOPLEFT", 12, cardCredits.bottomY)
    contributorsMsg:SetPoint("RIGHT", cardCredits.frame, "RIGHT", -12, 0)
    contributorsMsg:SetJustifyH("LEFT")
    ApplyFont(contributorsMsg, contribScheme, "body")
    SetThemedText(contributorsMsg, "This addon wouldn't be possible without the amazing community. " ..
        "Special thanks to the following contributors for their feedback, bug reports, and feature suggestions:", contribScheme, "text")

    local credY = cardCredits.bottomY - 50

    local contributors = {
        {name = "Abased", note = "Feature requests & testing"},
        {name = "BaronKarza", note = "Colorblind Safe theme suggestion & testing"},
        {name = "Castianna", note = "Bug reports"},
        {name = "Daxxlia", note = "Testing & feedback"},
        {name = "Discomanco", note = "Warband grouping debugging & testing"},
        {name = "Edengonedark", note = "Feature suggestions"},
        {name = "Filurina", note = "Quality variant bug report"},
        {name = "Fracture93", note = "Goblin Mode feature suggestion"},
        {name = "GnuclearGnome", note = "Bug reports & UI feedback"},
        {name = "Gravebait", note = "Feature suggestions"},
        {name = "Hydra", note = "Bug reports & testing"},
        {name = "JodouKast", note = "Community support & feature ideas"},
        {name = "Jonnyne", note = "Early adopter & support"},
        {name = "Katsmiaou", note = "Feature suggestions"},
        {name = "KeeperHarvest", note = "Detailed vendor audit & bug reports"},
        {name = "Medivha", note = "Detailed trainer & vendor data audit"},
        {name = "Nunuman", note = "Korean localization testing & bug reports"},
        {name = "Rylls", note = "Bug reports & UX feedback"},
        {name = "Tzunamis", note = "Trainer NPC ID bug report"},
    }

    for _, contributor in ipairs(contributors) do
        local line = cardCredits.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        line:SetPoint("TOPLEFT", cardCredits.frame, "TOPLEFT", 20, credY)
        line:SetText(HDG.Theme.C_Accent() .. "-|r " .. HDG.Theme.C_Warning() .. contributor.name .. "|r " .. HDG.Theme.C_Dim() .. "- " .. contributor.note .. "|r")
        ApplyFont(line, contribScheme, "body")
        credY = credY - 18
    end

    credY = credY - 10
    local joinMsg = cardCredits.frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    joinMsg:SetPoint("TOPLEFT", cardCredits.frame, "TOPLEFT", 12, credY)
    joinMsg:SetPoint("RIGHT", cardCredits.frame, "RIGHT", -12, 0)
    joinMsg:SetJustifyH("LEFT")
    ApplyFont(joinMsg, contribScheme, "body")
    SetThemedText(joinMsg, "Want to contribute? Join our Discord or report issues on CurseForge!", contribScheme, "dim")

    cardCredits.bottomY = credY - 18
    FinishCard(cardCredits)

    -- Update scroll child height based on total card content
    scrollChild:SetHeight(math.abs(lastCardBottom) + 50)

    return configFrame
end

-- ============================================================================
-- UPDATE FUNCTIONS
-- ============================================================================

-- Update lumber pin checkbox to match current setting
function HDG_ConfigTab.UpdateLumberPinCheckbox()
    if HDG_ConfigTab.lumberPinCheckbox then
        HDG_ConfigTab.lumberPinCheckbox:SetChecked(GetUIState("lumberPinned") or false)
    end
end

-- ============================================================================
-- SHOW / HIDE
-- ============================================================================

function HDG_ConfigTab.Show()
    if mainFrame and mainFrame.configFrame then
        mainFrame.configFrame:Show()
    end
end

function HDG_ConfigTab.Hide()
    if mainFrame and mainFrame.configFrame then
        mainFrame.configFrame:Hide()
    end
end

-- ============================================================================
-- EVENTBUS SUBSCRIPTIONS
-- Decouple UI refresh from direct function calls
-- ============================================================================

local function RegisterEventBusHandlers()
    local HDG = _G.HousingDecorGuide
    if not HDG or not HDG.EventBus then return end

    HDG.EventBus:Register("HDG_STATE_CHANGED", function(payload)
        if not payload or not payload.action then return end

        local action = payload.action

        -- UI state changes (e.g., lumber pin toggled externally)
        if action == "SET_UI" then
            HDG_ConfigTab.UpdateLumberPinCheckbox()
        end
    end)
end

-- EventBus is loaded by TOC order (Core/HDG_EventBus.lua before tab modules).
RegisterEventBusHandlers()

-- ============================================================================
-- EXPORT TO GLOBAL
-- ============================================================================

_G.HDG_ConfigTab = HDG_ConfigTab

return HDG_ConfigTab
