local _, ns = ...
local L = ns.L or {}

local UIData = ns.UIData or {}
local PREMIUM = UIData.PREMIUM or {}
local THEME = UIData.THEME or {}

local UISidebarView = {}
ns.UISidebarView = UISidebarView

local BACKDROP_TEMPLATE = BackdropTemplateMixin and "BackdropTemplate" or nil
local SIDEBAR_SORT_OPTIONS = UIData.SIDEBAR_SORT_OPTIONS or {}

local function getSidebarCharacterName(character)
    local name = character and (character.name or character.key) or ""
    return string.lower(tostring(name))
end

local function isPriorityScoreVisible()
    return not (ns.IsAltScoreHidden and ns:IsAltScoreHidden())
end

local function getPriorityInfo(character)
    if not isPriorityScoreVisible() then
        return nil
    end
    if ns.GetAltPriorityScore then
        return ns:GetAltPriorityScore(character)
    end
    return nil
end

local function getPriorityColor(priority)
    if priority and type(priority.color) == "table" then
        return priority.color[1] or 1, priority.color[2] or 1, priority.color[3] or 1
    end
    return THEME.metalInset[1], THEME.metalInset[2], THEME.metalInset[3]
end

local function formatSidebarMeta(character, priority, showScore)
    local metaParts = {
        string.format(L.SIDEBAR_ROW_LEVEL_SHORT or "Lvl %d", character.level or 0),
    }

    if character.itemLevel and character.itemLevel > 0 then
        metaParts[#metaParts + 1] = string.format(L.SIDEBAR_ROW_ILVL or "ilvl %d", character.itemLevel)
    end

    if showScore ~= false and priority and priority.coloredLabel then
        metaParts[#metaParts + 1] = priority.coloredLabel
    end

    return table.concat(metaParts, "  |  ")
end

local function showPriorityTooltip(owner, character, priority)
    if not owner or not priority then
        return
    end

    GameTooltip:SetOwner(owner, "ANCHOR_RIGHT")
    GameTooltip:AddLine(L.PRIORITY_SCORE_TOOLTIP_TITLE or "Alt Score", THEME.gold[1], THEME.gold[2], THEME.gold[3], true)
    local r, g, b = getPriorityColor(priority)
    GameTooltip:AddLine(string.format(L.PRIORITY_SCORE_TOOLTIP_VALUE or "AS: %d / 100", priority.score or 0), r, g, b, true)
    GameTooltip:AddLine(L.PRIORITY_SCORE_TOOLTIP_LINE or "Higher score means this character has more completed Vaultloom progress.", 0.72, 0.78, 0.88, true)
    if character and character.name then
        GameTooltip:AddLine(character.name, 0.86, 0.86, 0.82, true)
    end
    GameTooltip:AddLine(L.SIDEBAR_SET_MAIN_RIGHT_CLICK or "Right-click: set as Main", 0.72, 0.78, 0.88, true)
    GameTooltip:Show()
end

local function applyRenownPortraitFrame(texture, character)
    if not texture then
        return
    end

    local frameState = ns.GetRenownSelectedPortraitFrame and ns:GetRenownSelectedPortraitFrame(character) or nil
    if frameState and frameState.visible == false then
        texture:Hide()
        return
    end

    local frameTexture = nil
    if frameState and frameState.index and PREMIUM.renownFrames then
        frameTexture = PREMIUM.renownFrames[tonumber(frameState.index)]
    end
    texture:SetTexture(frameTexture or PREMIUM.classRing)
    texture:Show()
end

local function getWarbandBadgeTexture(character)
    if ns.IsRenownSystemEnabled and not ns:IsRenownSystemEnabled() then
        return nil, nil
    end

    local view = ns.GetRenownView and ns:GetRenownView(character, "warband") or nil
    local rank = type(view) == "table" and type(view.rank) == "table" and view.rank or nil
    local index = tonumber(rank and rank.assetIndex) or 1
    if PREMIUM.renownWarbandBadges and PREMIUM.renownWarbandBadges[index] then
        return PREMIUM.renownWarbandBadges[index], rank and rank.label
    end
    return PREMIUM.statusBadgeOpen or PREMIUM.statusBadgeComplete, rank and rank.label
end

local function getSidebarDisplayCharacters(characters, sortMode)
    if sortMode ~= "name" and sortMode ~= "level" and sortMode ~= "itemLevel" and sortMode ~= "priority" then
        return characters
    end
    if sortMode == "priority" and not isPriorityScoreVisible() then
        return characters
    end

    local ordered = {}
    local originalIndexByKey = {}
    for index, character in ipairs(characters or {}) do
        ordered[index] = character
        originalIndexByKey[character.key or tostring(index)] = index
    end

    table.sort(ordered, function(a, b)
        if sortMode == "name" then
            local aName = getSidebarCharacterName(a)
            local bName = getSidebarCharacterName(b)
            if aName ~= bName then
                return aName < bName
            end
        elseif sortMode == "level" then
            local aLevel = tonumber(a and a.level) or 0
            local bLevel = tonumber(b and b.level) or 0
            if aLevel ~= bLevel then
                return aLevel > bLevel
            end
        elseif sortMode == "itemLevel" then
            local aItemLevel = tonumber(a and a.itemLevel) or 0
            local bItemLevel = tonumber(b and b.itemLevel) or 0
            if aItemLevel ~= bItemLevel then
                return aItemLevel > bItemLevel
            end
        elseif sortMode == "priority" then
            local aScore = tonumber((getPriorityInfo(a) or {}).score) or 0
            local bScore = tonumber((getPriorityInfo(b) or {}).score) or 0
            if aScore ~= bScore then
                return aScore > bScore
            end
        end

        local aName = getSidebarCharacterName(a)
        local bName = getSidebarCharacterName(b)
        if aName ~= bName then
            return aName < bName
        end

        local aIndex = originalIndexByKey[a and a.key or ""] or 0
        local bIndex = originalIndexByKey[b and b.key or ""] or 0
        return aIndex < bIndex
    end)

    return ordered
end

local function applySidebarBaseOrder(characters, baseOrderKeys)
    if type(baseOrderKeys) ~= "table" or #baseOrderKeys == 0 then
        return characters
    end

    local byKey = {}
    local ordered = {}
    local seen = {}

    for _, character in ipairs(characters or {}) do
        if character and character.key then
            byKey[character.key] = character
        end
    end

    for _, key in ipairs(baseOrderKeys) do
        local character = byKey[key]
        if character then
            ordered[#ordered + 1] = character
            seen[key] = true
        end
    end

    for _, character in ipairs(characters or {}) do
        local key = character and character.key
        if key and not seen[key] then
            ordered[#ordered + 1] = character
        end
    end

    return ordered
end

local function createRosterRow(parent, helpers)
    local createLabel = helpers.createLabel
    local setTextureColor = helpers.setTextureColor

    local button = CreateFrame("Button", nil, parent, BACKDROP_TEMPLATE)
    button:SetHeight(58)
    if button.SetBackdrop then
        button:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        button:SetBackdropColor(THEME.metalDeep[1], THEME.metalDeep[2], THEME.metalDeep[3], 0.06)
        button:SetBackdropBorderColor(THEME.goldDim[1], THEME.goldDim[2], THEME.goldDim[3], 0.36)
    end

    button.bg = button:CreateTexture(nil, "BACKGROUND")
    button.bg:SetAllPoints()
    button.bg:SetTexture(PREMIUM.row)
    setTextureColor(button.bg, { 1, 1, 1 }, 0.88)

    button.iconBackplate = button:CreateTexture(nil, "ARTWORK")
    button.iconBackplate:SetSize(34, 34)
    button.iconBackplate:SetPoint("LEFT", 12, 0)
    button.iconBackplate:SetTexture(PREMIUM.classBackplate)

    button.classIcon = button:CreateTexture(nil, "OVERLAY")
    button.classIcon:SetSize(30, 30)
    button.classIcon:SetPoint("CENTER", button.iconBackplate, "CENTER", 0, 0)
    if button.classIcon.AddMaskTexture then
        button.classMask = button:CreateMaskTexture()
        button.classMask:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
        button.classMask:SetAllPoints(button.classIcon)
        button.classIcon:AddMaskTexture(button.classMask)
    end

    button.iconRing = button:CreateTexture(nil, "OVERLAY", nil, 1)
    button.iconRing:SetSize(34, 34)
    button.iconRing:SetPoint("CENTER", button.iconBackplate, "CENTER", 0, 0)
    button.iconRing:SetTexture(PREMIUM.classRing)

    button.fill = button:CreateTexture(nil, "BACKGROUND", nil, 1)
    button.fill:SetAllPoints()
    button.fill:SetTexture("Interface\\Buttons\\WHITE8X8")
    button.fill:SetColorTexture(THEME.metalInset[1], THEME.metalInset[2], THEME.metalInset[3], 0.03)

    button.classLine = button:CreateTexture(nil, "ARTWORK")
    button.classLine:SetPoint("TOPLEFT", 4, -5)
    button.classLine:SetPoint("BOTTOMLEFT", 4, 5)
    button.classLine:SetWidth(2)
    button.classLine:SetColorTexture(1, 1, 1, 1)

    button.name = createLabel(button, "GameFontNormal", "LEFT")
    button.name:SetPoint("TOPLEFT", 52, -10)
    button.name:SetPoint("TOPRIGHT", -56, -10)

    button.meta = createLabel(button, "GameFontHighlightSmall", "LEFT")
    button.meta:SetPoint("TOPLEFT", button.name, "BOTTOMLEFT", 0, -6)
    button.meta:SetPoint("TOPRIGHT", -16, 0)
    if button.meta.SetWordWrap then
        button.meta:SetWordWrap(false)
    end

    button.mainTag = createLabel(button, "GameFontNormalSmall", "RIGHT")
    button.mainTag:SetPoint("TOPRIGHT", -12, -10)
    button.mainTag:SetText(L.SIDEBAR_MAIN or "MAIN")
    button.mainTag:SetTextColor(1.00, 0.82, 0.18)
    button.mainTag:Hide()

    button.selection = button:CreateTexture(nil, "HIGHLIGHT")
    button.selection:SetPoint("TOPLEFT", 3, -3)
    button.selection:SetPoint("BOTTOMRIGHT", -3, 3)
    button.selection:SetTexture("Interface\\Buttons\\WHITE8X8")
    button.selection:SetColorTexture(THEME.gold[1], THEME.gold[2], THEME.gold[3], 0.10)
    button.selection:Hide()

    return button
end

local function createCurrentCharacterCard(parent, helpers)
    local createLabel = helpers.createLabel
    local setTextureColor = helpers.setTextureColor

    local card = CreateFrame("Button", nil, parent, BACKDROP_TEMPLATE)
    card:SetHeight(82)
    if card.SetBackdrop then
        card:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        card:SetBackdropColor(THEME.metalDeep[1], THEME.metalDeep[2], THEME.metalDeep[3], 0.08)
        card:SetBackdropBorderColor(THEME.goldDim[1], THEME.goldDim[2], THEME.goldDim[3], 0.48)
    end

    card.bg = card:CreateTexture(nil, "BACKGROUND")
    card.bg:SetAllPoints()
    card.bg:SetTexture(PREMIUM.row)
    setTextureColor(card.bg, { 1, 1, 1 }, 0.90)

    card.fill = card:CreateTexture(nil, "BACKGROUND", nil, 1)
    card.fill:SetAllPoints()
    card.fill:SetTexture("Interface\\Buttons\\WHITE8X8")
    card.fill:SetColorTexture(THEME.metalInset[1], THEME.metalInset[2], THEME.metalInset[3], 0.05)

    card.classLine = card:CreateTexture(nil, "ARTWORK")
    card.classLine:SetPoint("TOPLEFT", 5, -7)
    card.classLine:SetPoint("BOTTOMLEFT", 5, 7)
    card.classLine:SetWidth(3)
    card.classLine:SetColorTexture(1, 1, 1, 1)

    card.iconBackplate = card:CreateTexture(nil, "ARTWORK")
    card.iconBackplate:SetSize(52, 52)
    card.iconBackplate:SetPoint("LEFT", 18, 0)
    card.iconBackplate:SetTexture(PREMIUM.classBackplate)

    card.classIcon = card:CreateTexture(nil, "OVERLAY")
    card.classIcon:SetSize(46, 46)
    card.classIcon:SetPoint("CENTER", card.iconBackplate, "CENTER", 0, 0)
    if card.classIcon.AddMaskTexture then
        card.classMask = card:CreateMaskTexture()
        card.classMask:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
        card.classMask:SetAllPoints(card.classIcon)
        card.classIcon:AddMaskTexture(card.classMask)
    end

    card.iconRing = card:CreateTexture(nil, "OVERLAY", nil, 1)
    card.iconRing:SetSize(52, 52)
    card.iconRing:SetPoint("CENTER", card.iconBackplate, "CENTER", 0, 0)
    card.iconRing:SetTexture(PREMIUM.classRing)

    card.label = createLabel(card, "GameFontHighlightSmall", "LEFT")
    card.label:SetPoint("TOPLEFT", 84, -15)
    card.label:SetPoint("TOPRIGHT", -14, -15)
    card.label:SetTextColor(THEME.parchment[1], THEME.parchment[2], THEME.parchment[3], 0.78)
    card.label:SetText(L.SIDEBAR_CURRENT_CHARACTER or "Current character")

    card.name = createLabel(card, "GameFontNormalLarge", "LEFT")
    card.name:SetPoint("TOPLEFT", card.label, "BOTTOMLEFT", 0, -5)
    card.name:SetPoint("TOPRIGHT", -14, 0)

    card.meta = createLabel(card, "GameFontHighlightSmall", "LEFT")
    card.meta:SetPoint("TOPLEFT", card.name, "BOTTOMLEFT", 0, -6)
    card.meta:SetPoint("TOPRIGHT", -14, 0)
    if card.meta.SetWordWrap then
        card.meta:SetWordWrap(false)
    end

    card.selection = card:CreateTexture(nil, "HIGHLIGHT")
    card.selection:SetPoint("TOPLEFT", 3, -3)
    card.selection:SetPoint("BOTTOMRIGHT", -3, 3)
    card.selection:SetTexture("Interface\\Buttons\\WHITE8X8")
    card.selection:SetColorTexture(THEME.gold[1], THEME.gold[2], THEME.gold[3], 0.12)
    card.selection:Hide()

    card:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    card:SetScript("OnClick", function(selfButton, mouseButton)
        if not selfButton.characterKey then
            return
        end
        if mouseButton == "RightButton" and ns.SetMainCharacterKey then
            ns:SetMainCharacterKey(selfButton.characterKey)
        else
            ns:SetSelectedCharacterKey(selfButton.characterKey)
        end
    end)

    return card
end

local function ensureSidebarButtons(ui, count, helpers)
    while #ui.frame.sidebarButtons < count do
        local button = createRosterRow(ui.frame.sidebarScrollChild, helpers)
        local index = #ui.frame.sidebarButtons + 1
        button:SetPoint("TOPLEFT", 0, -((index - 1) * 62))
        button:SetPoint("TOPRIGHT", 0, -((index - 1) * 62))
        button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
        button:SetScript("OnClick", function(selfButton, mouseButton)
            if not selfButton.characterKey then
                return
            end
            if mouseButton == "RightButton" and ns.SetMainCharacterKey then
                ns:SetMainCharacterKey(selfButton.characterKey)
            else
                ns:SetSelectedCharacterKey(selfButton.characterKey)
            end
        end)
        ui.frame.sidebarButtons[index] = button
    end
end

local function ensureSidebarVisibilityRows(ui, count, helpers)
    local frame = ui.frame
    local createMiniActionButton = helpers.createMiniActionButton
    if not frame or not frame.sidebarVisibilityMenuScrollChild or type(createMiniActionButton) ~= "function" then
        return
    end

    frame.sidebarVisibilityRows = frame.sidebarVisibilityRows or {}
    while #frame.sidebarVisibilityRows < count do
        local button = createMiniActionButton(frame.sidebarVisibilityMenuScrollChild, "", 184)
        local index = #frame.sidebarVisibilityRows + 1
        button:SetPoint("TOPLEFT", 0, -((index - 1) * 26))
        button:SetPoint("TOPRIGHT", 0, -((index - 1) * 26))
        if button.label then
            button.label:ClearAllPoints()
            button.label:SetPoint("LEFT", 28, 0)
            button.label:SetPoint("RIGHT", -8, 0)
        end
        button.check = button:CreateTexture(nil, "ARTWORK", nil, 2)
        button.check:SetSize(14, 14)
        button.check:SetPoint("LEFT", 8, 0)
        button.check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
        button.check:SetVertexColor(THEME.gold[1], THEME.gold[2], THEME.gold[3], 1)
        button:SetScript("OnClick", function(selfButton)
            if not selfButton.characterKey or not ns.SetCharacterHidden then
                return
            end
            if ns:IsCurrentCharacterKey(selfButton.characterKey) then
                return
            end
            ns:SetCharacterHidden(selfButton.characterKey, selfButton.vlVisible == true)
        end)
        button:HookScript("OnEnter", function(selfButton)
            if not ns:IsCurrentCharacterKey(selfButton.characterKey) then
                return
            end
            GameTooltip:SetOwner(selfButton, "ANCHOR_RIGHT")
            GameTooltip:AddLine(L.SIDEBAR_CURRENT_CHARACTER or "Current character", THEME.gold[1], THEME.gold[2], THEME.gold[3], true)
            GameTooltip:AddLine(L.SIDEBAR_VISIBILITY_CURRENT_LOCKED or "The current character is always visible.", 0.72, 0.78, 0.88, true)
            GameTooltip:Show()
        end)
        button:HookScript("OnLeave", function()
            GameTooltip:Hide()
        end)
        frame.sidebarVisibilityRows[index] = button
    end
end

local function getSortLabel(sortMode)
    if not sortMode then
        return L.SIDEBAR_SORT_DEFAULT or "Manual"
    end
    if sortMode == "priority" and not isPriorityScoreVisible() then
        return L.SIDEBAR_SORT_DEFAULT or "Manual"
    end

    for _, option in ipairs(SIDEBAR_SORT_OPTIONS) do
        if option.key == sortMode then
            return option.label()
        end
    end

    return L.SIDEBAR_SORT_DEFAULT or "Manual"
end

local function refreshSidebarDropdowns(ui, allCharacters, helpers)
    local frame = ui.frame
    if not frame then
        return
    end

    local setMiniActionButtonActive = helpers.setMiniActionButtonActive

    if frame.sidebarSortDropdown and frame.sidebarSortDropdown.label then
        frame.sidebarSortDropdown.label:SetText(string.format(L.SIDEBAR_SORT_DROPDOWN or "Sort: %s", getSortLabel(ui.sidebarSortMode)))
    end
    for _, button in ipairs(frame.sidebarSortMenuRows or {}) do
        local rowVisible = not (button.sortKey == "priority" and not isPriorityScoreVisible())
        button:SetShown(rowVisible)
        if not rowVisible then
            if setMiniActionButtonActive then
                setMiniActionButtonActive(button, false)
            end
        else
            local labelText = button.sortLabel and button.sortLabel() or ""
            if button.label then
                button.label:SetText(labelText)
            end
            if setMiniActionButtonActive then
                setMiniActionButtonActive(button, (ui.sidebarSortMode or "") == (button.sortKey or ""))
            end
        end
    end

    local hiddenCount = ns:GetHiddenCharacterCount() or 0
    if frame.sidebarVisibilityDropdown and frame.sidebarVisibilityDropdown.label then
        if hiddenCount > 0 then
            frame.sidebarVisibilityDropdown.label:SetText(string.format(L.SIDEBAR_VISIBILITY_DROPDOWN_COUNT or "Characters: %d hidden", hiddenCount))
        else
            frame.sidebarVisibilityDropdown.label:SetText(L.SIDEBAR_VISIBILITY_DROPDOWN or "Characters")
        end
    end

    ensureSidebarVisibilityRows(ui, #allCharacters, helpers)
    for index, button in ipairs(frame.sidebarVisibilityRows or {}) do
        local character = allCharacters[index]
        if not character then
            button:Hide()
        else
            local key = character.key
            local isCurrent = ns:IsCurrentCharacterKey(key)
            local isHidden = (not isCurrent) and ns:IsCharacterHidden(key)
            local isVisible = not isHidden
            local labelText = character.name or character.key or ""
            if character.realm and character.realm ~= "" then
                labelText = string.format("%s-%s", labelText, character.realm)
            end

            button.characterKey = key
            button.vlVisible = isVisible
            button:SetAlpha(isCurrent and 0.65 or 1)
            button:EnableMouse(true)
            if button.label then
                button.label:SetText(labelText)
                button.label:SetTextColor(THEME.parchment[1], THEME.parchment[2], THEME.parchment[3], isVisible and 0.95 or 0.52)
            end
            if button.check then
                if isVisible then
                    button.check:Show()
                else
                    button.check:Hide()
                end
            end
            if setMiniActionButtonActive then
                setMiniActionButtonActive(button, isVisible)
            end
            button:SetAlpha(isCurrent and 0.65 or (isVisible and 1 or 0.72))
            button:Show()
        end
    end

    local menuHeight = math.min(220, math.max(58, 20 + (#allCharacters * 26)))
    if frame.sidebarVisibilityMenu then
        frame.sidebarVisibilityMenu:SetHeight(menuHeight)
    end
    if frame.sidebarVisibilityMenuScrollChild then
        frame.sidebarVisibilityMenuScrollChild:SetSize(190, math.max(10, (#allCharacters * 26) - 2))
    end
    if frame.sidebarVisibilityMenuScroll and frame.sidebarVisibilityMenuScroll.UpdateScrollChildRect then
        frame.sidebarVisibilityMenuScroll:UpdateScrollChildRect()
    end
end

local function getCurrentDisplayCharacter(characters)
    local currentInfo = ns:GetCurrentCharacterInfo()
    local currentKey = currentInfo and currentInfo.key
    local currentRecord

    for _, character in ipairs(characters or {}) do
        if character and character.key == currentKey then
            currentRecord = character
            break
        end
    end

    local _, classFile = UnitClass("player")
    return {
        key = currentKey,
        name = (currentInfo and currentInfo.name) or (currentRecord and currentRecord.name),
        realm = (currentInfo and currentInfo.realm) or (currentRecord and currentRecord.realm),
        className = (currentInfo and currentInfo.className) or (currentRecord and currentRecord.className),
        classFile = classFile or (currentRecord and (currentRecord.classFile or currentRecord.classToken or currentRecord.classFilename)),
        level = (currentInfo and currentInfo.level) or (currentRecord and currentRecord.level) or 0,
        itemLevel = (ns.GetCurrentEquippedItemLevel and ns:GetCurrentEquippedItemLevel()) or (currentRecord and currentRecord.itemLevel),
        lastSeen = time and time() or (currentRecord and currentRecord.lastSeen),
    }
end

local function refreshCurrentCharacterCard(ui, characters, selectedKey, helpers)
    local card = ui.frame and ui.frame.sidebarCurrentCard
    if not card then
        return
    end

    local getClassColor = helpers.getClassColor
    local setClassIconTexture = helpers.setClassIconTexture
    local character = getCurrentDisplayCharacter(characters)
    if not character or not character.key then
        card:Hide()
        return
    end

    local r, g, b = getClassColor(character.classFile or character.classToken or character.classFilename)
    local priority = getPriorityInfo(character)

    card:Show()
    card.characterKey = character.key
    card.label:SetText(L.SIDEBAR_CURRENT_CHARACTER or "Current character")
    card.name:SetText(character.name or character.key)
    card.name:SetTextColor(r, g, b)
    card.meta:SetText(formatSidebarMeta(character, priority, false))
    card.classLine:SetColorTexture(r, g, b, 1)
    card:SetScript("OnEnter", function(selfButton)
        showPriorityTooltip(selfButton, character, priority)
    end)
    card:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    local portraitApplied = false
    if type(SetPortraitTexture) == "function" then
        portraitApplied = pcall(SetPortraitTexture, card.classIcon, "player")
    end
    if not portraitApplied then
        setClassIconTexture(card.classIcon, character.classFile or character.classToken or character.classFilename)
    end
    applyRenownPortraitFrame(card.iconRing, character)

    local isSelected = character.key == selectedKey
    card.selection:SetShown(isSelected)
    card.fill:SetColorTexture(
        isSelected and THEME.selected[1] or THEME.metalInset[1],
        isSelected and THEME.selected[2] or THEME.metalInset[2],
        isSelected and THEME.selected[3] or THEME.metalInset[3],
        isSelected and 0.08 or 0.05
    )
    if card.SetBackdropBorderColor then
        if isSelected then
            card:SetBackdropBorderColor(THEME.gold[1], THEME.gold[2], THEME.gold[3], 0.82)
        else
            card:SetBackdropBorderColor(THEME.goldDim[1], THEME.goldDim[2], THEME.goldDim[3], 0.48)
        end
    end
end

local function refreshSidebar(ui, helpers)
    if not ui.frame then
        return
    end

    local getClassColor = helpers.getClassColor
    local setClassIconTexture = helpers.setClassIconTexture
    local setMiniActionButtonActive = helpers.setMiniActionButtonActive

    local characters = ns:GetLiveVaultloomCharacters()
    local allCharacters = (ns.GetAllVaultloomCharacters and ns:GetAllVaultloomCharacters()) or characters
    if ns.GetSidebarSortMode then
        ui.sidebarSortMode = ns:GetSidebarSortMode()
    end
    if ui.sidebarSortMode == "priority" and not isPriorityScoreVisible() then
        ui.sidebarSortMode = nil
        if ns.SetSidebarSortMode then
            ns:SetSidebarSortMode(nil, true)
        end
    end
    local displayCharacters
    if ui.sidebarSortMode then
        displayCharacters = getSidebarDisplayCharacters(characters, ui.sidebarSortMode)
    else
        displayCharacters = applySidebarBaseOrder(characters, ui.sidebarBaseOrderKeys)
        ui.sidebarBaseOrderKeys = {}
        for _, character in ipairs(displayCharacters or {}) do
            if character and character.key then
                ui.sidebarBaseOrderKeys[#ui.sidebarBaseOrderKeys + 1] = character.key
            end
        end
    end
    local selectedCharacter = ns:GetSelectedCharacter()
    local selectedKey = selectedCharacter and selectedCharacter.key or nil

    ensureSidebarButtons(ui, #displayCharacters, helpers)
    refreshCurrentCharacterCard(ui, characters, selectedKey, helpers)
    local renownEnabled = not ns.IsRenownSystemEnabled or ns:IsRenownSystemEnabled()
    local warbandBadgeVisible = renownEnabled and (not ns.GetRenownWarbandBadgeVisible or ns:GetRenownWarbandBadgeVisible())
    if ui.frame.sidebarWarbandBadgeBackplate then
        ui.frame.sidebarWarbandBadgeBackplate:SetShown(warbandBadgeVisible)
    end
    if ui.frame.sidebarWarbandBadge then
        if warbandBadgeVisible then
            local badgeTexture = getWarbandBadgeTexture(selectedCharacter)
            ui.frame.sidebarWarbandBadge:SetTexture(badgeTexture)
            ui.frame.sidebarWarbandBadge:SetTexCoord(0, 1, 0, 1)
            ui.frame.sidebarWarbandBadge:Show()
        else
            ui.frame.sidebarWarbandBadge:Hide()
        end
    end
    if ui.frame.sidebarWarbandBadgeHitbox then
        ui.frame.sidebarWarbandBadgeHitbox:SetShown(warbandBadgeVisible)
        if warbandBadgeVisible then
            ui.frame.sidebarWarbandBadgeHitbox:SetScript("OnEnter", function(selfFrame)
                GameTooltip:SetOwner(selfFrame, "ANCHOR_CURSOR_RIGHT")
                GameTooltip:AddLine(L.RENOWN_SCOPE_WARBAND or "Warband", THEME.gold[1], THEME.gold[2], THEME.gold[3], true)
                local _, rankLabel = getWarbandBadgeTexture(selectedCharacter)
                if rankLabel and rankLabel ~= "" then
                    GameTooltip:AddLine(rankLabel, 0.86, 0.86, 0.82, true)
                end
                GameTooltip:Show()
            end)
            ui.frame.sidebarWarbandBadgeHitbox:SetScript("OnLeave", function()
                GameTooltip:Hide()
            end)
        else
            ui.frame.sidebarWarbandBadgeHitbox:SetScript("OnEnter", nil)
            ui.frame.sidebarWarbandBadgeHitbox:SetScript("OnLeave", nil)
        end
    end
    ui.frame.sidebarSubtitle:SetText(string.format(L.SIDEBAR_FOOTER or "%d characters mirrored from live Vaultloom", #characters))
    ui.frame.sidebarEmpty:SetShown(#characters == 0)
    refreshSidebarDropdowns(ui, allCharacters, helpers)
    if ui.frame.sidebarHideButton then
        local canHideSelected = selectedKey ~= nil and ns:CanHideCharacter(selectedKey)
        ui.frame.sidebarHideButton.label:SetText(L.SIDEBAR_HIDE or "Hide")
        ui.frame.sidebarHideButton:SetAlpha(canHideSelected and 1 or 0.45)
        ui.frame.sidebarHideButton:EnableMouse(canHideSelected)
    end
    if ui.frame.sidebarUnhideButton then
        local hiddenCount = ns:GetHiddenCharacterCount() or 0
        ui.frame.sidebarUnhideButton.label:SetText(L.SIDEBAR_UNHIDE or "Show")
        ui.frame.sidebarUnhideButton:SetAlpha(hiddenCount > 0 and 1 or 0.45)
        ui.frame.sidebarUnhideButton:EnableMouse(hiddenCount > 0)
    end

    for index, button in ipairs(ui.frame.sidebarButtons or {}) do
        local character = displayCharacters[index]
        if not character then
            button:Hide()
        else
            local isSelected = character.key == selectedKey
            local r, g, b = getClassColor(character.classFile or character.classToken or character.classFilename)
            local priority = getPriorityInfo(character)

            button:Show()
            button.characterKey = character.key
            button.name:SetText(character.name or character.key)
            button.name:SetTextColor(r, g, b)
            button.meta:SetText(formatSidebarMeta(character, priority))
            button.classLine:SetColorTexture(r, g, b, 1)
            setClassIconTexture(button.classIcon, character.classFile or character.classToken or character.classFilename)
            if button.iconRing then
                button.iconRing:SetTexture(PREMIUM.classRing)
                button.iconRing:Show()
            end
            button.classLine:SetWidth(isSelected and 3 or 2)
            button.mainTag:SetShown(ns:IsLiveMainCharacter(character.key))
            button.selection:SetShown(isSelected)
            if isSelected then
                button.fill:SetColorTexture(THEME.selected[1], THEME.selected[2], THEME.selected[3], 0.07)
            else
                button.fill:SetColorTexture(THEME.metalInset[1], THEME.metalInset[2], THEME.metalInset[3], 0.03)
            end
            button:SetScript("OnEnter", function(selfButton)
                showPriorityTooltip(selfButton, character, priority)
            end)
            button:SetScript("OnLeave", function()
                GameTooltip:Hide()
            end)

            if button.SetBackdropBorderColor then
                if isSelected then
                    button:SetBackdropBorderColor(THEME.gold[1], THEME.gold[2], THEME.gold[3], 0.78)
                else
                    button:SetBackdropBorderColor(THEME.goldDim[1], THEME.goldDim[2], THEME.goldDim[3], 0.36)
                end
            end
        end
    end

    local totalHeight = math.max(10, (#displayCharacters * 64) - 6)
    ui.frame.sidebarScrollChild:SetSize(214, totalHeight)
end

UISidebarView.CreateRosterRow = createRosterRow
UISidebarView.CreateCurrentCharacterCard = createCurrentCharacterCard
UISidebarView.EnsureButtons = ensureSidebarButtons
UISidebarView.Refresh = refreshSidebar
