TargetMarkerAssistant = TargetMarkerAssistant or {}
local TMA = TargetMarkerAssistant
local L = TargetMarkerAssistantL or {}

local danmakuFrame
local cooldownReadyAlertFrame
local danmakuPool = {}
local activeDanmakus = {}
local DANMAKU_ICON_FRAME_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Icon\\TabButton.png"
local COOLDOWN_READY_BACKGROUND_TEXTURE = "Interface\\AddOns\\TargetMarkerAssistant\\Art\\Ui\\CooldownTips.png"
local COOLDOWN_READY_SOUND_FILE = "Interface\\AddOns\\TargetMarkerAssistant\\Sounds\\Glass.aiff"

local UpdateRestrictedDanmakus
local PushRestrictedDanmaku
local EnsureRestrictedDanmakuFrame
local GetRestrictedDanmaku
local ReleaseRestrictedDanmaku
local RepositionRestrictedDanmakus

local function DebugRestrictedDanmaku(msg)
    if TMA.DebugInterrupt then
        TMA.DebugInterrupt(msg)
    end
end

local function GetRestrictedDanmakuFont()
    if TMA.GetClientDefaultFont then
        local font = TMA.GetClientDefaultFont()
        if font and font ~= "" then
            return font
        end
    end

    if GameFontNormal and GameFontNormal.GetFont then
        local font = select(1, GameFontNormal:GetFont())
        if font and font ~= "" then
            return font
        end
    end

    return STANDARD_TEXT_FONT or UNIT_NAME_FONT or DAMAGE_TEXT_FONT
end

local function GetFirstInlineTexture(content)
    if type(content) ~= "string" then return nil end
    local before, texture, after = content:match("^(.-)|T([^:|]+)[^|]*|t%s*(.*)$")
    if texture then
        return before or "", texture, after or ""
    end
end

local function SetTextureValue(textureRegion, texture)
    if not textureRegion then return end
    local numericTexture = tonumber(texture)
    if numericTexture then
        textureRegion:SetTexture(numericTexture)
    else
        textureRegion:SetTexture(texture)
    end
end

local function ApplyDanmakuIconFrameStyle(iconFrame, size)
    if not iconFrame then return end

    if iconFrame.SetBackdrop then
        iconFrame:SetBackdrop(nil)
    end

    if not iconFrame.FrameTexture then
        iconFrame.FrameTexture = iconFrame:CreateTexture(nil, "BACKGROUND")
        iconFrame.FrameTexture:SetAllPoints(iconFrame)
    end
    iconFrame.FrameTexture:SetTexture(DANMAKU_ICON_FRAME_TEXTURE)
    iconFrame.FrameTexture:SetSize(size or iconFrame:GetWidth() or 0, size or iconFrame:GetHeight() or 0)
    iconFrame.FrameTexture:Show()
end

local function GetDanmakuIconOuterSize(cfg)
    return math.max(30, math.min(54, (cfg.fontSize or 22) + 10))
end

local function ApplyDanmakuFont(fontString, cfg)
    if not fontString then return end
    local font = GetRestrictedDanmakuFont()
    if font then
        fontString:SetFont(font, cfg.fontSize or 22, cfg.fontOutline or "OUTLINE")
    end

    if cfg.fontShadow then
        fontString:SetShadowOffset(cfg.shadowOffsetX or 1, cfg.shadowOffsetY or -1)
    else
        fontString:SetShadowOffset(0, 0)
    end

    fontString:SetJustifyH("LEFT")
end

local function CopyRestrictedDefault(value)
    if TMA.CopyDefaultValue then
        return TMA.CopyDefaultValue(value)
    end

    if type(value) ~= "table" then
        return value
    end

    local copy = {}
    for k, v in pairs(value) do
        copy[k] = CopyRestrictedDefault(v)
    end
    return copy
end

local function GetRestrictedDanmakuConfig()
    if TMA.EnsureRestrictedInterruptDanmakuDefaults then
        TMA.EnsureRestrictedInterruptDanmakuDefaults()
    end

    if not TMA.DB then
        return nil
    end

    if type(TMA.DB.restrictedInterruptDanmaku) ~= "table" then
        TMA.DB.restrictedInterruptDanmaku = {}
    end

    local cfg = TMA.DB.restrictedInterruptDanmaku
    local restrictedDanmakuDefaults = TMA.DEFAULT_RESTRICTED_INTERRUPT_DANMAKU or {}
    for key, value in pairs(restrictedDanmakuDefaults) do
        if cfg[key] == nil then
            cfg[key] = CopyRestrictedDefault(value)
        end
    end

    if type(cfg.color) ~= "table" then
        cfg.color = CopyRestrictedDefault(restrictedDanmakuDefaults.color or { r = 1, g = 0.82, b = 0 })
    else
        for key, value in pairs(restrictedDanmakuDefaults.color or {}) do
            if cfg.color[key] == nil then
                cfg.color[key] = value
            end
        end
    end

    if cfg.prefix == "[打断]" then
        cfg.prefix = ""
    end

    if TMA.NormalizeRestrictedInterruptDanmakuScrollStartRatio then
        cfg.scrollStartRatio = TMA.NormalizeRestrictedInterruptDanmakuScrollStartRatio(cfg.scrollStartRatio)
    end

    return cfg
end

local function GetRestrictedDanmakuStartX(cfg)
    local frameWidth = (danmakuFrame and danmakuFrame:GetWidth()) or (UIParent and UIParent:GetWidth()) or 0
    if frameWidth <= 0 and UIParent and UIParent.GetWidth then
        frameWidth = UIParent:GetWidth() or 0
    end

    local ratio = cfg and cfg.scrollStartRatio or 1.0
    if TMA.NormalizeRestrictedInterruptDanmakuScrollStartRatio then
        ratio = TMA.NormalizeRestrictedInterruptDanmakuScrollStartRatio(ratio)
    else
        ratio = tonumber(ratio) or 1.0
        if ratio > 1 then
            ratio = ratio / 100
        end
        ratio = math.max(0.70, math.min(1.0, ratio))
    end

    return frameWidth * ratio
end

local function GetRestrictedDanmakuAlpha(dm)
    local elapsed = tonumber(dm and dm.elapsed) or 0
    local lifetime = math.max(tonumber(dm and dm.lifetime) or 0, 0)
    local fadeInDuration = math.max(tonumber(dm and dm.fadeInDuration) or 0, 0)
    local fadeOutDuration = math.max(tonumber(dm and dm.fadeOutDuration) or 0, 0)
    local alpha = 1

    if fadeInDuration > 0 and elapsed < fadeInDuration then
        alpha = math.min(alpha, elapsed / fadeInDuration)
    end

    if lifetime > 0 and fadeOutDuration > 0 then
        local remaining = lifetime - elapsed
        if remaining < fadeOutDuration then
            alpha = math.min(alpha, math.max(remaining, 0) / fadeOutDuration)
        end
    end

    return math.max(0, math.min(1, alpha))
end

UpdateRestrictedDanmakus = function(elapsed)
    if not danmakuFrame then
        return
    end

    for i = #activeDanmakus, 1, -1 do
        local dm = activeDanmakus[i]

        dm.elapsed = (dm.elapsed or 0) + elapsed
        dm.x = (dm.x or 0) - (dm.speed or 300) * elapsed

        local region = dm.frame or dm.text
        region:ClearAllPoints()
        region:SetPoint("LEFT", danmakuFrame, "LEFT", dm.x, dm.y or 0)
        if dm.frame then
            dm.frame:SetAlpha(GetRestrictedDanmakuAlpha(dm))
        end

        local textWidth = dm.width or (dm.text and dm.text:GetStringWidth()) or 0
        if dm.elapsed >= (dm.lifetime or 8) or dm.x < -textWidth - 40 then
            table.remove(activeDanmakus, i)
            ReleaseRestrictedDanmaku(dm)
        end
    end
end

EnsureRestrictedDanmakuFrame = function(cfg)
    if danmakuFrame then
        return danmakuFrame
    end

    cfg = cfg or {}

    danmakuFrame = CreateFrame("Frame", "TMA_RestrictedInterruptDanmakuFrame", UIParent)
    danmakuFrame:SetFrameStrata(cfg.frameStrata or "MEDIUM")
    danmakuFrame:SetFrameLevel(cfg.frameLevel or 10)
    danmakuFrame:SetPoint(
        cfg.anchorPoint or "TOPLEFT",
        UIParent,
        cfg.anchorPoint or "TOPLEFT",
        cfg.xOffset or 0,
        cfg.yOffset or -120
    )
    danmakuFrame:SetSize(UIParent:GetWidth(), cfg.scrollHeight or 220)
    danmakuFrame:EnableMouse(false)

    -- 监听分辨率/UI缩放变化，动态更新弹幕容器宽度
    danmakuFrame:RegisterEvent("UI_SCALE_CHANGED")
    danmakuFrame:RegisterEvent("DISPLAY_SIZE_CHANGED")
    danmakuFrame:SetScript("OnEvent", function(self, event)
        if UIParent and UIParent.GetWidth then
            self:SetWidth(UIParent:GetWidth())
        end
    end)

    danmakuFrame:SetScript("OnUpdate", function(_, frameElapsed)
        UpdateRestrictedDanmakus(frameElapsed)
    end)

    return danmakuFrame
end

GetRestrictedDanmaku = function(cfg)
    local dm = table.remove(danmakuPool)

    if not dm then
        dm = {}
        dm.frame = CreateFrame("Frame", nil, EnsureRestrictedDanmakuFrame(cfg))
        dm.frame:EnableMouse(false)
        dm.text = dm.frame:CreateFontString(nil, "OVERLAY")
        dm.prefixText = dm.frame:CreateFontString(nil, "OVERLAY")
        dm.suffixText = dm.frame:CreateFontString(nil, "OVERLAY")
        dm.iconFrame = CreateFrame("Frame", nil, dm.frame, "BackdropTemplate")
        dm.icon = dm.iconFrame:CreateTexture(nil, "ARTWORK")
        ApplyDanmakuIconFrameStyle(dm.iconFrame)
    end

    dm.frame:SetParent(EnsureRestrictedDanmakuFrame(cfg))
    dm.frame:Show()
    dm.frame:SetAlpha(1)
    ApplyDanmakuFont(dm.text, cfg)
    ApplyDanmakuFont(dm.prefixText, cfg)
    ApplyDanmakuFont(dm.suffixText, cfg)

    dm.text:SetTextColor(1, 1, 1)
    dm.prefixText:SetTextColor(1, 1, 1)
    dm.suffixText:SetTextColor(1, 1, 1)

    return dm
end

ReleaseRestrictedDanmaku = function(dm)
    if not dm then
        return
    end

    if dm.frame then
        dm.frame:Hide()
        dm.frame:ClearAllPoints()
        dm.frame:SetAlpha(1)
    end

    local textRegions = { dm.text, dm.prefixText, dm.suffixText }
    for i = 1, #textRegions do
        local region = textRegions[i]
        if region then
            region:Hide()
            region:SetText("")
            region:ClearAllPoints()
        end
    end

    if dm.icon then
        dm.icon:Hide()
        dm.icon:SetTexture(nil)
        dm.icon:ClearAllPoints()
    end

    if dm.iconFrame then
        dm.iconFrame:Hide()
        dm.iconFrame:ClearAllPoints()
    end

    dm.x = nil
    dm.y = nil
    dm.elapsed = nil
    dm.lifetime = nil
    dm.speed = nil
    dm.width = nil
    dm.fadeInDuration = nil
    dm.fadeOutDuration = nil

    table.insert(danmakuPool, dm)
end

RepositionRestrictedDanmakus = function(cfg)
    local lineHeight = math.max((cfg.fontSize or 22) + 8, GetDanmakuIconOuterSize(cfg) + 4)

    for index, dm in ipairs(activeDanmakus) do
        dm.y = -((index - 1) * lineHeight)
    end
end

PushRestrictedDanmaku = function(content, cfg)
    cfg = cfg or {}
    EnsureRestrictedDanmakuFrame(cfg)

    local maxDanmaku = cfg.maxDanmaku or 5
    while #activeDanmakus >= maxDanmaku do
        local oldest = table.remove(activeDanmakus, 1)
        ReleaseRestrictedDanmaku(oldest)
    end

    local dm = GetRestrictedDanmaku(cfg)

    local color = cfg.color or { r = 1, g = 0.82, b = 0 }
    local baseR, baseG, baseB = color.r or 1, color.g or 0.82, color.b or 0
    local beforeIcon, iconTexture, afterIcon = GetFirstInlineTexture(content)
    if iconTexture then
        dm.text:Hide()

        dm.prefixText:SetTextColor(baseR, baseG, baseB)
        dm.suffixText:SetTextColor(baseR, baseG, baseB)
        dm.prefixText:SetText(beforeIcon or "")
        dm.suffixText:SetText(afterIcon or "")
        dm.prefixText:Show()
        dm.suffixText:Show()

        local iconOuterSize = GetDanmakuIconOuterSize(cfg)
        local iconInnerSize = math.max(16, iconOuterSize - 12)
        local iconGap = 4
        local prefixWidth = dm.prefixText:GetStringWidth() or 0
        local suffixWidth = dm.suffixText:GetStringWidth() or 0
        local frameHeight = math.max(iconOuterSize, cfg.fontSize or 22)

        dm.iconFrame:SetSize(iconOuterSize, iconOuterSize)
        ApplyDanmakuIconFrameStyle(dm.iconFrame, iconOuterSize)
        dm.iconFrame:Show()

        SetTextureValue(dm.icon, iconTexture)
        dm.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
        dm.icon:SetSize(iconInnerSize, iconInnerSize)
        dm.icon:Show()

        dm.frame:SetSize(prefixWidth + iconGap + iconOuterSize + iconGap + suffixWidth, frameHeight)
        dm.prefixText:ClearAllPoints()
        dm.prefixText:SetPoint("LEFT", dm.frame, "LEFT", 0, 0)

        dm.iconFrame:ClearAllPoints()
        dm.iconFrame:SetPoint("LEFT", dm.prefixText, "RIGHT", iconGap, 0)
        dm.icon:ClearAllPoints()
        dm.icon:SetPoint("CENTER", dm.iconFrame, "CENTER", 0, 0)

        dm.suffixText:ClearAllPoints()
        dm.suffixText:SetPoint("LEFT", dm.iconFrame, "RIGHT", iconGap, 0)
        dm.width = dm.frame:GetWidth()
    else
        dm.prefixText:Hide()
        dm.suffixText:Hide()
        dm.icon:Hide()
        dm.iconFrame:Hide()

        dm.text:SetTextColor(baseR, baseG, baseB)
        dm.text:SetText(content)
        dm.text:Show()
        dm.text:ClearAllPoints()
        dm.text:SetPoint("LEFT", dm.frame, "LEFT", 0, 0)
        dm.width = dm.text:GetStringWidth() or 0
        dm.frame:SetSize(dm.width, cfg.fontSize or 22)
    end

    dm.x = GetRestrictedDanmakuStartX(cfg)
    dm.y = 0
    dm.elapsed = 0
    dm.lifetime = cfg.lifetime or 8
    dm.speed = cfg.speed or 300
    dm.fadeInDuration = cfg.fadeInDuration or 0.25
    dm.fadeOutDuration = cfg.fadeOutDuration or 0.35

    dm.frame:ClearAllPoints()
    dm.frame:SetPoint("LEFT", danmakuFrame, "LEFT", dm.x, dm.y)
    dm.frame:SetAlpha(GetRestrictedDanmakuAlpha(dm))

    table.insert(activeDanmakus, dm)
    RepositionRestrictedDanmakus(cfg)

    if TMA.PlayRestrictedDanmakuSound then
        TMA.PlayRestrictedDanmakuSound()
    end
end

function TMA.ShowRestrictedInterruptDanmaku(localMsg)
    if not TMA.DB then
        DebugRestrictedDanmaku("弹幕取消：TMA.DB 不存在。")
        return false
    end

    if not TMA.IsInterruptAnnounceContextValid or not TMA.IsInterruptAnnounceContextValid() then
        DebugRestrictedDanmaku("弹幕取消：玩家不在队伍或团队中。")
        return false
    end

    if not TMA.DB.enableInterruptAnnounce then
        DebugRestrictedDanmaku("弹幕取消：启用通报开关已关闭。")
        return false
    end

    if not localMsg or localMsg == "" then
        DebugRestrictedDanmaku("弹幕取消：localMsg 为空。")
        return false
    end

    local cfg = GetRestrictedDanmakuConfig()
    if not cfg then
        DebugRestrictedDanmaku("弹幕取消：打断通报弹幕配置不存在。")
        return false
    end

    DebugRestrictedDanmaku("弹幕条件通过：准备推送打断通报弹幕。")
    PushRestrictedDanmaku(localMsg, cfg)
    DebugRestrictedDanmaku("弹幕推送完成。")
    return true
end

function TMA.PreviewRestrictedInterruptDanmaku(localMsg)
    if not TMA.DB then
        return false
    end

    if not localMsg or localMsg == "" then
        return false
    end

    local cfg = GetRestrictedDanmakuConfig()
    if not cfg then
        return false
    end

    -- Preview is a local visual test only. Formal interrupt notices still go
    -- through ShowRestrictedInterruptDanmaku and keep group/config checks.
    PushRestrictedDanmaku(localMsg, cfg)
    return true
end

local function GetCooldownReadyAlertMessage()
    return L["INTERRUPT_COOLDOWN_READY_ALERT"] or L["INTERRUPT_COOLDOWN_READY_SUFFIX"] or "冷却结束"
end

local function PlayCooldownReadyAlertSound()
    if not PlaySoundFile then return false end

    local ok, played = pcall(PlaySoundFile, COOLDOWN_READY_SOUND_FILE, "Master")
    return ok and played == true
end

local function EnsureCooldownReadyAlertFrame()
    if cooldownReadyAlertFrame then
        return cooldownReadyAlertFrame
    end

    local frame = CreateFrame("Frame", "TMA_InterruptCooldownReadyAlertFrame", UIParent)
    frame:SetSize(560, 75)
    frame:SetPoint("TOP", UIParent, "TOP", 0, -150)
    frame:SetFrameStrata("FULLSCREEN_DIALOG")
    frame:SetFrameLevel(80)
    frame:EnableMouse(false)
    frame:Hide()
    frame:SetAlpha(0)

    frame.Background = frame:CreateTexture(nil, "BACKGROUND")
    frame.Background:SetAllPoints(frame)
    frame.Background:SetTexture(COOLDOWN_READY_BACKGROUND_TEXTURE)

    frame.IconFrame = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    frame.IconFrame:SetSize(42, 42)
    frame.IconFrame:SetPoint("CENTER", frame, "CENTER", -64, 8)
    ApplyDanmakuIconFrameStyle(frame.IconFrame, 42)

    frame.Icon = frame.IconFrame:CreateTexture(nil, "ARTWORK")
    frame.Icon:SetSize(31, 31)
    frame.Icon:SetPoint("CENTER", frame.IconFrame, "CENTER", 0, 0)
    frame.Icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    frame.Text = frame:CreateFontString(nil, "OVERLAY")
    frame.Text:SetPoint("LEFT", frame.IconFrame, "RIGHT", 8, 0)
    frame.Text:SetPoint("RIGHT", frame, "CENTER", 160, 8)
    frame.Text:SetJustifyH("LEFT")
    frame.Text:SetTextColor(1.00, 0.86, 0.24)
    frame.Text:SetShadowOffset(1, -1)
    if not TMA.SetClientDefaultFont or not TMA.SetClientDefaultFont(frame.Text, 24, "OUTLINE") then
        local font = GetRestrictedDanmakuFont()
        if font then
            frame.Text:SetFont(font, 24, "OUTLINE")
        end
    end

    frame:SetScript("OnUpdate", function(self, elapsed)
        self.elapsed = (self.elapsed or 0) + elapsed
        local fadeIn = 0.12
        local hold = 1.75
        local fadeOut = 0.35
        local total = fadeIn + hold + fadeOut
        local alpha = 1

        if self.elapsed < fadeIn then
            alpha = self.elapsed / fadeIn
        elseif self.elapsed > fadeIn + hold then
            alpha = math.max(0, 1 - ((self.elapsed - fadeIn - hold) / fadeOut))
        end

        self:SetAlpha(alpha)
        if self.elapsed >= total then
            self:Hide()
            self:SetAlpha(0)
        end
    end)

    cooldownReadyAlertFrame = frame
    return frame
end

function TMA.ShowInterruptCooldownReadyAlert(spellID, spellName)
    if not UIParent then return false end

    local display = TMA.GetSpellDisplayInfo and TMA.GetSpellDisplayInfo(spellID, spellName) or nil
    local resolvedName = (display and display.name) or spellName or L["INTERRUPT_SPELL"] or "打断技能"
    local frame = EnsureCooldownReadyAlertFrame()

    frame.Text:SetText(GetCooldownReadyAlertMessage())
    SetTextureValue(frame.Icon, (display and display.icon) or 134400)
    frame.elapsed = 0
    frame:SetAlpha(0)
    frame:Show()
    if frame.Raise then
        pcall(frame.Raise, frame)
    end
    PlayCooldownReadyAlertSound()
    return true
end
