-- ============================================================================
-- Vamoose's Companion - Settings Panel v2
-- Two-column master-detail: left sidebar (companion select) + right content (4 tabs)
-- Overall: 760x580, sidebar 180px, content 580px
-- ============================================================================

VC = VC or {}
VC.Settings = {}

local WHITE = "Interface\\Buttons\\WHITE8x8"

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

function VC.Settings:Initialize()
    -- Lazy build: panel created on first Toggle()
end

function VC.Settings:Toggle()
    if InCombatLockdown() then return end
    if not self.frame then self:Build() end
    local f = self.frame
    if f:IsShown() then
        if not f._closeAG then
            local ag = f:CreateAnimationGroup()
            local a = ag:CreateAnimation("Alpha")
            a:SetFromAlpha(1); a:SetToAlpha(0); a:SetDuration(0.15); a:SetSmoothing("IN")
            local s = ag:CreateAnimation("Scale")
            s:SetOrigin("CENTER", 0, 0); s:SetScaleFrom(1.0, 1.0); s:SetScaleTo(0.95, 0.95)
            s:SetDuration(0.15); s:SetSmoothing("IN")
            ag:SetScript("OnFinished", function() f:Hide(); f:SetAlpha(1); f:SetScale(1) end)
            f._closeAG = ag
        end
        if f._openAG and f._openAG:IsPlaying() then f._openAG:Stop() end
        f._closeAG:Play()
    else
        f:Show(); f:SetAlpha(0); f:SetScale(0.95)
        if not f._openAG then
            local ag = f:CreateAnimationGroup()
            local a = ag:CreateAnimation("Alpha")
            a:SetFromAlpha(0); a:SetToAlpha(1); a:SetDuration(0.2); a:SetSmoothing("OUT")
            local s = ag:CreateAnimation("Scale")
            s:SetOrigin("CENTER", 0, 0); s:SetScaleFrom(0.95, 0.95); s:SetScaleTo(1.0, 1.0)
            s:SetDuration(0.2); s:SetSmoothing("OUT")
            ag:SetScript("OnFinished", function() f:SetAlpha(1); f:SetScale(1) end)
            f._openAG = ag
        end
        if f._closeAG and f._closeAG:IsPlaying() then f._closeAG:Stop() end
        f._openAG:Play()
        self:SelectTab(self.activeTab or "companion")
        self:Refresh()
    end
end

-- ============================================================================
-- BUILD
-- ============================================================================

function VC.Settings:Build()
    local S    = VC.Constants.SETTINGS
    local W    = S.WIDTH         -- 760
    local H    = S.HEIGHT        -- 580
    local TH   = S.TITLE_BAR_H  -- 32
    local TB   = S.TAB_BAR_H    -- 28
    local SW   = S.SIDEBAR_W    -- 180
    local FH   = S.FOOTER_H     -- 20
    local c    = VC.Theme:GetScheme()

    -- -------------------------------------------------------------------------
    -- Main frame
    -- -------------------------------------------------------------------------
    local f = CreateFrame("Frame", "VC_SettingsFrame", UIParent, "BackdropTemplate")
    f:SetSize(W, H)
    f:SetPoint("CENTER")
    f:SetFrameStrata("MEDIUM")
    f:SetMovable(true)
    f:SetClampedToScreen(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetBackdrop(VC.Theme.BACKDROP_PANEL)
    f:SetBackdropColor(c.bg.r, c.bg.g, c.bg.b, c.bg.a)
    f:SetBackdropBorderColor(c.border.r, c.border.g, c.border.b, 1)
    tinsert(UISpecialFrames, "VC_SettingsFrame")
    self.frame = f
    VC.Theme:Register(f, "Frame")

    -- -------------------------------------------------------------------------
    -- Title bar
    -- -------------------------------------------------------------------------
    local titleBar = CreateFrame("Frame", nil, f)
    titleBar:SetPoint("TOPLEFT", 0, 0)
    titleBar:SetPoint("TOPRIGHT", 0, 0)
    titleBar:SetHeight(TH)

    local titleGrad = titleBar:CreateTexture(nil, "BACKGROUND")
    titleGrad:SetAllPoints()
    titleGrad:SetTexture(WHITE)
    titleGrad:SetVertexColor(c.accent.r, c.accent.g, c.accent.b, 0.08)


    local titleSep = titleBar:CreateTexture(nil, "ARTWORK")
    titleSep:SetHeight(1)
    titleSep:SetPoint("BOTTOMLEFT", 0, 0); titleSep:SetPoint("BOTTOMRIGHT", 0, 0)
    titleSep:SetTexture(WHITE)
    titleSep:SetVertexColor(c.border.r, c.border.g, c.border.b, 1)
    titleSep._alpha = 1.0; VC.Theme:Register(titleSep, "Separator")

    local titleText = titleBar:CreateFontString(nil, "OVERLAY")
    titleText:SetPoint("LEFT", 10, 0)
    titleText:SetFont("Fonts\\MORPHEUS.ttf", 14, "")
    titleText:SetText("Vamoose's Companion")
    titleText:SetTextColor(c.text_header.r, c.text_header.g, c.text_header.b, 1)
    titleText:SetShadowOffset(1, -1); titleText:SetShadowColor(0, 0, 0, 1)
    titleText.isHeader = true; VC.Theme:Register(titleText, "Text")

    local closeBtn = CreateFrame("Button", nil, titleBar)
    closeBtn:SetSize(16, 16)
    closeBtn:SetPoint("RIGHT", titleBar, "RIGHT", -6, 0)
    local closeIcon = closeBtn:CreateTexture(nil, "OVERLAY")
    closeIcon:SetAllPoints(); closeIcon:SetAtlas("XMarksTheSpot")
    closeIcon:SetVertexColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(closeIcon, "DimText")

    closeBtn:SetScript("OnClick", function() f:Hide() end)
    closeBtn:SetScript("OnEnter", function() local s = VC.Theme:GetScheme(); closeIcon:SetVertexColor(s.text_header.r, s.text_header.g, s.text_header.b, 1) end)
    closeBtn:SetScript("OnLeave", function() local s = VC.Theme:GetScheme(); closeIcon:SetVertexColor(s.text_dim.r, s.text_dim.g, s.text_dim.b, 1) end)
    f.titleBar = titleBar

    -- -------------------------------------------------------------------------
    -- Footer (full-width, 20px, at very bottom)
    -- -------------------------------------------------------------------------
    local footerSep = f:CreateTexture(nil, "ARTWORK")
    footerSep:SetHeight(1)
    footerSep:SetPoint("BOTTOMLEFT", 0, FH)
    footerSep:SetPoint("BOTTOMRIGHT", 0, FH)
    footerSep:SetTexture(WHITE)
    footerSep:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.3)
    footerSep._alpha = 0.3; VC.Theme:Register(footerSep, "Separator")


    local version = C_AddOns and C_AddOns.GetAddOnMetadata("VamoosesCompanion", "Version") or "Dev"
    local footerL = f:CreateFontString(nil, "OVERLAY")
    footerL:SetPoint("BOTTOMLEFT", 8, 4)
    footerL:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    footerL:SetText("v" .. version)
    footerL:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(footerL, "Text")

    local footerR = f:CreateFontString(nil, "OVERLAY")
    footerR:SetPoint("BOTTOMRIGHT", -8, 4)
    footerR:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    footerR:SetText("/vc help for commands")
    footerR:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(footerR, "Text")
    self.footerText = footerL

    -- -------------------------------------------------------------------------
    -- Vertical divider between sidebar and content
    -- -------------------------------------------------------------------------
    local divider = f:CreateTexture(nil, "ARTWORK")
    divider:SetWidth(1)
    divider:SetPoint("TOPLEFT", SW, -TH)
    divider:SetPoint("BOTTOMLEFT", SW, FH)
    divider:SetTexture(WHITE)
    divider:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.8)
    divider._alpha = 0.8; VC.Theme:Register(divider, "Separator")


    -- -------------------------------------------------------------------------
    -- Left sidebar
    -- -------------------------------------------------------------------------
    local sidebarH = H - TH - FH  -- 528
    local sidebar = CreateFrame("Frame", nil, f)
    sidebar:SetPoint("TOPLEFT", 0, -TH)
    sidebar:SetSize(SW, sidebarH)

    -- Slightly lighter bg than main frame
    local sidebarBg = sidebar:CreateTexture(nil, "BACKGROUND")
    sidebarBg:SetAllPoints()
    sidebarBg:SetTexture(WHITE)
    sidebarBg:SetVertexColor(c.panel.r, c.panel.g, c.panel.b, 0.6)
    self._sidebarBg = sidebarBg

    self.sidebar = sidebar

    -- "COMPANIONS" header
    local sideHeader = sidebar:CreateFontString(nil, "OVERLAY")
    sideHeader:SetPoint("TOPLEFT", 10, -12)
    sideHeader:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    sideHeader:SetText("COMPANIONS")
    sideHeader:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(sideHeader, "DimText")


    -- Sidebar scroll area (TOPLEFT at -28, BOTTOMRIGHT at +20 above footer)
    local sideScroll = CreateFrame("ScrollFrame", nil, sidebar)
    sideScroll:SetPoint("TOPLEFT", 0, -28)
    sideScroll:SetPoint("BOTTOMRIGHT", 0, 28)  -- 28 = randomise row height + padding
    sideScroll:EnableMouseWheel(true)

    local sideContent = CreateFrame("Frame", nil, sideScroll)
    sideContent:SetWidth(SW)
    sideScroll:SetScrollChild(sideContent)
    sideScroll:SetScript("OnMouseWheel", function(self, delta)
        local cur = self:GetVerticalScroll()
        local maxS = math.max(0, sideContent:GetHeight() - self:GetHeight())
        self:SetVerticalScroll(math.max(0, math.min(maxS, cur - delta * 40)))
    end)
    self._sideContent = sideContent

    -- Randomise checkbox at sidebar bottom
    self.randomCheck = VC.UI:CreateCheckbox(sidebar, "Randomise", function(checked)
        VC.Store:Dispatch("SET_CONFIG", { key = "randomCompanion", value = checked })
    end)
    self.randomCheck:SetPoint("BOTTOMLEFT", 10, 6)
    -- Shrink checkbox label font to 9px to fit sidebar width
    if self.randomCheck.label then
        self.randomCheck.label:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    end

    -- -------------------------------------------------------------------------
    -- Right content area
    -- -------------------------------------------------------------------------
    local contentX = SW + 1
    local contentW = W - contentX  -- 579
    local contentH = H - TH        -- 548 (footer is handled separately, content reaches it)

    local contentFrame = CreateFrame("Frame", nil, f)
    contentFrame:SetPoint("TOPLEFT", contentX, -TH)
    contentFrame:SetSize(contentW, contentH)
    self.contentFrame = contentFrame

    -- Tab bar (28px inside content frame)
    local tabBar = CreateFrame("Frame", nil, contentFrame)
    tabBar:SetPoint("TOPLEFT", 0, 0)
    tabBar:SetPoint("TOPRIGHT", 0, 0)
    tabBar:SetHeight(TB)

    local tabSep = tabBar:CreateTexture(nil, "ARTWORK")
    tabSep:SetHeight(1)
    tabSep:SetPoint("BOTTOMLEFT", 0, 0); tabSep:SetPoint("BOTTOMRIGHT", 0, 0)
    tabSep:SetTexture(WHITE)
    tabSep:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.6)
    tabSep._alpha = 0.6; VC.Theme:Register(tabSep, "Separator")

    local TAB_DEFS = {
        { key = "companion", label = "Companion" },
        { key = "display",   label = "Display"   },
        { key = "events",    label = "Events"    },
        { key = "history",   label = "History"   },
        { key = "info",      label = "Info"      },
    }
    self.tabs = {}
    local tabX = 0
    local tabPad = 16
    for _, def in ipairs(TAB_DEFS) do
        local btn = VC.UI:CreateTabButton(tabBar, def.label, function()
            self:SelectTab(def.key)
        end)
        local fs = btn:GetFontString()
        local textW = fs and fs:GetStringWidth() or 60
        local btnW = math.max(60, math.floor(textW + tabPad * 2))
        btn:SetWidth(btnW)
        btn:SetPoint("LEFT", tabX, 0)
        btn._tabKey = def.key
        self.tabs[def.key] = btn
        tabX = tabX + btnW
    end
    f.tabBar = tabBar

    -- Content pages area (below tab bar)
    local pageH = contentH - TB - FH  -- 500
    self.pages = {}

    self:BuildCompanionPage(contentFrame, contentW, pageH)
    self:BuildDisplayPage(contentFrame, contentW, pageH)
    self:BuildEventsPage(contentFrame, contentW, pageH)
    self:BuildHistoryPage(contentFrame, contentW, pageH)
    self:BuildInfoPage(contentFrame, contentW, pageH)

    -- -------------------------------------------------------------------------
    -- Populate sidebar companion rows (after pages are built)
    -- -------------------------------------------------------------------------
    self:BuildSidebarRows()

    -- Listen for state changes
    VC.EventBus:Register("VC_STATE_CHANGED", function()
        if self.frame and self.frame:IsShown() then
            self:Refresh()
        end
    end)

    f:Hide()
end

-- ============================================================================
-- SIDEBAR ROW CONSTRUCTION
-- ============================================================================

function VC.Settings:BuildSidebarRows()
    local c = VC.Theme:GetScheme()
    local SW = VC.Constants.SETTINGS.SIDEBAR_W
    local ROW_H = 84
    local content = self._sideContent

    -- Gather companion keys in a stable order
    local keys = {}
    for k in pairs(VC_CompanionProfiles) do
        if VC_QuoteDB and VC_QuoteDB[k] then keys[#keys + 1] = k end
    end
    table.sort(keys)

    self._sidebarRows = {}

    local y = 0
    for _, key in ipairs(keys) do
        local profile = VC_CompanionProfiles[key]
        if profile then
            local row = self:_BuildSidebarRow(content, key, profile, ROW_H, SW, c)
            row:SetPoint("TOPLEFT", 0, y)
            self._sidebarRows[key] = row
            y = y - ROW_H
        end
    end

    content:SetHeight(math.max(1, math.abs(y)))
end

function VC.Settings:_BuildSidebarRow(parent, key, profile, ROW_H, SW, c)
    local row = CreateFrame("Button", nil, parent)
    row:SetSize(SW, ROW_H)
    row:EnableMouse(true)
    row._companionKey = key

    -- Row background (transparent default, hover fill)
    local rowBg = row:CreateTexture(nil, "BACKGROUND")
    rowBg:SetAllPoints()
    rowBg:SetTexture(WHITE)
    rowBg:SetVertexColor(0, 0, 0, 0)
    row._bg = rowBg

    -- Left accent bar (3px, hidden unless selected-for-editing)
    local accentBar = row:CreateTexture(nil, "ARTWORK")
    accentBar:SetWidth(3)
    accentBar:SetPoint("TOPLEFT", 0, 0)
    accentBar:SetPoint("BOTTOMLEFT", 0, 0)
    accentBar:SetTexture(WHITE)
    local tc = profile.themeColor or { r = 0.302, g = 0.788, b = 0.965 }
    accentBar:SetVertexColor(tc.r, tc.g, tc.b, 0)
    row._accentBar = accentBar
    row._themeColor = tc

    -- 3D preview card (same setup as variant grid cards)
    local previewFrame = CreateFrame("Frame", nil, row, "BackdropTemplate")
    previewFrame:SetSize(ROW_H - 4, ROW_H - 4)
    previewFrame:SetPoint("LEFT", row, "LEFT", 8, 0)
    previewFrame:SetBackdrop(VC.Theme.BACKDROP_INSET)
    previewFrame:SetBackdropColor(c.bg.r, c.bg.g, c.bg.b, 1)
    previewFrame:SetBackdropBorderColor(tc.r, tc.g, tc.b, 0.5)
    row._previewFrame = previewFrame

    local previewScene = VC.UI:CreateModelScene(previewFrame, 0)
    previewScene:SetPoint("TOPLEFT", 2, -2)
    previewScene:SetPoint("BOTTOMRIGHT", -2, 2)
    previewScene:EnableMouse(false)
    -- Zoom camera in for small creatures (squirrels, critters)
    if profile.portraitScale and profile.portraitScale < 0.5 then
        previewScene:SetCameraPosition(0.8, 0, 0)
    end
    row._previewScene = previewScene

    local displayID = profile.displayID or 19598
    VC.UI:SetCreatureOnScene(previewScene, displayID, { scale = 0.9 })

    -- Name label (LEFT of preview RIGHT+8, TOP-aligned at -10 from row top)
    local nameLabel = row:CreateFontString(nil, "OVERLAY")
    nameLabel:SetPoint("TOPLEFT", previewFrame, "TOPRIGHT", 8, -10)
    nameLabel:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    nameLabel:SetText(profile.name)
    nameLabel:SetTextColor(c.text_header.r, c.text_header.g, c.text_header.b, 1)
    nameLabel.isHeader = true; VC.Theme:Register(nameLabel, "Text")

    -- Title label (below name, gap 3)

    -- Active companion indicator dot (TOPRIGHT, 6px cyan dot)
    local activeDot = row:CreateTexture(nil, "OVERLAY")
    activeDot:SetSize(6, 6)
    activeDot:SetPoint("TOPRIGHT", row, "TOPRIGHT", -6, -6)
    activeDot:SetTexture(WHITE)
    activeDot:SetVertexColor(c.accent.r, c.accent.g, c.accent.b, 1)
    activeDot:Hide()
    VC.Theme:Register(activeDot, "AccentText")
    row._activeDot = activeDot

    -- Row divider at bottom
    local rowDiv = row:CreateTexture(nil, "ARTWORK")
    rowDiv:SetHeight(1)
    rowDiv:SetPoint("BOTTOMLEFT", 0, 0); rowDiv:SetPoint("BOTTOMRIGHT", 0, 0)
    rowDiv:SetTexture(WHITE)
    rowDiv:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.3)
    rowDiv._alpha = 0.3; VC.Theme:Register(rowDiv, "Separator")

    -- Click: set as active companion + select for editing
    row:SetScript("OnClick", function()
        VC.Store:Dispatch("SET_CONFIG", { key = "activeCompanion", value = key })
        if VC.Portrait and VC.Portrait.UpdateModel then VC.Portrait:UpdateModel(true) end
        self:SelectEditingCompanion(key)
    end)

    row:SetScript("OnEnter", function(r)
        if not r._editing then
            local s = VC.Theme:GetScheme()
            r._bg:SetVertexColor(s.panel_alt and s.panel_alt.r or 0.067,
                                 s.panel_alt and s.panel_alt.g or 0.122,
                                 s.panel_alt and s.panel_alt.b or 0.220, 0.5)
        end
    end)
    row:SetScript("OnLeave", function(r)
        if not r._editing then
            r._bg:SetVertexColor(0, 0, 0, 0)
        end
    end)

    return row
end

-- Select a companion for editing (updates right panel, does not change active companion)
function VC.Settings:SelectEditingCompanion(key)
    self._editingKey = key

    for rowKey, row in pairs(self._sidebarRows or {}) do
        local tc = row._themeColor or { r = 0.302, g = 0.788, b = 0.965 }
        if rowKey == key then
            row._editing = true
            row._accentBar:SetVertexColor(tc.r, tc.g, tc.b, 1)
            row._bg:SetVertexColor(tc.r, tc.g, tc.b, 0.06)
            row._previewFrame:SetBackdropBorderColor(tc.r, tc.g, tc.b, 0.9)
        else
            row._editing = false
            row._accentBar:SetVertexColor(tc.r, tc.g, tc.b, 0)
            row._bg:SetVertexColor(0, 0, 0, 0)
            row._previewFrame:SetBackdropBorderColor(tc.r, tc.g, tc.b, 0.5)
        end
    end

    -- Update right panel companion content
    self:RefreshCompanionPage(key)
end

-- ============================================================================
-- TAB SELECTION
-- ============================================================================

function VC.Settings:SelectTab(key)
    self.activeTab = key
    for tabKey, page in pairs(self.pages) do
        if tabKey == key then
            page:SetAlpha(1)
            page:Show()
        else
            page:Hide()
        end
    end
    for tabKey, btn in pairs(self.tabs) do
        VC.UI:SetTabActive(btn, tabKey == key)
    end
    if key == "history" then self:RefreshHistory() end
end

-- ============================================================================
-- DISPLAY PAGE (wider layout)
-- ============================================================================

function VC.Settings:BuildDisplayPage(parent, W, pageH)
    local c = VC.Theme:GetScheme()
    local TB = VC.Constants.SETTINGS.TAB_BAR_H

    local page = CreateFrame("Frame", nil, parent)
    page:SetPoint("TOPLEFT", 0, -TB)
    page:SetPoint("TOPRIGHT", 0, -TB)
    page:SetHeight(pageH)
    page:Hide()
    self.pages["display"] = page

    local y = -12

    -- Chattiness section
    local chatHeader = page:CreateFontString(nil, "OVERLAY")
    chatHeader:SetPoint("TOPLEFT", 12, y)
    chatHeader:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    chatHeader:SetText("Chattiness")
    chatHeader:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(chatHeader, "DimText")
    y = y - 18

    local segOptions = {
        { key = "1", label = "Minimal" },
        { key = "2", label = "Relaxed" },
        { key = "3", label = "Chatty"  },
        { key = "4", label = "Maximum" },
    }
    -- Cap segmented group at 280px (70px per segment looks good)
    local segGroup = VC.UI:CreateSegmentedGroup(page, segOptions, 280, 24, function(selKey)
        local level = tonumber(selKey)
        if level then VC.Store:Dispatch("SET_CHATTINESS", { level = level }) end
    end)
    segGroup:SetPoint("TOPLEFT", 12, y)
    self.chattinessGroup = segGroup
    y = y - 28

    local chatDesc = page:CreateFontString(nil, "OVERLAY")
    chatDesc:SetPoint("TOPLEFT", 12, y)
    chatDesc:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    chatDesc:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    chatDesc:SetText("")
    self.chattinessDesc = chatDesc
    VC.Theme:Register(chatDesc, "DimText")
    y = y - 18

    -- Separator
    local sep1 = page:CreateTexture(nil, "ARTWORK")
    sep1:SetHeight(1)
    sep1:SetPoint("TOPLEFT", 8, y); sep1:SetPoint("TOPRIGHT", -8, y)
    sep1:SetTexture(WHITE); sep1:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.4)
    sep1._alpha = 0.4; VC.Theme:Register(sep1, "Separator")
    y = y - 10

    -- Display Mode section
    local modeHeader = page:CreateFontString(nil, "OVERLAY")
    modeHeader:SetPoint("TOPLEFT", 12, y)
    modeHeader:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    modeHeader:SetText("Display Mode")
    modeHeader:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(modeHeader, "DimText")
    y = y - 18

    self.modeBtns = {}
    local modeOpts = { { key = "bubble", label = "Talking Head" }, { key = "chat", label = "Chat" } }
    local modeX = 12
    for _, mode in ipairs(modeOpts) do
        local btn = VC.UI:CreatePillButton(page, mode.label, 110, 22, function()
            VC.Store:Dispatch("TOGGLE_DISPLAY_MODE", { mode = mode.key })
            self:RefreshDisplayPage()
        end)
        btn:SetPoint("TOPLEFT", modeX, y)
        btn._modeKey = mode.key
        self.modeBtns[mode.key] = btn
        modeX = modeX + 116
    end
    y = y - 30

    -- Mute checkbox
    self.muteCheck = VC.UI:CreateCheckbox(page, "Mute all quotes", function(checked)
        VC.Store:Dispatch("SET_MUTED", { muted = checked })
    end)
    self.muteCheck:SetPoint("TOPLEFT", 12, y)
    y = y - 28

    -- Separator
    local sep2 = page:CreateTexture(nil, "ARTWORK")
    sep2:SetHeight(1)
    sep2:SetPoint("TOPLEFT", 8, y); sep2:SetPoint("TOPRIGHT", -8, y)
    sep2:SetTexture(WHITE); sep2:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.4)
    sep2._alpha = 0.4; VC.Theme:Register(sep2, "Separator")
    y = y - 10

    -- Portrait Scale section (+/- buttons)
    local scaleRow = CreateFrame("Frame", nil, page)
    scaleRow:SetHeight(24)
    scaleRow:SetPoint("TOPLEFT", 12, y)
    scaleRow:SetPoint("TOPRIGHT", -12, y)

    local scaleLabel = scaleRow:CreateFontString(nil, "OVERLAY")
    scaleLabel:SetPoint("LEFT", 0, 0)
    scaleLabel:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    scaleLabel:SetText("Portrait Scale")
    scaleLabel:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(scaleLabel, "DimText")

    local scaleUpBtn = VC.UI:CreateButton(scaleRow, "+", 24, 22, function()
        local cur = VC.Store:GetState().config.portraitScale or 1.0
        local newScale = math.min(cur + 0.1, 1.6)
        newScale = math.floor(newScale * 10 + 0.5) / 10
        VC.Store:Dispatch("SET_CONFIG", { key = "portraitScale", value = newScale })
        self._scaleValue:SetText(string.format("%.0f%%", newScale * 100))
        if VC.Portrait and VC.Portrait.frame and not InCombatLockdown() then VC.Portrait.frame:SetScale(newScale) end
    end)
    scaleUpBtn:SetPoint("RIGHT", 0, 0)

    local scaleValue = scaleRow:CreateFontString(nil, "OVERLAY")
    scaleValue:SetPoint("RIGHT", scaleUpBtn, "LEFT", -4, 0)
    scaleValue:SetWidth(36)
    scaleValue:SetJustifyH("CENTER")
    scaleValue:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    local curScale = VC.Store:GetState().config.portraitScale or 1.0
    scaleValue:SetText(string.format("%.0f%%", curScale * 100))
    scaleValue:SetTextColor(c.accent.r, c.accent.g, c.accent.b, 1)
    VC.Theme:Register(scaleValue, "AccentText")
    self._scaleValue = scaleValue

    local scaleDownBtn = VC.UI:CreateButton(scaleRow, "-", 24, 22, function()
        local cur = VC.Store:GetState().config.portraitScale or 1.0
        local newScale = math.max(cur - 0.1, 0.6)
        newScale = math.floor(newScale * 10 + 0.5) / 10
        VC.Store:Dispatch("SET_CONFIG", { key = "portraitScale", value = newScale })
        self._scaleValue:SetText(string.format("%.0f%%", newScale * 100))
        if VC.Portrait and VC.Portrait.frame and not InCombatLockdown() then VC.Portrait.frame:SetScale(newScale) end
    end)
    scaleDownBtn:SetPoint("RIGHT", scaleValue, "LEFT", -4, 0)
    y = y - 30

    -- Portrait Frame style
    local frameHeader = page:CreateFontString(nil, "OVERLAY")
    frameHeader:SetPoint("TOPLEFT", 12, y)
    frameHeader:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    frameHeader:SetText("Portrait Frame")
    frameHeader:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(frameHeader, "DimText")
    y = y - 18

    local frameOpts = {
        { key = "atlas",   label = "Classic" },
        { key = "ornate",  label = "Ornate"  },
        { key = "minimal", label = "Minimal" },
    }
    local frameGroup = VC.UI:CreateSegmentedGroup(page, frameOpts, 270, 24, function(selKey)
        VC.Store:Dispatch("SET_CONFIG", { key = "portraitFrame", value = selKey })
        if VC.Portrait and VC.Portrait.frame then
            VC.UI:ApplyPortraitFrameStyle(VC.Portrait.frame)
        end
    end)
    frameGroup:SetPoint("TOPLEFT", 12, y)
    self._frameGroup = frameGroup
    y = y - 28

    -- Toast Frame style
    local toastHeader = page:CreateFontString(nil, "OVERLAY")
    toastHeader:SetPoint("TOPLEFT", 12, y)
    toastHeader:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    toastHeader:SetText("Toast Frame")
    toastHeader:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(toastHeader, "DimText")
    y = y - 18

    local toastGroup = VC.UI:CreateSegmentedGroup(page, frameOpts, 270, 24, function(selKey)
        VC.Store:Dispatch("SET_CONFIG", { key = "toastFrame", value = selKey })
        if VC.Display and VC.Display.talkingHead then
            VC.Display:ApplyToastStyle(VC.Display.talkingHead)
        end
    end)
    toastGroup:SetPoint("TOPLEFT", 12, y)
    self._toastGroup = toastGroup
    y = y - 30

    -- Separator
    local sep3 = page:CreateTexture(nil, "ARTWORK")
    sep3:SetHeight(1)
    sep3:SetPoint("TOPLEFT", 8, y); sep3:SetPoint("TOPRIGHT", -8, y)
    sep3:SetTexture(WHITE); sep3:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.4)
    sep3._alpha = 0.4; VC.Theme:Register(sep3, "Separator")
    y = y - 10

    -- Theme label + name
    local themeRowLabel = page:CreateFontString(nil, "OVERLAY")
    themeRowLabel:SetPoint("TOPLEFT", 12, y)
    themeRowLabel:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    themeRowLabel:SetText("Theme:")
    themeRowLabel:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(themeRowLabel, "DimText")

    local themeNameLabel = page:CreateFontString(nil, "OVERLAY")
    themeNameLabel:SetPoint("LEFT", themeRowLabel, "RIGHT", 5, 0)
    themeNameLabel:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    themeNameLabel:SetTextColor(c.text.r, c.text.g, c.text.b, 1)
    VC.Theme:Register(themeNameLabel, "Text")
    self.themeNameLabel = themeNameLabel
    y = y - 20

    -- Theme color swatches (VPP pattern)
    local swatchSize = 20
    local swatchGap = 3
    local swatchX = 12
    self._themeChips = {}

    for _, themeKey in ipairs(VC.Constants.ThemeOrder) do
        local themeName = VC.Constants.ThemeNames[themeKey]
        local displayName = VC.Constants.ThemeDisplayNames[themeKey] or themeKey
        local themeScheme = VC.Colors.Schemes[themeName]
        local accent = themeScheme and themeScheme.accent

        local btn = CreateFrame("Button", nil, page)
        btn:SetSize(swatchSize, swatchSize)
        btn:SetPoint("TOPLEFT", swatchX, y)
        swatchX = swatchX + swatchSize + swatchGap

        local fill = btn:CreateTexture(nil, "ARTWORK")
        fill:SetPoint("TOPLEFT", 2, -2)
        fill:SetPoint("BOTTOMRIGHT", -2, 2)
        fill:SetTexture(WHITE)
        if accent then fill:SetVertexColor(accent.r, accent.g, accent.b, 1) end

        local border = btn:CreateTexture(nil, "BORDER")
        border:SetPoint("TOPLEFT", -1, 1)
        border:SetPoint("BOTTOMRIGHT", 1, -1)
        border:SetTexture(WHITE)
        border:SetVertexColor(1, 1, 1, 0.9)
        border:Hide()
        btn._fill = fill
        btn._border = border

        btn:SetScript("OnClick", function()
            local newTheme = themeKey
            VC.Store:Dispatch("SET_CONFIG", { key = "theme", value = newTheme })
            VC.Constants:ApplyTheme()
            VC.EventBus:Trigger("VC_THEME_UPDATE", { themeName = VC.Constants.ThemeNames[newTheme] })
            self:Refresh()
        end)
        btn:SetScript("OnEnter", function(self)
            if accent then self._fill:SetVertexColor(accent.r, accent.g, accent.b, 0.7) end
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(displayName, 1, 1, 1)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function(self)
            if accent then self._fill:SetVertexColor(accent.r, accent.g, accent.b, 1) end
            GameTooltip:Hide()
        end)

        self._themeChips[themeKey] = btn
    end
end

function VC.Settings:RefreshDisplayPage()
    local state = VC.Store:GetState()
    local c = VC.Theme:GetScheme()

    if self.chattinessGroup then
        self.chattinessGroup:SetValue(tostring(state.config.chattiness))
        local tier = VC.Constants.CHATTINESS[state.config.chattiness]
        if tier and self.chattinessDesc then
            self.chattinessDesc:SetText(tier.label .. " -- global CD " .. tier.globalCD .. "s")
        end
    end

    for key, btn in pairs(self.modeBtns or {}) do
        local isActive = state.config.displayModes and state.config.displayModes[key]
        VC.UI:SetPillActive(btn, isActive)
    end

    if self.muteCheck then self.muteCheck.checkbox:SetChecked(state.config.muted) end

    -- Update portrait scale display
    if self._scaleValue then
        local s = state.config.portraitScale or 1.0
        self._scaleValue:SetText(string.format("%.0f%%", s * 100))
    end

    if self._frameGroup then
        self._frameGroup:SetValue(state.config.portraitFrame or "atlas")
    end
    if self._toastGroup then
        self._toastGroup:SetValue(state.config.toastFrame or "atlas")
    end

    local themeKey = state.config.theme or "slipstreamdark"
    local displayName = VC.Constants.ThemeDisplayNames[themeKey] or themeKey
    if self.themeNameLabel then self.themeNameLabel:SetText(displayName) end
    -- Update theme chip borders
    if self._themeChips then
        for key, btn in pairs(self._themeChips) do
            btn._border:SetShown(key == themeKey)
        end
    end
end

-- ============================================================================
-- EVENTS PAGE (3-column toggle layout at 548px inner width)
-- ============================================================================

function VC.Settings:BuildEventsPage(parent, W, pageH)
    local c = VC.Theme:GetScheme()
    local TB = VC.Constants.SETTINGS.TAB_BAR_H

    local page = CreateFrame("Frame", nil, parent)
    page:SetPoint("TOPLEFT", 0, -TB)
    page:SetPoint("TOPRIGHT", 0, -TB)
    page:SetHeight(pageH)
    page:Hide()
    self.pages["events"] = page

    local sf, content = VC.UI:CreateScrollFrame(page, W - 8, pageH - 4)
    sf:SetPoint("TOPLEFT", 4, -2)
    VC.UI.StyleScrollBar(sf)

    local y = 0
    self.eventChecks = {}

    local GROUPS = {
        {
            label = "ADVENTURING",
            events = { "boss", "death", "levelup", "zone", "achieve", "quest", "ambient", "scenario" },
        },
        {
            label = "ECONOMY",
            events = { "gold", "farming", "rep", "reprank", "crafting", "fishing", "mission" },
        },
        {
            label = "SOCIAL",
            events = { "login", "friendonline", "combatend", "group", "afkreturn", "logout" },
        },
        {
            label = "COLLECTIONS",
            events = { "housing", "transmog", "appearance", "transmogcollection", "mythicplus", "petbattle", "achieveprogress" },
        },
        {
            label = "COMBAT",
            events = { "resurrection", "battlerez", "travelerslog", "craftingorder", "rarespawn" },
        },
    }

    local innerW = W - 44
    -- 3 columns at 548px inner width = ~182px each
    local NCOLS = 3
    local colW = math.floor((innerW - 8) / NCOLS)

    for _, group in ipairs(GROUPS) do
        local header = VC.UI:CreateSectionLabel(content, group.label)
        header:SetPoint("TOPLEFT", 0, y)
        header:SetWidth(innerW - 90)
        y = y - 4

        local allBtn = VC.UI:CreateButton(content, "All", 40, 16, function()
            for _, et in ipairs(group.events) do
                VC.Store:Dispatch("SET_EVENT_TOGGLE", { eventType = et, enabled = true })
            end
        end)
        allBtn:SetPoint("TOPRIGHT", content, "TOPRIGHT", -50, y + 4)

        local noneBtn = VC.UI:CreateButton(content, "None", 40, 16, function()
            for _, et in ipairs(group.events) do
                VC.Store:Dispatch("SET_EVENT_TOGGLE", { eventType = et, enabled = false })
            end
        end)
        noneBtn:SetPoint("TOPRIGHT", content, "TOPRIGHT", -4, y + 4)

        y = y - 22

        for i, eventType in ipairs(group.events) do
            local label = VC.Constants.EVENT_TYPE_LABELS[eventType] or eventType
            local cb = VC.UI:CreateCheckbox(content, label, function(checked)
                VC.Store:Dispatch("SET_EVENT_TOGGLE", { eventType = eventType, enabled = checked })
            end)
            local col = (i - 1) % NCOLS
            local row = math.floor((i - 1) / NCOLS)
            cb:SetPoint("TOPLEFT", col * colW + 4, y - row * 24)
            self.eventChecks[eventType] = cb
        end

        local groupRows = math.ceil(#group.events / NCOLS)
        y = y - groupRows * 24 - 8
    end

    content:SetHeight(math.max(1, math.abs(y) + 20))
end

-- ============================================================================
-- HISTORY PAGE (wider: quote text wraps at ~420px)
-- ============================================================================

function VC.Settings:BuildHistoryPage(parent, W, pageH)
    local c = VC.Theme:GetScheme()
    local TB = VC.Constants.SETTINGS.TAB_BAR_H

    local page = CreateFrame("Frame", nil, parent)
    page:SetPoint("TOPLEFT", 0, -TB)
    page:SetPoint("TOPRIGHT", 0, -TB)
    page:SetHeight(pageH)
    page:Hide()
    self.pages["history"] = page

    local filterBarH = 30
    local filterBar = CreateFrame("Frame", nil, page)
    filterBar:SetPoint("TOPLEFT", 4, -4); filterBar:SetPoint("TOPRIGHT", -4, -4)
    filterBar:SetHeight(filterBarH)
    self.historyFilter = "all"
    self.historyFilterBtns = {}

    local fkeys = { "all" }
    for k in pairs(VC_CompanionProfiles) do
        if VC_QuoteDB and VC_QuoteDB[k] then fkeys[#fkeys + 1] = k end
    end
    table.sort(fkeys, function(a, b) return (a == "all") or (b ~= "all" and a < b) end)

    local fx = 0
    for _, fk in ipairs(fkeys) do
        local lbl = (fk == "all") and "All" or (VC_CompanionProfiles[fk] and VC_CompanionProfiles[fk].name or fk)
        local fw = math.max(36, math.floor(#lbl * 7 + 16))
        local fbtn = VC.UI:CreatePillButton(filterBar, lbl, fw, 22, function()
            self.historyFilter = fk
            for k2, b in pairs(self.historyFilterBtns) do
                VC.UI:SetPillActive(b, k2 == fk)
            end
            self:RefreshHistory()
        end)
        fbtn:SetPoint("LEFT", fx, 0)
        self.historyFilterBtns[fk] = fbtn
        fx = fx + fw + 4
    end
    VC.UI:SetPillActive(self.historyFilterBtns["all"], true)

    local filterSep = page:CreateTexture(nil, "ARTWORK")
    filterSep:SetHeight(1)
    filterSep:SetPoint("TOPLEFT", 4, -(filterBarH + 6)); filterSep:SetPoint("TOPRIGHT", -4, -(filterBarH + 6))
    filterSep:SetTexture(WHITE); filterSep:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.4)
    filterSep._alpha = 0.4; VC.Theme:Register(filterSep, "Separator")

    local histScrollH = pageH - filterBarH - 8 - 24
    local histScrollW = W - 8
    local histScroll, histContent = VC.UI:CreateScrollFrame(page, histScrollW, histScrollH)
    histScroll:SetPoint("TOPLEFT", 4, -(filterBarH + 8))
    VC.UI.StyleScrollBar(histScroll)
    self.historyContent = histContent
    self.historyRows = {}

    -- Clear button (dim text, bottom right)
    local clearBtn = page:CreateFontString(nil, "OVERLAY")
    clearBtn:SetPoint("BOTTOMRIGHT", -8, 4)
    clearBtn:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    clearBtn:SetText("Clear History")
    clearBtn:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(clearBtn, "DimText")

    local clearClickArea = CreateFrame("Button", nil, page)
    clearClickArea:SetPoint("BOTTOMRIGHT", -8, 2)
    clearClickArea:SetSize(90, 18)
    clearClickArea:SetScript("OnClick", function()
        VC.Store:Dispatch("CLEAR_HISTORY")
        if VC.Engine and VC.Engine.RebuildHistorySet then VC.Engine:RebuildHistorySet() end
        self:RefreshHistory()
    end)
    clearClickArea:SetScript("OnEnter", function() local s = VC.Theme:GetScheme(); clearBtn:SetTextColor(s.text_header.r, s.text_header.g, s.text_header.b, 1) end)
    clearClickArea:SetScript("OnLeave", function() local s = VC.Theme:GetScheme(); clearBtn:SetTextColor(s.text_dim.r, s.text_dim.g, s.text_dim.b, 1) end)
end

-- ============================================================================
-- REFRESH (sync all controls to current state)
-- ============================================================================
-- INFO PAGE (discord, support, about)
-- ============================================================================

function VC.Settings:BuildInfoPage(parent, W, pageH)
    local c = VC.Theme:GetScheme()
    local TB = VC.Constants.SETTINGS.TAB_BAR_H

    local page = CreateFrame("Frame", nil, parent)
    page:SetPoint("TOPLEFT", 0, -TB)
    page:SetPoint("TOPRIGHT", 0, -TB)
    page:SetHeight(pageH)
    page:Hide()
    self.pages["info"] = page

    local y = -12

    -- Discord link
    local discordRow = CreateFrame("Frame", nil, page)
    discordRow:SetHeight(24)
    discordRow:SetPoint("TOPLEFT", 12, y)
    discordRow:SetPoint("TOPRIGHT", -12, y)

    local discordIcon = discordRow:CreateTexture(nil, "ARTWORK")
    discordIcon:SetPoint("LEFT", 0, 0)
    discordIcon:SetSize(20, 20)
    discordIcon:SetAtlas("UI-ChatIcon-Discord")

    local discordLabel = discordRow:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    discordLabel:SetPoint("LEFT", discordIcon, "RIGHT", 6, 0)
    discordLabel:SetText("Discord")

    local discordBox = CreateFrame("EditBox", nil, discordRow)
    discordBox:SetPoint("LEFT", discordLabel, "RIGHT", 8, 0)
    discordBox:SetPoint("RIGHT", 0, 0)
    discordBox:SetHeight(20)
    discordBox:SetAutoFocus(false)
    discordBox:SetFontObject(GameFontHighlightSmall)
    discordBox:SetText("https://discord.gg/RWZaxJaHFP")
    discordBox:SetCursorPosition(0)
    discordBox:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(discordBox, "DimText")
    discordBox:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
    discordBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    discordBox:SetScript("OnTextChanged", function(self) self:SetText("https://discord.gg/RWZaxJaHFP"); self:SetCursorPosition(0) end)
    y = y - 26

    -- Buy Me a Coffee link
    local bmacRow = CreateFrame("Frame", nil, page)
    bmacRow:SetHeight(24)
    bmacRow:SetPoint("TOPLEFT", 12, y)
    bmacRow:SetPoint("TOPRIGHT", -12, y)

    local bmacIcon = bmacRow:CreateTexture(nil, "ARTWORK")
    bmacIcon:SetPoint("LEFT", 0, 0)
    bmacIcon:SetSize(20, 20)
    bmacIcon:SetAtlas("auctionhouse-icon-coin-default")

    local bmacLabel = bmacRow:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    bmacLabel:SetPoint("LEFT", bmacIcon, "RIGHT", 6, 0)
    bmacLabel:SetText("Support")

    local bmacBox = CreateFrame("EditBox", nil, bmacRow)
    bmacBox:SetPoint("LEFT", bmacLabel, "RIGHT", 8, 0)
    bmacBox:SetPoint("RIGHT", 0, 0)
    bmacBox:SetHeight(20)
    bmacBox:SetAutoFocus(false)
    bmacBox:SetFontObject(GameFontHighlightSmall)
    bmacBox:SetText("https://buymeacoffee.com/Vamoose")
    bmacBox:SetCursorPosition(0)
    bmacBox:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(bmacBox, "DimText")
    bmacBox:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
    bmacBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    bmacBox:SetScript("OnTextChanged", function(self) self:SetText("https://buymeacoffee.com/Vamoose"); self:SetCursorPosition(0) end)
    y = y - 30

    -- Separator
    local sep = page:CreateTexture(nil, "ARTWORK")
    sep:SetHeight(1)
    sep:SetPoint("TOPLEFT", 8, y); sep:SetPoint("TOPRIGHT", -8, y)
    sep:SetTexture(WHITE); sep:SetVertexColor(c.border.r, c.border.g, c.border.b, 0.4)
    sep._alpha = 0.4; VC.Theme:Register(sep, "Separator")
    y = y - 14

    -- About text
    local about = page:CreateFontString(nil, "OVERLAY")
    about:SetPoint("TOPLEFT", 12, y)
    about:SetPoint("TOPRIGHT", -12, y)
    about:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
    about:SetJustifyH("LEFT")
    about:SetWordWrap(true)
    about:SetSpacing(4)
    about:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
    VC.Theme:Register(about, "DimText")
    about:SetText(
        "Vamoose's Companion gives you a friend who pays attention while you play. " ..
        "They react to what you do - boss kills, achievements, fishing, crafting, exploring new zones, and dozens more moments. " ..
        "Each companion has their own personality and hundreds of hand-written quotes.\n\n" ..
        "Pick a companion, adjust the chattiness, and just play. They'll take it from there."
    )
end

-- ============================================================================

function VC.Settings:Refresh()
    if not self.frame then return end
    local state = VC.Store:GetState()
    local c = VC.Theme:GetScheme()
    local activeKey = state.config.activeCompanion

    -- Ensure editing key is set (default to active companion)
    if not self._editingKey then
        self._editingKey = activeKey
    end

    -- Sidebar background tint
    if self._sidebarBg then
        self._sidebarBg:SetVertexColor(c.panel.r, c.panel.g, c.panel.b, 0.6)
    end

    -- Update sidebar active dot + bond fills + mood indicators
    self:RefreshSidebar(activeKey)

    -- Update right panel companion content for editing key
    self:RefreshCompanionPage(self._editingKey)

    -- Display page
    self:RefreshDisplayPage()

    -- Event toggles
    for eventType, cb in pairs(self.eventChecks or {}) do
        cb.checkbox:SetChecked(state.config.eventToggles[eventType] ~= false)
    end

    -- Random companion checkbox
    if self.randomCheck and self.randomCheck.checkbox then
        self.randomCheck.checkbox:SetChecked(state.config.randomCompanion)
    end

    -- History (only if visible)
    if self.activeTab == "history" then self:RefreshHistory() end
end

function VC.Settings:RefreshSidebar(activeKey)
    if not self._sidebarRows then return end
    local c = VC.Theme:GetScheme()

    for key, row in pairs(self._sidebarRows) do
        -- Active companion dot
        if row._activeDot then row._activeDot:SetShown(key == activeKey) end
        -- Preview frame bg (theme-aware, preserve companion border color)
        if row._previewFrame and row._previewFrame.GetBackdrop and row._previewFrame:GetBackdrop() then
            row._previewFrame:SetBackdropColor(c.bg.r, c.bg.g, c.bg.b, 1)
        end

        -- Bond fill
        if row._bondFill and VC.Bonding then
            local info = VC.Bonding:GetLevelInfo(key)
            if info then
                local pct = 0
                if info.nextThreshold and info.nextThreshold > 0 then
                    local levels = VC.Constants.BONDING.LEVELS
                    local prevThresh = 0
                    for _, lvl in ipairs(levels) do
                        if lvl.name == info.name then break end
                        prevThresh = lvl.threshold
                    end
                    local span = info.nextThreshold - prevThresh
                    if span > 0 then pct = math.max(0, math.min(1, (info.rapport - prevThresh) / span)) end
                else
                    pct = 1
                end
                local fw = math.max(1, math.floor((row._bondFillMaxW or 110) * pct))
                row._bondFill:SetWidth(fw)
            end
        end

        -- Mood dot + name
        if row._moodDot and VC.Mood then
            local mood = VC.Mood:GetMood()
            local mc = VC.Constants.MOOD_COLORS[mood] or VC.Constants.MOOD_COLORS.NEUTRAL
            row._moodDot:SetVertexColor(mc.r, mc.g, mc.b, 1)
            if row._moodName then
                local moodStr = mood:sub(1,1) .. mood:sub(2):lower()
                row._moodName:SetText(moodStr)
                row._moodName:SetTextColor(mc.r, mc.g, mc.b, 1)
            end
        end
    end
end

-- ============================================================================
-- HISTORY REFRESH
-- ============================================================================

function VC.Settings:RefreshHistory()
    if not self.historyContent then return end
    local state = VC.Store:GetState()
    local history = state.quoteHistory
    local count = #history
    local c = VC.Theme:GetScheme()
    local filterKey = self.historyFilter or "all"
    local W = VC.Constants.SETTINGS.WIDTH - VC.Constants.SETTINGS.SIDEBAR_W

    for _, row in ipairs(self.historyRows) do
        row:Hide()
    end

    local rowH = 44
    local rowW = W - 36
    local y = 0
    local visIdx = 0

    for i = count, math.max(1, count - 99), -1 do
        local entry = history[i]
        if type(entry) ~= "table" then break end

        if filterKey == "all" or entry.companionKey == filterKey then
            visIdx = visIdx + 1
            local row = self.historyRows[visIdx]
            if not row then
                row = CreateFrame("Button", nil, self.historyContent)
                row:SetSize(rowW, rowH)
                row:EnableMouse(true)

                local rowBg = row:CreateTexture(nil, "BACKGROUND")
                rowBg:SetAllPoints(); rowBg:SetColorTexture(1, 1, 1, 0)
                row.bg = rowBg

                row:SetScript("OnEnter", function(self)
                    self.bg:SetColorTexture(c.panel_alt and c.panel_alt.r or 0.067,
                                           c.panel_alt and c.panel_alt.g or 0.122,
                                           c.panel_alt and c.panel_alt.b or 0.220, 0.5)
                end)
                row:SetScript("OnLeave", function(self) self.bg:SetColorTexture(1, 1, 1, 0) end)

                local rowSep = row:CreateTexture(nil, "ARTWORK")
                rowSep:SetHeight(1)
                rowSep:SetPoint("BOTTOMLEFT", 0, 0); rowSep:SetPoint("BOTTOMRIGHT", 0, 0)
                rowSep:SetColorTexture(c.border.r, c.border.g, c.border.b, 0.4)

                local dot = row:CreateTexture(nil, "ARTWORK")
                dot:SetSize(8, 8); dot:SetPoint("TOPLEFT", 2, -8)
                dot:SetTexture(WHITE)
                row.dot = dot

                -- Wider quote text: fills from LEFT+16 to RIGHT-36 (more room at 580px)
                local qt = row:CreateFontString(nil, "OVERLAY")
                qt:SetPoint("TOPLEFT", 16, -4)
                qt:SetPoint("RIGHT", row, "RIGHT", -36, 0)
                qt:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
                qt:SetJustifyH("LEFT"); qt:SetMaxLines(1)
                qt:SetShadowOffset(1, -1); qt:SetShadowColor(0, 0, 0, 0.8)
                row.quoteText = qt

                local meta = row:CreateFontString(nil, "OVERLAY")
                meta:SetPoint("TOPLEFT", 16, -20)
                meta:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
                meta:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
                row.metaText = meta

                local replayBtn = CreateFrame("Button", nil, row)
                replayBtn:SetSize(28, 20); replayBtn:SetPoint("RIGHT", row, "RIGHT", -4, 4)
                local replayLabel = replayBtn:CreateFontString(nil, "OVERLAY")
                replayLabel:SetAllPoints(); replayLabel:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
                replayLabel:SetText(">"); replayLabel:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1)
                replayBtn:SetScript("OnEnter", function() replayLabel:SetTextColor(c.accent.r, c.accent.g, c.accent.b, 1) end)
                replayBtn:SetScript("OnLeave", function() replayLabel:SetTextColor(c.text_dim.r, c.text_dim.g, c.text_dim.b, 1) end)
                row.replayBtn = replayBtn

                self.historyRows[visIdx] = row
            end

            row:SetPoint("TOPLEFT", 0, y)
            row:Show()

            local qc = VC.Constants.QUALITY_COLORS[entry.quality] or VC.Constants.QUALITY_COLORS.common
            row.dot:SetColorTexture(qc.r, qc.g, qc.b, 1)

            local text = entry.text or ""
            if #text > 100 then text = text:sub(1, 97) .. "..." end
            row.quoteText:SetText('"' .. text .. '"')
            row.quoteText:SetTextColor(qc.r, qc.g, qc.b)

            local companionName = ""
            local cp = VC_CompanionProfiles and VC_CompanionProfiles[entry.companionKey]
            if cp then companionName = cp.name end
            local eventLabel = VC.Constants.EVENT_TYPE_LABELS[entry.eventType] or entry.eventType or ""
            local timeAgo = ""
            if entry.timestamp then
                local delta = math.max(0, GetTime() - entry.timestamp)
                if delta < 60 then
                    timeAgo = "just now"
                elseif delta < 3600 then
                    timeAgo = math.floor(delta / 60) .. "m ago"
                else
                    timeAgo = math.floor(delta / 3600) .. "h ago"
                end
            end
            row.metaText:SetText(companionName .. " / " .. eventLabel .. " / " .. timeAgo)

            local replayEntry = entry
            row.replayBtn:SetScript("OnClick", function()
                local companion = VC_CompanionProfiles and VC_CompanionProfiles[replayEntry.companionKey]
                if companion then
                    VC.EventBus:Trigger("VC_QUOTE_READY", {
                        quote = { id = replayEntry.id, text = replayEntry.text, quality = replayEntry.quality, tags = {} },
                        companion = companion,
                        event = { eventType = replayEntry.eventType, context = {} },
                    })
                end
            end)

            y = y - rowH
        end
    end

    if self.historyContent then
        self.historyContent:SetHeight(math.max(1, math.abs(y)))
    end
end
