local addonName, RF = ...
local T

-- Pending appearance values (applied on button click)
local pendingScale = nil
local pendingOpacity = nil

-------------------------------------------------------------------------------
-- Settings Panel: Config, J-key override, tooltip augmentation
-- Card-based two-column layout
-------------------------------------------------------------------------------
function RF:InitSettingsPanel(parent)
    T = RF.Theme

    -- Scroll frame wrapper
    local scrollFrame = CreateFrame("ScrollFrame", nil, parent, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT", 0, 0)
    scrollFrame:SetPoint("BOTTOMRIGHT", -24, 0)

    local scrollContent = CreateFrame("Frame", nil, scrollFrame)
    scrollContent:SetWidth(scrollFrame:GetWidth() or 880)
    scrollContent:SetHeight(1)
    scrollFrame:SetScrollChild(scrollContent)

    scrollFrame:SetScript("OnSizeChanged", function(self)
        scrollContent:SetWidth(self:GetWidth())
    end)

    ---------------------------------------------------------------------------
    -- Layout Constants
    ---------------------------------------------------------------------------
    local SIDE_PAD = 10
    local GAP = 8
    local INNER_PAD = 10
    local parentWidth = scrollFrame:GetWidth() or 1060
    local COL_WIDTH = math.floor((parentWidth - SIDE_PAD * 2 - GAP) / 2)
    local INNER_WIDTH = COL_WIDTH - INNER_PAD * 2

    local CARD_BACKDROP = {
        bgFile = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    }

    ---------------------------------------------------------------------------
    -- Helper: CreateCard
    ---------------------------------------------------------------------------
    local function CreateCard(title, x, y, width, height)
        local card = CreateFrame("Frame", nil, scrollContent, "BackdropTemplate")
        card:SetSize(width, height)
        card:SetPoint("TOPLEFT", x, y)
        card:SetBackdrop(CARD_BACKDROP)
        card:SetBackdropColor(unpack(T.bgSecondary))
        card:SetBackdropBorderColor(unpack(T.border))

        local header = RF:CreateLabel(card, title, RF.FONT_SIZES.medium, "accent")
        header:SetPoint("TOPLEFT", INNER_PAD, -INNER_PAD)

        local underline = card:CreateTexture(nil, "ARTWORK")
        underline:SetHeight(1)
        underline:SetPoint("TOPLEFT", INNER_PAD, -(INNER_PAD + 18))
        underline:SetPoint("TOPRIGHT", -INNER_PAD, -(INNER_PAD + 18))
        underline:SetColorTexture(unpack(T.accentDim))

        return card, -(INNER_PAD + 18 + 8)
    end

    ---------------------------------------------------------------------------
    -- Helper: AddCardCheckbox
    ---------------------------------------------------------------------------
    local function AddCardCheckbox(card, y, text, getValue, setValue)
        local frame = CreateFrame("Frame", nil, card)
        frame:SetHeight(22)
        frame:SetPoint("TOPLEFT", INNER_PAD, y)
        frame:SetPoint("TOPRIGHT", -INNER_PAD, y)

        local check = CreateFrame("Button", nil, frame)
        check:SetSize(16, 16)
        check:SetPoint("LEFT", 0, 0)

        local checkBg = check:CreateTexture(nil, "BACKGROUND")
        checkBg:SetAllPoints()
        checkBg:SetColorTexture(unpack(T.bgTertiary))

        local checkMark = check:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        checkMark:SetAllPoints()
        checkMark:SetFont(checkMark:GetFont(), 12, "")
        checkMark:SetTextColor(unpack(T.accent))

        local label = RF:CreateLabel(frame, text, RF.FONT_SIZES.normal, "textSecondary")
        label:SetPoint("LEFT", check, "RIGHT", 6, 0)

        local function UpdateCheck()
            checkMark:SetText(getValue() and "X" or "")
        end

        check:SetScript("OnClick", function()
            setValue(not getValue())
            UpdateCheck()
        end)

        local labelBtn = CreateFrame("Button", nil, frame)
        labelBtn:SetPoint("LEFT", label, "LEFT")
        labelBtn:SetPoint("RIGHT", label, "RIGHT")
        labelBtn:SetHeight(22)
        labelBtn:SetScript("OnClick", function()
            setValue(not getValue())
            UpdateCheck()
        end)

        UpdateCheck()
        return y - 24
    end

    ---------------------------------------------------------------------------
    -- Helper: AddCardStepper
    ---------------------------------------------------------------------------
    local function AddCardStepper(card, y, labelText, min, max, step, getVal, setVal, fmtStr)
        fmtStr = fmtStr or "%.2f"
        local frame = CreateFrame("Frame", nil, card)
        frame:SetSize(INNER_WIDTH, 24)
        frame:SetPoint("TOPLEFT", INNER_PAD, y)

        local label = RF:CreateLabel(frame, labelText, RF.FONT_SIZES.normal, "textSecondary")
        label:SetPoint("LEFT", 0, 0)

        local minusBtn = RF:CreateButton(frame, "-", 28, 22)
        minusBtn:SetPoint("LEFT", 160, 0)

        local valueLabel = RF:CreateLabel(frame, format(fmtStr, getVal()), RF.FONT_SIZES.normal, "textPrimary")
        valueLabel:SetPoint("LEFT", minusBtn, "RIGHT", 6, 0)
        valueLabel:SetWidth(50)
        valueLabel:SetJustifyH("CENTER")

        local plusBtn = RF:CreateButton(frame, "+", 28, 22)
        plusBtn:SetPoint("LEFT", valueLabel, "RIGHT", 6, 0)

        local function Refresh()
            valueLabel:SetText(format(fmtStr, getVal()))
        end

        minusBtn:SetScript("OnClick", function()
            local v = math.max(min, getVal() - step)
            setVal(v)
            Refresh()
        end)

        plusBtn:SetScript("OnClick", function()
            local v = math.min(max, getVal() + step)
            setVal(v)
            Refresh()
        end)

        return y - 28
    end

    ---------------------------------------------------------------------------
    -- Helper: AddCardDropdown
    ---------------------------------------------------------------------------
    local function AddCardDropdown(card, y, labelText, items, defText, defVal, onChanged)
        local frame = CreateFrame("Frame", nil, card)
        frame:SetHeight(24)
        frame:SetPoint("TOPLEFT", INNER_PAD, y)
        frame:SetPoint("TOPRIGHT", -INNER_PAD, y)

        local label = RF:CreateLabel(frame, labelText, RF.FONT_SIZES.normal, "textSecondary")
        label:SetPoint("LEFT", 0, 0)

        local dropdown = RF:CreateDropdown(frame, {
            width = 120,
            defaultText = defText,
            defaultValue = defVal,
            items = items,
            onChanged = onChanged,
        })
        dropdown:SetPoint("LEFT", label, "RIGHT", 8, 0)

        return y - 30
    end

    ---------------------------------------------------------------------------
    -- Helper: AddCardInput
    ---------------------------------------------------------------------------
    local function AddCardInput(card, y, labelText, getText, saveText, maxLetters)
        local lbl = RF:CreateLabel(card, labelText, RF.FONT_SIZES.normal, "textSecondary")
        lbl:SetPoint("TOPLEFT", INNER_PAD, y)
        y = y - 20

        local input = CreateFrame("EditBox", nil, card, "BackdropTemplate")
        input:SetHeight(24)
        input:SetPoint("TOPLEFT", INNER_PAD, y)
        input:SetPoint("TOPRIGHT", -INNER_PAD, y)
        input:SetFontObject("ChatFontNormal")
        local fontPath = select(1, ChatFontNormal:GetFont())
        input:SetFont(fontPath, RF.FONT_SIZES.normal, "")
        input:SetTextColor(unpack(T.textPrimary))
        input:SetAutoFocus(false)
        input:SetMaxLetters(maxLetters or 500)
        input:SetBackdrop(CARD_BACKDROP)
        input:SetBackdropColor(0.1, 0.1, 0.12, 1)
        input:SetBackdropBorderColor(unpack(T.border))
        input:SetText(getText())

        input:SetScript("OnEditFocusLost", function(self)
            saveText(self:GetText())
            self:SetBackdropBorderColor(unpack(T.border))
        end)
        input:SetScript("OnEditFocusGained", function(self)
            self:SetBackdropBorderColor(unpack(T.accent))
        end)
        input:SetScript("OnEnterPressed", function(self)
            saveText(self:GetText())
            self:ClearFocus()
        end)
        input:SetScript("OnEscapePressed", function(self)
            self:ClearFocus()
        end)

        return y - 30
    end

    ---------------------------------------------------------------------------
    -- Column positioning (each column flows independently)
    ---------------------------------------------------------------------------
    local LEFT_X = SIDE_PAD
    local RIGHT_X = SIDE_PAD + COL_WIDTH + GAP

    -- Left column: General -> Appearance -> Visible Columns
    local L1_Y = -SIDE_PAD;  local L1_H = 324
    local L2_Y = L1_Y - L1_H - GAP;  local L2_H = 200
    local L3_Y = L2_Y - L2_H - GAP;  local L3_H = 240

    -- Right column: Chat -> Sync -> Data Management -> Teams
    local R1_Y = -SIDE_PAD;  local R1_H = 450
    local R2_Y = R1_Y - R1_H - GAP;  local R2_H = 180
    local R3_Y = R2_Y - R2_H - GAP;  local R3_H = 140
    local R4_Y = R3_Y - R3_H - GAP;  local R4_H = 320

    -- About card: full width, below both columns
    -- (ROW4_Y recalculated after L4 card is created)
    local ROW4_H = 60

    ---------------------------------------------------------------------------
    -- Card 1: General (left, row 1)
    ---------------------------------------------------------------------------
    local generalCard, gy = CreateCard("General", LEFT_X, L1_Y, COL_WIDTH, L1_H)

    gy = AddCardCheckbox(generalCard, gy, "Replace default guild panel (J key)", function()
        return RF.charDB and RF.charDB.replaceGuildPanel or false
    end, function(val)
        if RF.charDB then
            RF.charDB.replaceGuildPanel = val
            RF:UpdateJKeyOverride()
        end
    end)

    gy = AddCardCheckbox(generalCard, gy, "Show tooltip augmentation", function()
        return RF.charDB and RF.charDB.tooltipAugment or false
    end, function(val)
        if RF.charDB then RF.charDB.tooltipAugment = val end
    end)

    gy = AddCardCheckbox(generalCard, gy, "Rejoin detection alerts", function()
        return RF.charDB and RF.charDB.rejoinDetection or false
    end, function(val)
        if RF.charDB then RF.charDB.rejoinDetection = val end
    end)

    gy = AddCardCheckbox(generalCard, gy, "Show minimap button", function()
        return not (RF.charDB and RF.charDB.minimap and RF.charDB.minimap.hide)
    end, function(val)
        if RF.charDB then
            if not RF.charDB.minimap then RF.charDB.minimap = {} end
            RF.charDB.minimap.hide = not val
            local LDBIcon = LibStub and LibStub("LibDBIcon-1.0", true)
            if LDBIcon then
                if val then LDBIcon:Show("RosterForge") else LDBIcon:Hide("RosterForge") end
            end
        end
    end)

    gy = AddCardCheckbox(generalCard, gy, "Start in compact mode", function()
        return RF.charDB and RF.charDB.compactMode or false
    end, function(val)
        if RF.charDB then RF.charDB.compactMode = val end
    end)

    gy = AddCardCheckbox(generalCard, gy, "Show roles in tooltip", function()
        return RF.charDB and RF.charDB.tooltipShowTags ~= false
    end, function(val)
        if RF.charDB then RF.charDB.tooltipShowTags = val end
    end)

    gy = AddCardCheckbox(generalCard, gy, "High contrast text", function()
        return RF.charDB and RF.charDB.highContrastMode or false
    end, function(val)
        if RF.charDB then
            RF.charDB.highContrastMode = val
            RF:ApplyHighContrast()
        end
    end)

    gy = AddCardCheckbox(generalCard, gy, "Debug mode", function()
        return RF.charDB and RF.charDB.debugMode or false
    end, function(val)
        if RF.charDB then RF.charDB.debugMode = val end
    end)

    -- Alt rank dropdown: select guild rank that designates alts
    local altRankItems = { { label = "Disabled", value = nil } }
    if GuildControlGetNumRanks then
        local numRanks = GuildControlGetNumRanks()
        for i = 1, numRanks do
            local rankName = GuildControlGetRankName(i)
            if rankName and rankName ~= "" then
                tinsert(altRankItems, { label = rankName, value = i - 1 })
            end
        end
    end
    local altRankCurrent = RF.charDB and RF.charDB.altAutoRankIndex
    local altRankText = "Disabled"
    if altRankCurrent and GuildControlGetRankName then
        local rn = GuildControlGetRankName(altRankCurrent + 1)
        if rn and rn ~= "" then altRankText = rn end
    end
    gy = AddCardDropdown(generalCard, gy, "Alt rank:", altRankItems, altRankText, altRankCurrent,
        function(val)
            if RF.charDB then
                RF.charDB.altAutoRankIndex = val
                RF._analyticsCache = nil
            end
        end)

    gy = AddCardInput(generalCard, gy, "Custom slash command (reload required):", function()
        return RF.charDB and RF.charDB.customSlashCommand or ""
    end, function(val)
        if RF.charDB then
            local cmd = val:lower():gsub("^/", ""):trim()
            RF.charDB.customSlashCommand = cmd
            if cmd ~= "" then
                RF:Print("Custom command /" .. cmd .. " will be active after /reload")
            end
        end
    end, 20)

    ---------------------------------------------------------------------------
    -- Card 2: Chat (right, row 1)
    ---------------------------------------------------------------------------
    local chatCard, cy = CreateCard("Chat", RIGHT_X, R1_Y, COL_WIDTH, R1_H)

    -- Font face dropdown
    local fontFaceItems = {}
    for _, entry in ipairs(RF.CHAT_FONTS) do
        tinsert(fontFaceItems, { label = entry.label, value = entry.label })
    end
    cy = AddCardDropdown(chatCard, cy, "Chat font:", fontFaceItems,
        RF.charDB and RF.charDB.chatFontFace or "Default",
        RF.charDB and RF.charDB.chatFontFace or "Default",
        function(val)
            if RF.charDB then RF.charDB.chatFontFace = val; RF:ApplyChatFont() end
        end)

    -- Font size stepper
    cy = AddCardStepper(chatCard, cy, "Chat font size", 8, 20, 1, function()
        return RF.charDB and RF.charDB.chatFontSize or 12
    end, function(v)
        if RF.charDB then RF.charDB.chatFontSize = v; RF:ApplyChatFont() end
    end, "%.0f")

    -- Outline dropdown
    local outlineItems = {}
    for _, entry in ipairs(RF.CHAT_OUTLINES) do
        tinsert(outlineItems, { label = entry.label, value = entry.value })
    end
    local curOutlineLabel, curOutlineValue = "None", RF.charDB and RF.charDB.chatFontOutline or ""
    for _, entry in ipairs(RF.CHAT_OUTLINES) do
        if entry.value == curOutlineValue then curOutlineLabel = entry.label; break end
    end
    cy = AddCardDropdown(chatCard, cy, "Chat outline:", outlineItems,
        curOutlineLabel, curOutlineValue,
        function(val)
            if RF.charDB then RF.charDB.chatFontOutline = val; RF:ApplyChatFont() end
        end)

    -- Timestamp format dropdown
    local tsItems = {}
    for _, entry in ipairs(RF.CHAT_TIMESTAMP_FORMATS) do
        tinsert(tsItems, { label = entry.label, value = entry.value })
    end
    local curTsLabel, curTsValue = "12-hour", RF.charDB and RF.charDB.chatTimestampFormat or "12h"
    for _, entry in ipairs(RF.CHAT_TIMESTAMP_FORMATS) do
        if entry.value == curTsValue then curTsLabel = entry.label; break end
    end
    cy = AddCardDropdown(chatCard, cy, "Timestamp:", tsItems, curTsLabel, curTsValue,
        function(val)
            if RF.charDB then RF.charDB.chatTimestampFormat = val end
        end)

    -- Keywords input
    cy = AddCardInput(chatCard, cy, "Keywords (comma-separated):", function()
        local kw = RF.charDB and RF.charDB.chatKeywords or {}
        return table.concat(kw, ", ")
    end, function(text)
        if not RF.charDB then return end
        local result = {}
        for word in text:gmatch("[^,]+") do
            word = word:match("^%s*(.-)%s*$")
            if word ~= "" then tinsert(result, word) end
        end
        RF.charDB.chatKeywords = result
    end, 500)

    -- Keyword color dropdown with swatch
    local kwColorOptions = {
        { label = "Yellow", value = "FFFF00" },
        { label = "Red",    value = "FF4444" },
        { label = "Green",  value = "44FF44" },
        { label = "Cyan",   value = "44FFFF" },
        { label = "Orange", value = "FF9900" },
        { label = "Pink",   value = "FF66CC" },
        { label = "Blue",   value = "3B82F6" },
    }
    local curKwColor = RF.charDB and RF.charDB.chatKeywordColor or "FFFF00"
    local curKwColorLabel = "Yellow"
    for _, opt in ipairs(kwColorOptions) do
        if opt.value == curKwColor then curKwColorLabel = opt.label; break end
    end

    local kwColorFrame = CreateFrame("Frame", nil, chatCard)
    kwColorFrame:SetHeight(24)
    kwColorFrame:SetPoint("TOPLEFT", INNER_PAD, cy)
    kwColorFrame:SetPoint("TOPRIGHT", -INNER_PAD, cy)

    local kwColorLabel = RF:CreateLabel(kwColorFrame, "Keyword color:", RF.FONT_SIZES.normal, "textSecondary")
    kwColorLabel:SetPoint("LEFT", 0, 0)

    local kwSwatch = kwColorFrame:CreateTexture(nil, "ARTWORK")
    kwSwatch:SetSize(16, 16)

    local kwColorDropdown = RF:CreateDropdown(kwColorFrame, {
        width = 100,
        defaultText = curKwColorLabel,
        defaultValue = curKwColor,
        items = kwColorOptions,
        onChanged = function(val)
            if RF.charDB then RF.charDB.chatKeywordColor = val end
            local r2 = tonumber(val:sub(1, 2), 16) / 255
            local g2 = tonumber(val:sub(3, 4), 16) / 255
            local b2 = tonumber(val:sub(5, 6), 16) / 255
            kwSwatch:SetColorTexture(r2, g2, b2, 1)
        end,
    })
    kwColorDropdown:SetPoint("LEFT", kwColorLabel, "RIGHT", 8, 0)
    kwSwatch:SetPoint("LEFT", kwColorDropdown, "RIGHT", 8, 0)
    do
        local r2 = tonumber(curKwColor:sub(1, 2), 16) / 255
        local g2 = tonumber(curKwColor:sub(3, 4), 16) / 255
        local b2 = tonumber(curKwColor:sub(5, 6), 16) / 255
        kwSwatch:SetColorTexture(r2, g2, b2, 1)
    end
    cy = cy - 30

    -- Sound alert + cooldown
    cy = AddCardCheckbox(chatCard, cy, "Play sound on keyword mention", function()
        return RF.charDB and RF.charDB.chatSoundAlerts or false
    end, function(val)
        if RF.charDB then RF.charDB.chatSoundAlerts = val end
    end)

    cy = AddCardStepper(chatCard, cy, "Sound cooldown (s)", 0, 30, 1, function()
        return RF.charDB and RF.charDB.chatSoundCooldown or 5
    end, function(v)
        if RF.charDB then RF.charDB.chatSoundCooldown = math.floor(v) end
    end, "%.0f")

    -- Feature checkboxes
    cy = AddCardCheckbox(chatCard, cy, "Show unread message badge", function()
        return RF.charDB and RF.charDB.chatShowUnreadBadge ~= false
    end, function(val)
        if RF.charDB then
            RF.charDB.chatShowUnreadBadge = val
            RF:UpdateChatUnreadBadge()
        end
    end)

    cy = AddCardCheckbox(chatCard, cy, "Show date separators", function()
        return RF.charDB and RF.charDB.chatShowDateSeparators ~= false
    end, function(val)
        if RF.charDB then RF.charDB.chatShowDateSeparators = val end
    end)

    cy = AddCardCheckbox(chatCard, cy, "Show new message indicator", function()
        return RF.charDB and RF.charDB.chatShowNewMsgIndicator ~= false
    end, function(val)
        if RF.charDB then RF.charDB.chatShowNewMsgIndicator = val end
    end)

    cy = AddCardCheckbox(chatCard, cy, "Show chat background", function()
        return RF.charDB and RF.charDB.chatShowBackground ~= false
    end, function(val)
        if RF.charDB then
            RF.charDB.chatShowBackground = val
            if RF.mainFrame and RF.mainFrame.tabFrames then
                local chatParent = RF.mainFrame.tabFrames["Chat"]
                if chatParent and chatParent.chatBg then
                    if val then chatParent.chatBg:Show() else chatParent.chatBg:Hide() end
                end
            end
        end
    end)

    cy = AddCardCheckbox(chatCard, cy, "Pause scroll when reading history", function()
        return RF.charDB and RF.charDB.chatAutoScroll ~= false
    end, function(val)
        if RF.charDB then RF.charDB.chatAutoScroll = val end
    end)

    cy = AddCardStepper(chatCard, cy, "Message spacing (px)", 0, 10, 1, function()
        return RF.charDB and RF.charDB.chatMessageSpacing or 2
    end, function(v)
        if RF.charDB then
            RF.charDB.chatMessageSpacing = v
            if RF.chatFrame then RF.chatFrame:SetSpacing(v) end
        end
    end, "%.0f")

    ---------------------------------------------------------------------------
    -- Card 3: Appearance (left, row 2)
    ---------------------------------------------------------------------------
    local appearCard, ay = CreateCard("Appearance", LEFT_X, L2_Y, COL_WIDTH, L2_H)

    pendingScale = RF.charDB and RF.charDB.frameScale or 1.0
    pendingOpacity = RF.charDB and RF.charDB.frameOpacity or 0.95

    ay = AddCardStepper(appearCard, ay, "Frame Scale", 0.50, 1.50, 0.05, function()
        return pendingScale
    end, function(v) pendingScale = v end)

    ay = AddCardStepper(appearCard, ay, "Frame Opacity", 0.50, 1.00, 0.05, function()
        return pendingOpacity
    end, function(v) pendingOpacity = v end)

    ay = AddCardStepper(appearCard, ay, "Roster font size", 8, 16, 1, function()
        return RF.charDB and RF.charDB.rosterFontSize or 10
    end, function(val)
        if RF.charDB then RF.charDB.rosterFontSize = val end
        RF:Print("Roster font size set to " .. val .. ". Reopen the window to apply.")
    end, "%d")

    -- Default online filter
    local filterItems = {
        { label = "All", value = 1 },
        { label = "Online", value = 2 },
        { label = "Offline", value = 3 },
    }
    local curFilterVal = RF.charDB and RF.charDB.defaultOnlineFilter or 2
    local curFilterLabel = "Online"
    for _, item in ipairs(filterItems) do
        if item.value == curFilterVal then curFilterLabel = item.label; break end
    end
    ay = AddCardDropdown(appearCard, ay, "Default filter:", filterItems,
        curFilterLabel, curFilterVal,
        function(val)
            if RF.charDB then RF.charDB.defaultOnlineFilter = val end
        end)

    local applyBtn = RF:CreateButton(appearCard, "Apply", 80, 26, "accent")
    applyBtn:SetPoint("TOPLEFT", INNER_PAD, ay)
    applyBtn:SetScript("OnClick", function()
        if RF.charDB then
            RF.charDB.frameScale = pendingScale
            RF.charDB.frameOpacity = pendingOpacity
            if RF.mainFrame then
                RF.mainFrame:SetScale(pendingScale)
                RF.mainFrame:SetAlpha(pendingOpacity)
            end
            RF:Print(format("Applied: Scale %.2f, Opacity %.2f", pendingScale, pendingOpacity))
        end
    end)

    ---------------------------------------------------------------------------
    -- Card 4: Sync (right, row 2)
    ---------------------------------------------------------------------------
    local syncCard, sy = CreateCard("Sync", RIGHT_X, R2_Y, COL_WIDTH, R2_H)

    sy = AddCardCheckbox(syncCard, sy, "Enable sync (share with guildmates)", function()
        return RF.charDB and RF.charDB.syncEnabled ~= false
    end, function(val)
        if RF.charDB then RF.charDB.syncEnabled = val end
    end)

    sy = AddCardCheckbox(syncCard, sy, "Send sync data (disable = receive only)", function()
        return RF.charDB and RF.charDB.syncWriteEnabled ~= false
    end, function(val)
        if RF.charDB then RF.charDB.syncWriteEnabled = val end
    end)

    sy = AddCardCheckbox(syncCard, sy, "Officer-only sync", function()
        return RF.charDB and RF.charDB.syncOfficerOnly or false
    end, function(val)
        if RF.charDB then RF.charDB.syncOfficerOnly = val end
    end)

    -- Sync status ticker
    local syncStatusText = RF:CreateLabel(syncCard, "", RF.FONT_SIZES.normal, "textMuted")
    syncStatusText:SetPoint("TOPLEFT", INNER_PAD, sy)

    local function UpdateSyncStatus()
        local userCount = RF.GetOnlineSyncUsers and RF:GetOnlineSyncUsers() or 0
        local lastSync = RF._lastSyncDataReceived
        local lastStr = "never"
        if lastSync then
            local diff = time() - lastSync
            if diff < 60 then
                lastStr = "just now"
            else
                lastStr = format("%dm ago", math.floor(diff / 60))
            end
        end
        syncStatusText:SetText(format("RF users online: %d  |  Last sync: %s", userCount, lastStr))
    end

    UpdateSyncStatus()
    C_Timer.NewTicker(5, function()
        if syncCard:IsVisible() then UpdateSyncStatus() end
    end)
    sy = sy - 24

    local syncNowBtn = RF:CreateButton(syncCard, "Sync Now", 90, 26, "accent")
    syncNowBtn:SetPoint("TOPLEFT", INNER_PAD, sy)
    syncNowBtn:SetScript("OnClick", function()
        if RF.ForceRequestFullSync then
            RF:ForceRequestFullSync()
            RF:Print("Manual sync requested.")
        end
    end)

    ---------------------------------------------------------------------------
    -- Card 5: Visible Columns (left, row 3) -- 2 sub-columns
    ---------------------------------------------------------------------------
    local colCard, coly = CreateCard("Roster - Visible Columns", LEFT_X, L3_Y, COL_WIDTH, L3_H)

    local visibleCols = RF.charDB and RF.charDB.visibleColumns
    if not visibleCols then
        visibleCols = {}
        for k, v in pairs(RF.DEFAULT_VISIBLE_COLUMNS) do visibleCols[k] = v end
        if RF.charDB then RF.charDB.visibleColumns = visibleCols end
    end

    local colDefs = RF.COLUMN_DEFS
    local leftCount = math.ceil(#colDefs / 2)
    local SUB_GAP = 16
    local subW = math.floor((INNER_WIDTH - SUB_GAP) / 2)
    local labelMaxW = subW - 24 -- checkbox (16) + gap (6) + margin (2)
    local leftY, rightY = coly, coly

    for i, colDef in ipairs(colDefs) do
        local isLeft = (i <= leftCount)
        local anchorX = isLeft and INNER_PAD or (INNER_PAD + subW + SUB_GAP)
        local anchorY = isLeft and leftY or rightY

        local frame = CreateFrame("Frame", nil, colCard)
        frame:SetSize(subW, 22)
        frame:SetPoint("TOPLEFT", anchorX, anchorY)

        local check = CreateFrame("Button", nil, frame)
        check:SetSize(16, 16)
        check:SetPoint("LEFT", 0, 0)

        local checkBg = check:CreateTexture(nil, "BACKGROUND")
        checkBg:SetAllPoints()
        checkBg:SetColorTexture(unpack(T.bgTertiary))

        local checkMark = check:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        checkMark:SetAllPoints()
        checkMark:SetFont(checkMark:GetFont(), 12, "")
        checkMark:SetTextColor(unpack(T.accent))

        local label = RF:CreateLabel(frame, colDef.label, RF.FONT_SIZES.normal, "textSecondary")
        label:SetPoint("LEFT", check, "RIGHT", 6, 0)
        label:SetWidth(labelMaxW)
        label:SetJustifyH("LEFT")
        label:SetWordWrap(false)

        local key = colDef.key
        local function UpdateCheck()
            checkMark:SetText(visibleCols[key] ~= false and "X" or "")
        end
        local function Toggle()
            visibleCols[key] = not (visibleCols[key] ~= false)
            UpdateCheck()
            RF:FireCallbacks("COLUMNS_CHANGED")
        end

        check:SetScript("OnClick", Toggle)
        local labelBtn = CreateFrame("Button", nil, frame)
        labelBtn:SetPoint("LEFT", check, "LEFT")
        labelBtn:SetPoint("RIGHT", frame, "RIGHT")
        labelBtn:SetHeight(22)
        labelBtn:SetScript("OnClick", Toggle)
        UpdateCheck()

        if isLeft then leftY = leftY - 22 else rightY = rightY - 22 end
    end

    local colBtnY = math.min(leftY, rightY) - 8
    local resetOrderBtn = RF:CreateButton(colCard, "Reset Column Order", 150, 24)
    resetOrderBtn:SetPoint("TOPLEFT", INNER_PAD, colBtnY)
    resetOrderBtn:SetScript("OnClick", function()
        if RF.charDB then RF.charDB.columnOrder = nil end
        RF:FireCallbacks("COLUMNS_CHANGED")
        RF:Print("Column order reset to default for all views.")
    end)

    ---------------------------------------------------------------------------
    -- Card 6: Health Dashboard (left, row 4)
    ---------------------------------------------------------------------------
    local L4_Y = L3_Y - L3_H - GAP;  local L4_H = 280
    local dashCard, dhy = CreateCard("Health Dashboard", LEFT_X, L4_Y, COL_WIDTH, L4_H)

    dhy = AddCardStepper(dashCard, dhy, "\"Active\" threshold (days)", 1, 30, 1, function()
        return RF.charDB and RF.charDB.dashboardActiveDays or 7
    end, function(v)
        if RF.charDB then
            RF.charDB.dashboardActiveDays = math.floor(v)
            RF._analyticsCache = nil
        end
    end, "%.0f")

    dhy = AddCardStepper(dashCard, dhy, "Top M+ performers", 3, 15, 1, function()
        return RF.charDB and RF.charDB.dashboardTopPerformers or 5
    end, function(v)
        if RF.charDB then
            RF.charDB.dashboardTopPerformers = math.floor(v)
            RF._analyticsCache = nil
        end
    end, "%.0f")

    -- Sub-header
    dhy = dhy - 4
    local dhSubHeader = RF:CreateLabel(dashCard, "Visible Cards", RF.FONT_SIZES.small, "textMuted")
    dhSubHeader:SetPoint("TOPLEFT", INNER_PAD, dhy)
    dhy = dhy - 16

    -- 13 checkboxes in 2 sub-columns (same pattern as Visible Columns)
    local dashCards = RF.DASHBOARD_CARDS
    local dhLeftCount = 7  -- first 7 are left column cards
    local dhSubGap = 16
    local dhSubW = math.floor((INNER_WIDTH - dhSubGap) / 2)
    local dhLabelMaxW = dhSubW - 24
    local dhLeftY, dhRightY = dhy, dhy

    if not RF.charDB.dashboardHiddenCards then
        RF.charDB.dashboardHiddenCards = {}
    end
    local hiddenCards = RF.charDB.dashboardHiddenCards

    for i, cardDef in ipairs(dashCards) do
        local isLeft = (i <= dhLeftCount)
        local anchorX = isLeft and INNER_PAD or (INNER_PAD + dhSubW + dhSubGap)
        local anchorY = isLeft and dhLeftY or dhRightY

        local frame = CreateFrame("Frame", nil, dashCard)
        frame:SetSize(dhSubW, 22)
        frame:SetPoint("TOPLEFT", anchorX, anchorY)

        local check = CreateFrame("Button", nil, frame)
        check:SetSize(16, 16)
        check:SetPoint("LEFT", 0, 0)

        local checkBg = check:CreateTexture(nil, "BACKGROUND")
        checkBg:SetAllPoints()
        checkBg:SetColorTexture(unpack(T.bgTertiary))

        local checkMark = check:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        checkMark:SetAllPoints()
        checkMark:SetFont(checkMark:GetFont(), 12, "")
        checkMark:SetTextColor(unpack(T.accent))

        local label = RF:CreateLabel(frame, cardDef.label, RF.FONT_SIZES.normal, "textSecondary")
        label:SetPoint("LEFT", check, "RIGHT", 6, 0)
        label:SetWidth(dhLabelMaxW)
        label:SetJustifyH("LEFT")
        label:SetWordWrap(false)

        local key = cardDef.key
        local function UpdateCheck()
            checkMark:SetText(not hiddenCards[key] and "X" or "")
        end
        local function Toggle()
            if hiddenCards[key] then
                hiddenCards[key] = nil
            else
                hiddenCards[key] = true
            end
            UpdateCheck()
        end

        check:SetScript("OnClick", Toggle)
        local labelBtn = CreateFrame("Button", nil, frame)
        labelBtn:SetPoint("LEFT", check, "LEFT")
        labelBtn:SetPoint("RIGHT", frame, "RIGHT")
        labelBtn:SetHeight(22)
        labelBtn:SetScript("OnClick", Toggle)
        UpdateCheck()

        if isLeft then dhLeftY = dhLeftY - 22 else dhRightY = dhRightY - 22 end
    end

    ---------------------------------------------------------------------------
    -- Card 7: Data Management (right, row 3)
    ---------------------------------------------------------------------------
    local dataCard, dy = CreateCard("Data Management", RIGHT_X, R3_Y, COL_WIDTH, R3_H)

    dy = AddCardStepper(dataCard, dy, "Max history entries", 100, 2000, 100, function()
        return RF.charDB and RF.charDB.maxHistoryEntries or 500
    end, function(v)
        if RF.charDB then RF.charDB.maxHistoryEntries = math.floor(v) end
    end, "%.0f")

    dy = AddCardStepper(dataCard, dy, "Log rotation (days)", 7, 365, 7, function()
        return RF.charDB and RF.charDB.logRotationDays or 90
    end, function(v)
        if RF.charDB then RF.charDB.logRotationDays = math.floor(v) end
    end, "%.0f")

    local purgeBtn = RF:CreateButton(dataCard, "Purge Old Data", 120, 26, "danger")
    purgeBtn:SetPoint("TOPLEFT", INNER_PAD, dy)
    purgeBtn:SetScript("OnClick", function()
        RF:RotateLogs()
        RF:Print("Old log data purged.")
    end)

    ---------------------------------------------------------------------------
    -- Card 8: Teams (right, row 4)
    ---------------------------------------------------------------------------
    local teamsCard, ty = CreateCard("Teams", RIGHT_X, R4_Y, COL_WIDTH, R4_H)

    local roleItems = {}
    for _, r in ipairs(RF.TEAM_ROLES) do
        tinsert(roleItems, { label = r.label, value = r.key })
    end
    local curRole = RF.charDB and RF.charDB.teamDefaultRole or "DPS"
    local curRoleLabel = "DPS"
    for _, r in ipairs(RF.TEAM_ROLES) do
        if r.key == curRole then curRoleLabel = r.label; break end
    end
    ty = AddCardDropdown(teamsCard, ty, "Default role for new members:", roleItems, curRoleLabel, curRole,
        function(val)
            if RF.charDB then RF.charDB.teamDefaultRole = val end
        end)

    local typeItems = {}
    for _, tt in ipairs(RF.TEAM_TYPES) do
        tinsert(typeItems, { label = tt.label, value = tt.key })
    end
    local curType = RF.charDB and RF.charDB.teamDefaultType or "raid"
    local curTypeLabel = "Mythic Raid"
    for _, tt in ipairs(RF.TEAM_TYPES) do
        if tt.key == curType then curTypeLabel = tt.label; break end
    end
    ty = AddCardDropdown(teamsCard, ty, "Default team type:", typeItems, curTypeLabel, curType,
        function(val)
            if RF.charDB then RF.charDB.teamDefaultType = val end
        end)

    ty = AddCardCheckbox(teamsCard, ty, "Show teams in tooltips", function()
        return not RF.charDB or RF.charDB.tooltipShowTeams ~= false
    end, function(val)
        if RF.charDB then RF.charDB.tooltipShowTeams = val end
    end)

    ty = AddCardCheckbox(teamsCard, ty, "Show composition warnings", function()
        return RF.charDB and RF.charDB.teamShowCompWarnings ~= false
    end, function(val)
        if RF.charDB then RF.charDB.teamShowCompWarnings = val end
        RF:FireCallbacks("TEAMS_CHANGED")
    end)

    ty = AddCardCheckbox(teamsCard, ty, "Show only online in assignment list", function()
        return RF.charDB and RF.charDB.teamShowOnlineOnly or false
    end, function(val)
        if RF.charDB then RF.charDB.teamShowOnlineOnly = val end
        RF:FireCallbacks("TEAMS_CHANGED")
    end)

    ty = AddCardCheckbox(teamsCard, ty, "Sync teams with guild", function()
        return not RF.charDB or RF.charDB.teamSyncEnabled ~= false
    end, function(val)
        if RF.charDB then RF.charDB.teamSyncEnabled = val end
    end)

    ty = AddCardCheckbox(teamsCard, ty, "Only accept team sync from officers", function()
        return RF.charDB and RF.charDB.teamSyncOfficerOnly or false
    end, function(val)
        if RF.charDB then RF.charDB.teamSyncOfficerOnly = val end
    end)

    ty = AddCardStepper(teamsCard, ty, "Max teams:", 1, 100, 5,
        function() return RF.MAX_TEAMS end,
        function(val)
            RF.MAX_TEAMS = val
            if RF.charDB then RF.charDB.maxTeams = val end
        end, "%d")

    local clearTeamsBtn = RF:CreateButton(teamsCard, "Delete All Teams", 130, 26, "danger")
    clearTeamsBtn:SetPoint("TOPLEFT", INNER_PAD, ty)
    clearTeamsBtn:SetScript("OnClick", function()
        RF:ShowConfirmDialog("Delete All Teams",
            "This will permanently delete all teams. This cannot be undone.\n\nAre you sure?",
            function()
                local guildDB = RF:GetGuildDB()
                if guildDB then
                    wipe(guildDB.teams)
                    wipe(guildDB.teamTimestamps)
                    RF:FireCallbacks("TEAMS_CHANGED")
                    RF:Print("All teams deleted.")
                end
            end)
    end)

    ---------------------------------------------------------------------------
    -- Card 9: About & Reset (full width, below all columns)
    ---------------------------------------------------------------------------
    local ROW4_Y = math.min(L4_Y - L4_H, R4_Y - R4_H) - GAP
    local FULL_WIDTH = COL_WIDTH * 2 + GAP
    local aboutCard = CreateFrame("Frame", nil, scrollContent, "BackdropTemplate")
    aboutCard:SetSize(FULL_WIDTH, ROW4_H)
    aboutCard:SetPoint("TOPLEFT", LEFT_X, ROW4_Y)
    aboutCard:SetBackdrop(CARD_BACKDROP)
    aboutCard:SetBackdropColor(unpack(T.bgSecondary))
    aboutCard:SetBackdropBorderColor(unpack(T.border))

    local purgeAllBtn = RF:CreateButton(aboutCard, "Purge All Data", 130, 26, "danger")
    purgeAllBtn:SetPoint("RIGHT", -INNER_PAD, 0)
    purgeAllBtn:SetScript("OnClick", function()
        RF:ShowConfirmDialog("Purge All Data",
            "This will permanently erase ALL guild data for this guild (tracking, alts, notes, bans, chat logs, analytics, tags, favorites, and all history). This cannot be undone.\n\nAre you sure?",
            function()
                local guildDB = RF:GetGuildDB()
                if not guildDB then return end
                local key = RF:GetGuildKey()
                if not key then return end
                -- Wipe and re-initialize with defaults
                wipe(guildDB)
                RF:MergeDefaults(RF.db.global.guilds[key], RF.DB_DEFAULTS.guild)
                RF._analyticsCache = nil
                RF:Print("All guild data has been purged. /reload recommended.")
            end)
    end)

    local resetBtn = RF:CreateButton(aboutCard, "Reset to Defaults", 150, 26, "danger")
    resetBtn:SetPoint("RIGHT", purgeAllBtn, "LEFT", -8, 0)
    resetBtn:SetScript("OnClick", function()
        if not RF.charDB then return end
        local defaults = RF.DB_DEFAULTS.char
        for key, val in pairs(defaults) do
            if key == "minimap" then
                RF.charDB.minimap = { hide = false }
            elseif key == "chatKeywords" then
                RF.charDB.chatKeywords = {}
            elseif type(val) == "table" then
                -- skip complex tables, keep current
            else
                RF.charDB[key] = val
            end
        end
        RF.charDB.visibleColumns = nil
        RF.charDB.columnOrder = nil
        RF.charDB.framePos = nil
        RF.charDB.dashboardHiddenCards = {}
        RF._analyticsCache = nil
        RF:UpdateJKeyOverride()
        if RF.mainFrame then
            RF.mainFrame:SetScale(1.0)
            RF.mainFrame:SetAlpha(0.95)
        end
        local LDBIcon = LibStub and LibStub("LibDBIcon-1.0", true)
        if LDBIcon then LDBIcon:Show("RosterForge") end
        RF:Print("All settings reset to defaults. /reload to fully apply.")
    end)

    -- Final content height for scroll
    local totalHeight = math.abs(ROW4_Y) + ROW4_H + SIDE_PAD
    scrollContent:SetHeight(totalHeight)
end

-------------------------------------------------------------------------------
-- J-Key Override: Replace default guild panel
-------------------------------------------------------------------------------
function RF:UpdateJKeyOverride()
    if InCombatLockdown() then
        -- Defer until combat ends — protected functions can't be called in combat
        RF._jKeyPending = true
        return
    end

    if RF.charDB and RF.charDB.replaceGuildPanel then
        SetOverrideBindingClick(RF.eventFrame, false, "J", "RosterForgeToggleBinding")
    else
        ClearOverrideBindings(RF.eventFrame)
    end
end

RF:RegisterCallback("PLAYER_ENTERING_WORLD", function()
    C_Timer.After(1, function()
        RF:UpdateJKeyOverride()
    end)
end)

-- Retry J-key override after combat ends
RF:RegisterCallback("PLAYER_REGEN_ENABLED", function()
    if RF._jKeyPending then
        RF._jKeyPending = nil
        RF:UpdateJKeyOverride()
    end
end)

-------------------------------------------------------------------------------
-- Tooltip Augmentation: Show RosterForge data on unit tooltips
-------------------------------------------------------------------------------
local function OnTooltipSetUnit(tooltip)
    if not RF.charDB or not RF.charDB.tooltipAugment then return end
    if not IsInGuild() then return end

    local _, unit = tooltip:GetUnit()
    if not unit then return end

    local name = UnitName(unit)
    if not name then return end

    local member = RF:GetMemberByName(name)
    if not member then return end

    tooltip:AddLine(" ")
    tooltip:AddLine("|cFF3B82F6[RosterForge]|r", 0.23, 0.51, 0.96)

    local joinDate = RF:GetJoinDate(name)
    if joinDate then
        tooltip:AddDoubleLine("Joined:", RF:FormatTimestamp(joinDate), 0.7, 0.7, 0.7, 1, 1, 1)
    end

    if member.altMain then
        tooltip:AddDoubleLine("Alt/Main:", member.altMain, 0.7, 0.7, 0.7, 0.4, 0.7, 1.0)
    end

    if member.mplusRating and member.mplusRating > 0 then
        local mc = member.mplusRatingColor
        if mc then
            tooltip:AddDoubleLine("M+ Rating:", tostring(member.mplusRating), 0.7, 0.7, 0.7, mc.r, mc.g, mc.b)
        else
            tooltip:AddDoubleLine("M+ Rating:", tostring(member.mplusRating), 0.7, 0.7, 0.7, 1, 1, 1)
        end
    end

    -- Show tags/roles
    if RF.charDB and RF.charDB.tooltipShowTags ~= false then
        local guildDB = RF:GetGuildDB()
        local tags = guildDB and guildDB.tags and guildDB.tags[name]
        if tags and #tags > 0 then
            local parts = {}
            for _, t in ipairs(tags) do
                local r = tonumber(t.color:sub(1, 2), 16) / 255
                local g = tonumber(t.color:sub(3, 4), 16) / 255
                local b = tonumber(t.color:sub(5, 6), 16) / 255
                tinsert(parts, format("|cFF%s%s|r", t.color, t.label))
            end
            tooltip:AddDoubleLine("Roles:", table.concat(parts, ", "), 0.7, 0.7, 0.7, 1, 1, 1)
        end
    end

    -- Show teams
    if (not RF.charDB or RF.charDB.tooltipShowTeams ~= false) and RF.GetMemberTeams then
        local memberTeams = RF:GetMemberTeams(name)
        if #memberTeams > 0 then
            local parts = {}
            for _, info in ipairs(memberTeams) do
                tinsert(parts, info.teamName)
            end
            tooltip:AddDoubleLine("Teams:", table.concat(parts, ", "), 0.7, 0.7, 0.7, 0.23, 0.51, 0.96)
        end
    end

    local extNote = RF:GetExtendedNote(name)
    if extNote and extNote ~= "" then
        tooltip:AddLine("RosterNote: " .. extNote, 0.6, 0.8, 0.6, true)
    end

    tooltip:Show()
end

RF:RegisterCallback("ADDON_LOADED", function()
    if TooltipDataProcessor and TooltipDataProcessor.AddTooltipPostCall then
        TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Unit, OnTooltipSetUnit)
    elseif GameTooltip.HookScript then
        GameTooltip:HookScript("OnTooltipSetUnit", OnTooltipSetUnit)
    end
end)
