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

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

local MythicPlusView = {}
ns.UIMythicPlusView = MythicPlusView

local DEFAULT_GOLD = { 0.92, 0.76, 0.24 }
local DEFAULT_PARCHMENT = { 0.93, 0.89, 0.77 }

local function getThemeColor(key, fallback)
    local color = THEME[key]
    if type(color) ~= "table" then
        color = fallback
    end

    return color[1], color[2], color[3]
end

local function normalizeMythicPlusKey(text)
    local value = string.lower(tostring(text or ""))
    value = value:gsub("[\195\132\195\164]", "a")
    value = value:gsub("[\195\150\195\182]", "o")
    value = value:gsub("[\195\156\195\188]", "u")
    value = value:gsub("\195\159", "ss")
    value = value:gsub("[^%w]+", "-")
    value = value:gsub("%-+", "-")
    value = value:gsub("^%-", "")
    value = value:gsub("%-$", "")
    return value
end

local function requestCurrentSeasonMapInfo()
    if C_MythicPlus and type(C_MythicPlus.RequestMapInfo) == "function" then
        pcall(C_MythicPlus.RequestMapInfo)
    end

    if C_ChallengeMode and type(C_ChallengeMode.RequestMapInfo) == "function" then
        pcall(C_ChallengeMode.RequestMapInfo)
    end
end

local function extractColorTriplet(color, fallback)
    fallback = fallback or DEFAULT_GOLD
    if type(color) == "table" then
        if color.r then
            return color.r, color.g, color.b
        end
        return tonumber(color[1]) or fallback[1], tonumber(color[2]) or fallback[2], tonumber(color[3]) or fallback[3]
    end

    return fallback[1], fallback[2], fallback[3]
end

local function buildMythicPlusTimestamp(dateInfo)
    if type(dateInfo) ~= "table" then
        return nil
    end

    local ok, timestamp = pcall(time, {
        year = tonumber(dateInfo.year) or 1970,
        month = tonumber(dateInfo.month) or 1,
        day = tonumber(dateInfo.day) or 1,
        hour = tonumber(dateInfo.hour) or 0,
        min = tonumber(dateInfo.minute) or 0,
        sec = 0,
    })
    if ok then
        return timestamp
    end

    return nil
end

local function formatMythicPlusDuration(durationSec)
    local totalSeconds = math.max(0, math.floor(tonumber(durationSec) or 0))
    local hours = math.floor(totalSeconds / 3600)
    local minutes = math.floor((totalSeconds % 3600) / 60)
    local seconds = totalSeconds % 60

    if hours > 0 then
        return string.format("%d:%02d:%02d", hours, minutes, seconds)
    end

    return string.format("%d:%02d", minutes, seconds)
end

local function formatMythicPlusDelta(durationSec, timeLimitSec)
    local runTime = tonumber(durationSec)
    local timeLimit = tonumber(timeLimitSec)
    if not runTime or runTime <= 0 or not timeLimit or timeLimit <= 0 then
        return nil
    end

    local delta = runTime - timeLimit
    local prefix = delta <= 0 and "-" or "+"
    return prefix .. formatMythicPlusDuration(math.abs(delta))
end

local function formatMythicPlusDate(dateInfo)
    local timestamp = buildMythicPlusTimestamp(dateInfo)
    if not timestamp then
        return nil
    end

    return date("%b %d  %H:%M", timestamp)
end

function MythicPlusView.GetScoreColor(score)
    local numericScore = tonumber(score) or 0

    if C_ChallengeMode and type(C_ChallengeMode.GetDungeonScoreRarityColor) == "function" then
        local ok, color = pcall(C_ChallengeMode.GetDungeonScoreRarityColor, numericScore)
        if ok and color then
            return extractColorTriplet(color, THEME.gold or DEFAULT_GOLD)
        end
    end

    if numericScore >= 3000 then
        return 1.00, 0.55, 0.18
    elseif numericScore >= 2500 then
        return 0.73, 0.44, 0.98
    elseif numericScore >= 2000 then
        return 0.24, 0.72, 1.00
    elseif numericScore >= 1500 then
        return 0.34, 0.86, 0.52
    elseif numericScore > 0 then
        return getThemeColor("gold", DEFAULT_GOLD)
    end

    return getThemeColor("parchment", DEFAULT_PARCHMENT)
end

local function getOwnedKeystoneNameFromBags()
    local containerAPI = C_Container
    local getNumSlots = containerAPI and containerAPI.GetContainerNumSlots or GetContainerNumSlots
    local getItemLink = containerAPI and containerAPI.GetContainerItemLink or GetContainerItemLink
    if type(getNumSlots) ~= "function" or type(getItemLink) ~= "function" then
        return nil
    end

    for bag = 0, 4 do
        for slot = 1, tonumber(getNumSlots(bag)) or 0 do
            local link = getItemLink(bag, slot)
            if type(link) == "string" and link:lower():find("keystone", 1, true) then
                local bracketText = link:match("%[(.-)%]")
                if bracketText then
                    local dungeonName = bracketText:gsub("^[Kk]eystone:%s*", "")
                    if dungeonName and dungeonName ~= "" then
                        return dungeonName
                    end
                end
            end
        end
    end

    return nil
end

local function getMythicPlusMapName(mapChallengeModeID)
    if not (C_ChallengeMode and type(C_ChallengeMode.GetMapUIInfo) == "function") then
        return nil
    end

    local ok, name = pcall(C_ChallengeMode.GetMapUIInfo, mapChallengeModeID)
    if ok and type(name) == "string" and name ~= "" then
        return name
    end

    return nil
end

local function getMythicPlusCurrentKeyInfo()
    local keyLevel = 0
    local mapChallengeModeID = 0

    if C_MythicPlus and type(C_MythicPlus.GetOwnedKeystoneLevel) == "function" then
        local ok, value = pcall(C_MythicPlus.GetOwnedKeystoneLevel)
        if ok then
            keyLevel = tonumber(value) or 0
        end
    end

    if C_MythicPlus and type(C_MythicPlus.GetOwnedKeystoneChallengeMapID) == "function" then
        local ok, value = pcall(C_MythicPlus.GetOwnedKeystoneChallengeMapID)
        if ok then
            mapChallengeModeID = tonumber(value) or 0
        end
    end

    if (keyLevel <= 0 or mapChallengeModeID <= 0)
        and C_ChallengeMode
        and type(C_ChallengeMode.GetActiveKeystoneInfo) == "function"
    then
        local ok, activeLevel, activeMapID = pcall(C_ChallengeMode.GetActiveKeystoneInfo)
        if ok then
            keyLevel = math.max(keyLevel, tonumber(activeLevel) or 0)
            mapChallengeModeID = math.max(mapChallengeModeID, tonumber(activeMapID) or 0)
        end
    end

    local mapName = mapChallengeModeID > 0 and getMythicPlusMapName(mapChallengeModeID) or nil
    if not mapName then
        mapName = getOwnedKeystoneNameFromBags()
    end

    return keyLevel, mapChallengeModeID, mapName
end

local function buildMythicPlusRunSummary(label, info, timeLimitSec)
    if type(info) ~= "table" or (tonumber(info.level) or 0) <= 0 then
        return nil
    end

    local statusText
    if tonumber(timeLimitSec) and tonumber(info.durationSec) then
        statusText = tonumber(info.durationSec) <= tonumber(timeLimitSec)
            and (L.MYTHIC_PLUS_TIMED or "Timed")
            or (L.MYTHIC_PLUS_OVERTIME or "Over time")
    end

    local parts = {
        label or "",
        string.format("+%d", tonumber(info.level) or 0),
        formatMythicPlusDuration(info.durationSec),
    }
    local deltaText = formatMythicPlusDelta(info.durationSec, timeLimitSec)
    if deltaText then
        parts[#parts + 1] = deltaText
    end
    if statusText then
        parts[#parts + 1] = statusText
    end

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

local function collectMythicPlusAffixes()
    local entries = {}
    if not (C_MythicPlus and type(C_MythicPlus.GetCurrentAffixes) == "function") then
        return entries
    end

    local ok, affixes = pcall(C_MythicPlus.GetCurrentAffixes)
    if not ok or type(affixes) ~= "table" then
        return entries
    end

    for _, affix in ipairs(affixes) do
        local affixName = affix and affix.name or nil
        local affixDescription
        if affix and affix.id and C_ChallengeMode and type(C_ChallengeMode.GetAffixInfo) == "function" then
            local okInfo, name, description = pcall(C_ChallengeMode.GetAffixInfo, affix.id)
            if okInfo then
                affixName = affixName or name
                affixDescription = description
            end
        end

        if type(affixName) == "string" and affixName ~= "" then
            entries[#entries + 1] = {
                label = affixName,
                status = "open",
                text = L.MYTHIC_PLUS_AFFIX_ACTIVE or "Active",
                tooltipTitle = affixName,
                tooltipLines = affixDescription and { affixDescription } or nil,
            }
        end
    end

    return entries
end

local function buildMythicPlusAffixSplitText(mapChallengeModeID)
    if not (C_MythicPlus and type(C_MythicPlus.GetSeasonBestAffixScoreInfoForMap) == "function") then
        return L.MYTHIC_PLUS_DUNGEON_SPLIT_NONE or "No Fortified or Tyrannical score yet"
    end

    local ok, affixScores = pcall(C_MythicPlus.GetSeasonBestAffixScoreInfoForMap, mapChallengeModeID)
    if not ok or type(affixScores) ~= "table" then
        return L.MYTHIC_PLUS_DUNGEON_SPLIT_NONE or "No Fortified or Tyrannical score yet"
    end

    local parts = {}
    for _, affixScore in ipairs(affixScores) do
        local affixName = affixScore and affixScore.name or nil
        if (not affixName or affixName == "")
            and affixScore
            and affixScore.affixID
            and C_ChallengeMode
            and type(C_ChallengeMode.GetAffixInfo) == "function"
        then
            local okInfo, name = pcall(C_ChallengeMode.GetAffixInfo, affixScore.affixID)
            if okInfo then
                affixName = name
            end
        end

        local scoreValue = affixScore and (
            tonumber(affixScore.score)
            or tonumber(affixScore.dungeonScore)
            or tonumber(affixScore.bestScore)
            or tonumber(affixScore.bestOverAllScore)
        ) or 0
        local levelValue = affixScore and (
            tonumber(affixScore.level)
            or tonumber(affixScore.bestLevel)
            or tonumber(affixScore.bestRunLevel)
        ) or 0

        if type(affixName) == "string" and affixName ~= "" then
            if levelValue > 0 then
                parts[#parts + 1] = string.format("%s +%d", affixName, levelValue)
            elseif scoreValue > 0 then
                parts[#parts + 1] = string.format("%s %d", affixName, math.floor(scoreValue + 0.5))
            end
        end
    end

    if #parts == 0 then
        return L.MYTHIC_PLUS_DUNGEON_SPLIT_NONE or "No Fortified or Tyrannical score yet"
    end

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

function MythicPlusView.BuildSeasonOneView(character)
    if not character or not (ns.IsCurrentCharacterKey and ns:IsCurrentCharacterKey(character.key)) then
        local storedSnapshot = ns.GetStoredMythicPlusSnapshot and ns:GetStoredMythicPlusSnapshot(character) or nil
        if storedSnapshot then
            return storedSnapshot
        end

        return {
            available = false,
            liveOnly = true,
            message = L.MYTHIC_PLUS_NO_SNAPSHOT or "No saved Mythic+ snapshot yet for this character. Log onto the character once to capture it.",
        }
    end

    if type(C_ChallengeMode) ~= "table"
        or type(C_MythicPlus) ~= "table"
        or type(C_ChallengeMode.GetMapTable) ~= "function"
        or type(C_ChallengeMode.GetMapUIInfo) ~= "function"
    then
        return {
            available = false,
            message = L.MYTHIC_PLUS_UNAVAILABLE or "Mythic+ data is not available yet.",
        }
    end

    requestCurrentSeasonMapInfo()
    if type(C_MythicPlus.RequestCurrentAffixes) == "function" then
        pcall(C_MythicPlus.RequestCurrentAffixes)
    end
    if type(C_MythicPlus.RequestRewards) == "function" then
        pcall(C_MythicPlus.RequestRewards)
    end

    local seasonalMapIDs = C_ChallengeMode.GetMapTable()
    if type(seasonalMapIDs) ~= "table" or #seasonalMapIDs == 0 then
        return {
            available = false,
            message = L.MYTHIC_PLUS_UNAVAILABLE or "Mythic+ data is not available yet.",
        }
    end

    local overallScore = 0
    if type(C_ChallengeMode.GetOverallDungeonScore) == "function" then
        local ok, score = pcall(C_ChallengeMode.GetOverallDungeonScore)
        if ok then
            overallScore = tonumber(score) or 0
        end
    end
    if overallScore <= 0
        and C_PlayerInfo
        and type(C_PlayerInfo.GetPlayerMythicPlusRatingSummary) == "function"
    then
        local ok, summary = pcall(C_PlayerInfo.GetPlayerMythicPlusRatingSummary, "player")
        if ok and type(summary) == "table" then
            overallScore = tonumber(summary.currentSeasonScore) or overallScore
        end
    end

    local keyLevel, keyMapID, keyName = getMythicPlusCurrentKeyInfo()
    local currentWeekBestLevel = 0
    local weeklyRewardLevel = 0
    if type(C_MythicPlus.GetWeeklyChestRewardLevel) == "function" then
        local ok, bestLevel, rewardLevel = pcall(C_MythicPlus.GetWeeklyChestRewardLevel)
        if ok then
            currentWeekBestLevel = tonumber(bestLevel) or 0
            weeklyRewardLevel = tonumber(rewardLevel) or 0
        end
    end

    local dungeons = {}
    local timedCount = 0
    local portalCount = 0
    for _, mapChallengeModeID in ipairs(seasonalMapIDs) do
        local okInfo, dungeonName, _, timeLimitSec, texture, backgroundTexture = pcall(C_ChallengeMode.GetMapUIInfo, mapChallengeModeID)
        if okInfo and type(dungeonName) == "string" and dungeonName ~= "" then
            local intimeInfo
            local overtimeInfo
            if type(C_MythicPlus.GetSeasonBestForMap) == "function" then
                local okBest, intime, overtime = pcall(C_MythicPlus.GetSeasonBestForMap, mapChallengeModeID)
                if okBest then
                    intimeInfo = intime
                    overtimeInfo = overtime
                end
            end

            local weeklyInfo
            if type(C_MythicPlus.GetWeeklyBestForMap) == "function" then
                local okWeekly, durationSec, level, completionDate, affixIDs, members, dungeonScore =
                    pcall(C_MythicPlus.GetWeeklyBestForMap, mapChallengeModeID)
                if okWeekly and (tonumber(level) or 0) > 0 then
                    weeklyInfo = {
                        durationSec = tonumber(durationSec) or 0,
                        level = tonumber(level) or 0,
                        completionDate = completionDate,
                        affixIDs = affixIDs,
                        members = members,
                        dungeonScore = tonumber(dungeonScore) or 0,
                    }
                end
            end

            local bestInfo
            local bestStatus
            if type(intimeInfo) == "table" and type(overtimeInfo) == "table" then
                local intimeScore = tonumber(intimeInfo.dungeonScore) or 0
                local overtimeScore = tonumber(overtimeInfo.dungeonScore) or 0
                if intimeScore >= overtimeScore then
                    bestInfo = intimeInfo
                    bestStatus = "timed"
                else
                    bestInfo = overtimeInfo
                    bestStatus = "overtime"
                end
            elseif type(intimeInfo) == "table" then
                bestInfo = intimeInfo
                bestStatus = "timed"
            elseif type(overtimeInfo) == "table" then
                bestInfo = overtimeInfo
                bestStatus = "overtime"
            end

            if type(intimeInfo) == "table" and (tonumber(intimeInfo.level) or 0) > 0 then
                timedCount = timedCount + 1
                if (tonumber(intimeInfo.level) or 0) >= 10 then
                    portalCount = portalCount + 1
                end
            end

            local bestText = buildMythicPlusRunSummary(L.MYTHIC_PLUS_BEST or "Best", bestInfo, timeLimitSec)
                or (L.MYTHIC_PLUS_DUNGEON_BEST_NONE or "No season best yet")
            local weeklyText = buildMythicPlusRunSummary(L.MYTHIC_PLUS_THIS_WEEK or "This week", weeklyInfo, timeLimitSec)
                or (L.MYTHIC_PLUS_DUNGEON_WEEKLY_NONE or "No weekly run yet")
            local splitText = buildMythicPlusAffixSplitText(mapChallengeModeID)

            local tooltipLines = {
                bestText,
                weeklyText,
                splitText,
            }
            local bestDateText = bestInfo and formatMythicPlusDate(bestInfo.completionDate) or nil
            if bestDateText then
                tooltipLines[#tooltipLines + 1] = string.format("%s  %s", L.MYTHIC_PLUS_BEST or "Best", bestDateText)
            end
            local weeklyDateText = weeklyInfo and formatMythicPlusDate(weeklyInfo.completionDate) or nil
            if weeklyDateText then
                tooltipLines[#tooltipLines + 1] = string.format("%s  %s", L.MYTHIC_PLUS_THIS_WEEK or "This week", weeklyDateText)
            end

            dungeons[#dungeons + 1] = {
                mapChallengeModeID = tonumber(mapChallengeModeID) or 0,
                name = dungeonName,
                texture = texture or backgroundTexture,
                timeLimitSec = tonumber(timeLimitSec) or 0,
                best = bestInfo,
                weekly = weeklyInfo,
                bestStatus = bestStatus,
                score = tonumber(bestInfo and bestInfo.dungeonScore) or tonumber(weeklyInfo and weeklyInfo.dungeonScore) or 0,
                bestText = bestText,
                weeklyText = weeklyText,
                splitText = splitText,
                tooltipTitle = dungeonName,
                tooltipLines = tooltipLines,
            }
        end
    end

    table.sort(dungeons, function(a, b)
        local aScore = tonumber(a and a.score) or 0
        local bScore = tonumber(b and b.score) or 0
        if (aScore == 0) ~= (bScore == 0) then
            return aScore == 0
        end
        if aScore ~= bScore then
            return aScore < bScore
        end
        return normalizeMythicPlusKey(a and a.name or "") < normalizeMythicPlusKey(b and b.name or "")
    end)

    local affixEntries = collectMythicPlusAffixes()
    local rewardEntries = {
        {
            label = L.MYTHIC_PLUS_REWARD_EXPLORER or "Explorer",
            text = string.format("%d/1", timedCount > 0 and 1 or 0),
            status = timedCount > 0 and "complete" or (overallScore > 0 and "turnin" or "missing"),
            tooltipTitle = L.MYTHIC_PLUS_REWARD_EXPLORER or "Explorer",
            tooltipLines = { string.format("%d/1", timedCount > 0 and 1 or 0) },
        },
        {
            label = L.MYTHIC_PLUS_REWARD_CONQUEROR or "Conqueror",
            text = "1500+",
            status = overallScore >= 1500 and "complete" or ((overallScore >= 1200) and "turnin" or "missing"),
            tooltipTitle = L.MYTHIC_PLUS_REWARD_CONQUEROR or "Conqueror",
            tooltipLines = { "1500+" },
        },
        {
            label = L.MYTHIC_PLUS_REWARD_MASTER or "Master",
            text = "2000+",
            status = overallScore >= 2000 and "complete" or ((overallScore >= 1700) and "turnin" or "missing"),
            tooltipTitle = L.MYTHIC_PLUS_REWARD_MASTER or "Master",
            tooltipLines = { "2000+" },
        },
        {
            label = L.MYTHIC_PLUS_REWARD_HERO or "Hero",
            text = "2500+",
            status = overallScore >= 2500 and "complete" or ((overallScore >= 2200) and "turnin" or "missing"),
            tooltipTitle = L.MYTHIC_PLUS_REWARD_HERO or "Hero",
            tooltipLines = { "2500+" },
        },
        {
            label = L.MYTHIC_PLUS_REWARD_LEGEND or "Legend",
            text = "3000+",
            status = overallScore >= 3000 and "complete" or ((overallScore >= 2700) and "turnin" or "missing"),
            tooltipTitle = L.MYTHIC_PLUS_REWARD_LEGEND or "Legend",
            tooltipLines = { "3000+" },
        },
        {
            label = L.MYTHIC_PLUS_REWARD_UMBRAL_HERO or "Umbral Hero",
            text = "0.1%",
            status = overallScore > 0 and "open" or "missing",
            tooltipTitle = L.MYTHIC_PLUS_REWARD_UMBRAL_HERO or "Umbral Hero",
            tooltipLines = { "0.1%" },
        },
    }

    local recentRuns = {}
    if type(C_MythicPlus.GetRunHistory) == "function" then
        local ok, history = pcall(C_MythicPlus.GetRunHistory, true, false)
        if ok and type(history) == "table" then
            for _, run in ipairs(history) do
                if #recentRuns >= 6 then
                    break
                end

                if type(run) == "table" and (tonumber(run.level) or 0) > 0 then
                    local runMapName = getMythicPlusMapName(run.mapChallengeModeID) or (L.UNKNOWN or "Unknown")
                    local runParts = {
                        run.completed and (L.MYTHIC_PLUS_TIMED or "Timed") or (L.MYTHIC_PLUS_OVERTIME or "Over time"),
                    }
                    if (tonumber(run.runScore) or 0) > 0 then
                        runParts[#runParts + 1] = tostring(math.floor((tonumber(run.runScore) or 0) + 0.5))
                    end
                    if run.thisWeek then
                        runParts[#runParts + 1] = L.MYTHIC_PLUS_THIS_WEEK or "This week"
                    end

                    recentRuns[#recentRuns + 1] = {
                        label = string.format("%s  +%d", runMapName, tonumber(run.level) or 0),
                        text = table.concat(runParts, "  |  "),
                        status = run.completed and "complete" or "turnin",
                        tooltipTitle = runMapName,
                        tooltipLines = {
                            string.format("+%d", tonumber(run.level) or 0),
                            run.thisWeek and (L.MYTHIC_PLUS_THIS_WEEK or "This week") or nil,
                        },
                    }
                end
            end
        end
    end

    local view = {
        available = true,
        updatedAt = time(),
        currentWeekBestLevel = currentWeekBestLevel,
        weeklyRewardLevel = weeklyRewardLevel,
        currentKey = {
            level = keyLevel,
            mapChallengeModeID = keyMapID,
            name = keyName,
        },
        timedCount = timedCount,
        portalCount = portalCount,
        dungeonCount = #dungeons,
        summary = {
            score = {
                label = L.MYTHIC_PLUS_SUMMARY_SCORE or "Season Score",
                value = overallScore > 0 and tostring(math.floor(overallScore + 0.5)) or "0",
                meta = string.format("%d/%d %s", timedCount, #dungeons, L.MYTHIC_PLUS_TIMED_META or "Timed dungeons"),
                score = overallScore,
            },
            key = {
                label = L.MYTHIC_PLUS_SUMMARY_KEY or "Current Key",
                value = keyLevel > 0 and string.format("+%d", keyLevel) or (L.MYTHIC_PLUS_KEY_NONE or "No keystone"),
                meta = keyName or "",
            },
            vault = {
                label = L.MYTHIC_PLUS_SUMMARY_VAULT or "Great Vault",
                value = weeklyRewardLevel > 0 and tostring(weeklyRewardLevel) or (L.MYTHIC_PLUS_VAULT_NONE or "No run yet"),
                meta = currentWeekBestLevel > 0
                    and string.format("%s  +%d", L.MYTHIC_PLUS_THIS_WEEK or "This week", currentWeekBestLevel)
                    or (L.MYTHIC_PLUS_VAULT_NONE or "No run yet"),
            },
            portals = {
                label = L.MYTHIC_PLUS_SUMMARY_PORTALS or "Portals",
                value = string.format("%d/%d", portalCount, #dungeons),
                meta = L.MYTHIC_PLUS_PORTALS_META or "Timed +10 or higher",
            },
        },
        dungeons = dungeons,
        affixes = affixEntries,
        rewards = rewardEntries,
        recentRuns = recentRuns,
    }

    if ns.RememberMythicPlusSnapshot then
        ns:RememberMythicPlusSnapshot(view, character)
    end
    return view
end
