local addonName, addonTable = ...
---@type MK
local MK = addonTable.MK
local core = addonTable
local TRANSLATE = core.TRANSLATE or function(msg) return msg end
local MythicSeason = core.MythicSeason or {}
local IsSecretValue = issecretvalue

local SECURE_ATTRIBUTE_NIL_VALUE = {}

local secureAttributeEventFrame
local pendingSecureAttributeButtons = {}

local function IsInCombatLockdown()
    return InCombatLockdown and InCombatLockdown()
end

local function NotifySecureAttributesApplied(button)
    if not (button and button.mkTeleportSecureStatePendingNotify) then
        return
    end

    button.mkTeleportSecureStatePendingNotify = nil
    if button then
        button.mkTeleportSecureMapID = button.mkTeleportDesiredMapID
        button.mkTeleportEffectiveMapID = button.mkTeleportDesiredMapID
    end

    local callback = button and button.mkSecureAttributesApplied
    if type(callback) == "function" then
        pcall(callback, button)
    end
end

local function ApplySecureButtonAttributes(button, ...)
    if not (button and button.SetAttribute) then
        return false
    end

    local count = select("#", ...)
    for index = 1, count, 2 do
        local key = select(index, ...)
        if key ~= nil then
            local value = select(index + 1, ...)
            button:SetAttribute(key, value)
        end
    end
    NotifySecureAttributesApplied(button)
    return true
end

local function FlushSecureButtonAttributes()
    if IsInCombatLockdown() then
        return false
    end

    for button, pending in pairs(pendingSecureAttributeButtons) do
        pendingSecureAttributeButtons[button] = nil
        if button and button.SetAttribute and type(pending) == "table" then
            for _, key in ipairs(pending.order or {}) do
                local value = pending.values and pending.values[key]
                if value == SECURE_ATTRIBUTE_NIL_VALUE then
                    value = nil
                end
                button:SetAttribute(key, value)
            end
            NotifySecureAttributesApplied(button)
        end
    end
    return true
end

local function EnsureSecureAttributeEventFrame()
    if secureAttributeEventFrame then
        return
    end

    secureAttributeEventFrame = CreateFrame("Frame")
    secureAttributeEventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
    secureAttributeEventFrame:SetScript("OnEvent", FlushSecureButtonAttributes)
end

local function QueueSecureButtonAttributes(button, ...)
    if not (button and button.SetAttribute) then
        return false
    end

    if not IsInCombatLockdown() then
        return ApplySecureButtonAttributes(button, ...)
    end

    EnsureSecureAttributeEventFrame()

    local pending = pendingSecureAttributeButtons[button]
    if not pending then
        pending = {
            order = {},
            known = {},
            values = {},
        }
        pendingSecureAttributeButtons[button] = pending
    end

    local count = select("#", ...)
    for index = 1, count, 2 do
        local key = select(index, ...)
        if key ~= nil then
            if not pending.known[key] then
                pending.known[key] = true
                pending.order[#pending.order + 1] = key
            end

            local value = select(index + 1, ...)
            if value == nil then
                value = SECURE_ATTRIBUTE_NIL_VALUE
            end
            pending.values[key] = value
        end
    end
    return false
end

core.SetSecureButtonAttributes = QueueSecureButtonAttributes
core.FlushSecureButtonAttributes = FlushSecureButtonAttributes

local function GetSecureButtonMacroText(button)
    if not (button and button.GetAttribute) then
        return nil
    end

    local macroText = button:GetAttribute("macrotext1") or button:GetAttribute("macrotext")
    if type(macroText) == "string" and macroText ~= "" then
        return macroText
    end

    return nil
end

local function IsSecureButtonMacroReady(button)
    if not (button and button.GetAttribute) then
        return false
    end

    local secureType = button:GetAttribute("type1") or button:GetAttribute("type")
    return secureType == "macro" and GetSecureButtonMacroText(button) ~= nil
end

local function ApplyDungeonTeleportButtonSecureState(button, canUseTeleport, macroText, mapID)
    if not button then
        return false, false, mapID
    end

    local desiredReady = canUseTeleport == true and type(macroText) == "string" and macroText ~= ""
    local currentReady = IsSecureButtonMacroReady(button)
    local currentMacroText = GetSecureButtonMacroText(button)
    local inCombat = IsInCombatLockdown()

    button.mkTeleportDesiredReady = desiredReady
    button.mkTeleportDesiredMapID = mapID
    button.mkTeleportSecureStatePendingNotify = true
    QueueSecureButtonAttributes(
        button,
        "type", desiredReady and "macro" or nil,
        "type1", desiredReady and "macro" or nil,
        "macrotext", desiredReady and macroText or nil,
        "macrotext1", desiredReady and macroText or nil,
        "spell", nil,
        "spell1", nil
    )

    if inCombat then
        local currentMatchesDesired = currentReady == desiredReady
            and (not desiredReady or currentMacroText == macroText)
        button.mkTeleportSecurePending = not currentMatchesDesired
        button.mkTeleportReady = currentReady
        button.mkTeleportEffectiveMapID = currentReady and button.mkTeleportSecureMapID or mapID
        return currentReady, button.mkTeleportSecurePending, button.mkTeleportEffectiveMapID
    end

    button.mkTeleportSecurePending = nil
    button.mkTeleportReady = desiredReady
    button.mkTeleportSecureMapID = mapID
    button.mkTeleportEffectiveMapID = mapID
    return desiredReady, false, mapID
end

local DUNGEON_TELEPORT_SPELLS = type(MythicSeason.GetTeleportSpellMap) == "function"
    and MythicSeason.GetTeleportSpellMap()
    or {}
local DUNGEON_TELEPORT_MAP_ID_BY_SPELL_ID = {}
for mapID, teleportData in pairs(DUNGEON_TELEPORT_SPELLS) do
    if teleportData.spellID then
        DUNGEON_TELEPORT_MAP_ID_BY_SPELL_ID[teleportData.spellID] = mapID
    end
end

local function IsDungeonTeleportSecretValue(value)
    if type(IsSecretValue) ~= "function" then
        return false
    end

    local ok, isSecret = pcall(IsSecretValue, value)
    return not ok or isSecret == true
end

local function NormalizeDungeonTeleportSpellID(spellID)
    if IsDungeonTeleportSecretValue(spellID) or spellID == nil then
        return nil
    end

    local ok, numericSpellID = pcall(tonumber, spellID)
    if not ok or IsDungeonTeleportSecretValue(numericSpellID) or numericSpellID == nil then
        return nil
    end

    if type(numericSpellID) ~= "number" then
        return nil
    end

    return numericSpellID
end

local function GetDungeonTeleportMapIDBySpellID(spellID)
    local numericSpellID = NormalizeDungeonTeleportSpellID(spellID)
    if not numericSpellID then
        return nil
    end

    return DUNGEON_TELEPORT_MAP_ID_BY_SPELL_ID[numericSpellID]
end

local function GetDungeonTeleportSpellID(mapID)
    local teleportData = mapID and DUNGEON_TELEPORT_SPELLS[mapID] or nil
    return teleportData and teleportData.spellID or nil
end

local function GetDungeonTeleportLocalizedSpellName(mapID)
    local spellID = GetDungeonTeleportSpellID(mapID)
    if spellID then
        if C_Spell and C_Spell.GetSpellInfo then
            local ok, spellInfo = pcall(C_Spell.GetSpellInfo, spellID)
            if ok and type(spellInfo) == "table" and type(spellInfo.name) == "string" and spellInfo.name ~= "" then
                return spellInfo.name
            end
        end

        if C_Spell and C_Spell.GetSpellName then
            local ok, spellName = pcall(C_Spell.GetSpellName, spellID)
            if ok and type(spellName) == "string" and spellName ~= "" then
                return spellName
            end
        end

        if type(GetSpellInfo) == "function" then
            local ok, spellName = pcall(GetSpellInfo, spellID)
            if ok and type(spellName) == "string" and spellName ~= "" then
                return spellName
            end
        end
    end

    return nil
end

local function GetDungeonTeleportSpellName(mapID)
    return GetDungeonTeleportLocalizedSpellName(mapID)
end

local function GetDungeonTeleportSpellLink(mapID)
    local spellID = GetDungeonTeleportSpellID(mapID)
    if spellID then
        if C_Spell and C_Spell.GetSpellLink then
            local ok, spellLink = pcall(C_Spell.GetSpellLink, spellID)
            if ok and type(spellLink) == "string" and spellLink ~= "" then
                return spellLink
            end
        end

        if type(GetSpellLink) == "function" then
            local ok, spellLink = pcall(GetSpellLink, spellID)
            if ok and type(spellLink) == "string" and spellLink ~= "" then
                return spellLink
            end
        end
    end

    return GetDungeonTeleportSpellName(mapID)
end

local function IsDungeonTeleportLearned(spellID)
    if not spellID then
        return false
    end

    if type(IsSpellKnownOrOverridesKnown) == "function" then
        local ok, known = pcall(IsSpellKnownOrOverridesKnown, spellID)
        if ok then
            return known == true
        end
    end

    if type(IsSpellKnown) == "function" then
        local ok, known = pcall(IsSpellKnown, spellID)
        if ok then
            return known == true
        end
    end

    return false
end

local function IsDungeonTeleportOnCooldown(spellID)
    if not spellID then
        return false
    end

    if C_Spell and C_Spell.GetSpellCooldown then
        local ok, cooldownInfo = pcall(C_Spell.GetSpellCooldown, spellID)
        if ok and type(cooldownInfo) == "table" then
            local startTime = cooldownInfo.startTime or 0
            local duration = cooldownInfo.duration or 0
            local isEnabled = cooldownInfo.isEnabled

            if isEnabled == false or startTime <= 0 or duration <= 0 then
                return false
            end

            return (startTime + duration) > (GetTime and GetTime() or 0)
        end
    end

    if type(GetSpellCooldown) ~= "function" then
        return false
    end

    local ok, startTime, duration, isEnabled = pcall(GetSpellCooldown, spellID)
    if not ok then
        return false
    end
    if isEnabled == 0 or not startTime or startTime <= 0 or not duration or duration <= 0 then
        return false
    end

    return (startTime + duration) > (GetTime and GetTime() or 0)
end

local function GetDungeonTeleportStatus(mapID)
    local spellID = GetDungeonTeleportSpellID(mapID)
    if not spellID then
        return nil, nil
    end

    if not IsDungeonTeleportLearned(spellID) then
        return "not_learned", spellID
    end

    if IsDungeonTeleportOnCooldown(spellID) then
        return "cooldown", spellID
    end

    return "ready", spellID
end

local function UpdateDungeonTeleportBadge(tile, mapID)
    local badge = tile and tile.teleportBadge
    if not badge then
        return
    end

    if not mapID then
        badge:Hide()
        return
    end

    if tile and tile.mkTeleportSecurePending and tile.mkTeleportReady == true then
        if badge.SetDesaturated then
            badge:SetDesaturated(false)
        end
        badge:SetVertexColor(1, 1, 1, 0.95)
        badge:SetAlpha(1)
        badge:Show()
        return
    end

    local teleportStatus = GetDungeonTeleportStatus(mapID)
    if teleportStatus == "ready" then
        if badge.SetDesaturated then
            badge:SetDesaturated(false)
        end
        badge:SetVertexColor(1, 1, 1, 0.95)
        badge:SetAlpha(1)
        badge:Show()
        return
    end

    if teleportStatus == "cooldown" then
        if badge.SetDesaturated then
            badge:SetDesaturated(true)
        end
        badge:SetVertexColor(0.45, 0.45, 0.45, 0.85)
        badge:SetAlpha(0.82)
        badge:Show()
        return
    end

    if teleportStatus == "not_learned" then
        if badge.SetDesaturated then
            badge:SetDesaturated(true)
        end
        badge:SetVertexColor(0.45, 0.45, 0.45, 0.72)
        badge:SetAlpha(0.72)
        badge:Show()
        return
    end

    if tile and tile.dungeonAvailable == false then
        if badge.SetDesaturated then
            badge:SetDesaturated(true)
        end
        badge:SetVertexColor(0.45, 0.45, 0.45, 0.78)
        badge:SetAlpha(0.72)
        badge:Show()
        return
    end

    badge:Hide()
end

local function GetDungeonTooltipScoreAndBestTime(mapID)
    if not mapID or not C_MythicPlus then
        return nil, nil
    end

    local score
    local bestDurationSec

    if C_MythicPlus.GetSeasonBestAffixScoreInfoForMap then
        local ok, affixScores, overallScore = pcall(C_MythicPlus.GetSeasonBestAffixScoreInfoForMap, mapID)
        if ok then
            score = tonumber(overallScore)
            if type(affixScores) == "table" then
                for _, affixScore in ipairs(affixScores) do
                    local durationSec = tonumber(affixScore and affixScore.durationSec)
                    if durationSec and durationSec > 0 and (not bestDurationSec or durationSec < bestDurationSec) then
                        bestDurationSec = durationSec
                    end
                end
            end
        end
    end

    if (not score or not bestDurationSec) and C_MythicPlus.GetSeasonBestForMap then
        local ok, inTimeInfo, overtimeInfo = pcall(C_MythicPlus.GetSeasonBestForMap, mapID)
        if ok then
            for _, seasonBestInfo in ipairs({ inTimeInfo, overtimeInfo }) do
                if type(seasonBestInfo) == "table" then
                    local dungeonScore = tonumber(seasonBestInfo.dungeonScore)
                    if dungeonScore and (not score or dungeonScore > score) then
                        score = dungeonScore
                    end

                    local durationSec = tonumber(seasonBestInfo.durationSec)
                    if durationSec and durationSec > 0 and (not bestDurationSec or durationSec < bestDurationSec) then
                        bestDurationSec = durationSec
                    end
                end
            end
        end
    end

    return score, bestDurationSec
end

local DUNGEON_TOOLTIP_LABEL_COLOR = "ffffd100"
local DUNGEON_TOOLTIP_VALUE_COLOR = "ffffffff"
local DUNGEON_TELEPORT_READY_TOOLTIP_COLOR = { 0.1, 1, 0.1 }
local DUNGEON_TELEPORT_COOLDOWN_TOOLTIP_COLOR = { 1, 0.15, 0.15 }
local DUNGEON_TELEPORT_UNAVAILABLE_TOOLTIP_COLOR = { 0.58, 0.58, 0.58 }

local function WrapDungeonTooltipColor(text, color)
    return string.format("|c%s%s|r", color or DUNGEON_TOOLTIP_VALUE_COLOR, text or "")
end

local function GetDungeonTooltipScoreColor(score)
    if not score then
        return DUNGEON_TOOLTIP_VALUE_COLOR
    end

    if MK.GetSpecificDungeonScoreColorString then
        return MK:GetSpecificDungeonScoreColorString(score)
    end

    return DUNGEON_TOOLTIP_VALUE_COLOR
end

local function AddDungeonTooltipScoreLine(tooltip, mapID, options)
    if not tooltip then
        return false
    end

    local score, bestDurationSec = GetDungeonTooltipScoreAndBestTime(mapID)
    if (not score or score <= 0) and not bestDurationSec then
        if type(options) == "table" and options.showEmpty == true then
            tooltip:AddLine(TRANSLATE("暂无通关记录"), 0.58, 0.58, 0.58, true)
            local scoreLine = WrapDungeonTooltipColor(TRANSLATE("评分："), DUNGEON_TOOLTIP_LABEL_COLOR)
                .. WrapDungeonTooltipColor("--", DUNGEON_TOOLTIP_VALUE_COLOR)
            tooltip:AddLine(scoreLine, 1, 1, 1)
            local bestLine = WrapDungeonTooltipColor(TRANSLATE("最佳成绩："), DUNGEON_TOOLTIP_LABEL_COLOR)
                .. WrapDungeonTooltipColor("--", DUNGEON_TOOLTIP_VALUE_COLOR)
            tooltip:AddLine(bestLine, 1, 1, 1)
            tooltip:AddLine(" ")
        end
        return false
    end

    local roundedScore = math.floor((score or 0) + 0.5)
    local scoreColor = GetDungeonTooltipScoreColor(roundedScore)
    local scoreLine = WrapDungeonTooltipColor(TRANSLATE("评分："), DUNGEON_TOOLTIP_LABEL_COLOR)
        .. WrapDungeonTooltipColor(tostring(roundedScore), scoreColor)
        .. WrapDungeonTooltipColor(TRANSLATE(" 分"), DUNGEON_TOOLTIP_LABEL_COLOR)
    tooltip:AddLine(scoreLine, 1, 1, 1)
    tooltip:AddLine(" ")

    if bestDurationSec then
        local totalSeconds = math.floor(bestDurationSec + 0.5)
        local minutes = math.floor(totalSeconds / 60)
        local seconds = totalSeconds % 60
        local bestLine = WrapDungeonTooltipColor(TRANSLATE("最佳成绩："), DUNGEON_TOOLTIP_LABEL_COLOR)
            .. WrapDungeonTooltipColor(string.format("%d : %02d", minutes, seconds), DUNGEON_TOOLTIP_VALUE_COLOR)
        tooltip:AddLine(bestLine, 1, 1, 1)
        tooltip:AddLine(" ")
        return true
    end

    local bestLine = WrapDungeonTooltipColor(TRANSLATE("最佳成绩："), DUNGEON_TOOLTIP_LABEL_COLOR)
        .. WrapDungeonTooltipColor("--", DUNGEON_TOOLTIP_VALUE_COLOR)
    tooltip:AddLine(bestLine, 1, 1, 1)
    tooltip:AddLine(" ")
    return true
end

local function GetDungeonTeleportTooltipStatus(status)
    if status == "ready" then
        return TRANSLATE("英雄之路已就绪"), DUNGEON_TELEPORT_READY_TOOLTIP_COLOR
    end

    if status == "cooldown" then
        return TRANSLATE("英雄之路冷却中"), DUNGEON_TELEPORT_COOLDOWN_TOOLTIP_COLOR
    end

    return TRANSLATE("未获得英雄之路"), DUNGEON_TELEPORT_UNAVAILABLE_TOOLTIP_COLOR
end

AddDungeonTeleportTooltipLine = function(tooltip, mapID, options)
    if not tooltip then
        return
    end

    local includeDestinationTitle = not (type(options) == "table" and options.includeDestinationTitle == false)
    if includeDestinationTitle then
        local dungeonName = MK.GetLocalizedDungeonName and MK:GetLocalizedDungeonName(mapID) or TRANSLATE("未知地下城")
        tooltip:AddDoubleLine(TRANSLATE("传送至"), dungeonName, 1, 0.82, 0, 1, 1, 1)
    end

    local status = GetDungeonTeleportStatus(mapID)
    local statusText, statusColor = GetDungeonTeleportTooltipStatus(status)
    tooltip:AddLine(statusText, statusColor[1], statusColor[2], statusColor[3], true)
end

local function GetDungeonTeleportMacroText(mapID)
    local spellName = GetDungeonTeleportLocalizedSpellName(mapID)
    if not spellName or spellName == "" then
        return nil
    end

    return "/cast " .. spellName
end

local function GetDungeonTeleportDebugFields(tile, mapID)
    local spellID = GetDungeonTeleportSpellID(mapID)
    local status = GetDungeonTeleportStatus(mapID)
    local spellName = GetDungeonTeleportSpellName(mapID)
    local secureType = tile and tile.GetAttribute and tile:GetAttribute("type1") or nil
    local secureAction = tile and tile.GetAttribute and (tile:GetAttribute("spell1") or tile:GetAttribute("macrotext1")) or nil

    return spellID, spellName, status, secureType, secureAction
end

core.DungeonTeleport = {
    GetSpellID = GetDungeonTeleportSpellID,
    GetSpellName = GetDungeonTeleportSpellName,
    GetSpellLink = GetDungeonTeleportSpellLink,
    GetMapIDBySpellID = GetDungeonTeleportMapIDBySpellID,
    NormalizeSpellID = NormalizeDungeonTeleportSpellID,
    GetStatus = GetDungeonTeleportStatus,
    GetMacroText = GetDungeonTeleportMacroText,
    ApplyButtonSecureState = ApplyDungeonTeleportButtonSecureState,
    UpdateBadge = UpdateDungeonTeleportBadge,
    AddTooltipScoreLine = AddDungeonTooltipScoreLine,
    AddTeleportTooltipLine = AddDungeonTeleportTooltipLine,
    GetDebugFields = GetDungeonTeleportDebugFields,
}
