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

local Filters = {}
ns.Filters = Filters

Filters.topParent = {}
Filters.achParent = {}
Filters.achCatName = {}
Filters.achExpansion = {}
Filters.mapsBuilt = false
Filters.buildHash = ""

local function countSelections(db)
    local total, selected = 0, 0
    for _, val in pairs(db or {}) do
        if type(val) == "boolean" then
            total = total + 1
            if val then selected = selected + 1 end
        end
    end
    return total, selected
end

local function detectExpansion(name, topName)
    local hay = ((name or "") .. " " .. (topName or "")):lower()
    local match = {
        ["Classic"] = { "classic" },
        ["The Burning Crusade"] = { "burning crusade" },
        ["Wrath of the Lich King"] = { "wrath", "lich king" },
        ["Cataclysm"] = { "cataclysm" },
        ["Mists of Pandaria"] = { "mists", "pandaria" },
        ["Warlords of Draenor"] = { "warlords", "draenor" },
        ["Legion"] = { "legion" },
        ["Battle for Azeroth"] = { "battle for azeroth", "azeroth" },
        ["Shadowlands"] = { "shadowlands" },
        ["Dragonflight"] = { "dragonflight" },
        ["The War Within"] = { "war within" },
        ["Midnight"] = { "midnight" },
    }
    for exp, keys in pairs(match) do
        for _, key in ipairs(keys) do
            if hay:find(key, 1, true) then return exp end
        end
    end
    return nil
end

function Filters:BuildMaps(force)
    local build = tostring(select(1, GetBuildInfo()) or "") .. ":" .. tostring(select(4, GetBuildInfo()) or "")
    if not force and self.mapsBuilt and self.buildHash == build then
        return
    end
    wipe(self.topParent)
    wipe(self.achParent)
    wipe(self.achCatName)
    wipe(self.achExpansion)

    local cats = GetCategoryList() or {}
    for _, catID in ipairs(cats) do
        local name, parent = GetCategoryInfo(catID)
        local topName = name
        local parentIter = parent
        while parentIter and parentIter > 0 do
            local n, p = GetCategoryInfo(parentIter)
            if not n then break end
            topName = n
            parentIter = p
        end
        self.topParent[catID] = topName
        local catExpansion = detectExpansion(name, topName)
        local numAch = GetCategoryNumAchievements(catID) or 0
        for i = 1, numAch do
            local achID = select(1, GetAchievementInfo(catID, i))
            if achID then
                self.achParent[achID] = topName
                self.achCatName[achID] = name
                if catExpansion then
                    self.achExpansion[achID] = catExpansion
                end
            end
        end
    end
    self.mapsBuilt = true
    self.buildHash = build
end

local function playerKnowsProfession(achID)
    local known = {}
    if GetProfessions and GetProfessionInfo then
        local a, b, c, d, e = GetProfessions()
        for _, idx in ipairs({ a, b, c, d, e }) do
            if idx then
                local name = GetProfessionInfo(idx)
                if name then known[name] = true end
            end
        end
    end
    if next(known) == nil and C_TradeSkillUI and C_TradeSkillUI.GetAllProfessionInfo then
        local profs = C_TradeSkillUI.GetAllProfessionInfo()
        if profs then
            for _, p in ipairs(profs) do
                if p and p.name then known[p.name] = true end
            end
        end
    end
    if next(known) == nil then
        return false
    end
    local num = GetAchievementNumCriteria(achID) or 0
    for i = 1, num do
        local critName = select(1, GetAchievementCriteriaInfo(achID, i))
        if critName then
            for profName in pairs(known) do
                if critName:find(profName, 1, true) then
                    return true
                end
            end
        end
    end
    return false
end

local function anyHolidayActive()
    if not C_Calendar or not C_DateAndTime then return false end
    if C_Calendar.OpenCalendar then pcall(C_Calendar.OpenCalendar) end
    local function dayHasHoliday(offset)
        local now = C_DateAndTime.GetCurrentCalendarTime()
        if not now or not now.monthDay or now.monthDay <= 0 then return false end
        local day = math.max(1, now.monthDay + (offset or 0))
        local num = C_Calendar.GetNumDayEvents(0, day) or 0
        for i = 1, num do
            local info = C_Calendar.GetHolidayInfo(0, day, i)
            if info and (info.texture or info.startTime or info.endTime) then
                return true
            end
        end
        return false
    end
    return dayHasHoliday(0) or dayHasHoliday(-1) or dayHasHoliday(1)
end

local holidayCache = { value = nil, expires = 0 }
local function cachedHolidayActive()
    local now = time()
    if holidayCache.value ~= nil and holidayCache.expires > now then
        return holidayCache.value
    end
    local result = anyHolidayActive()
    holidayCache.value = result
    holidayCache.expires = now + 60
    return result
end

local function holidayMatchesCategory(catName)
    if not catName or catName == "" then return cachedHolidayActive() end
    local needle = tostring(catName):lower()
    if not C_Calendar or not C_DateAndTime then return false end
    if C_Calendar.OpenCalendar then pcall(C_Calendar.OpenCalendar) end
    local now = C_DateAndTime.GetCurrentCalendarTime()
    if not now or not now.monthDay or now.monthDay <= 0 then return false end
    local function dayHasName(offset)
        local day = math.max(1, now.monthDay + (offset or 0))
        local num = C_Calendar.GetNumDayEvents(0, day) or 0
        for i = 1, num do
            local info = C_Calendar.GetHolidayInfo(0, day, i)
            local title = info and (info.name or info.description)
            if title and tostring(title):lower():find(needle, 1, true) then
                return true
            end
        end
        return false
    end
    for d = -1, 1 do
        if dayHasName(d) then return true end
    end
    return false
end

function Filters:CategoryAllowed(achID, cfg)
    local catDB = ACAU_CategoryDB or Const.CATEGORY_DEFAULTS
    local total, selected = countSelections(catDB)
    local top = self.achParent[achID]
    if top == "Feats of Strength" or top == "Legacy" then return false end

    if top == "Professions" then
        local mode = catDB["ProfessionsMode"] or "All"
        if mode == "None" then return false end
        if mode == "Learned" and not playerKnowsProfession(achID) then
            return false
        end
        return true
    end
    if top == "World Events" then
        local mode = catDB["WorldEventsMode"] or "Active"
        if mode == "None" then return false end
        if mode == "Active" and not holidayMatchesCategory(self.achCatName[achID]) then
            return false
        end
        return true
    end

    if selected == 0 then return false end

    local allSelected = (selected == total)
    if not top then
        return allSelected
    end

    if not allSelected and catDB[top] ~= true then
        return false
    end

    return true
end

function Filters:ExpansionAllowed(achID)
    local exp = self.achExpansion[achID]
    local expDB = ACAU_ExpFilterDB or Const.EXPANSION_DEFAULTS
    local total, selected = countSelections(expDB)

    if selected == total then return true end
    if selected == 0 then return true end

    if not exp then
        return false
    end

    return expDB[exp] == true
end

function Filters:ShouldShow(achID, rewardText)
    self:BuildMaps()
    if not self:CategoryAllowed(achID, ACAU_CategoryDB) then return false end
    if not self:ExpansionAllowed(achID) then return false end

    local filterMode = ACAU_Config and ACAU_Config.filterMode or Const.DEFAULTS.filterMode
    local matcher = Const.REWARD_MATCHERS[filterMode] or Const.REWARD_MATCHERS["All"]
    return matcher(rewardText)
end

function Filters:ResetDB()
    ACAU_CategoryDB = Util:MergeDefaults(ACAU_CategoryDB or {}, Const.CATEGORY_DEFAULTS)
    ACAU_ExpFilterDB = Util:MergeDefaults(ACAU_ExpFilterDB or {}, Const.EXPANSION_DEFAULTS)
    self.mapsBuilt = false
end
