TargetMarkerAssistant = TargetMarkerAssistant or {}
TMA_Ui = TMA_Ui or {}

local Ui = TMA_Ui
local TMA = TargetMarkerAssistant

local HINT_DESCRIPTION_ICON = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Icon\\TMATips.png"
local IMPORTANT_DESCRIPTION_ICON = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Icon\\TMAImportantTips.png"
local SECTION_HEADER_LEFT_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Ui\\TittleLeft.png"
local SECTION_HEADER_BODY_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Ui\\TittleBody.png"
local SECTION_HEADER_RIGHT_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Ui\\TittleRight.png"
local COLUMN_DIVIDER_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Ui\\Line.png"

local function IsProtectedFrame(frame)
    if not frame or not frame.IsProtected then
        return false
    end

    local ok, protected = pcall(frame.IsProtected, frame)
    return ok and protected == true
end

local function SafeShowRegion(region)
    if not region or not region.Show or IsProtectedFrame(region) then return end
    pcall(region.Show, region)
end

local function SafeHideRegion(region)
    if not region or not region.Hide or IsProtectedFrame(region) then return end
    pcall(region.Hide, region)
end

local function SafeSetRegionShown(region, shown)
    if not region or not region.SetShown or IsProtectedFrame(region) then return end
    pcall(region.SetShown, region, shown)
end

local function SafeSetRegionAlpha(region, alpha)
    if not region or not region.SetAlpha or IsProtectedFrame(region) then return end
    pcall(region.SetAlpha, region, alpha)
end

local function SafeEnableMouseRegion(region, enabled)
    if not region or not region.EnableMouse or IsProtectedFrame(region) then return end
    pcall(region.EnableMouse, region, enabled)
end

local function SafeSetDrawLayer(region, layer, sublevel)
    if not region or not region.SetDrawLayer or IsProtectedFrame(region) then return end
    pcall(region.SetDrawLayer, region, layer, sublevel)
end

function Ui.ApplyClientDefaultFont(fontObject, size, flags)
    if TMA.SetClientDefaultFont then
        return TMA.SetClientDefaultFont(fontObject, size, flags)
    end

    if not fontObject or not fontObject.SetFont then
        return false
    end

    local font
    if GameFontNormal and GameFontNormal.GetFont then
        font = select(1, GameFontNormal:GetFont())
    end
    font = font or STANDARD_TEXT_FONT or UNIT_NAME_FONT or DAMAGE_TEXT_FONT
    if not font or font == "" then
        return false
    end

    local ok = pcall(fontObject.SetFont, fontObject, font, size or 12, flags or "")
    return ok == true
end

function Ui.UseClientDefaultFont(fontObject)
    if not fontObject or not fontObject.GetFont then
        return false
    end

    local _, size, flags = fontObject:GetFont()
    return Ui.ApplyClientDefaultFont(fontObject, size or 12, flags or "")
end

local PANEL_CONTENT_LEFT_INSET = 24
local PANEL_CONTENT_RIGHT_INSET = 24
local PANEL_CONTENT_TOP_INSET = 52
local PANEL_CONTENT_BOTTOM_INSET = 42
local PANEL_VISIBLE_RIGHT_BORDER_INSET = 4
local TAB_PAGE_LEFT_INSET = 25
local TAB_PAGE_TOP_INSET = 20
local TAB_PAGE_RIGHT_INSET = TAB_PAGE_LEFT_INSET
local TAB_PAGE_BOTTOM_INSET = TAB_PAGE_TOP_INSET
local TAB_SCROLLBAR_GUTTER = 0
local TAB_SCROLLBAR_WIDTH = 22
local TAB_SCROLLBAR_VERTICAL_INSET = 0
local TAB_SCROLL_STEP = 36
local TAB_SCROLL_MIN_THUMB_HEIGHT = 34
local TAB_SCROLL_THUMB_WIDTH = 8
local TAB_SCROLL_THUMB_CAP_HEIGHT = 8

local function HideLegacyScrollBarResidue(scrollFrame)
    local frameName = scrollFrame and scrollFrame.GetName and scrollFrame:GetName()
    if not frameName then return end

    local legacyPrefixes = { frameName .. "ScrollBar" }
    local baseName = frameName:gsub("ScrollFrame$", "")
    if baseName ~= frameName then
        legacyPrefixes[#legacyPrefixes + 1] = baseName .. "ScrollBar"
    end

    local legacySuffixes = {
        "", "ScrollUpButton", "ScrollDownButton", "ThumbTexture", "Top", "Middle", "Bottom",
    }

    for _, prefix in ipairs(legacyPrefixes) do
        for _, suffix in ipairs(legacySuffixes) do
            local region = _G[prefix .. suffix]
            if region then
                SafeHideRegion(region)
                SafeSetRegionAlpha(region, 0)
                SafeEnableMouseRegion(region, false)
            end
        end
    end
end

local function GetTabScrollBar(scrollFrame)
    if not scrollFrame then return nil end
    return scrollFrame.ScrollBar
end

local function HideTabScrollBar(scrollFrame)
    local scrollBar = GetTabScrollBar(scrollFrame)
    if scrollBar then
        SafeHideRegion(scrollBar)
        SafeSetRegionAlpha(scrollBar, 0)
        SafeEnableMouseRegion(scrollBar, false)
    end
    HideLegacyScrollBarResidue(scrollFrame)
end

local function SetTabScrollBarVisible(scrollFrame, visible)
    local scrollBar = GetTabScrollBar(scrollFrame)
    if scrollBar then
        SafeSetRegionShown(scrollBar, visible)
        SafeSetRegionAlpha(scrollBar, visible and 1 or 0)
        SafeEnableMouseRegion(scrollBar, visible)
    end
    HideLegacyScrollBarResidue(scrollFrame)
end

local function ClampScrollValue(scrollFrame, value)
    local maxScroll = scrollFrame and (scrollFrame:GetVerticalScrollRange() or 0) or 0
    return math.max(0, math.min(maxScroll, value or 0))
end

function Ui.RefreshTabScrollBar(scrollFrame)
    local scrollBar = GetTabScrollBar(scrollFrame)
    if not scrollFrame or not scrollBar then return end

    if scrollFrame.HideSystemScrollBar then
        HideTabScrollBar(scrollFrame)
        return
    end

    local maxScroll = scrollFrame:GetVerticalScrollRange() or 0
    local current = math.min(scrollFrame:GetVerticalScroll() or 0, maxScroll)

    if maxScroll <= 1 then
        scrollFrame:SetVerticalScroll(0)
        SetTabScrollBarVisible(scrollFrame, false)
    else
        SetTabScrollBarVisible(scrollFrame, true)

        local track = scrollBar.Track
        local thumb = scrollBar.Thumb
        if track and thumb then
            local trackHeight = track:GetHeight() or 0
            if trackHeight <= 1 then
                C_Timer.After(0, function()
                    Ui.RefreshTabScrollBar(scrollFrame)
                end)
                return
            end

            local viewportHeight = math.max(scrollFrame:GetHeight() or 1, 1)
            local contentHeight = math.max((scrollFrame.content and scrollFrame.content:GetHeight()) or (viewportHeight + maxScroll), viewportHeight + maxScroll)
            local ratio = math.min(1, viewportHeight / math.max(contentHeight, 1))
            local thumbHeight = math.max(TAB_SCROLL_MIN_THUMB_HEIGHT, math.floor(trackHeight * ratio))
            thumbHeight = math.min(trackHeight, thumbHeight)

            local movableRange = math.max(trackHeight - thumbHeight, 1)
            local percent = maxScroll > 0 and (current / maxScroll) or 0

            thumb:SetHeight(thumbHeight)
            thumb:ClearAllPoints()
            thumb:SetPoint("TOP", track, "TOP", 0, -(movableRange * percent))
            thumb:Show()
        end
    end
end

local function ScrollTabFrameBy(scrollFrame, delta)
    if not scrollFrame then return end
    local current = scrollFrame:GetVerticalScroll() or 0
    scrollFrame:SetVerticalScroll(ClampScrollValue(scrollFrame, current + (delta or 0)))
    Ui.RefreshTabScrollBar(scrollFrame)
end

local function StyleTaskLogScrollButton(button, direction)
    local prefix = direction == "UP" and "top" or "bottom"
    local normalAtlas = "minimal-scrollbar-arrow-" .. prefix
    local overAtlas = normalAtlas .. "-over"
    local downAtlas = normalAtlas .. "-down"

    button:SetSize(18, 18)

    local function ApplyButtonTexture(setter, atlas, desaturate)
        local texture = button:CreateTexture(nil, "ARTWORK")
        if texture.SetAtlas then
            texture:SetAtlas(atlas, true)
        else
            texture:SetTexture("Interface\\Buttons\\UI-ScrollBar-Scroll" .. (direction == "UP" and "Up" or "Down") .. "Button-Up")
            texture:SetSize(18, 18)
        end
        texture:SetPoint("CENTER", button, "CENTER", 0, 0)
        if desaturate and texture.SetDesaturated then
            texture:SetDesaturated(true)
            texture:SetVertexColor(0.55, 0.55, 0.55, 1)
        end
        setter(button, texture)
    end

    ApplyButtonTexture(button.SetNormalTexture, normalAtlas)
    ApplyButtonTexture(button.SetPushedTexture, downAtlas)
    ApplyButtonTexture(button.SetHighlightTexture, overAtlas)
    ApplyButtonTexture(button.SetDisabledTexture, normalAtlas, true)

    local highlight = button:GetHighlightTexture()
    if highlight then
        highlight:SetBlendMode("ADD")
    end
end

local function CreateScrollBarAtlasTexture(parent, layer, atlas, fallbackTexture)
    local texture = parent:CreateTexture(nil, layer)
    if texture.SetAtlas then
        texture:SetAtlas(atlas, true)
    elseif fallbackTexture then
        texture:SetTexture(fallbackTexture)
    end
    return texture
end

local function SetScrollBarAtlasTexture(texture, atlas, fallbackTexture)
    if not texture then return end
    if texture.SetAtlas then
        texture:SetAtlas(atlas, true)
    elseif fallbackTexture then
        texture:SetTexture(fallbackTexture)
    end
end

local function SetTabScrollThumbState(thumb, state)
    if not thumb then return end

    local suffix = state == "PUSHED" and "-down" or state == "HOVER" and "-over" or ""
    SetScrollBarAtlasTexture(thumb.TopTexture, "minimal-scrollbar-thumb-top" .. suffix, "Interface\\Buttons\\UI-ScrollBar-Knob")
    SetScrollBarAtlasTexture(thumb.MiddleTexture, "minimal-scrollbar-thumb-middle" .. suffix, "Interface\\Buttons\\UI-ScrollBar-Knob")
    SetScrollBarAtlasTexture(thumb.BottomTexture, "minimal-scrollbar-thumb-bottom" .. suffix, "Interface\\Buttons\\UI-ScrollBar-Knob")
end

local function StopTabScrollThumbDrag(scrollBar)
    if not scrollBar then return end
    scrollBar.DraggingThumb = nil
    scrollBar.DragOffsetY = nil
    scrollBar:SetScript("OnUpdate", nil)
end

local function UpdateTabScrollThumbDrag(scrollBar)
    if not scrollBar or not scrollBar.DraggingThumb then return end

    local scrollFrame = scrollBar.ScrollFrame
    local track = scrollBar.Track
    local thumb = scrollBar.Thumb
    if not scrollFrame or not track or not thumb then
        StopTabScrollThumbDrag(scrollBar)
        return
    end

    local maxScroll = scrollFrame:GetVerticalScrollRange() or 0
    local trackHeight = track:GetHeight() or 0
    local thumbHeight = thumb:GetHeight() or 0
    local trackTop = track:GetTop()
    if maxScroll <= 0 or trackHeight <= 0 or thumbHeight <= 0 or not trackTop then
        StopTabScrollThumbDrag(scrollBar)
        return
    end

    local _, cursorY = GetCursorPosition()
    cursorY = cursorY / UIParent:GetEffectiveScale()

    local thumbTop = cursorY + (scrollBar.DragOffsetY or 0)
    local offset = trackTop - thumbTop
    local movableRange = math.max(trackHeight - thumbHeight, 1)
    local percent = math.max(0, math.min(1, offset / movableRange))
    scrollFrame:SetVerticalScroll(maxScroll * percent)
    Ui.RefreshTabScrollBar(scrollFrame)
end

local function CreateTabScrollBar(ctx, scrollFrame, tabPage, name, options)
    local scrollBar = CreateFrame("Frame", name .. "CustomScrollBar", tabPage)
    scrollBar:SetWidth(options.scrollBarWidth or TAB_SCROLLBAR_WIDTH)
    scrollBar:SetFrameLevel(tabPage:GetFrameLevel() + 4)
    scrollBar.ScrollFrame = scrollFrame
    scrollFrame.ScrollBar = scrollBar
    scrollBar:EnableMouseWheel(true)
    scrollBar:SetScript("OnMouseWheel", function(_, delta)
        ScrollTabFrameBy(scrollFrame, delta < 0 and TAB_SCROLL_STEP or -TAB_SCROLL_STEP)
    end)

    scrollBar:ClearAllPoints()
    if options.scrollBarAnchorFrame and options.scrollBarCenterXOffset then
        scrollBar:SetPoint("TOP", options.scrollBarAnchorFrame, "TOPRIGHT", options.scrollBarCenterXOffset, -(options.scrollBarTopOffset or 4))
        scrollBar:SetPoint("BOTTOM", options.scrollBarAnchorFrame, "BOTTOMRIGHT", options.scrollBarCenterXOffset, options.scrollBarBottomOffset or 4)
    else
        scrollBar:SetPoint("TOPRIGHT", tabPage, "TOPRIGHT", options.scrollBarRightOffset or 34, -(options.scrollBarTopOffset or 4))
        scrollBar:SetPoint("BOTTOMRIGHT", tabPage, "BOTTOMRIGHT", options.scrollBarRightOffset or 34, options.scrollBarBottomOffset or 4)
    end

    local upButton = CreateFrame("Button", name .. "ScrollUpButton", scrollBar)
    StyleTaskLogScrollButton(upButton, "UP")
    upButton:SetPoint("TOP", scrollBar, "TOP", 0, -1)
    upButton:SetFrameLevel(scrollBar:GetFrameLevel() + 3)
    upButton:SetScript("OnClick", function()
        ScrollTabFrameBy(scrollFrame, -TAB_SCROLL_STEP)
    end)

    local downButton = CreateFrame("Button", name .. "ScrollDownButton", scrollBar)
    StyleTaskLogScrollButton(downButton, "DOWN")
    downButton:SetPoint("BOTTOM", scrollBar, "BOTTOM", 0, 1)
    downButton:SetFrameLevel(scrollBar:GetFrameLevel() + 3)
    downButton:SetScript("OnClick", function()
        ScrollTabFrameBy(scrollFrame, TAB_SCROLL_STEP)
    end)

    local track = CreateFrame("Button", name .. "ScrollTrack", scrollBar)
    track:SetWidth(12)
    track:SetPoint("TOP", upButton, "BOTTOM", 0, -2)
    track:SetPoint("BOTTOM", downButton, "TOP", 0, 2)
    track:SetFrameLevel(scrollBar:GetFrameLevel() + 1)
    track:EnableMouseWheel(true)

    local trackTop = CreateScrollBarAtlasTexture(track, "BACKGROUND", "minimal-scrollbar-track-top", "Interface\\PaperDollInfoFrame\\UI-Character-ScrollBar")
    trackTop:SetPoint("TOP", track, "TOP", 0, 0)
    trackTop:SetSize(10, 10)

    local trackBottom = CreateScrollBarAtlasTexture(track, "BACKGROUND", "minimal-scrollbar-track-bottom", "Interface\\PaperDollInfoFrame\\UI-Character-ScrollBar")
    trackBottom:SetPoint("BOTTOM", track, "BOTTOM", 0, 0)
    trackBottom:SetSize(10, 10)

    local trackMiddle = CreateScrollBarAtlasTexture(track, "BACKGROUND", "!minimal-scrollbar-track-middle", "Interface\\PaperDollInfoFrame\\UI-Character-ScrollBar")
    trackMiddle:SetPoint("TOP", trackTop, "BOTTOM", 0, 0)
    trackMiddle:SetPoint("BOTTOM", trackBottom, "TOP", 0, 0)
    trackMiddle:SetWidth(10)

    local thumb = CreateFrame("Button", name .. "ScrollThumb", track)
    thumb:SetSize(12, TAB_SCROLL_MIN_THUMB_HEIGHT)
    thumb:SetFrameLevel(scrollBar:GetFrameLevel() + 4)
    thumb.TopTexture = CreateScrollBarAtlasTexture(thumb, "ARTWORK", "minimal-scrollbar-thumb-top", "Interface\\Buttons\\UI-ScrollBar-Knob")
    thumb.TopTexture:SetPoint("TOP", thumb, "TOP", 0, 0)

    thumb.BottomTexture = CreateScrollBarAtlasTexture(thumb, "ARTWORK", "minimal-scrollbar-thumb-bottom", "Interface\\Buttons\\UI-ScrollBar-Knob")
    thumb.BottomTexture:SetPoint("BOTTOM", thumb, "BOTTOM", 0, 0)

    thumb.MiddleTexture = CreateScrollBarAtlasTexture(thumb, "ARTWORK", "minimal-scrollbar-thumb-middle", "Interface\\Buttons\\UI-ScrollBar-Knob")
    thumb.MiddleTexture:SetPoint("TOP", thumb, "TOP", 0, -TAB_SCROLL_THUMB_CAP_HEIGHT)
    thumb.MiddleTexture:SetPoint("BOTTOM", thumb, "BOTTOM", 0, TAB_SCROLL_THUMB_CAP_HEIGHT + 2)
    thumb.MiddleTexture:SetWidth(TAB_SCROLL_THUMB_WIDTH)
    thumb:RegisterForDrag("LeftButton")
    thumb:EnableMouseWheel(true)
    thumb:SetScript("OnEnter", function(self)
        SetTabScrollThumbState(self, "HOVER")
    end)
    thumb:SetScript("OnLeave", function(self)
        SetTabScrollThumbState(self, "NORMAL")
    end)
    thumb:SetScript("OnMouseDown", function(self)
        SetTabScrollThumbState(self, "PUSHED")
    end)
    thumb:SetScript("OnDragStart", function(self)
        local _, cursorY = GetCursorPosition()
        cursorY = cursorY / UIParent:GetEffectiveScale()
        scrollBar.DragOffsetY = (self:GetTop() or cursorY) - cursorY
        scrollBar.DraggingThumb = true
        scrollBar:SetScript("OnUpdate", UpdateTabScrollThumbDrag)
        SetTabScrollThumbState(self, "PUSHED")
    end)
    thumb:SetScript("OnDragStop", function()
        StopTabScrollThumbDrag(scrollBar)
    end)
    thumb:SetScript("OnMouseUp", function(self)
        StopTabScrollThumbDrag(scrollBar)
        SetTabScrollThumbState(self, self:IsMouseOver() and "HOVER" or "NORMAL")
    end)
    thumb:SetScript("OnHide", function()
        StopTabScrollThumbDrag(scrollBar)
    end)

    track:SetScript("OnMouseDown", function()
        local thumbTop = thumb:GetTop()
        local thumbBottom = thumb:GetBottom()
        if not thumbTop or not thumbBottom then return end

        local _, cursorY = GetCursorPosition()
        cursorY = cursorY / UIParent:GetEffectiveScale()
        local pageStep = math.max((scrollFrame:GetHeight() or TAB_SCROLL_STEP) * 0.8, TAB_SCROLL_STEP)

        if cursorY > thumbTop then
            ScrollTabFrameBy(scrollFrame, -pageStep)
        elseif cursorY < thumbBottom then
            ScrollTabFrameBy(scrollFrame, pageStep)
        end
    end)
    track:SetScript("OnMouseWheel", function(_, delta)
        ScrollTabFrameBy(scrollFrame, delta < 0 and TAB_SCROLL_STEP or -TAB_SCROLL_STEP)
    end)
    thumb:SetScript("OnMouseWheel", function(_, delta)
        ScrollTabFrameBy(scrollFrame, delta < 0 and TAB_SCROLL_STEP or -TAB_SCROLL_STEP)
    end)

    scrollBar.ScrollUpButton = upButton
    scrollBar.ScrollDownButton = downButton
    scrollBar.Track = track
    scrollBar.Thumb = thumb

    return scrollBar
end

function Ui.CreateTabScrollFrame(ctx, parent, name, contentHeight, options)
    options = options or {}
    local tabPage = CreateFrame("Frame", name .. "TabPage", parent)
    tabPage:SetPoint("TOPLEFT", parent, "TOPLEFT", options.pageLeftInset or TAB_PAGE_LEFT_INSET, -(options.pageTopInset or TAB_PAGE_TOP_INSET))
    tabPage:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", -(options.pageRightInset or TAB_PAGE_RIGHT_INSET), options.pageBottomInset or TAB_PAGE_BOTTOM_INSET)
    tabPage:SetFrameLevel(ctx.contentBorder:GetFrameLevel() + 1)

    local scrollFrame = CreateFrame("ScrollFrame", name .. "ScrollFrame", tabPage)
    local scrollBarGutter = options.hideScrollBar and 0 or (options.scrollBarGutter or TAB_SCROLLBAR_GUTTER)
    scrollFrame:SetPoint("TOPLEFT", tabPage, "TOPLEFT", 0, 0)
    scrollFrame:SetPoint("BOTTOMRIGHT", tabPage, "BOTTOMRIGHT", -scrollBarGutter, 0)
    scrollFrame:SetFrameLevel(tabPage:GetFrameLevel() + 1)
    scrollFrame.TabPage = tabPage
    scrollFrame.HideSystemScrollBar = options.hideScrollBar and true or false

    local content = CreateFrame("Frame", name .. "Content", scrollFrame)
    content.MinHeight = options.minContentHeight or 1
    content:SetSize(options.contentWidth or ctx.TAB_CONTENT_WIDTH, contentHeight)
    scrollFrame:SetScrollChild(content)
    scrollFrame.content = content

    scrollFrame:EnableMouseWheel(true)
    scrollFrame:SetScript("OnMouseWheel", function(self, delta)
        local current = self:GetVerticalScroll()
        local maxScroll = self:GetVerticalScrollRange()
        local step = 36

        if delta < 0 then
            self:SetVerticalScroll(math.min(current + step, maxScroll))
        else
            self:SetVerticalScroll(math.max(current - step, 0))
        end
        Ui.RefreshTabScrollBar(self)
    end)

    if options.hideScrollBar then
        HideTabScrollBar(scrollFrame)
        scrollFrame:SetScript("OnShow", function(self)
            HideTabScrollBar(self)
            self:SetVerticalScroll(0)
        end)
    end

    if not options.hideScrollBar then
        CreateTabScrollBar(ctx, scrollFrame, tabPage, name, options)
        HideLegacyScrollBarResidue(scrollFrame)

        scrollFrame:SetScript("OnScrollRangeChanged", Ui.RefreshTabScrollBar)
        scrollFrame:SetScript("OnVerticalScroll", Ui.RefreshTabScrollBar)
        scrollFrame:SetScript("OnShow", function(self)
            self:SetVerticalScroll(0)
            HideLegacyScrollBarResidue(self)
            C_Timer.After(0, function()
                Ui.RefreshTabScrollBar(self)
            end)
        end)
    end

    return scrollFrame, content
end

function Ui.SetTabScrollFrameShown(scrollFrame, shown)
    if not scrollFrame then return end
    if scrollFrame.TabPage then
        scrollFrame.TabPage:SetShown(shown)
    else
        scrollFrame:SetShown(shown)
    end
end

function Ui.UpdateScrollContentHeight(scrollFrame, content, lastRegion, bottomPadding)
    if not scrollFrame or not content or not lastRegion then return end

    local function Apply()
        local viewportHeight = math.max(scrollFrame:GetHeight() or 0, content.MinHeight or 1)
        if viewportHeight <= 1 then
            viewportHeight = math.max(content:GetHeight() or 0, content.MinHeight or 1)
        end
        local contentTop = content:GetTop()
        local lastBottom

        if type(lastRegion) == "table" and not lastRegion.GetBottom then
            for _, region in ipairs(lastRegion) do
                local bottom = region and region.GetBottom and region:GetBottom()
                if bottom then
                    lastBottom = lastBottom and math.min(lastBottom, bottom) or bottom
                end
            end
        else
            lastBottom = lastRegion.GetBottom and lastRegion:GetBottom()
        end

        if contentTop and lastBottom then
            local height = math.max(viewportHeight, contentTop - lastBottom + (bottomPadding or 36))
            content:SetHeight(height)
        else
            content:SetHeight(viewportHeight)
        end

        Ui.RefreshTabScrollBar(scrollFrame)
    end

    Apply()
    C_Timer.After(0, Apply)
end

function Ui.FitScrollContentToViewport(scrollFrame, content)
    if not scrollFrame or not content then return end

    local function Apply()
        local viewportHeight = math.max(scrollFrame:GetHeight() or 0, content.MinHeight or 1)
        if viewportHeight <= 1 then
            viewportHeight = math.max(content:GetHeight() or 0, content.MinHeight or 1)
        end

        content:SetHeight(viewportHeight)
        scrollFrame:SetVerticalScroll(0)
        Ui.RefreshTabScrollBar(scrollFrame)
    end

    Apply()
    C_Timer.After(0, Apply)
end

function Ui.DisableTabScrolling(scrollFrame, content)
    if not scrollFrame then return end

    scrollFrame.HideSystemScrollBar = true
    scrollFrame:SetVerticalScroll(0)
    HideTabScrollBar(scrollFrame)

    scrollFrame:SetScript("OnMouseWheel", function(self)
        self:SetVerticalScroll(0)
        HideTabScrollBar(self)
    end)
    scrollFrame:SetScript("OnScrollRangeChanged", function(self)
        self:SetVerticalScroll(0)
        HideTabScrollBar(self)
    end)
    scrollFrame:SetScript("OnVerticalScroll", function(self)
        if (self:GetVerticalScroll() or 0) ~= 0 then
            self:SetVerticalScroll(0)
        end
        HideTabScrollBar(self)
    end)
    scrollFrame:SetScript("OnShow", function(self)
        Ui.FitScrollContentToViewport(self, content)
        HideTabScrollBar(self)
    end)
end

function Ui.ApplyContentTextStyle(ctx, fontString, style)
    if not fontString or not style then return end
    Ui.ApplyClientDefaultFont(fontString, style.size, style.flags)
    fontString:SetTextColor(style.color.r, style.color.g, style.color.b)
    fontString:SetShadowColor(0, 0, 0, style.shadowAlpha or 0.85)
    fontString:SetShadowOffset(1, -1)
end

function Ui.CreateSectionLabel(ctx, parent, text, width)
    local headerFrame = CreateFrame("Frame", nil, parent)
    headerFrame:SetFrameLevel(parent:GetFrameLevel() + 1)

    local leftBg = headerFrame:CreateTexture(nil, "BACKGROUND")
    leftBg:SetTexture(SECTION_HEADER_LEFT_TEXTURE)

    local bodyBg = headerFrame:CreateTexture(nil, "BACKGROUND")
    bodyBg:SetTexture(SECTION_HEADER_BODY_TEXTURE)

    local rightBg = headerFrame:CreateTexture(nil, "BACKGROUND")
    rightBg:SetTexture(SECTION_HEADER_RIGHT_TEXTURE)

    local label = headerFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    label:SetText(text)
    Ui.ApplyContentTextStyle(ctx, label, ctx.TEXT_STYLE_PRIMARY_TITLE)
    label:SetJustifyH("LEFT")
    label:SetJustifyV("MIDDLE")
    label:SetWordWrap(false)

    headerFrame:SetHeight(ctx.SECTION_HEADER_HEIGHT)
    headerFrame:SetWidth(math.max(
        (width and width > 0) and width or ctx.SECTION_HEADER_TEXTURE_WIDTH,
        (ctx.SECTION_HEADER_EDGE_WIDTH or 1) * 2 + 1
    ))

    leftBg:ClearAllPoints()
    leftBg:SetPoint("TOPLEFT", headerFrame, "TOPLEFT", 0, 0)
    leftBg:SetPoint("BOTTOMLEFT", headerFrame, "BOTTOMLEFT", 0, 0)
    leftBg:SetWidth(ctx.SECTION_HEADER_EDGE_WIDTH)
    leftBg:SetAlpha(ctx.SECTION_HEADER_BG_ALPHA)

    rightBg:ClearAllPoints()
    rightBg:SetPoint("TOPRIGHT", headerFrame, "TOPRIGHT", 0, 0)
    rightBg:SetPoint("BOTTOMRIGHT", headerFrame, "BOTTOMRIGHT", 0, 0)
    rightBg:SetWidth(ctx.SECTION_HEADER_EDGE_WIDTH)
    rightBg:SetAlpha(ctx.SECTION_HEADER_BG_ALPHA)

    bodyBg:ClearAllPoints()
    bodyBg:SetPoint("TOPLEFT", leftBg, "TOPRIGHT", 0, 0)
    bodyBg:SetPoint("BOTTOMRIGHT", rightBg, "BOTTOMLEFT", 0, 0)
    bodyBg:SetAlpha(ctx.SECTION_HEADER_BG_ALPHA)

    label:ClearAllPoints()
    label:SetPoint("TOPLEFT", headerFrame, "TOPLEFT", ctx.SECTION_HEADER_TEXT_LEFT_PADDING, ctx.SECTION_HEADER_TEXT_TOP_OFFSET)
    label:SetPoint("BOTTOMRIGHT", headerFrame, "BOTTOMRIGHT", -ctx.SECTION_HEADER_TEXT_RIGHT_PADDING, ctx.SECTION_HEADER_TEXT_BOTTOM_OFFSET)

    headerFrame.Text = label
    headerFrame.Background = bodyBg
    headerFrame.BackgroundLeft = leftBg
    headerFrame.BackgroundBody = bodyBg
    headerFrame.BackgroundRight = rightBg
    headerFrame.GetText = function() return label:GetText() end
    headerFrame.SetText = function(_, newText) label:SetText(newText) end

    return headerFrame
end

function Ui.CreateFieldLabel(ctx, parent, text, width)
    local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    label:SetText(text)
    Ui.ApplyContentTextStyle(ctx, label, ctx.TEXT_STYLE_SECONDARY_TITLE)
    label:SetWidth(width or ctx.labelWidth)
    label:SetJustifyH("LEFT")
    return label
end

function Ui.CreateDescription(ctx, parent, text, width, style)
    local desc = parent:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    desc:SetText(text)
    Ui.ApplyContentTextStyle(ctx, desc, style or ctx.TEXT_STYLE_DESCRIPTION)
    desc:SetWidth(width or ctx.sectionWidth)
    desc:SetJustifyH("LEFT")
    desc:SetWordWrap(true)
    return desc
end

local function FormatHintDescriptionText(ctx, text)
    local iconSize = ctx and ctx.DESCRIPTION_ICON_SIZE or 17
    return string.format("|T%s:%d:%d:0:0:64:64:0:64:0:64|t %s", HINT_DESCRIPTION_ICON, iconSize, iconSize, tostring(text or ""))
end

function Ui.CreateHintDescription(ctx, parent, text, width)
    return Ui.CreateDescription(ctx, parent, FormatHintDescriptionText(ctx, text), width, ctx.TEXT_STYLE_DESCRIPTION_HINT)
end

local function FormatImportantDescriptionText(ctx, text)
    local iconSize = ctx and ctx.DESCRIPTION_ICON_SIZE or 17
    return string.format("|T%s:%d:%d:0:0:64:64:0:64:0:64|t %s", IMPORTANT_DESCRIPTION_ICON, iconSize, iconSize, tostring(text or ""))
end

function Ui.CreateImportantDescription(ctx, parent, text, width)
    return Ui.CreateDescription(ctx, parent, FormatImportantDescriptionText(ctx, text), width, ctx.TEXT_STYLE_DESCRIPTION_IMPORTANT)
end

function Ui.CreateColumnAnchor(parent, columnX, verticalAnchor, verticalPoint, yOffset)
    local anchor = CreateFrame("Frame", nil, parent)
    anchor:SetSize(1, 1)
    anchor:SetPoint("LEFT", parent, "LEFT", columnX, 0)
    anchor:SetPoint("TOP", verticalAnchor, verticalPoint or "BOTTOM", 0, yOffset or 0)
    return anchor
end

function Ui.CreateColumnDivider(ctx, parent, name)
    if not parent then return nil end

    local centerX = (ctx.formSectionWidth or 0) + ((ctx.FORM_COLUMN_GAP or 0) / 2)
    local divider = parent:CreateTexture(name, "ARTWORK")
    divider:SetTexture(COLUMN_DIVIDER_TEXTURE)
    divider:SetWidth(ctx.COLUMN_DIVIDER_WIDTH or 12)
    divider:SetPoint("TOP", parent, "TOPLEFT", centerX, -(ctx.COLUMN_DIVIDER_TOP_INSET or 0))
    divider:SetPoint("BOTTOM", parent, "BOTTOMLEFT", centerX, ctx.COLUMN_DIVIDER_BOTTOM_INSET or 0)
    divider:SetTexCoord(0, 1, 1, 1, 0, 0, 1, 0)
    divider:SetAlpha(ctx.COLUMN_DIVIDER_ALPHA or 0.92)

    return divider
end

function Ui.ConfigureCheckText(ctx, check, width)
    check.Text:SetWidth(width)
    check.Text:SetJustifyH("LEFT")
    check.Text:SetWordWrap(false)
    Ui.ApplyContentTextStyle(ctx, check.Text, ctx.TEXT_STYLE_SECONDARY_TITLE)
end

function Ui.StyleEditBox(editBox)
    if not editBox then return end
    Ui.UseClientDefaultFont(editBox)
    editBox:SetTextColor(1, 0.96, 0.86)
    editBox:SetShadowColor(0, 0, 0, 0.8)
    editBox:SetShadowOffset(1, -1)
    if editBox.SetTextInsets then
        editBox:SetTextInsets(10, 10, 0, 0)
    end

    local name = editBox.GetName and editBox:GetName()
    if name then
        local templateRegions = {
            _G[name .. "Left"],
            _G[name .. "Middle"],
            _G[name .. "Right"],
            _G[name .. "LeftTexture"],
            _G[name .. "MiddleTexture"],
            _G[name .. "RightTexture"],
        }
        for _, region in ipairs(templateRegions) do
            if region and region.Hide then
                region:Hide()
                if region.SetAlpha then
                    region:SetAlpha(0)
                end
            end
        end
    end

    if editBox.Left then editBox.Left:Hide() end
    if editBox.Middle then editBox.Middle:Hide() end
    if editBox.Right then editBox.Right:Hide() end

    local parent = editBox:GetParent() or editBox
    if not editBox.TMA_InputFrame then
        editBox.TMA_InputFrame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
        editBox.TMA_InputFrame:EnableMouse(false)
        editBox.TMA_InputFrame:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = false,
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        editBox.TMA_InputFrame.InnerBorder = CreateFrame("Frame", nil, editBox.TMA_InputFrame, "BackdropTemplate")
        editBox.TMA_InputFrame.InnerBorder:SetPoint("TOPLEFT", editBox.TMA_InputFrame, "TOPLEFT", 3, -3)
        editBox.TMA_InputFrame.InnerBorder:SetPoint("BOTTOMRIGHT", editBox.TMA_InputFrame, "BOTTOMRIGHT", -3, 3)
        editBox.TMA_InputFrame.InnerBorder:SetBackdrop({
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            edgeSize = 1,
        })
        editBox.TMA_InputFrame.InnerBorder:SetBackdropBorderColor(0.12, 0.12, 0.12, 0.95)

        editBox:HookScript("OnEditFocusGained", function(self)
            if self.TMA_InputFrame then
                self.TMA_InputFrame:SetBackdropBorderColor(0.86, 0.78, 0.58, 1)
            end
        end)
        editBox:HookScript("OnEditFocusLost", function(self)
            if self.TMA_InputFrame then
                self.TMA_InputFrame:SetBackdropBorderColor(0.46, 0.46, 0.46, 0.95)
            end
        end)
        editBox:HookScript("OnShow", function(self)
            if self.TMA_InputFrame then
                self.TMA_InputFrame:Show()
            end
        end)
        editBox:HookScript("OnHide", function(self)
            if self.TMA_InputFrame then
                self.TMA_InputFrame:Hide()
            end
        end)
    end

    editBox.TMA_InputFrame:ClearAllPoints()
    editBox.TMA_InputFrame:SetPoint("TOPLEFT", editBox, "TOPLEFT", -3, 3)
    editBox.TMA_InputFrame:SetPoint("BOTTOMRIGHT", editBox, "BOTTOMRIGHT", 3, -3)
    editBox.TMA_InputFrame:SetFrameLevel(math.max((editBox:GetFrameLevel() or 1) - 1, 0))
    editBox.TMA_InputFrame:SetBackdropColor(0.015, 0.015, 0.015, 0.92)
    editBox.TMA_InputFrame:SetBackdropBorderColor(0.46, 0.46, 0.46, 0.95)
    editBox.TMA_InputFrame:SetShown(editBox:IsShown())
    editBox:SetFrameLevel((editBox.TMA_InputFrame:GetFrameLevel() or 0) + 1)
end

function Ui.StylePreviewFrame(frame)
    if not frame or not frame.SetBackdrop then return end

    frame:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 16,
        edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    frame:SetBackdropColor(0, 0, 0, 0.45)
    frame:SetBackdropBorderColor(0.55, 0.45, 0.25, 0.9)
end

function Ui.RefreshDropdown(dropdown)
    if dropdown and dropdown.GenerateMenu then
        dropdown:GenerateMenu()
    end
end

function Ui.SetupRadioDropdown(dropdown, tag, getText, getValue, setValue, getItems, afterSelect)
    dropdown:SetSelectionText(function()
        return getText()
    end)

    dropdown:SetupMenu(function(_, rootDescription)
        rootDescription:SetTag(tag)

        local function IsSelected(data)
            return getValue() == data.value
        end

        local function SetSelected(data)
            setValue(data.value, data)
            if afterSelect then
                afterSelect(data)
            end
            Ui.RefreshDropdown(dropdown)
        end

        for _, data in ipairs(getItems()) do
            rootDescription:CreateRadio(data.text, IsSelected, SetSelected, data)
        end
    end)
end

function Ui.CreateSectionRule(ctx, parent, anchor, width)
    -- 不再显示装饰线，仅返回一个不可见占位Frame用于统一标题下方锚点。
    local rule = CreateFrame("Frame", nil, parent)
    local ruleHeight = math.max(ctx.SECTION_RULE_HEIGHT or 0, 1)
    rule:SetSize(width or 1, ruleHeight)
    rule:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -(ctx.SECTION_RULE_TITLE_GAP or 2))
    rule:Hide()
    
    return rule
end

local function SelectTab(tab, selected)
    if selected then
        if PanelTemplates_SelectTab then PanelTemplates_SelectTab(tab) else tab:LockHighlight() end
    else
        if PanelTemplates_DeselectTab then PanelTemplates_DeselectTab(tab) else tab:UnlockHighlight() end
    end
end

local function SetOptionsFramePortrait(frame, texturePath)
    if not frame or not texturePath then return end

    if frame.SetPortraitTextureRaw then
        frame:SetPortraitTextureRaw(texturePath)
    elseif frame.SetPortraitToAsset then
        frame:SetPortraitToAsset(texturePath)
    elseif frame.PortraitContainer and frame.PortraitContainer.portrait then
        frame.PortraitContainer.portrait:SetTexture(texturePath)
    end

    if frame.SetPortraitTexCoord then
        frame:SetPortraitTexCoord(0, 1, 0, 1)
    end

    if frame.PortraitContainer and frame.PortraitContainer.portrait then
        local portraitContainer = frame.PortraitContainer
        local portrait = portraitContainer.portrait
        portrait:ClearAllPoints()
        portrait:SetSize(63, 63)
        portrait:SetPoint("TOPLEFT", portraitContainer, "TOPLEFT", -6, 8.5)
        portrait:SetTexCoord(0, 1, 0, 1)
        if portraitContainer.CircleMask then
            portraitContainer.CircleMask:ClearAllPoints()
            portraitContainer.CircleMask:SetPoint("TOPLEFT", portraitContainer, "TOPLEFT", -3, 7)
            portraitContainer.CircleMask:SetPoint("BOTTOMRIGHT", portraitContainer, "TOPLEFT", 55, -51)
        end
    end
end

function Ui.ShowPage(ctx, page)
    local currentPage = page or "MARKER"
    ctx.currentPage = currentPage
    local showMarker = currentPage == "MARKER"
    local showAlert = currentPage == "ALERT"
    local showCombat = currentPage == "COMBAT"

    Ui.SetTabScrollFrameShown(ctx.markerScrollFrame, showMarker)
    Ui.SetTabScrollFrameShown(ctx.castAlertScrollFrame, showAlert)
    Ui.SetTabScrollFrameShown(ctx.combatScrollFrame, showCombat)

    ctx.markerScrollFrame:SetVerticalScroll(0)
    ctx.castAlertScrollFrame:SetVerticalScroll(0)
    ctx.combatScrollFrame:SetVerticalScroll(0)
    Ui.RefreshTabScrollBar(ctx.markerScrollFrame)
    Ui.RefreshTabScrollBar(ctx.castAlertScrollFrame)
    Ui.RefreshTabScrollBar(ctx.combatScrollFrame)

    SelectTab(ctx.markerTab, showMarker)
    SelectTab(ctx.castAlertTab, showAlert)
    SelectTab(ctx.combatTab, showCombat)
end

function Ui.CreateOptionsShell(options)
    options = options or {}
    local L = options.L or TargetMarkerAssistantL or {}
    local addonVersion = options.addonVersion or "1.1.3"
    local minimapIconPath = options.minimapIconPath

    local optionsFrame = CreateFrame("Frame", "TargetMarkerAssistantOptionsFrame", UIParent, "ButtonFrameBaseTemplate")
    optionsFrame:SetSize(900, 520)
    optionsFrame:SetPoint("CENTER")
    optionsFrame:SetFrameStrata("MEDIUM")
    optionsFrame:SetFrameLevel(10)
    if optionsFrame.SetToplevel then
        optionsFrame:SetToplevel(true)
    end
    optionsFrame:SetMovable(true)
    optionsFrame:EnableMouse(true)
    optionsFrame:SetClampedToScreen(true)
    optionsFrame:RegisterForDrag("LeftButton")
    optionsFrame:SetScript("OnDragStart", optionsFrame.StartMoving)
    optionsFrame:SetScript("OnDragStop", optionsFrame.StopMovingOrSizing)
    optionsFrame:Hide()
    table.insert(UISpecialFrames, "TargetMarkerAssistantOptionsFrame")

    SetOptionsFramePortrait(optionsFrame, minimapIconPath)

    if optionsFrame.SetTitle then
        optionsFrame:SetTitle(L["ADDON_TITLE"])
    elseif optionsFrame.TitleText then
        optionsFrame.TitleText:SetText(L["ADDON_TITLE"])
    end

    if optionsFrame.NineSlice then
        if optionsFrame.NineSlice.BottomLeftCorner then
            SafeShowRegion(optionsFrame.NineSlice.BottomLeftCorner)
            SafeSetDrawLayer(optionsFrame.NineSlice.BottomLeftCorner, "OVERLAY", 1)
        end
        if optionsFrame.NineSlice.BottomRightCorner then
            SafeShowRegion(optionsFrame.NineSlice.BottomRightCorner)
            SafeSetDrawLayer(optionsFrame.NineSlice.BottomRightCorner, "OVERLAY", 1)
        end
        if optionsFrame.NineSlice.BottomEdge then
            SafeShowRegion(optionsFrame.NineSlice.BottomEdge)
            SafeSetDrawLayer(optionsFrame.NineSlice.BottomEdge, "OVERLAY", 0)
        end
    end

    local frameTitle = optionsFrame.TitleText or (optionsFrame.TitleContainer and optionsFrame.TitleContainer.TitleText)
    if frameTitle then
        frameTitle:SetText("")
        SafeHideRegion(frameTitle)
    end

    TMA.CustomTitle = optionsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    TMA.CustomTitle:SetPoint("TOP", optionsFrame, "TOP", 0, -25)
    TMA.CustomTitle:SetWidth(460)
    TMA.CustomTitle:SetHeight(28)
    TMA.CustomTitle:SetJustifyH("CENTER")
    TMA.CustomTitle:SetJustifyV("MIDDLE")
    TMA.CustomTitle:SetText(L["ADDON_TITLE"])
    Ui.UseClientDefaultFont(TMA.CustomTitle)
    TMA.CustomTitle:SetTextColor(1, 0.82, 0)
    TMA.CustomTitle:SetShadowColor(0, 0, 0, 0.9)
    TMA.CustomTitle:SetShadowOffset(1, -1)

    local footerTextY = 15
    local footerTextLeftX = PANEL_CONTENT_LEFT_INSET + 8

    TMA.FooterVersionText = optionsFrame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    TMA.FooterVersionText:SetPoint("BOTTOMLEFT", optionsFrame, "BOTTOMLEFT", footerTextLeftX, footerTextY)
    TMA.FooterVersionText:SetSize(168, 18)
    TMA.FooterVersionText:SetJustifyH("LEFT")
    if TMA.FooterVersionText.SetJustifyV then
        TMA.FooterVersionText:SetJustifyV("MIDDLE")
    end
    Ui.UseClientDefaultFont(TMA.FooterVersionText)
    TMA.FooterVersionText:SetText(string.format(L["SETTINGS_VERSION_FORMAT"] or "Current version: %s", addonVersion))
    TMA.FooterVersionText:SetTextColor(1, 0.82, 0)
    TMA.FooterVersionText:SetShadowColor(0, 0, 0, 0.85)
    TMA.FooterVersionText:SetShadowOffset(1, -1)

    local contentFrame = CreateFrame("Frame", "TargetMarkerAssistantContentFrame", optionsFrame, "InsetFrameTemplate")
    contentFrame:SetPoint("TOPLEFT", PANEL_CONTENT_LEFT_INSET, -PANEL_CONTENT_TOP_INSET)
    contentFrame:SetPoint("BOTTOMRIGHT", -PANEL_CONTENT_RIGHT_INSET, PANEL_CONTENT_BOTTOM_INSET)

    if contentFrame.Bg and not IsProtectedFrame(contentFrame.Bg) then
        SafeShowRegion(contentFrame.Bg)
        contentFrame.Bg:SetAlpha(0.88)
        contentFrame.Bg:SetTexture("Interface\\Collections\\CollectionsBackgroundTile", true, true)
        if contentFrame.Bg.SetHorizTile then contentFrame.Bg:SetHorizTile(true) end
        if contentFrame.Bg.SetVertTile then contentFrame.Bg:SetVertTile(true) end
    end

    local contentBorder = CreateFrame("Frame", "TargetMarkerAssistantContentBorder", contentFrame, "QuestLogBorderFrameTemplate")
    contentBorder:SetPoint("TOPLEFT", contentFrame, "TOPLEFT", 0, 0)
    contentBorder:SetPoint("BOTTOMRIGHT", contentFrame, "BOTTOMRIGHT", 0, 0)
    contentBorder:SetFrameLevel(contentFrame:GetFrameLevel() + 1)

    if contentBorder.NineSlice then
        if contentBorder.NineSlice.BottomLeftCorner then
            SafeShowRegion(contentBorder.NineSlice.BottomLeftCorner)
            SafeSetDrawLayer(contentBorder.NineSlice.BottomLeftCorner, "OVERLAY", 2)
        end
        if contentBorder.NineSlice.BottomRightCorner then
            SafeShowRegion(contentBorder.NineSlice.BottomRightCorner)
            SafeSetDrawLayer(contentBorder.NineSlice.BottomRightCorner, "OVERLAY", 2)
        end
        if contentBorder.NineSlice.BottomEdge then
            SafeShowRegion(contentBorder.NineSlice.BottomEdge)
            SafeSetDrawLayer(contentBorder.NineSlice.BottomEdge, "OVERLAY", 1)
        end
    end

    local tabContentWidth = 900 - PANEL_CONTENT_LEFT_INSET - PANEL_CONTENT_RIGHT_INSET - TAB_PAGE_LEFT_INSET - TAB_PAGE_RIGHT_INSET
    local scrollBarCenterXOffset = (PANEL_CONTENT_RIGHT_INSET - PANEL_VISIBLE_RIGHT_BORDER_INSET) / 2

    local clientDefaultFont = (TMA.GetClientDefaultFont and TMA.GetClientDefaultFont())
        or (GameFontNormal and GameFontNormal.GetFont and select(1, GameFontNormal:GetFont()))
        or STANDARD_TEXT_FONT
        or UNIT_NAME_FONT
        or DAMAGE_TEXT_FONT

    local ctx = {
        ui = Ui,
        L = L,
        optionsFrame = optionsFrame,
        contentFrame = contentFrame,
        contentBorder = contentBorder,
        TAB_CONTENT_WIDTH = tabContentWidth,
        labelWidth = 90,
        FORM_COLUMN_GAP = 20,
        formLeftX = 0,
        formTopY = 0,
        combatColumnX = 0,
        combatFullWidth = tabContentWidth,
        combatDropdownWidth = 360,
        combatInputWidth = tabContentWidth - 8,
        combatSpellCardWidth = 560,
        formInlineDropdownWidth = 120,
        formInlineDropdownRightInset = 20,
        compactDropdownWidth = 208,
        castAlertInlineControlGap = 14,
        castAlertTestButtonWidth = 100,
        defaultTextFont = clientDefaultFont,
        sectionTitleFont = clientDefaultFont,
        TEXT_STYLE_PRIMARY_TITLE = { size = 17, flags = "OUTLINE", color = { r = 1, g = 0.82, b = 0.08 }, shadowAlpha = 0.9 },
        TEXT_STYLE_SECONDARY_TITLE = { size = 15, flags = "", color = { r = 0.96, g = 0.78, b = 0.26 }, shadowAlpha = 0.85 },
        TEXT_STYLE_DESCRIPTION = { size = 13, flags = "", color = { r = 0.66, g = 0.66, b = 0.62 }, shadowAlpha = 0.85 },
        TEXT_STYLE_DESCRIPTION_HINT = { size = 13, flags = "", color = { r = 0.95, g = 0.72, b = 0.32 }, shadowAlpha = 0.9 },
        TEXT_STYLE_DESCRIPTION_IMPORTANT = { size = 13, flags = "", color = { r = 0.86, g = 0.42, b = 1.0 }, shadowAlpha = 0.9 },
        DESCRIPTION_ICON_SIZE = 17,
        SECTION_HEADER_TEXTURE_WIDTH = 260,
        SECTION_HEADER_TEXTURE_HEIGHT = 34,
        SECTION_HEADER_HEIGHT = 34,
        SECTION_HEADER_EDGE_WIDTH = 107,
        SECTION_HEADER_TEXT_LEFT_PADDING = 34,
        SECTION_HEADER_TEXT_RIGHT_PADDING = 22,
        SECTION_HEADER_TEXT_TOP_OFFSET = -1,
        SECTION_HEADER_TEXT_BOTTOM_OFFSET = 2,
        SECTION_HEADER_BG_ALPHA = 0.9,
        SECTION_RULE_TITLE_GAP = 4,
        SECTION_RULE_CONTENT_GAP = 12,
        SECTION_RULE_HEIGHT = 1,
        SECTION_CONTENT_LEFT_INSET = 10,
        TEXT_DESCRIPTION_GAP = 4,
        LABEL_CONTROL_GAP = 8,
        CONTROL_DESCRIPTION_GAP = 8,
        DESCRIPTION_HINT_GAP = 7,
        CHECKBOX_BLOCK_GAP = 40,
        FIELD_BLOCK_GAP = 20,
        SECTION_BLOCK_GAP = 26,
        HOTKEY_ROW_GAP = 36,
        COLUMN_DIVIDER_WIDTH = 2,
        COLUMN_DIVIDER_TOP_INSET = 12,
        COLUMN_DIVIDER_BOTTOM_INSET = 12,
        COLUMN_DIVIDER_ALPHA = 0.92,
        COMBAT_TOP_COLUMN_GAP = 28,
        COMBAT_TOP_ROW_HEIGHT = 82,
        COMBAT_CHECK_BLOCK_HEIGHT = 70,
        COMBAT_FIELD_BLOCK_HEIGHT = 64,
        COMBAT_FIELD_WITH_HINT_BLOCK_HEIGHT = 86,
        COMBAT_CARD_BLOCK_HEIGHT = 104,
        COMBAT_TEMPLATE_BLOCK_HEIGHT = 110,
        COMBAT_DANMAKU_FIELD_BLOCK_HEIGHT = 52,
    }

    ctx.FORM_COLUMN_WIDTH = (tabContentWidth - ctx.FORM_COLUMN_GAP) / 2
    ctx.sectionWidth = ctx.FORM_COLUMN_WIDTH
    ctx.formRightX = ctx.FORM_COLUMN_WIDTH + ctx.FORM_COLUMN_GAP
    ctx.formSectionWidth = ctx.FORM_COLUMN_WIDTH
    ctx.formDescriptionWidth = ctx.formSectionWidth - 16
    ctx.formInputWidth = ctx.formSectionWidth - 8
    ctx.singleColumnSectionWidth = tabContentWidth
    ctx.singleColumnDescriptionWidth = ctx.singleColumnSectionWidth - 16
    ctx.castAlertSoundDropdownWidth = ctx.compactDropdownWidth + ctx.castAlertInlineControlGap + ctx.castAlertTestButtonWidth

    local sharedTabLayoutOptions = {
        contentWidth = tabContentWidth,
        pageLeftInset = TAB_PAGE_LEFT_INSET,
        pageTopInset = TAB_PAGE_TOP_INSET,
        pageRightInset = TAB_PAGE_RIGHT_INSET,
        pageBottomInset = TAB_PAGE_BOTTOM_INSET,
        scrollBarGutter = TAB_SCROLLBAR_GUTTER,
        scrollBarAnchorFrame = contentBorder,
        scrollBarCenterXOffset = scrollBarCenterXOffset,
        scrollBarTopOffset = TAB_SCROLLBAR_VERTICAL_INSET,
        scrollBarBottomOffset = TAB_SCROLLBAR_VERTICAL_INSET,
    }

    ctx.markerScrollFrame, ctx.markerPage = Ui.CreateTabScrollFrame(ctx, contentBorder, "TMA_MarkSettings", 1, sharedTabLayoutOptions)
    ctx.castAlertScrollFrame, ctx.castAlertPage = Ui.CreateTabScrollFrame(ctx, contentBorder, "TMA_InterruptAlert", 1, sharedTabLayoutOptions)
    ctx.combatScrollFrame, ctx.combatPage = Ui.CreateTabScrollFrame(ctx, contentBorder, "TMA_InterruptAnnounce", 1, sharedTabLayoutOptions)
    Ui.DisableTabScrolling(ctx.markerScrollFrame, ctx.markerPage)
    Ui.DisableTabScrolling(ctx.castAlertScrollFrame, ctx.castAlertPage)

    ctx.markerTab = CreateFrame("Button", "TargetMarkerAssistantMarkerHotkeyTab", optionsFrame, "PanelTabButtonTemplate")
    ctx.markerTab:SetID(1)
    ctx.markerTab:SetText(L["TAB_MARKER_HOTKEY"])
    ctx.markerTab:SetPoint("TOPLEFT", optionsFrame, "BOTTOMLEFT", 11, 2)
    if PanelTemplates_TabResize then PanelTemplates_TabResize(ctx.markerTab, 26) end

    ctx.combatTab = CreateFrame("Button", "TargetMarkerAssistantCombatTab", optionsFrame, "PanelTabButtonTemplate")
    ctx.combatTab:SetID(3)
    ctx.combatTab:SetText(L["TAB_COMBAT_REPORT"])
    if PanelTemplates_TabResize then PanelTemplates_TabResize(ctx.combatTab, 26) end

    ctx.castAlertTab = CreateFrame("Button", "TargetMarkerAssistantCastAlertTab", optionsFrame, "PanelTabButtonTemplate")
    ctx.castAlertTab:SetID(2)
    ctx.castAlertTab:SetText(L["TAB_INTERRUPT_ALERT"])
    ctx.castAlertTab:SetPoint("LEFT", ctx.markerTab, "RIGHT", 4, 0)
    if PanelTemplates_TabResize then PanelTemplates_TabResize(ctx.castAlertTab, 26) end
    ctx.combatTab:SetPoint("LEFT", ctx.castAlertTab, "RIGHT", 4, 0)

    ctx.resetButton = CreateFrame("Button", "TargetMarkerAssistantResetButton", optionsFrame, "UIPanelButtonTemplate")
    ctx.resetButton:SetSize(ctx.castAlertTestButtonWidth, 24)
    ctx.resetButton:SetPoint("BOTTOMRIGHT", optionsFrame, "BOTTOMRIGHT", -34, 12)
    ctx.resetButton:SetText(L["RESET_DEFAULTS"])
    ctx.resetButton:SetFrameLevel(optionsFrame:GetFrameLevel() + 8)

    return ctx
end
