local ADDON_NAME, ns = ...
ns = ns or {}
local Const = ns.Const
local Util = ns.Util
local Filters = ns.Filters
local Meta = ns.Meta

local Scan = {}
ns.Scan = Scan

local C_Timer = C_Timer
local floor = math.floor
local GetAchievementInfo = GetAchievementInfo
local GetAchievementNumCriteria = GetAchievementNumCriteria
local GetAchievementCriteriaInfo = GetAchievementCriteriaInfo
local GetCategoryList = GetCategoryList
local GetCategoryNumAchievements = GetCategoryNumAchievements

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 comparator(mode)
    if mode == "Percent Asc." then
        return function(a, b)
            if a.percent == b.percent then
                return (a.name or ""):lower() < (b.name or ""):lower()
            end
            return a.percent < b.percent
        end
    elseif mode == "Name Asc." then
        return function(a, b) return (a.name or ""):lower() < (b.name or ""):lower() end
    elseif mode == "Name Desc." then
        return function(a, b) return (a.name or ""):lower() > (b.name or ""):lower() end
    elseif mode == "Points Desc." then
        return function(a, b)
            if (a.points or 0) == (b.points or 0) then
                return a.percent > b.percent
            end
            return (a.points or 0) > (b.points or 0)
        end
    elseif mode == "Points Asc." then
        return function(a, b)
            if (a.points or 0) == (b.points or 0) then
                return a.percent > b.percent
            end
            return (a.points or 0) < (b.points or 0)
        end
    end
    return function(a, b)
        if a.percent == b.percent then
            return (a.name or ""):lower() < (b.name or ""):lower()
        end
        return a.percent > b.percent
    end
end

Scan.active = false
Scan.results = {}
Scan.raw = {}

function Scan:Start(onComplete, onProgress)
    if self.active then return end
    Filters:BuildMaps()
    self.active = true
    wipe(self.results)
    wipe(self.raw)

    local cfg = ACAU_Config or Const.DEFAULTS
    local threshold = cfg.threshold or Const.DEFAULTS.threshold
    local categories = GetCategoryList() or {}
    local totalCategories = #categories
    local currentCat, currentAch = 1, 1
    local scanned, totalToScan = 0, 0

    local batch = Const.SPEED_PRESETS.Smooth.batch
    local delay = Const.SPEED_PRESETS.Smooth.delay

    if cfg.parseSpeed == "Fast" or cfg.parseSpeed == "Smooth" or cfg.parseSpeed == "Slow" then
        local preset = Const.SPEED_PRESETS[cfg.parseSpeed]
        batch, delay = preset.batch, preset.delay
    else
        local v = threshold
        local preset = (v <= 50 and Const.SPEED_PRESETS.Slow) or (v <= 90 and Const.SPEED_PRESETS.Smooth) or Const.SPEED_PRESETS.Fast
        batch, delay = preset.batch, preset.delay
    end

    for ci = 1, totalCategories do
        totalToScan = totalToScan + (GetCategoryNumAchievements(categories[ci]) or 0)
    end

    local function step()
        local processed = 0
        while currentCat <= totalCategories and processed < batch do
            local catID = categories[currentCat]
            local numAch = GetCategoryNumAchievements(catID) or 0
            if currentAch <= numAch then
                local achID = select(1, GetAchievementInfo(catID, currentAch))
                if achID then
                    local _, name, points, completed, _, _, _, _, _, icon, rewardText = GetAchievementInfo(achID)
                    if not rewardText then
                        local all = { GetAchievementInfo(achID) }
                        rewardText = all[11] or all[12] or all[13] or ""
                    end
                    if not completed then
                        local percent = completionPercent(achID)
                        table.insert(self.raw, {
                            id = achID,
                            name = name or ("[" .. tostring(achID) .. "]"),
                            percent = percent,
                            category = catID,
                            icon = icon,
                            reward = rewardText,
                            points = points or 0,
                        })
                    end
                end
                currentAch = currentAch + 1
                scanned = scanned + 1
                processed = processed + 1
                if onProgress then onProgress(scanned, totalToScan) end
            else
                currentCat = currentCat + 1
                currentAch = 1
            end
        end

        if currentCat > totalCategories then
            self.active = false
            local function appendMetaLeaves()
                if not (Meta and Meta.GetMetas and Meta.GetMetaDetail) then return end
                if not Util:IsRetail() then return end
                local seen = {}
                for _, row in ipairs(self.raw or {}) do
                    if row and row.id then
                        seen[row.id] = true
                    end
                end
                local metas = Meta:GetMetas() or {}
                for _, list in pairs(metas) do
                    for _, meta in ipairs(list) do
                        if meta and meta.id then
                            local leaves = Meta:GetMetaDetail(meta.id) or {}
                            for _, leaf in ipairs(leaves) do
                                local achID = leaf and leaf.id
                                if achID and not seen[achID] then
                                    local _, name, points, completed, _, _, _, _, _, icon, rewardText = GetAchievementInfo(achID)
                                    if not completed then
                                        if not rewardText then
                                            local all = { GetAchievementInfo(achID) }
                                            rewardText = all[11] or all[12] or all[13] or ""
                                        end
                                        table.insert(self.raw, {
                                            id = achID,
                                            name = name or ("[" .. tostring(achID) .. "]"),
                                            percent = leaf.percent or completionPercent(achID),
                                            category = leaf.category,
                                            icon = icon,
                                            reward = rewardText,
                                            points = points or 0,
                                        })
                                        seen[achID] = true
                                    end
                                end
                            end
                        end
                    end
                end
            end
            appendMetaLeaves()
            if ACAU_Cache then
                ACAU_Cache.raw = Util:DeepCopy(self.raw)
                ACAU_Cache.build = Filters.buildHash
                ACAU_Cache.saved = time()
            end
            local filtered = self:ApplyFilters()
            if onComplete then onComplete(filtered) end
        else
            C_Timer.After(delay, step)
        end
    end

    step()
end

function Scan:ApplyFilters()
    local cfg = ACAU_Config or Const.DEFAULTS
    local threshold = cfg.threshold or Const.DEFAULTS.threshold
    Filters:BuildMaps()
    wipe(self.results)
    if not self.raw or #self.raw == 0 then
        return self.results
    end
    for _, data in ipairs(self.raw or {}) do
        if not ((ACAU_Ignore and ACAU_Ignore[data.id]) or (ACAU_Global and ACAU_Global.ignoreAll and ACAU_Global.ignoreAll[data.id])) then
            local percent = data.percent or 0
            if percent >= threshold and Filters:ShouldShow(data.id, data.reward) then
                local skip = false
                if percent >= 100 then
                    local _, _, completed = GetAchievementInfo(data.id)
                    if completed then
                        skip = true
                    end
                end
                if not skip then
                    table.insert(self.results, data)
                end
            end
        end
    end
    Util:Sort(self.results, comparator(cfg.sortMode))
    return self.results
end
