local _, ns = ...

local L = ns.L or {}

local PriorityScore = {}
ns.PriorityScore = PriorityScore

local CACHE_SECONDS = 8

local TIERS = {
    { min = 80, key = "orange", color = { 1.00, 0.50, 0.00 }, hex = "ffff8000" },
    { min = 60, key = "purple", color = { 0.64, 0.21, 0.93 }, hex = "ffa335ee" },
    { min = 40, key = "blue", color = { 0.00, 0.44, 0.87 }, hex = "ff0070dd" },
    { min = 20, key = "green", color = { 0.12, 1.00, 0.00 }, hex = "ff1eff00" },
    { min = 0, key = "gray", color = { 0.62, 0.62, 0.62 }, hex = "ff9d9d9d" },
}

local SCORE_CACHE = {}

local WORLD_SCORE_KEYS = {
    world_boss_weekly = true,
    world_key_shards = true,
    world_dundun_shards = true,
}

local function getNow()
    return type(time) == "function" and time() or 0
end

local function clamp(value, minimum, maximum)
    value = tonumber(value) or 0
    if value < minimum then
        return minimum
    end
    if value > maximum then
        return maximum
    end
    return value
end

local function safeBuild(builder, ...)
    if type(builder) ~= "function" then
        return nil
    end

    local ok, result = pcall(builder, ...)
    if ok and type(result) == "table" then
        return result
    end
    return nil
end

local function getTier(score)
    score = tonumber(score) or 0
    for _, tier in ipairs(TIERS) do
        if score >= tier.min then
            return tier
        end
    end
    return TIERS[#TIERS]
end

local function isEntryAvailable(entry)
    if type(entry) ~= "table" then
        return false
    end

    local status = tostring(entry.status or "")
    if entry.available == false or entry.unlocked == false then
        return false
    end
    return status ~= "locked" and status ~= "unavailable" and status ~= "disabled"
end

local function isEntryComplete(entry)
    if type(entry) ~= "table" then
        return false
    end

    local status = tostring(entry.status or "")
    if entry.completed == true or status == "complete" or status == "completed" or status == "done" then
        return true
    end

    local count = tonumber(entry.count or entry.current or entry.progress)
    local maxCount = tonumber(entry.maxCount or entry.cap or entry.total)
    return maxCount and maxCount > 0 and count and count >= maxCount
end

local function addComponent(result, key, amount, label, value)
    amount = tonumber(amount) or 0
    if amount <= 0 then
        return
    end

    result.rawScore = result.rawScore + amount
    result.details[#result.details + 1] = {
        key = key,
        label = label,
        value = value,
        amount = amount,
    }
end

local function addAvailableWeight(result, weight)
    result.maxScore = (tonumber(result.maxScore) or 0) + math.max(0, tonumber(weight) or 0)
end

local function addRowsCompletion(result, key, rows, weight, label)
    local total = 0
    local completed = 0

    for _, row in ipairs(type(rows) == "table" and rows or {}) do
        local entry = row and row.entry
        if isEntryAvailable(entry) then
            total = total + 1
            if isEntryComplete(entry) then
                completed = completed + 1
            end
        end
    end

    if total > 0 then
        addAvailableWeight(result, weight)
        if completed > 0 then
            addComponent(result, key, weight * (completed / total), label, string.format("%d/%d", completed, total))
        end
    end
end

local function getRowActivityKey(row)
    local entry = row and row.entry
    return tostring((entry and entry.key) or row.key or row.id or "")
end

local function addFilteredRowsCompletion(result, key, rows, weight, label, predicate)
    local filteredRows = {}
    for _, row in ipairs(type(rows) == "table" and rows or {}) do
        if type(predicate) ~= "function" or predicate(row, getRowActivityKey(row)) then
            filteredRows[#filteredRows + 1] = row
        end
    end

    addRowsCompletion(result, key, filteredRows, weight, label)
end

local function addVaultComponent(result, snapshot)
    local rows = type(snapshot) == "table" and type(snapshot.rows) == "table" and snapshot.rows or nil
    if not rows then
        return
    end

    local total = 0
    local completedRatio = 0

    for _, row in pairs(rows) do
        local maxThreshold = tonumber(row and row.goal and row.goal.maxThreshold)
            or tonumber(row and row.thresholds and row.thresholds[#row.thresholds])
            or 0
        if maxThreshold > 0 then
            local progress = tonumber(row and row.goal and row.goal.displayCount)
                or tonumber(row and row.completedCount)
                or 0
            total = total + 1
            completedRatio = completedRatio + clamp(progress / maxThreshold, 0, 1)
        end
    end

    if total > 0 then
        addAvailableWeight(result, 18)
        if completedRatio > 0 then
            addComponent(
                result,
                "vault",
                18 * (completedRatio / total),
                L.PRIORITY_SCORE_VAULT or "Great Vault",
                string.format("%d%%", math.floor((completedRatio / total) * 100 + 0.5))
            )
        end
    end
end

local function addPreyComponent(result, view)
    if type(view) ~= "table" then
        return
    end

    local total = 0
    local completed = 0

    if isEntryAvailable(view.weeklyQuest) then
        total = total + 1
        if isEntryComplete(view.weeklyQuest) then
            completed = completed + 1
        end
    end

    for _, key in ipairs({ "normal", "hard", "nightmare" }) do
        local difficulty = view.difficulties and view.difficulties[key]
        local entry = difficulty and difficulty.entry
        if isEntryAvailable(entry) then
            local cap = math.max(1, tonumber(entry.cap or view.difficultyCap) or 1)
            local count = clamp(tonumber(entry.count) or 0, 0, cap)
            total = total + cap
            completed = completed + count
        end
    end

    if total > 0 then
        addAvailableWeight(result, 8)
        if completed > 0 then
            addComponent(
                result,
                "prey",
                8 * (completed / total),
                L.PRIORITY_SCORE_PREY or "Prey",
                string.format("%d/%d", completed, total)
            )
        end
    end
end

local function buildScore(character)
    local result = {
        rawScore = 0,
        maxScore = 0,
        details = {},
    }
    local snapshot = ns.GetLiveVaultSnapshot and ns:GetLiveVaultSnapshot(character) or nil
    local pve = ns.UIPveView or {}
    local weeklyView = safeBuild(pve.BuildWeeklyListView, character, snapshot)
    local eventView = safeBuild(pve.BuildEventsListView, character, snapshot)
    local worldView = safeBuild(pve.BuildWorldListView, character, snapshot)
    local delveView = safeBuild(pve.BuildDelvesListView, character, snapshot)
    local preyView = safeBuild(pve.BuildPreyView, character, snapshot)
    local pvpView = safeBuild(pve.BuildPvpWeeklyListView, character, snapshot)
    local professionView = safeBuild(pve.BuildProfessionWeeklyListView, character, snapshot)

    addVaultComponent(result, snapshot)
    addRowsCompletion(result, "weekly", weeklyView and weeklyView.rows, 10, L.PRIORITY_SCORE_WEEKLY or "Weeklies")
    addRowsCompletion(result, "events", eventView and eventView.rows, 8, L.PRIORITY_SCORE_EVENTS or "Events")
    addFilteredRowsCompletion(result, "world", worldView and worldView.rows, 8, L.PRIORITY_SCORE_WORLD or "World", function(_, rowKey)
        return WORLD_SCORE_KEYS[rowKey] == true
    end)
    addRowsCompletion(result, "delves", delveView and delveView.rows, 10, L.PRIORITY_SCORE_DELVES or "Delves")
    addPreyComponent(result, preyView)
    addRowsCompletion(result, "pvp", pvpView and pvpView.rows, 5, L.PRIORITY_SCORE_PVP or "PvP")
    addFilteredRowsCompletion(result, "professions", professionView and professionView.rows, 7, L.PRIORITY_SCORE_PROFESSIONS or "Professions", function(_, rowKey)
        return rowKey:find("^profession_service_") ~= nil
    end)

    local score = 0
    if result.maxScore > 0 then
        score = math.floor(clamp((result.rawScore / result.maxScore) * 100, 0, 100) + 0.5)
    end
    local tier = getTier(score)
    local label = string.format(L.PRIORITY_SCORE_FORMAT or "AS %d", score)

    result.score = score
    result.tier = tier.key
    result.color = tier.color
    result.hex = tier.hex
    result.label = label
    result.coloredLabel = string.format("|c%s%s|r", tier.hex, label)
    return result
end

function ns:GetAltPriorityScore(character)
    if type(character) ~= "table" then
        return nil
    end

    local key = character.key or character.name
    local now = getNow()
    if key and SCORE_CACHE[key] and (now - (SCORE_CACHE[key].at or 0)) < CACHE_SECONDS then
        return SCORE_CACHE[key].value
    end

    local score = buildScore(character)
    if key then
        SCORE_CACHE[key] = {
            at = now,
            value = score,
        }
    end
    return score
end

function ns:ClearAltPriorityScoreCache(characterKey)
    if characterKey then
        SCORE_CACHE[characterKey] = nil
        return
    end
    if type(wipe) == "function" then
        wipe(SCORE_CACHE)
    else
        SCORE_CACHE = {}
    end
end
