TargetMarkerAssistant = TargetMarkerAssistant or {}
TMA_Config_Notify = TMA_Config_Notify or {}

local Module = TMA_Config_Notify
local TMA = TargetMarkerAssistant
local SAY_PREVIEW_ICON_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Icon\\SayIcon.png"
local INTERRUPT_SPELL_ICON_FRAME_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Icon\\SkillButton.png"

function Module.Create(ctx)
    local ui = ctx.ui
    local L = ctx.L
    local page = ctx.combatPage
    local module = {}

    local function CreateSectionLabel(parent, text, width) return ui.CreateSectionLabel(ctx, parent, text, width) end
    local function CreateFieldLabel(parent, text, width) return ui.CreateFieldLabel(ctx, parent, text, width) end
    local function CreateDescription(parent, text, width, style) return ui.CreateDescription(ctx, parent, text, width, style) end
    local function CreateHintDescription(parent, text, width) return ui.CreateHintDescription(ctx, parent, text, width) end
    local function CreateImportantDescription(parent, text, width) return ui.CreateImportantDescription(ctx, parent, text, width) end
    local function CreateSectionRule(parent, anchor, width) return ui.CreateSectionRule(ctx, parent, anchor, width) end
    local function ConfigureCheckText(check, width) return ui.ConfigureCheckText(ctx, check, width) end
    local RefreshInterruptAnnounceDependentControls

    local function IsInterruptAnnounceEnabled()
        return TargetMarkerAssistantDB and TargetMarkerAssistantDB.enableInterruptAnnounce == true
    end

    local function SetControlEnabled(control, enabled)
        if not control then return end
        enabled = enabled == true
        local alpha = enabled and 1 or 0.45
        if control.SetAlpha then
            control:SetAlpha(alpha)
        end
        if control.EnableMouse then
            control:EnableMouse(enabled)
        end
        if control.SetEnabled then
            pcall(control.SetEnabled, control, enabled)
        elseif enabled and control.Enable then
            pcall(control.Enable, control)
        elseif (not enabled) and control.Disable then
            pcall(control.Disable, control)
        end
    end

    local function SetRegionEnabled(region, enabled)
        if not region then return end
        if region.SetAlpha then
            region:SetAlpha(enabled and 1 or 0.45)
        end
    end

    TMA.InterruptModeItems = {
        { text = L["INTERRUPT_MODE_AUTO"], value = "auto" },
        { text = L["INTERRUPT_MODE_MANUAL"], value = "manual" },
    }

    function TMA.GetInterruptModeText(value)
        value = value or (TargetMarkerAssistantDB and TargetMarkerAssistantDB.interruptSpellMode) or "auto"
        for _, item in ipairs(TMA.InterruptModeItems) do
            if item.value == value then return item.text end
        end
        return L["INTERRUPT_MODE_AUTO"]
    end

    function TMA.GetSpellCardDescription(spellID)
        spellID = tonumber(spellID)
        local description

        if spellID and C_Spell and C_Spell.GetSpellDescription then
            local ok, result = pcall(C_Spell.GetSpellDescription, spellID)
            if ok then description = result end
        end
        if not description and spellID and GetSpellDescription then
            local ok, result = pcall(GetSpellDescription, spellID)
            if ok then description = result end
        end

        description = ctx.SanitizeChatPlainText(description, "")
        description = tostring(description or ""):gsub("[\r\n]+", " "):gsub("%s%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
        if description == "" then
            return L["INTERRUPT_SPELL_CARD_DESCRIPTION_EMPTY"]
        end
        return description
    end

    function TMA.GetSpellCardCooldownText(spellID)
        local cooldownGetter = ctx.GetObservedInterruptCooldownDuration or ctx.GetPlayerInterruptCooldownDuration
        local cooldown = cooldownGetter and cooldownGetter(spellID) or nil
        if cooldown and cooldown > 0 then
            return string.format(L["INTERRUPT_SPELL_CARD_COOLDOWN_FORMAT"], tostring(cooldown))
        end
        return L["INTERRUPT_SPELL_CARD_COOLDOWN_EMPTY"]
    end

    local function CreateInterruptSpellCard(parent)
        local card = CreateFrame("Frame", "TargetMarkerAssistantInterruptSpellCard", parent, "BackdropTemplate")
        card:SetSize(ctx.combatSpellCardWidth, 74)

        local iconFrameSize = 58
        local iconTextureSize = 44

        card.IconFrame = CreateFrame("Frame", nil, card)
        card.IconFrame:SetSize(iconFrameSize, iconFrameSize)
        card.IconFrame:SetPoint("LEFT", card, "LEFT", 0, 0)

        card.IconFrameTexture = card.IconFrame:CreateTexture(nil, "BACKGROUND")
        card.IconFrameTexture:SetSize(iconFrameSize, iconFrameSize)
        card.IconFrameTexture:SetPoint("CENTER", card.IconFrame, "CENTER", 0, 0)
        card.IconFrameTexture:SetTexture(INTERRUPT_SPELL_ICON_FRAME_TEXTURE)

        card.Icon = card.IconFrame:CreateTexture(nil, "ARTWORK")
        card.Icon:SetSize(iconTextureSize, iconTextureSize)
        card.Icon:SetPoint("CENTER", card.IconFrame, "CENTER", 0, 0)
        card.Icon:SetTexture(134400)
        card.Icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

        card.IconFrame:EnableMouse(true)
        card.IconFrame:RegisterForDrag("LeftButton")
        card.IconFrame:SetScript("OnDragStart", function()
            if GameTooltip then GameTooltip:Hide() end
            if TMA.PickupInterruptFocusMacro then
                TMA.PickupInterruptFocusMacro()
            end
        end)
        card.IconFrame:SetScript("OnEnter", function()
            GameTooltip:SetOwner(card.IconFrame, "ANCHOR_RIGHT")
            GameTooltip:SetText(card.__macroSpellName or L["INTERRUPT_CURRENT_SPELL_LABEL"], 1, 0.82, 0)
            GameTooltip:AddLine(L["INTERRUPT_FOCUS_MACRO_TOOLTIP"], 1, 1, 1, true)
            GameTooltip:Show()
        end)
        card.IconFrame:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)

        card.DescriptionFrame = CreateFrame("Frame", nil, card, "BackdropTemplate")
        card.DescriptionFrame:SetPoint("LEFT", card.IconFrame, "RIGHT", 10, 0)
        card.DescriptionFrame:SetPoint("RIGHT", card, "RIGHT", 0, 0)
        card.DescriptionFrame:SetHeight(60)
        card.DescriptionFrame: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 },
        })
        card.DescriptionFrame:SetBackdropColor(0, 0, 0, 0.58)
        card.DescriptionFrame:SetBackdropBorderColor(1.0, 0.82, 0.16, 1.0)

        card.NameText = card.DescriptionFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        card.NameText:SetPoint("TOPLEFT", card.DescriptionFrame, "TOPLEFT", 12, -8)
        card.NameText:SetWidth(240)
        card.NameText:SetJustifyH("LEFT")
        card.NameText:SetWordWrap(false)
        ui.ApplyContentTextStyle(ctx, card.NameText, ctx.TEXT_STYLE_SECONDARY_TITLE)

        card.IDText = card.DescriptionFrame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
        card.IDText:SetPoint("LEFT", card.NameText, "LEFT", 0, 0)
        card.IDText:SetWidth(120)
        card.IDText:SetJustifyH("LEFT")
        card.IDText:SetWordWrap(false)
        ui.ApplyClientDefaultFont(card.IDText, 11, "")
        card.IDText:SetTextColor(0.58, 0.58, 0.54)
        card.IDText:SetShadowColor(0, 0, 0, 0.85)
        card.IDText:SetShadowOffset(1, -1)

        card.CooldownText = card.DescriptionFrame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
        card.CooldownText:SetPoint("BOTTOMRIGHT", card.DescriptionFrame, "BOTTOMRIGHT", -12, 10)
        card.CooldownText:SetWidth(118)
        card.CooldownText:SetJustifyH("RIGHT")
        card.CooldownText:SetWordWrap(false)
        card.CooldownText:SetTextColor(0.86, 0.84, 0.76)
        card.CooldownText:SetShadowColor(0, 0, 0, 0.85)
        card.CooldownText:SetShadowOffset(1, -1)

        card.DescriptionText = card.DescriptionFrame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
        card.DescriptionText:SetPoint("TOPLEFT", card.NameText, "BOTTOMLEFT", 0, -9)
        card.DescriptionText:SetPoint("RIGHT", card.CooldownText, "LEFT", -12, 0)
        card.DescriptionText:SetHeight(22)
        card.DescriptionText:SetJustifyH("LEFT")
        card.DescriptionText:SetWordWrap(false)
        ui.ApplyContentTextStyle(ctx, card.DescriptionText, ctx.TEXT_STYLE_DESCRIPTION)

        return card
    end

    local function UpdateInterruptSpellCard(card, spellID, spellName)
        if not card then return end

        local display = TMA.GetSpellDisplayInfo(spellID, spellName)
        local resolvedID = tonumber(display and display.spellID)
        local resolvedName = display and display.name
        local resolvedIcon = display and display.icon

        if not resolvedID and not resolvedName then
            resolvedName = L["INTERRUPT_SPELL_CARD_EMPTY"]
        end

        card.__macroSpellName = resolvedName
        card.Icon:SetTexture(resolvedIcon or 134400)
        card.NameText:SetText(resolvedName or L["INTERRUPT_SPELL_CARD_EMPTY"])
        card.IDText:ClearAllPoints()
        card.IDText:SetPoint("LEFT", card.NameText, "LEFT", math.min((card.NameText:GetStringWidth() or 0) + 10, 220), -1)
        card.IDText:SetText(string.format(L["INTERRUPT_SPELL_ID_FORMAT"], tostring(resolvedID or "-")))
        card.DescriptionText:SetText(TMA.GetSpellCardDescription(resolvedID))
        card.CooldownText:SetText(TMA.GetSpellCardCooldownText(resolvedID))
    end

    local interruptAnnounceSection = CreateSectionLabel(page, L["SECTION_INTERRUPT_DETECT"], ctx.combatFullWidth)
    interruptAnnounceSection:SetPoint("TOPLEFT", page, "TOPLEFT", ctx.combatColumnX, ctx.formTopY)
    local interruptAnnounceRule = CreateSectionRule(page, interruptAnnounceSection, ctx.combatFullWidth)

    local interruptAnnounceCheck = CreateFrame("CheckButton", "TargetMarkerAssistantInterruptAnnounceCheckbox", page, "InterfaceOptionsCheckButtonTemplate")
    interruptAnnounceCheck:SetPoint("TOPLEFT", interruptAnnounceRule, "BOTTOMLEFT", ctx.SECTION_CONTENT_LEFT_INSET, -ctx.SECTION_RULE_CONTENT_GAP)
    interruptAnnounceCheck.Text:SetText(L["ENABLE_INTERRUPT_ANNOUNCE_DESC"])
    ConfigureCheckText(interruptAnnounceCheck, ctx.combatFullWidth - 30)
    interruptAnnounceCheck:SetScript("OnClick", function(self)
        TargetMarkerAssistantDB.enableInterruptAnnounce = self:GetChecked()
        TargetMarkerAssistantDB.interruptAnnounceEnabled = TargetMarkerAssistantDB.enableInterruptAnnounce
        if TMA.RefreshInterruptCombatLogFrame then
            TMA.RefreshInterruptCombatLogFrame("OPTIONS_TOGGLE")
        end
        if RefreshInterruptAnnounceDependentControls then
            RefreshInterruptAnnounceDependentControls()
        end
    end)

    local interruptAnnounceDesc = CreateDescription(page, L["DESC_INTERRUPT_ANNOUNCE"], ctx.singleColumnDescriptionWidth)
    interruptAnnounceDesc:SetPoint("TOPLEFT", interruptAnnounceCheck.Text, "BOTTOMLEFT", 0, -ctx.TEXT_DESCRIPTION_GAP)

    local interruptAnnounceHint = CreateHintDescription(page, L["DESC_INTERRUPT_ANNOUNCE_GROUP_ONLY"], ctx.singleColumnDescriptionWidth)
    interruptAnnounceHint:SetPoint("TOPLEFT", interruptAnnounceDesc, "BOTTOMLEFT", 0, -ctx.DESCRIPTION_HINT_GAP)

    local interruptAnnounceImportant = CreateImportantDescription(page, L["DESC_INTERRUPT_ANNOUNCE_API_LIMIT"], ctx.singleColumnDescriptionWidth)
    interruptAnnounceImportant:SetPoint("TOPLEFT", interruptAnnounceHint, "BOTTOMLEFT", 0, -ctx.DESCRIPTION_HINT_GAP)

    local interruptMonitorSection = CreateSectionLabel(page, L["SECTION_INTERRUPT_MONITOR"], ctx.combatFullWidth)
    interruptMonitorSection:SetPoint("TOPLEFT", interruptAnnounceImportant, "BOTTOMLEFT", 0, -ctx.SECTION_BLOCK_GAP)
    local interruptMonitorRule = CreateSectionRule(page, interruptMonitorSection, ctx.combatFullWidth)

    TMA.InterruptModeLabel = CreateFieldLabel(page, L["INTERRUPT_SPELL_MODE"])
    TMA.InterruptModeLabel:SetPoint("TOPLEFT", interruptMonitorRule, "BOTTOMLEFT", ctx.SECTION_CONTENT_LEFT_INSET, -ctx.SECTION_RULE_CONTENT_GAP)

    TMA.InterruptModeDropdown = CreateFrame("DropdownButton", "TargetMarkerAssistantInterruptModeDropdown", page, "WowStyle1DropdownTemplate")
    TMA.InterruptModeDropdown:SetPoint("LEFT", TMA.InterruptModeLabel, "RIGHT", 12, 0)
    TMA.InterruptModeDropdown:SetWidth(150)
    ui.SetupRadioDropdown(TMA.InterruptModeDropdown, "MENU_TARGETMARKERASSISTANT_INTERRUPT_MODE", function()
        return TMA.GetInterruptModeText()
    end, function()
        return TargetMarkerAssistantDB.interruptSpellMode or "auto"
    end, function(value)
        TargetMarkerAssistantDB.interruptSpellMode = value
        if value == "auto" then
            TMA.AutoDetectInterruptSpell(true)
        end
    end, function()
        return TMA.InterruptModeItems
    end, ctx.RequestRefresh)

    TMA.CurrentInterruptSpellLabel = CreateFieldLabel(page, L["INTERRUPT_CURRENT_SPELL_LABEL"], 180)
    TMA.CurrentInterruptSpellLabel:SetPoint("TOPLEFT", TMA.InterruptModeDropdown, "BOTTOMLEFT", 0, -ctx.FIELD_BLOCK_GAP)

    TMA.CurrentInterruptSpellCard = CreateInterruptSpellCard(page)
    TMA.CurrentInterruptSpellCard:SetPoint("TOPLEFT", TMA.CurrentInterruptSpellLabel, "BOTTOMLEFT", 0, -ctx.LABEL_CONTROL_GAP)

    local focusMacroFrame = CreateFrame("Frame", "TargetMarkerAssistantFocusMacroHelpFrame", page, "BackdropTemplate")
    focusMacroFrame:SetPoint("TOPLEFT", TMA.CurrentInterruptSpellCard, "BOTTOMLEFT", 0, -ctx.FIELD_BLOCK_GAP)
    ui.StylePreviewFrame(focusMacroFrame)

    local focusMacroTextGroup = CreateFrame("Frame", nil, focusMacroFrame)
    focusMacroTextGroup:SetSize(ctx.singleColumnDescriptionWidth - 20, 36)
    focusMacroTextGroup:SetPoint("CENTER", focusMacroFrame, "CENTER", 0, 0)

    local focusMacroDesc = CreateDescription(focusMacroTextGroup, L["DESC_INTERRUPT_FOCUS_MACRO"], ctx.singleColumnDescriptionWidth - 20)
    focusMacroDesc:SetPoint("TOPLEFT", focusMacroTextGroup, "TOPLEFT", 0, 0)
    focusMacroDesc:SetHeight(15)

    local focusMacroHint = CreateHintDescription(focusMacroTextGroup, L["DESC_INTERRUPT_FOCUS_MACRO_HINT"], ctx.singleColumnDescriptionWidth - 20)
    focusMacroHint:SetPoint("TOPLEFT", focusMacroDesc, "BOTTOMLEFT", 0, -ctx.TEXT_DESCRIPTION_GAP)
    focusMacroHint:SetHeight(15)

    local rescanInterruptButton = CreateFrame("Button", "TargetMarkerAssistantRescanInterruptButton", page, "UIPanelButtonTemplate")
    rescanInterruptButton:SetSize(100, 24)
    rescanInterruptButton:SetPoint("LEFT", TMA.InterruptModeDropdown, "RIGHT", 8, 0)
    rescanInterruptButton:SetText(L["INTERRUPT_RESCAN"])
    rescanInterruptButton:SetScript("OnClick", function()
        TMA.AutoDetectInterruptSpell(true)
        ctx.RequestRefresh()
    end)

    local interruptModeDesc = CreateHintDescription(page, L["DESC_INTERRUPT_MODE"], ctx.singleColumnDescriptionWidth)
    interruptModeDesc:SetPoint("TOPLEFT", TMA.InterruptModeDropdown, "BOTTOMLEFT", 0, -ctx.CONTROL_DESCRIPTION_GAP)

    TMA.InterruptSpellNameLabel = CreateFieldLabel(page, L["INTERRUPT_MANUAL_SPELL_ID"], 220)
    TMA.InterruptSpellNameLabel:SetWordWrap(false)
    TMA.InterruptSpellNameLabel:SetPoint("TOPLEFT", TMA.CurrentInterruptSpellCard, "BOTTOMLEFT", 0, -ctx.FIELD_BLOCK_GAP)

    TMA.InterruptSpellHelpText = CreateDescription(page, L["DESC_INTERRUPT_MANUAL"], ctx.singleColumnDescriptionWidth)
    TMA.InterruptSpellHelpText:SetPoint("TOPLEFT", TMA.InterruptSpellNameLabel, "BOTTOMLEFT", 0, -ctx.TEXT_DESCRIPTION_GAP)
    TMA.InterruptSpellHelpText:SetWidth(ctx.singleColumnDescriptionWidth)
    TMA.InterruptSpellHelpText:SetJustifyH("LEFT")

    TMA.InterruptSpellNameBox = CreateFrame("EditBox", "TargetMarkerAssistantInterruptSpellNameBox", page, "InputBoxTemplate")
    TMA.InterruptSpellNameBox:SetSize(ctx.combatInputWidth - 100, 24)
    TMA.InterruptSpellNameBox:SetPoint("TOPLEFT", TMA.InterruptSpellHelpText, "BOTTOMLEFT", 3, -ctx.LABEL_CONTROL_GAP)
    TMA.InterruptSpellNameBox:SetAutoFocus(false)
    TMA.InterruptSpellNameBox:SetMaxLetters(60)
    ui.StyleEditBox(TMA.InterruptSpellNameBox)
    local applyingInterruptSpellInput = false
    local function ApplyInterruptSpellInput(clearFocus)
        if applyingInterruptSpellInput then return end
        applyingInterruptSpellInput = true
        ctx.SetConfiguredInterruptSpellInput(TMA.InterruptSpellNameBox:GetText())
        if clearFocus then
            TMA.InterruptSpellNameBox:ClearFocus()
        end
        ctx.RequestRefresh()
        applyingInterruptSpellInput = false
    end
    TMA.InterruptSpellNameBox:SetScript("OnEnterPressed", function()
        ApplyInterruptSpellInput(true)
    end)
    TMA.InterruptSpellNameBox:SetScript("OnEditFocusLost", function()
        ApplyInterruptSpellInput(false)
    end)

    TMA.InterruptSpellApplyButton = CreateFrame("Button", "TargetMarkerAssistantInterruptSpellApplyButton", page, "UIPanelButtonTemplate")
    TMA.InterruptSpellApplyButton:SetSize(82, 24)
    TMA.InterruptSpellApplyButton:SetPoint("LEFT", TMA.InterruptSpellNameBox, "RIGHT", 12, 0)
    TMA.InterruptSpellApplyButton:SetText(L["APPLY"])
    TMA.InterruptSpellApplyButton:SetScript("OnClick", function()
        ApplyInterruptSpellInput(false)
    end)

    local interruptChatSection = CreateSectionLabel(page, L["SECTION_INTERRUPT_CHAT"], ctx.combatFullWidth)
    interruptChatSection:SetPoint("TOPLEFT", interruptAnnounceHint, "BOTTOMLEFT", 0, -ctx.SECTION_BLOCK_GAP)
    local interruptChatRule = CreateSectionRule(page, interruptChatSection, ctx.combatFullWidth)

    local interruptAddonSyncCheck = CreateFrame("CheckButton", "TargetMarkerAssistantInterruptAddonSyncCheckbox", page, "InterfaceOptionsCheckButtonTemplate")
    interruptAddonSyncCheck:SetPoint("TOPLEFT", interruptAnnounceHint, "BOTTOMLEFT", 0, -ctx.FIELD_BLOCK_GAP)
    interruptAddonSyncCheck.Text:SetText(L["INTERRUPT_ADDON_SYNC_ENABLE"])
    ConfigureCheckText(interruptAddonSyncCheck, ctx.combatFullWidth - 30)
    interruptAddonSyncCheck:SetScript("OnClick", function(self)
        TargetMarkerAssistantDB.interruptAddonSyncEnabled = self:GetChecked()
    end)

    local interruptAddonSyncDesc = CreateDescription(page, L["DESC_INTERRUPT_ADDON_SYNC"], ctx.singleColumnDescriptionWidth)
    interruptAddonSyncDesc:SetPoint("TOPLEFT", interruptAddonSyncCheck.Text, "BOTTOMLEFT", 0, -ctx.TEXT_DESCRIPTION_GAP)

    local interruptAddonSyncHint = CreateHintDescription(page, L["DESC_INTERRUPT_ADDON_SYNC_HINT"], ctx.singleColumnDescriptionWidth)
    interruptAddonSyncHint:SetPoint("TOPLEFT", interruptAddonSyncDesc, "BOTTOMLEFT", 0, -ctx.DESCRIPTION_HINT_GAP)

    local interruptTemplateLabel = CreateFieldLabel(page, L["INTERRUPT_CHAT_TEMPLATE"])
    interruptTemplateLabel:SetPoint("TOPLEFT", interruptChatRule, "BOTTOMLEFT", ctx.SECTION_CONTENT_LEFT_INSET, -ctx.SECTION_RULE_CONTENT_GAP)

    local interruptTemplateHelp = CreateHintDescription(page, L["INTERRUPT_CHAT_TEMPLATE_HELP"], ctx.singleColumnDescriptionWidth)
    interruptTemplateHelp:SetPoint("TOPLEFT", interruptTemplateLabel, "BOTTOMLEFT", 0, -ctx.CONTROL_DESCRIPTION_GAP)

    local interruptTemplateBox = CreateFrame("EditBox", "TargetMarkerAssistantInterruptTemplateBox", page, "InputBoxTemplate")
    interruptTemplateBox:SetSize(ctx.combatInputWidth, 24)
    interruptTemplateBox:SetPoint("TOPLEFT", interruptTemplateHelp, "BOTTOMLEFT", 3, -ctx.LABEL_CONTROL_GAP)
    interruptTemplateBox:SetAutoFocus(false)
    interruptTemplateBox:SetMaxLetters(180)
    ui.StyleEditBox(interruptTemplateBox)
    interruptTemplateBox:SetScript("OnEnterPressed", function(self)
        TargetMarkerAssistantDB.interruptAnnounceTemplate = self:GetText()
        self:ClearFocus()
        ctx.RequestRefresh()
    end)
    interruptTemplateBox:SetScript("OnEditFocusLost", function(self)
        TargetMarkerAssistantDB.interruptAnnounceTemplate = self:GetText()
        ctx.RequestRefresh()
    end)

    local interruptTemplatePreviewFrame = CreateFrame("Frame", "TargetMarkerAssistantInterruptPreviewFrame", page, "BackdropTemplate")
    interruptTemplatePreviewFrame:SetHeight(34)
    interruptTemplatePreviewFrame:SetPoint("TOPLEFT", interruptTemplateBox, "BOTTOMLEFT", -3, -ctx.DESCRIPTION_HINT_GAP)
    ui.StylePreviewFrame(interruptTemplatePreviewFrame)

    local interruptTemplatePreviewIcon = interruptTemplatePreviewFrame:CreateTexture(nil, "ARTWORK")
    interruptTemplatePreviewIcon:SetSize(18, 18)
    interruptTemplatePreviewIcon:SetPoint("LEFT", interruptTemplatePreviewFrame, "LEFT", 10, 0)
    interruptTemplatePreviewIcon:SetTexture(SAY_PREVIEW_ICON_TEXTURE)

    local interruptTemplatePreview = CreateDescription(interruptTemplatePreviewFrame, L["INTERRUPT_CHAT_PREVIEW"], ctx.combatInputWidth - 44)
    interruptTemplatePreview:SetPoint("TOPLEFT", interruptTemplatePreviewFrame, "TOPLEFT", 34, 0)
    interruptTemplatePreview:SetPoint("BOTTOMRIGHT", interruptTemplatePreviewFrame, "BOTTOMRIGHT", -10, 0)
    interruptTemplatePreview:SetWordWrap(false)
    if interruptTemplatePreview.SetJustifyV then
        interruptTemplatePreview:SetJustifyV("MIDDLE")
    end
    interruptTemplateBox:SetScript("OnTextChanged", function(self)
        TargetMarkerAssistantDB.interruptAnnounceTemplate = self:GetText()
        interruptTemplatePreview:SetText(ctx.FormatInterruptChatPreview())
    end)

    local cooldownReadySection = CreateSectionLabel(page, L["SECTION_INTERRUPT_COOLDOWN_READY"], ctx.combatFullWidth)
    cooldownReadySection:SetPoint("TOPLEFT", interruptTemplatePreviewFrame, "BOTTOMLEFT", 0, -ctx.SECTION_BLOCK_GAP)
    local cooldownReadyRule = CreateSectionRule(page, cooldownReadySection, ctx.combatFullWidth)

    local cooldownReadyCheck = CreateFrame("CheckButton", "TargetMarkerAssistantCooldownReadyCheckbox", page, "InterfaceOptionsCheckButtonTemplate")
    cooldownReadyCheck:SetPoint("TOPLEFT", cooldownReadyRule, "BOTTOMLEFT", ctx.SECTION_CONTENT_LEFT_INSET, -ctx.SECTION_RULE_CONTENT_GAP)
    cooldownReadyCheck.Text:SetText(L["INTERRUPT_COOLDOWN_READY_ENABLE"])
    ConfigureCheckText(cooldownReadyCheck, ctx.combatFullWidth - 160)
    cooldownReadyCheck:SetScript("OnClick", function(self)
        TargetMarkerAssistantDB.enableInterruptCooldownReadyAlert = self:GetChecked() == true
        if not TargetMarkerAssistantDB.enableInterruptCooldownReadyAlert and TMA.ResetInterruptCooldownReadyAlert then
            TMA.ResetInterruptCooldownReadyAlert()
        elseif TMA.TrackConfiguredInterruptCooldownReady then
            TMA.TrackConfiguredInterruptCooldownReady("OPTIONS_TOGGLE")
        end
    end)

    local previewCooldownReadyButton = CreateFrame("Button", "TargetMarkerAssistantPreviewCooldownReadyButton", page, "UIPanelButtonTemplate")
    previewCooldownReadyButton:SetSize(100, 24)
    previewCooldownReadyButton:SetText(L["INTERRUPT_COOLDOWN_READY_PREVIEW"])
    local function PreviewCooldownReadyAlert()
        local spellID = tonumber(TargetMarkerAssistantDB and TargetMarkerAssistantDB.interruptSpellID)
        local spellName = (spellID and TMA.GetPublicSpellNameByID and TMA.GetPublicSpellNameByID(spellID))
            or (TargetMarkerAssistantDB and TargetMarkerAssistantDB.interruptSpellName)
            or L["INTERRUPT_SPELL"]
        if TMA.ShowInterruptCooldownReadyAlert then
            return TMA.ShowInterruptCooldownReadyAlert(spellID, spellName)
        end
        return false
    end
    previewCooldownReadyButton:SetScript("OnClick", function()
        if PreviewCooldownReadyAlert() then return end
        if C_Timer and C_Timer.After then
            C_Timer.After(0.1, PreviewCooldownReadyAlert)
        end
    end)

    local cooldownReadyDesc = CreateDescription(page, L["DESC_INTERRUPT_COOLDOWN_READY"], ctx.singleColumnDescriptionWidth)
    cooldownReadyDesc:SetPoint("TOPLEFT", cooldownReadyCheck.Text, "BOTTOMLEFT", 0, -ctx.TEXT_DESCRIPTION_GAP)

    local cooldownReadyHint = CreateHintDescription(page, L["DESC_INTERRUPT_COOLDOWN_READY_LOCAL_ONLY"], ctx.singleColumnDescriptionWidth)
    cooldownReadyHint:SetPoint("TOPLEFT", cooldownReadyDesc, "BOTTOMLEFT", 0, -ctx.DESCRIPTION_HINT_GAP)

    local danmakuSection = CreateSectionLabel(page, L["INTERRUPT_DANMAKU_SECTION"], ctx.combatFullWidth)
    danmakuSection:SetPoint("TOPLEFT", cooldownReadyHint, "BOTTOMLEFT", 0, -ctx.SECTION_BLOCK_GAP)
    local danmakuRule = CreateSectionRule(page, danmakuSection, ctx.combatFullWidth)

    local function SetRestrictedDanmakuValue(key, value)
        TMA.EnsureRestrictedInterruptDanmakuDefaults()
        TargetMarkerAssistantDB.restrictedInterruptDanmaku[key] = value
    end

    local function SnapSliderValue(value, minValue, maxValue, step)
        local number = tonumber(value) or minValue
        number = math.max(minValue, math.min(maxValue, number))
        step = tonumber(step) or 1
        if step > 0 then
            number = math.floor((number - minValue) / step + 0.5) * step + minValue
        end
        return math.max(minValue, math.min(maxValue, number))
    end

    local function SetSliderDisplay(slider, value)
        if not slider or not slider.ValueText then return end
        local displayValue = SnapSliderValue(value, slider.MinValue, slider.MaxValue, slider.StepValue)
        slider.ValueText:SetText(string.format(slider.ValueFormat or "%d", displayValue))
    end

    local function SetDanmakuSliderValue(slider, value)
        if not slider then return end
        local displayValue = SnapSliderValue(value, slider.MinValue, slider.MaxValue, slider.StepValue)
        slider.__settingValue = true
        slider:SetValue(displayValue)
        slider.__settingValue = false
        SetSliderDisplay(slider, displayValue)
    end

    local function NormalizeDanmakuScrollStartRatio(value)
        if TMA.NormalizeRestrictedInterruptDanmakuScrollStartRatio then
            return TMA.NormalizeRestrictedInterruptDanmakuScrollStartRatio(value)
        end
        local number = tonumber(value) or 1.0
        if number > 1 then number = number / 100 end
        return math.max(0.50, math.min(1.0, number))
    end

    local function GetDanmakuScrollStartText(value)
        local ratio = NormalizeDanmakuScrollStartRatio(value)
        if ratio >= 0.995 then
            return L["INTERRUPT_DANMAKU_SCROLL_START_EDGE"]
        end
        return string.format(L["INTERRUPT_DANMAKU_SCROLL_START_PERCENT_FORMAT"], math.floor(ratio * 100 + 0.5))
    end

    local function GetSelectedDanmakuAlertSound()
        TMA.EnsureRestrictedInterruptDanmakuDefaults()
        local cfg = TargetMarkerAssistantDB.restrictedInterruptDanmaku
        if ctx.GetDanmakuAlertSound then
            return ctx.GetDanmakuAlertSound(cfg.sound)
        end
        return (ctx.danmakuAlertSounds and ctx.danmakuAlertSounds[1]) or { text = "", value = "" }
    end

    local function CreateDanmakuSlider(name, labelText, key, minValue, maxValue, stepValue, valueFormat)
        local label = CreateFieldLabel(page, labelText, 160)
        local valueText = CreateDescription(page, "", 72, ctx.TEXT_STYLE_DESCRIPTION_HINT)
        valueText:SetJustifyH("RIGHT")
        valueText:SetWordWrap(false)

        local slider = CreateFrame("Slider", name, page, "OptionsSliderTemplate")
        slider:SetMinMaxValues(minValue, maxValue)
        slider:SetValueStep(stepValue)
        if slider.SetObeyStepOnDrag then
            slider:SetObeyStepOnDrag(true)
        end
        slider:SetSize(320, 18)
        slider.MinValue = minValue
        slider.MaxValue = maxValue
        slider.StepValue = stepValue
        slider.ValueFormat = valueFormat or "%d"
        slider.ValueText = valueText

        local sliderText = _G[name .. "Text"]
        local sliderLow = _G[name .. "Low"]
        local sliderHigh = _G[name .. "High"]
        if sliderText then sliderText:SetText("") end
        if sliderLow then sliderLow:SetText("") end
        if sliderHigh then sliderHigh:SetText("") end

        slider:SetScript("OnValueChanged", function(self, value)
            local displayValue = SnapSliderValue(value, minValue, maxValue, stepValue)
            if not self.__settingValue and math.abs((tonumber(value) or displayValue) - displayValue) > 0.001 then
                self.__settingValue = true
                self:SetValue(displayValue)
                self.__settingValue = false
            end
            SetSliderDisplay(self, displayValue)
            if not self.__settingValue then
                SetRestrictedDanmakuValue(key, displayValue)
            end
        end)

        return label, valueText, slider
    end

    local danmakuScrollStartLabel = CreateFieldLabel(page, L["INTERRUPT_DANMAKU_SCROLL_AREA"], 160)
    danmakuScrollStartLabel:SetPoint("TOPLEFT", danmakuRule, "BOTTOMLEFT", ctx.SECTION_CONTENT_LEFT_INSET, -ctx.SECTION_RULE_CONTENT_GAP)
    local danmakuScrollStartValue = CreateDescription(page, "", 72, ctx.TEXT_STYLE_DESCRIPTION_HINT)
    danmakuScrollStartValue:SetJustifyH("RIGHT")
    danmakuScrollStartValue:SetWordWrap(false)

    local danmakuScrollStartSlider = CreateFrame("Slider", "TargetMarkerAssistantDanmakuScrollStartSlider", page, "OptionsSliderTemplate")
    danmakuScrollStartSlider:SetMinMaxValues(50, 100)
    danmakuScrollStartSlider:SetValueStep(5)
    if danmakuScrollStartSlider.SetObeyStepOnDrag then
        danmakuScrollStartSlider:SetObeyStepOnDrag(true)
    end
    danmakuScrollStartSlider:SetSize(320, 18)
    danmakuScrollStartSlider.MinValue = 50
    danmakuScrollStartSlider.MaxValue = 100
    danmakuScrollStartSlider.StepValue = 5
    danmakuScrollStartSlider.ValueText = danmakuScrollStartValue

    local scrollStartSliderText = _G["TargetMarkerAssistantDanmakuScrollStartSliderText"]
    local scrollStartSliderLow = _G["TargetMarkerAssistantDanmakuScrollStartSliderLow"]
    local scrollStartSliderHigh = _G["TargetMarkerAssistantDanmakuScrollStartSliderHigh"]
    if scrollStartSliderText then scrollStartSliderText:SetText("") end
    if scrollStartSliderLow then scrollStartSliderLow:SetText("") end
    if scrollStartSliderHigh then scrollStartSliderHigh:SetText("") end

    local function SetDanmakuScrollStartDisplay(slider, value)
        if not slider or not slider.ValueText then return end
        local displayValue = SnapSliderValue(value, slider.MinValue, slider.MaxValue, slider.StepValue)
        slider.ValueText:SetText(GetDanmakuScrollStartText(displayValue / 100))
    end

    local function SetDanmakuScrollStartSliderValue(slider, value)
        if not slider then return end
        local ratio = NormalizeDanmakuScrollStartRatio(value)
        local displayValue = SnapSliderValue(ratio * 100, slider.MinValue, slider.MaxValue, slider.StepValue)
        slider.__settingValue = true
        slider:SetValue(displayValue)
        slider.__settingValue = false
        SetDanmakuScrollStartDisplay(slider, displayValue)
    end

    danmakuScrollStartSlider:SetScript("OnValueChanged", function(self, value)
        local displayValue = SnapSliderValue(value, self.MinValue, self.MaxValue, self.StepValue)
        if not self.__settingValue and math.abs((tonumber(value) or displayValue) - displayValue) > 0.001 then
            self.__settingValue = true
            self:SetValue(displayValue)
            self.__settingValue = false
        end
        SetDanmakuScrollStartDisplay(self, displayValue)
        if not self.__settingValue then
            SetRestrictedDanmakuValue("scrollStartRatio", displayValue / 100)
        end
    end)

    local danmakuSoundCheck = CreateFrame("CheckButton", "TargetMarkerAssistantDanmakuSoundCheckbox", page, "InterfaceOptionsCheckButtonTemplate")
    danmakuSoundCheck.Text:SetText(L["INTERRUPT_DANMAKU_SOUND_ENABLE"])
    ConfigureCheckText(danmakuSoundCheck, 150)
    danmakuSoundCheck:SetScript("OnClick", function(self)
        SetRestrictedDanmakuValue("soundEnabled", self:GetChecked() == true)
        if self.UpdateSoundControlsEnabled then
            self.UpdateSoundControlsEnabled(self:GetChecked() == true)
        end
    end)

    local danmakuSoundDropdown = CreateFrame("DropdownButton", "TargetMarkerAssistantDanmakuSoundDropdown", page, "WowStyle1DropdownTemplate")
    danmakuSoundDropdown:SetWidth(120)
    ui.SetupRadioDropdown(danmakuSoundDropdown, "MENU_TARGETMARKERASSISTANT_DANMAKU_SOUND", function()
        return GetSelectedDanmakuAlertSound().text
    end, function()
        return GetSelectedDanmakuAlertSound().value
    end, function(value)
        SetRestrictedDanmakuValue("sound", value)
    end, function()
        return ctx.danmakuAlertSounds or {}
    end, function()
        if ctx.PlayDanmakuAlertSound then
            ctx.PlayDanmakuAlertSound(true)
        end
    end)

    local danmakuSoundDesc = CreateHintDescription(page, L["DESC_INTERRUPT_DANMAKU_SOUND"], ctx.singleColumnDescriptionWidth)

    local function SetDanmakuSoundControlsEnabled(enabled)
        enabled = enabled == true and IsInterruptAnnounceEnabled()
        local alpha = enabled and 1 or 0.45
        if danmakuSoundDropdown then
            danmakuSoundDropdown:SetAlpha(alpha)
            if danmakuSoundDropdown.EnableMouse then
                danmakuSoundDropdown:EnableMouse(enabled)
            end
            if danmakuSoundDropdown.SetEnabled then
                pcall(danmakuSoundDropdown.SetEnabled, danmakuSoundDropdown, enabled)
            elseif enabled and danmakuSoundDropdown.Enable then
                pcall(danmakuSoundDropdown.Enable, danmakuSoundDropdown)
            elseif (not enabled) and danmakuSoundDropdown.Disable then
                pcall(danmakuSoundDropdown.Disable, danmakuSoundDropdown)
            end
        end
    end
    danmakuSoundCheck.UpdateSoundControlsEnabled = SetDanmakuSoundControlsEnabled

    RefreshInterruptAnnounceDependentControls = function()
        local announceEnabled = IsInterruptAnnounceEnabled()
        SetControlEnabled(interruptAddonSyncCheck, announceEnabled)
        SetRegionEnabled(interruptAddonSyncDesc, announceEnabled)
        SetRegionEnabled(interruptAddonSyncHint, announceEnabled)

        SetControlEnabled(danmakuSoundCheck, announceEnabled)
        SetRegionEnabled(danmakuSoundDesc, announceEnabled)
        SetDanmakuSoundControlsEnabled(danmakuSoundCheck:GetChecked() == true)
    end

    local danmakuFontSizeLabel, danmakuFontSizeValue, danmakuFontSizeSlider = CreateDanmakuSlider(
        "TargetMarkerAssistantDanmakuFontSizeSlider",
        L["INTERRUPT_DANMAKU_FONT_SIZE"],
        "fontSize",
        8,
        48,
        1,
        "%d"
    )
    danmakuFontSizeLabel:SetPoint("TOPLEFT", danmakuScrollStartLabel, "BOTTOMLEFT", 0, -ctx.FIELD_BLOCK_GAP)

    local danmakuSpeedLabel, danmakuSpeedValue, danmakuSpeedSlider = CreateDanmakuSlider(
        "TargetMarkerAssistantDanmakuSpeedSlider",
        L["INTERRUPT_DANMAKU_SPEED"],
        "speed",
        120,
        600,
        10,
        "%d"
    )

    local danmakuLifetimeLabel, danmakuLifetimeValue, danmakuLifetimeSlider = CreateDanmakuSlider(
        "TargetMarkerAssistantDanmakuLifetimeSlider",
        L["INTERRUPT_DANMAKU_LIFETIME"],
        "lifetime",
        3,
        15,
        1,
        "%d"
    )

    local danmakuMaxLabel, danmakuMaxValue, danmakuMaxSlider = CreateDanmakuSlider(
        "TargetMarkerAssistantDanmakuMaxSlider",
        L["INTERRUPT_DANMAKU_MAX"],
        "maxDanmaku",
        1,
        10,
        1,
        "%d"
    )

    local function SaveRestrictedDanmakuInputs()
        TMA.EnsureRestrictedInterruptDanmakuDefaults()
        local cfg = TargetMarkerAssistantDB.restrictedInterruptDanmaku
        cfg.enabled = TargetMarkerAssistantDB.enableInterruptAnnounce == true

        local fontSize = tonumber(danmakuFontSizeSlider:GetValue()) or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.fontSize
        local speed = tonumber(danmakuSpeedSlider:GetValue()) or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.speed
        local lifetime = tonumber(danmakuLifetimeSlider:GetValue()) or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.lifetime
        local maxDanmaku = tonumber(danmakuMaxSlider:GetValue()) or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.maxDanmaku

        cfg.fontSize = math.max(fontSize, 8)
        cfg.speed = speed > 0 and speed or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.speed
        cfg.lifetime = lifetime > 0 and lifetime or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.lifetime
        cfg.maxDanmaku = math.max(maxDanmaku, 1)
        cfg.scrollStartRatio = NormalizeDanmakuScrollStartRatio(cfg.scrollStartRatio)
        cfg.soundEnabled = danmakuSoundCheck:GetChecked() == true
        cfg.sound = GetSelectedDanmakuAlertSound().value
    end

    local previewDanmakuButton = CreateFrame("Button", "TargetMarkerAssistantPreviewDanmakuButton", page, "UIPanelButtonTemplate")
    previewDanmakuButton:SetSize(100, 24)
    previewDanmakuButton:SetPoint("TOPRIGHT", danmakuRule, "BOTTOMRIGHT", -ctx.formInlineDropdownRightInset, -ctx.SECTION_RULE_CONTENT_GAP + 2)
    previewDanmakuButton:SetText(L["INTERRUPT_DANMAKU_PREVIEW"])
    previewDanmakuButton:SetScript("OnClick", function()
        SaveRestrictedDanmakuInputs()

        if not TargetMarkerAssistantDB.enableInterruptAnnounce then
            if DEFAULT_CHAT_FRAME then
                DEFAULT_CHAT_FRAME:AddMessage(L["INTERRUPT_DANMAKU_PREVIEW_DISABLED"])
            end
            return
        end

        local localMsg = ctx.FormatInterruptDanmakuPreviewMessage()
        if TMA.PreviewRestrictedInterruptDanmaku then
            TMA.PreviewRestrictedInterruptDanmaku(localMsg)
        end
    end)
    previewDanmakuButton:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(L["INTERRUPT_DANMAKU_PREVIEW"], 1, 0.82, 0)
        GameTooltip:AddLine(L["INTERRUPT_DANMAKU_PREVIEW_TOOLTIP"], 1, 1, 1, true)
        GameTooltip:Show()
    end)
    previewDanmakuButton:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    local function SetCombatControlPoint(region, x, y)
        if not region then return end
        region:ClearAllPoints()
        region:SetPoint("TOPLEFT", page, "TOPLEFT", x, y)
    end

    local function SetStyledEditBoxShown(editBox, shown)
        if not editBox then return end
        editBox:SetShown(shown == true)
        if editBox.TMA_InputFrame then
            editBox.TMA_InputFrame:SetShown(shown == true)
        end
    end

    local function FitFieldLabelWidth(label, minWidth)
        if not label then return end
        local textWidth = label:GetStringWidth() or 0
        label:SetWidth(math.max(minWidth or 64, math.ceil(textWidth) + 2))
    end

    local function LayoutCombatSectionTitle(label, rule, y)
        label:ClearAllPoints()
        label:SetPoint("TOPLEFT", page, "TOPLEFT", ctx.combatColumnX, y)
        label:Show()

        if rule then
            local ruleHeight = math.max(ctx.SECTION_RULE_HEIGHT or 0, 1)
            rule:ClearAllPoints()
            rule:SetSize(ctx.combatFullWidth, ruleHeight)
            rule:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 0, -(ctx.SECTION_RULE_TITLE_GAP or 2))
        end

        return y - (
            ctx.SECTION_HEADER_HEIGHT
            + (ctx.SECTION_RULE_TITLE_GAP or 2)
            + math.max(ctx.SECTION_RULE_HEIGHT or 0, 1)
            + ctx.SECTION_RULE_CONTENT_GAP
        )
    end

    function module.LayoutCombatReportPage(showManualSpell)
        local contentInset = ctx.SECTION_CONTENT_LEFT_INSET
        local x = ctx.combatColumnX + contentInset
        local y = ctx.formTopY
        local contentWidth = ctx.combatFullWidth - contentInset * 2
        local templateWidth = contentWidth
        local topColumnGap = ctx.COMBAT_TOP_COLUMN_GAP
        local topColumnWidth = math.floor((contentWidth - topColumnGap) / 2)
        local topDescriptionWidth = topColumnWidth - 30
        local topRightX = x + topColumnWidth + topColumnGap
        local fieldInputXOffset = 3
        local templateInputWidth = templateWidth - fieldInputXOffset
        local dropdownWidth = 150
        local rescanButtonWidth = 100
        local manualButtonWidth = 88
        local manualInputWidth = templateWidth - manualButtonWidth - 16
        local danmakuLabelWidth = 120
        local danmakuRowGap = 14
        local danmakuValueWidth = 96
        local danmakuSliderWidth = contentWidth - danmakuLabelWidth - (danmakuRowGap * 2) - danmakuValueWidth
        local danmakuSliderX = x + danmakuLabelWidth + danmakuRowGap
        local danmakuValueRightX = x + contentWidth
        local inlineControlGap = 12
        local rescanButtonGap = ctx.LABEL_CONTROL_GAP
        local descriptionGap = ctx.TEXT_DESCRIPTION_GAP
        local hintGap = ctx.DESCRIPTION_HINT_GAP
        local controlDescriptionGap = ctx.CONTROL_DESCRIPTION_GAP
        local labelControlGap = ctx.LABEL_CONTROL_GAP
        local reportSectionGap = ctx.SECTION_BLOCK_GAP
        local interruptMonitorContentTopGap = 0
        local manualInputTopGap = 0
        local interruptAnnounceBlockHeight = ctx.COMBAT_TOP_ROW_HEIGHT + 24
        local interruptModeBlockHeight = ctx.COMBAT_FIELD_BLOCK_HEIGHT
        local currentSpellBlockHeight = ctx.COMBAT_CARD_BLOCK_HEIGHT
        local focusMacroFrameHeight = 60
        local focusMacroTextGroupHeight = 36
        local focusMacroBlockHeight = focusMacroFrameHeight
        local manualInputBlockHeight = ctx.COMBAT_FIELD_WITH_HINT_BLOCK_HEIGHT
        local templateBlockHeight = ctx.COMBAT_TEMPLATE_BLOCK_HEIGHT
        local cooldownReadyBlockHeight = 86
        local danmakuSoundBlockHeight = 64
        local danmakuFieldBlockHeight = ctx.COMBAT_DANMAKU_FIELD_BLOCK_HEIGHT

        local function LayoutDanmakuSliderRow(label, valueText, slider, rowY)
            SetCombatControlPoint(label, x, rowY)
            label:SetWidth(danmakuLabelWidth)
            label:Show()

            slider:ClearAllPoints()
            slider:SetPoint("TOPLEFT", page, "TOPLEFT", danmakuSliderX, rowY - 4)
            slider:SetWidth(danmakuSliderWidth)
            slider:Show()

            valueText:ClearAllPoints()
            valueText:SetPoint("TOPRIGHT", page, "TOPLEFT", danmakuValueRightX, rowY)
            valueText:SetWidth(danmakuValueWidth)
            valueText:Show()

            return rowY - danmakuFieldBlockHeight
        end

        y = LayoutCombatSectionTitle(interruptAnnounceSection, interruptAnnounceRule, y)

        SetCombatControlPoint(interruptAnnounceCheck, x, y)
        ConfigureCheckText(interruptAnnounceCheck, topDescriptionWidth)
        interruptAnnounceCheck:Show()

        interruptAnnounceDesc:ClearAllPoints()
        interruptAnnounceDesc:SetPoint("TOPLEFT", interruptAnnounceCheck.Text, "BOTTOMLEFT", 0, -descriptionGap)
        interruptAnnounceDesc:SetWidth(topDescriptionWidth)
        interruptAnnounceDesc:Show()

        interruptAnnounceHint:ClearAllPoints()
        interruptAnnounceHint:SetPoint("TOPLEFT", interruptAnnounceDesc, "BOTTOMLEFT", 0, -hintGap)
        interruptAnnounceHint:SetWidth(topDescriptionWidth)
        interruptAnnounceHint:Show()

        interruptAnnounceImportant:ClearAllPoints()
        interruptAnnounceImportant:SetPoint("TOPLEFT", interruptAnnounceHint, "BOTTOMLEFT", 0, -hintGap)
        interruptAnnounceImportant:SetWidth(topDescriptionWidth)
        interruptAnnounceImportant:Show()

        SetCombatControlPoint(interruptAddonSyncCheck, topRightX, y)
        ConfigureCheckText(interruptAddonSyncCheck, topDescriptionWidth)
        interruptAddonSyncCheck:Show()

        interruptAddonSyncDesc:ClearAllPoints()
        interruptAddonSyncDesc:SetPoint("TOPLEFT", interruptAddonSyncCheck.Text, "BOTTOMLEFT", 0, -descriptionGap)
        interruptAddonSyncDesc:SetWidth(topDescriptionWidth)
        interruptAddonSyncDesc:Show()

        interruptAddonSyncHint:ClearAllPoints()
        interruptAddonSyncHint:SetPoint("TOPLEFT", interruptAddonSyncDesc, "BOTTOMLEFT", 0, -hintGap)
        interruptAddonSyncHint:SetWidth(topDescriptionWidth)
        interruptAddonSyncHint:Show()
        y = y - interruptAnnounceBlockHeight

        y = y - reportSectionGap
        y = LayoutCombatSectionTitle(interruptMonitorSection, interruptMonitorRule, y)
        y = y - interruptMonitorContentTopGap

        SetCombatControlPoint(TMA.InterruptModeLabel, x, y)
        FitFieldLabelWidth(TMA.InterruptModeLabel, 64)
        TMA.InterruptModeLabel:Show()

        TMA.InterruptModeDropdown:ClearAllPoints()
        TMA.InterruptModeDropdown:SetPoint("LEFT", TMA.InterruptModeLabel, "RIGHT", inlineControlGap, 0)
        TMA.InterruptModeDropdown:SetWidth(dropdownWidth)
        TMA.InterruptModeDropdown:Show()

        rescanInterruptButton:ClearAllPoints()
        rescanInterruptButton:SetSize(rescanButtonWidth, 24)
        rescanInterruptButton:SetPoint("LEFT", TMA.InterruptModeDropdown, "RIGHT", rescanButtonGap, 0)
        if showManualSpell then
            rescanInterruptButton:Hide()
        else
            rescanInterruptButton:Show()
        end

        interruptModeDesc:ClearAllPoints()
        interruptModeDesc:SetPoint("TOPLEFT", TMA.InterruptModeLabel, "BOTTOMLEFT", 0, -controlDescriptionGap)
        interruptModeDesc:SetWidth(contentWidth)
        interruptModeDesc:Show()
        y = y - interruptModeBlockHeight

        if showManualSpell then
            y = y - manualInputTopGap

            SetCombatControlPoint(TMA.InterruptSpellNameLabel, x, y)
            TMA.InterruptSpellNameLabel:SetWidth(220)
            TMA.InterruptSpellNameLabel:Show()

            TMA.InterruptSpellHelpText:ClearAllPoints()
            TMA.InterruptSpellHelpText:SetPoint("TOPLEFT", TMA.InterruptSpellNameLabel, "BOTTOMLEFT", 0, -descriptionGap)
            TMA.InterruptSpellHelpText:SetWidth(contentWidth)
            TMA.InterruptSpellHelpText:Show()

            TMA.InterruptSpellNameBox:ClearAllPoints()
            TMA.InterruptSpellNameBox:SetPoint("TOPLEFT", TMA.InterruptSpellHelpText, "BOTTOMLEFT", 3, -labelControlGap)
            TMA.InterruptSpellNameBox:SetSize(manualInputWidth, 24)
            SetStyledEditBoxShown(TMA.InterruptSpellNameBox, true)

            TMA.InterruptSpellApplyButton:ClearAllPoints()
            TMA.InterruptSpellApplyButton:SetPoint("LEFT", TMA.InterruptSpellNameBox, "RIGHT", 12, 0)
            TMA.InterruptSpellApplyButton:SetSize(manualButtonWidth, 24)
            TMA.InterruptSpellApplyButton:Show()
            y = y - manualInputBlockHeight
        else
            TMA.InterruptSpellNameLabel:Hide()
            SetStyledEditBoxShown(TMA.InterruptSpellNameBox, false)
            TMA.InterruptSpellApplyButton:Hide()
            TMA.InterruptSpellHelpText:Hide()
        end

        SetCombatControlPoint(TMA.CurrentInterruptSpellLabel, x, y)
        TMA.CurrentInterruptSpellLabel:Show()

        TMA.CurrentInterruptSpellCard:ClearAllPoints()
        TMA.CurrentInterruptSpellCard:SetPoint("TOPLEFT", TMA.CurrentInterruptSpellLabel, "BOTTOMLEFT", 0, -labelControlGap)
        TMA.CurrentInterruptSpellCard:SetWidth(templateWidth)
        TMA.CurrentInterruptSpellCard:Show()
        y = y - currentSpellBlockHeight

        focusMacroFrame:ClearAllPoints()
        SetCombatControlPoint(focusMacroFrame, x, y)
        focusMacroFrame:SetSize(templateWidth, focusMacroFrameHeight)
        focusMacroFrame:Show()

        focusMacroTextGroup:ClearAllPoints()
        focusMacroTextGroup:SetPoint("CENTER", focusMacroFrame, "CENTER", 0, 0)
        focusMacroTextGroup:SetSize(templateWidth - 20, focusMacroTextGroupHeight)
        focusMacroTextGroup:Show()

        focusMacroDesc:ClearAllPoints()
        focusMacroDesc:SetPoint("TOPLEFT", focusMacroTextGroup, "TOPLEFT", 0, 0)
        focusMacroDesc:SetWidth(templateWidth - 20)
        focusMacroDesc:SetHeight(15)
        focusMacroDesc:Show()

        focusMacroHint:ClearAllPoints()
        focusMacroHint:SetPoint("TOPLEFT", focusMacroDesc, "BOTTOMLEFT", 0, -descriptionGap)
        focusMacroHint:SetWidth(templateWidth - 20)
        focusMacroHint:SetHeight(15)
        focusMacroHint:Show()
        y = y - focusMacroBlockHeight

        y = y - reportSectionGap
        y = LayoutCombatSectionTitle(interruptChatSection, interruptChatRule, y)

        SetCombatControlPoint(interruptTemplateLabel, x, y)
        interruptTemplateLabel:Show()

        interruptTemplateHelp:ClearAllPoints()
        interruptTemplateHelp:SetPoint("TOPLEFT", interruptTemplateLabel, "BOTTOMLEFT", 0, -controlDescriptionGap)
        interruptTemplateHelp:SetWidth(contentWidth)
        interruptTemplateHelp:Show()

        interruptTemplateBox:ClearAllPoints()
        interruptTemplateBox:SetPoint("TOPLEFT", interruptTemplateHelp, "BOTTOMLEFT", fieldInputXOffset, -labelControlGap)
        interruptTemplateBox:SetSize(templateInputWidth, 24)
        interruptTemplateBox:Show()

        interruptTemplatePreviewFrame:ClearAllPoints()
        interruptTemplatePreviewFrame:SetPoint("TOPLEFT", interruptTemplateBox, "BOTTOMLEFT", -fieldInputXOffset, -hintGap)
        interruptTemplatePreviewFrame:SetSize(templateWidth, 34)
        interruptTemplatePreviewFrame:Show()

        interruptTemplatePreview:ClearAllPoints()
        interruptTemplatePreview:SetPoint("TOPLEFT", interruptTemplatePreviewFrame, "TOPLEFT", 34, 0)
        interruptTemplatePreview:SetPoint("BOTTOMRIGHT", interruptTemplatePreviewFrame, "BOTTOMRIGHT", -10, 0)
        interruptTemplatePreview:Show()
        y = y - templateBlockHeight

        y = y - reportSectionGap
        y = LayoutCombatSectionTitle(cooldownReadySection, cooldownReadyRule, y)

        SetCombatControlPoint(cooldownReadyCheck, x, y)
        ConfigureCheckText(cooldownReadyCheck, contentWidth - 160)
        cooldownReadyCheck:Show()

        previewCooldownReadyButton:ClearAllPoints()
        previewCooldownReadyButton:SetPoint("TOPRIGHT", page, "TOPLEFT", x + contentWidth, y + 3)
        previewCooldownReadyButton:SetSize(100, 24)
        previewCooldownReadyButton:Show()

        cooldownReadyDesc:ClearAllPoints()
        cooldownReadyDesc:SetPoint("TOPLEFT", cooldownReadyCheck.Text, "BOTTOMLEFT", 0, -descriptionGap)
        cooldownReadyDesc:SetWidth(contentWidth - 28)
        cooldownReadyDesc:Show()

        cooldownReadyHint:ClearAllPoints()
        cooldownReadyHint:SetPoint("TOPLEFT", cooldownReadyDesc, "BOTTOMLEFT", 0, -hintGap)
        cooldownReadyHint:SetWidth(contentWidth - 28)
        cooldownReadyHint:Show()
        y = y - cooldownReadyBlockHeight

        y = y - reportSectionGap
        y = LayoutCombatSectionTitle(danmakuSection, danmakuRule, y)

        SetCombatControlPoint(danmakuSoundCheck, x, y)
        ConfigureCheckText(danmakuSoundCheck, 100)
        danmakuSoundCheck:Show()

        previewDanmakuButton:ClearAllPoints()
        previewDanmakuButton:SetPoint("TOPRIGHT", page, "TOPLEFT", x + contentWidth, y + 3)
        previewDanmakuButton:SetSize(100, 24)
        previewDanmakuButton:Show()

        danmakuSoundDropdown:ClearAllPoints()
        danmakuSoundDropdown:SetPoint("RIGHT", previewDanmakuButton, "LEFT", -12, 0)
        danmakuSoundDropdown:SetWidth(120)
        danmakuSoundDropdown:Show()

        danmakuSoundDesc:ClearAllPoints()
        danmakuSoundDesc:SetPoint("TOPLEFT", danmakuSoundCheck.Text, "BOTTOMLEFT", 0, -(descriptionGap + 2))
        danmakuSoundDesc:SetWidth(contentWidth - 28)
        danmakuSoundDesc:Show()

        y = y - danmakuSoundBlockHeight
        y = LayoutDanmakuSliderRow(danmakuScrollStartLabel, danmakuScrollStartValue, danmakuScrollStartSlider, y)
        y = LayoutDanmakuSliderRow(danmakuFontSizeLabel, danmakuFontSizeValue, danmakuFontSizeSlider, y)
        y = LayoutDanmakuSliderRow(danmakuSpeedLabel, danmakuSpeedValue, danmakuSpeedSlider, y)
        y = LayoutDanmakuSliderRow(danmakuLifetimeLabel, danmakuLifetimeValue, danmakuLifetimeSlider, y)
        y = LayoutDanmakuSliderRow(danmakuMaxLabel, danmakuMaxValue, danmakuMaxSlider, y)

        page.__layoutBottomRegion = danmakuMaxSlider
    end

    function module.Refresh()
        interruptAnnounceCheck:SetChecked(TargetMarkerAssistantDB.enableInterruptAnnounce)
        cooldownReadyCheck:SetChecked(TargetMarkerAssistantDB.enableInterruptCooldownReadyAlert ~= false)
        interruptTemplateBox:SetText(TargetMarkerAssistantDB.interruptAnnounceTemplate or ctx.GetDefaultInterruptChatTemplate())
        TMA.InterruptSpellNameBox:SetText(TargetMarkerAssistantDB.interruptSpellInput or "")

        local interruptSpellMode = TargetMarkerAssistantDB.interruptSpellMode or "auto"
        if interruptSpellMode ~= "manual" then
            interruptSpellMode = "auto"
            TargetMarkerAssistantDB.interruptSpellMode = "auto"
        end
        if interruptSpellMode == "auto" then
            TMA.AutoDetectInterruptSpell(true)
        end

        local interruptSpellID = tonumber(TargetMarkerAssistantDB.interruptSpellID)
        local interruptSpellName = TargetMarkerAssistantDB.interruptSpellName or TMA.GetSpellNameByID(interruptSpellID)
        UpdateInterruptSpellCard(TMA.CurrentInterruptSpellCard, interruptSpellID, interruptSpellName)

        module.LayoutCombatReportPage(interruptSpellMode == "manual")
        ui.RefreshDropdown(TMA.InterruptModeDropdown)

        TMA.EnsureRestrictedInterruptDanmakuDefaults()
        interruptAddonSyncCheck:SetChecked(TargetMarkerAssistantDB.interruptAddonSyncEnabled)
        local danmakuCfg = TargetMarkerAssistantDB.restrictedInterruptDanmaku
        danmakuCfg.enabled = TargetMarkerAssistantDB.enableInterruptAnnounce == true
        danmakuCfg.scrollStartRatio = NormalizeDanmakuScrollStartRatio(danmakuCfg.scrollStartRatio)
        danmakuCfg.sound = GetSelectedDanmakuAlertSound().value
        danmakuSoundCheck:SetChecked(danmakuCfg.soundEnabled == true)
        SetDanmakuSoundControlsEnabled(danmakuCfg.soundEnabled == true)
        if RefreshInterruptAnnounceDependentControls then
            RefreshInterruptAnnounceDependentControls()
        end
        ui.RefreshDropdown(danmakuSoundDropdown)
        SetDanmakuScrollStartSliderValue(danmakuScrollStartSlider, danmakuCfg.scrollStartRatio)
        SetDanmakuSliderValue(danmakuFontSizeSlider, danmakuCfg.fontSize or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.fontSize)
        SetDanmakuSliderValue(danmakuSpeedSlider, danmakuCfg.speed or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.speed)
        SetDanmakuSliderValue(danmakuLifetimeSlider, danmakuCfg.lifetime or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.lifetime)
        SetDanmakuSliderValue(danmakuMaxSlider, danmakuCfg.maxDanmaku or TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU.maxDanmaku)
        interruptTemplatePreview:SetText(ctx.FormatInterruptChatPreview())
        ui.UpdateScrollContentHeight(ctx.combatScrollFrame, page, page.__layoutBottomRegion or previewDanmakuButton, 28)
    end

    return module
end
