local ADDON_NAME, ns = ...
ns = ns or {}

local Meta = {}
ns.Meta = Meta

if WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE then
    function Meta:Init() end
    function Meta:GetMetas() return {} end
    function Meta:GetMetaDetail() return {} end
    function Meta:IsExpanded() return false end
    function Meta:ToggleExpansion() end
    return
end

local Util = ns.Util
local Const = ns.Const

local ACHIEVEMENT_CRITERIA_TYPE_ACHIEVEMENT = _G.ACHIEVEMENT_CRITERIA_TYPE_ACHIEVEMENT or 8

local META_CHILD_OVERRIDES = {
    [62386] = {
        { id = 62261, name = "Forever Song" },
        { id = 61453, name = "Making an Amani Out of You" },
        { id = 62260, name = "That's Aln, Folks!" },
        { id = 62256, name = "Yelling into the Voidstorm" },
    },
}

local META_DEFS = {
    { exp = "Midnight",       id = 62386, name = "Light Up the Night", forceName = true },
    { exp = "The War Within", id = 61451, name = "Worldsoul-Searching" },
    { exp = "Dragonflight",   id = 19458, name = "A World Awoken" },
    { exp = "Shadowlands",    id = 20501, name = "Back from the Beyond" },
    { exp = "Battle for Azeroth", id = 40953, name = "A Farewell to Arms" },
    { exp = "Legion",         id = 11446, name = "Broken Isles Pathfinder", forceName = true },
    { exp = "Warlords of Draenor", id = 10018, name = "Draenor Pathfinder" },
}

local function primeAchievement(achID)
    if not achID then return end
    if C_AchievementInfo and C_AchievementInfo.RequestAchievementInfo then
        C_AchievementInfo.RequestAchievementInfo(achID)
    end
    if C_AchievementInfo and C_AchievementInfo.GetAchievementInfo then
        C_AchievementInfo.GetAchievementInfo(achID)
    end
    local num = GetAchievementNumCriteria and GetAchievementNumCriteria(achID) or 0
    for i = 1, num do
        if GetAchievementCriteriaInfo then
            GetAchievementCriteriaInfo(achID, i)
        end
    end
end

local function completionPercent(achID)
    local num = GetAchievementNumCriteria(achID)
    if not num or num == 0 then return 0 end
    local done = 0
    for i = 1, num do
        local _, _, completed, qty, req = GetAchievementCriteriaInfo(achID, i)
        if completed then
            done = done + 1
        elseif qty and req and req > 0 then
            done = done + (qty / req)
        end
    end
    return (done / num) * 100
end

local function basicInfo(achID)
    if not achID then
        return {
            id = nil,
            name = "",
            icon = 134400,
            completed = false,
            percent = 0,
            points = 0,
        }
    end
    primeAchievement(achID)
    local _, name, points, completed, _, _, _, _, _, icon = GetAchievementInfo(achID)
    return {
        id = achID,
        name = name or ("[" .. tostring(achID) .. "]"),
        icon = icon or 134400,
        completed = completed == true,
        percent = completionPercent(achID),
        points = points or 0,
    }
end

local function gatherCriteria(achID)
    local override = META_CHILD_OVERRIDES[achID]
    if override then
        local list = {}
        for _, entry in ipairs(override) do
            table.insert(list, {
                name = entry.name,
                type = ACHIEVEMENT_CRITERIA_TYPE_ACHIEVEMENT,
                completed = false,
                qty = nil,
                req = nil,
                assetID = entry.id,
            })
        end
        return list
    end
    local list = {}
    local num = GetAchievementNumCriteria(achID) or 0
    for i = 1, num do
        local critName, critType, completed, qty, req, _, _, assetID = GetAchievementCriteriaInfo(achID, i)
        table.insert(list, {
            name = critName or ("Criteria " .. i),
            type = critType,
            completed = completed == true,
            qty = qty,
            req = req,
            assetID = assetID,
        })
    end
    return list
end

local function buildNode(achID, visited)
    if not achID or (visited and visited[achID]) then return nil end
    visited = visited or {}
    visited[achID] = true
    local info = basicInfo(achID)
    info.children = {}
    info.requirements = {}

    local criteria = gatherCriteria(achID)
    for _, crit in ipairs(criteria) do
        if crit.type == ACHIEVEMENT_CRITERIA_TYPE_ACHIEVEMENT and crit.assetID and crit.assetID > 0 then
            local child = buildNode(crit.assetID, visited)
            if child then table.insert(info.children, child) end
        else
            table.insert(info.requirements, crit)
        end
    end

    visited[achID] = nil
    return info
end

local function collectLeafAchievements(node, out)
    if not node then return end
    if node.children and #node.children > 0 then
        for _, child in ipairs(node.children) do
            collectLeafAchievements(child, out)
        end
        return
    end
    table.insert(out, node)
end

local function averageSecondLevelPercent(node)
    if not node or not node.children or #node.children == 0 then return nil end
    local total, count = 0, 0
    for _, child in ipairs(node.children) do
        if type(child.percent) == "number" then
            total = total + child.percent
            count = count + 1
        end
    end
    if count == 0 then return nil end
    return total / count
end

local function sortMetas(list)
    Util:Sort(list, function(a, b)
        local ap, bp = a.percent or 0, b.percent or 0
        if ap == bp then
            return (a.name or ""):lower() < (b.name or ""):lower()
        end
        return ap > bp
    end)
end

local function sortDetail(list)
    Util:Sort(list, function(a, b)
        if a.completed ~= b.completed then
            return not a.completed and b.completed
        end
        if not a.completed then
            local ap, bp = a.percent or 0, b.percent or 0
            if ap == bp then
                return (a.name or ""):lower() < (b.name or ""):lower()
            end
            return ap > bp
        end
        return (a.name or ""):lower() < (b.name or ""):lower()
    end)
end

Meta.state = {
    expanded = {},
    detailFilter = "All",
    metas = nil,
    detailCache = {},
}

function Meta:Invalidate()
    if self.state then
        self.state.metas = nil
        self.state.detailCache = {}
    end
end

function Meta:Init(events)
    if events then
        events:On("ACHIEVEMENT_EARNED", function() Meta:Invalidate() end)
        events:On("CRITERIA_UPDATE", function() Meta:Invalidate() end)
    end
end

function Meta:IsExpanded(key)
    return self.state and self.state.expanded and self.state.expanded[key]
end

function Meta:ToggleExpansion(key)
    self.state = self.state or { expanded = {} }
    self.state.expanded[key] = not not (not self.state.expanded[key])
end

function Meta:GetMetas()
    if self.state and self.state.metas then
        return self.state.metas
    end
    if UIParentLoadAddOn then
        UIParentLoadAddOn("Blizzard_AchievementUI")
    end
    local expansions = {}
    for _, exp in ipairs(Const.EXPANSION_LIST or {}) do
        expansions[exp] = {}
    end

    for _, def in ipairs(META_DEFS) do
        local node
        if def.id then
            node = buildNode(def.id) or basicInfo(def.id)
        else
            node = {
                id = nil,
                name = def.name or "Coming Soon",
                icon = def.icon or "Interface\\Icons\\Spell_Arcane_Arcane04",
                percent = 0,
                completed = false,
                points = 0,
                requirements = {},
            }
        end
        node = node or { id = def.id, name = "[" .. tostring(def.id) .. "]", icon = 134400, percent = 0, completed = false, points = 0, requirements = {} }
        if def.name and (def.forceName or not node.name or node.name == "" or node.name:find("^%[")) then
            node.name = def.name
        end
        node.exp = def.exp
        node.key = "meta:" .. tostring(def.id or def.name or def.exp)
        node.avg_percent = averageSecondLevelPercent(node) or node.percent
        expansions[def.exp] = expansions[def.exp] or {}
        table.insert(expansions[def.exp], node)
    end

    for _, list in pairs(expansions) do
        sortMetas(list)
    end

    self.state.metas = expansions
    return expansions
end

function Meta:SetDetailFilter(filter)
    self.state = self.state or {}
    self.state.detailFilter = filter or "All"
end

function Meta:GetDetailFilter()
    return (self.state and self.state.detailFilter) or "All"
end

function Meta:GetMetaDetail(metaID)
    if not metaID then return {} end
    self.state = self.state or { detailCache = {} }
    if self.state.detailCache[metaID] then
        return self.state.detailCache[metaID]
    end

    local root = buildNode(metaID) or basicInfo(metaID)
    if not root then
        self.state.detailCache[metaID] = {}
        return {}
    end

    local leaves = {}
    if root.children and #root.children > 0 then
        collectLeafAchievements(root, leaves)
    else
        table.insert(leaves, root)
    end

    sortDetail(leaves)
    self.state.detailCache[metaID] = leaves
    return leaves
end
