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

local DATA = ns.Data or {}
local KNOWN_MIDNIGHT_BOUNTIFUL_POIS = DATA.KNOWN_MIDNIGHT_BOUNTIFUL_POIS or {}
local KNOWN_DELVE_MAP_IDS = DATA.KNOWN_DELVE_MAP_IDS or {}
local BOUNTIFUL_SAFETY_MAP_IDS = DATA.BOUNTIFUL_SAFETY_MAP_IDS or {}

local Util = ns.Util or {}
local normalizeText = Util.NormalizeText
local containsPattern = Util.ContainsPattern

local DailyPveBountiful = {}
ns.DailyPveBountiful = DailyPveBountiful

local BOUNTIFUL_DELVES_MIN_LEVEL = 90

local function getCharacterLevel(character)
    if type(character) == "table" then
        local level = tonumber(character.level)
        if level then
            return math.floor(level)
        end
        if not (ns.IsCurrentCharacterKey and ns:IsCurrentCharacterKey(character.key)) then
            return nil
        end
    end

    if type(UnitLevel) == "function" then
        local ok, level = pcall(UnitLevel, "player")
        level = ok and tonumber(level) or nil
        if level then
            return math.floor(level)
        end
    end

    return nil
end

local function isBountifulDelvesLevelEligible(character)
    return (tonumber(getCharacterLevel(character)) or 0) >= BOUNTIFUL_DELVES_MIN_LEVEL
end

local function gatherDelveMapIDs()
    local mapIDs = {}
    local seen = {}

    local function addMapID(mapID)
        mapID = tonumber(mapID)
        if not mapID or mapID <= 0 or seen[mapID] then
            return
        end
        seen[mapID] = true
        mapIDs[#mapIDs + 1] = mapID
    end

    if C_Map and type(C_Map.GetBestMapForUnit) == "function" then
        local bestMap = C_Map.GetBestMapForUnit("player")
        addMapID(bestMap)

        if type(C_Map.GetMapInfo) == "function" then
            local cursor = bestMap
            local safety = 0
            while cursor and safety < 12 do
                local info = C_Map.GetMapInfo(cursor)
                local parentMapID = info and info.parentMapID
                if not parentMapID or parentMapID == 0 then
                    break
                end
                addMapID(parentMapID)
                cursor = parentMapID
                safety = safety + 1
            end
        end
    end

    if C_Map and type(C_Map.GetMapChildrenInfo) == "function" then
        local snapshot = {}
        for _, mapID in ipairs(mapIDs) do
            snapshot[#snapshot + 1] = mapID
        end

        local mapTypes = {}
        if Enum and Enum.UIMapType then
            mapTypes[#mapTypes + 1] = Enum.UIMapType.Continent
            mapTypes[#mapTypes + 1] = Enum.UIMapType.Zone
        end
        if #mapTypes == 0 then
            mapTypes[#mapTypes + 1] = nil
        end

        for _, mapID in ipairs(snapshot) do
            for _, mapType in ipairs(mapTypes) do
                local ok, children = pcall(C_Map.GetMapChildrenInfo, mapID, mapType)
                if ok and type(children) == "table" then
                    for _, child in ipairs(children) do
                        addMapID(child and child.mapID)
                    end
                end
            end
        end
    end

    return mapIDs
end

local function buildBountifulArgSets()
    local argSets = {
        {},
        { true },
        { false },
    }

    for _, mapID in ipairs(gatherDelveMapIDs()) do
        argSets[#argSets + 1] = { mapID }
        argSets[#argSets + 1] = { mapID, true }
        argSets[#argSets + 1] = { mapID, false }
    end

    return argSets
end

local function getKnownDelveMapIDs()
    local merged = {}
    local seen = {}

    for _, mapID in ipairs(KNOWN_DELVE_MAP_IDS) do
        if not seen[mapID] then
            seen[mapID] = true
            merged[#merged + 1] = mapID
        end
    end

    for _, mapID in ipairs(gatherDelveMapIDs()) do
        if not seen[mapID] then
            seen[mapID] = true
            merged[#merged + 1] = mapID
        end
    end

    return merged
end

local function countBountifulFromTableResult(result)
    if type(result) ~= "table" then
        return nil
    end

    local total = 0
    local remaining = 0

    for _, item in ipairs(result) do
        local isBountiful = false

        if type(item) == "table" then
            if item.isBountiful or item.bountiful or item.isBountifulDelve then
                isBountiful = true
            end

            if not isBountiful then
                local textBlob = normalizeText(table.concat({
                    tostring(item.atlasName or ""),
                    tostring(item.atlas or ""),
                    tostring(item.label or ""),
                    tostring(item.name or ""),
                    tostring(item.description or ""),
                    tostring(item.textureKit or ""),
                }, " "))
                if containsPattern(textBlob, { "bountiful", "grosszuegig", "grosszugig", "gro\195\159z\195\188gig" }) then
                    isBountiful = true
                end
            end
        end

        if isBountiful then
            total = total + 1
            local isCompleted = (item and (item.completed or item.isCompleted or item.wasCompletedToday)) and true or false
            local isAvailable = (item and (item.isActive or item.active or item.available ~= false)) and true or false
            if not isCompleted and isAvailable then
                remaining = remaining + 1
            end
        end
    end

    if total <= 0 then
        return nil
    end

    if remaining <= 0 and total > 0 and total <= 4 then
        remaining = total
    end

    return math.max(0, math.min(4, remaining))
end

local function isBountifulPoiInfo(info)
    if type(info) ~= "table" then
        return false, false
    end

    local textBlob = normalizeText(table.concat({
        tostring(info.atlasName or ""),
        tostring(info.textureKit or ""),
        tostring(info.name or ""),
        tostring(info.description or ""),
    }, " "))
    local looksBountiful = containsPattern(textBlob, {
        "delves%-bountiful",
        "bountiful",
        "grosszuegig",
        "gro\195\159z\195\188gig",
        "grosszugig",
        "gro\195\159z\195\188gig",
    })

    if not looksBountiful then
        return false, false
    end

    local isCompleted = (info.isCompleted or info.completed or info.wasCompletedToday) and true or false
    return true, isCompleted
end

local function findBountifulRemainingFromKnownMidnightPOIs()
    if not (C_AreaPoiInfo and type(C_AreaPoiInfo.GetAreaPOIInfo) == "function") then
        return nil, nil
    end

    local scanned = 0
    local active = 0

    for _, entry in ipairs(KNOWN_MIDNIGHT_BOUNTIFUL_POIS) do
        local ok, info = pcall(C_AreaPoiInfo.GetAreaPOIInfo, entry.mapID, entry.poiID)
        if ok and type(info) == "table" then
            scanned = scanned + 1
            if info.atlasName == "delves-bountiful" then
                active = active + 1
            end
        end
    end

    if scanned <= 0 then
        return nil, nil
    end

    return math.max(0, math.min(4, active)), "known-midnight-poi"
end

local function findBountifulRemainingFromAreaPOIs()
    if not (C_AreaPoiInfo and type(C_AreaPoiInfo.GetAreaPOIsForMap) == "function" and type(C_AreaPoiInfo.GetAreaPOIInfo) == "function") then
        return nil, nil
    end

    local function countFromMapList(mapList)
        local total = 0
        local active = 0
        local seenPoi = {}

        for _, mapID in ipairs(mapList) do
            local ok, poiIDs = pcall(C_AreaPoiInfo.GetAreaPOIsForMap, mapID)
            if ok and type(poiIDs) == "table" then
                for _, poiID in ipairs(poiIDs) do
                    local okInfo, info = pcall(C_AreaPoiInfo.GetAreaPOIInfo, mapID, poiID)
                    if okInfo and type(info) == "table" then
                        local uniquePoi = tostring(info.areaPoiID or poiID)
                        if not seenPoi[uniquePoi] then
                            seenPoi[uniquePoi] = true
                            local isBountiful, isCompleted = isBountifulPoiInfo(info)
                            if isBountiful then
                                total = total + 1
                                if not isCompleted then
                                    active = active + 1
                                end
                            end
                        end
                    end
                end
            end
        end

        return total, active
    end

    local candidateMaps = gatherDelveMapIDs()
    local seen = {}
    for _, mapID in ipairs(candidateMaps) do
        seen[mapID] = true
    end
    for _, mapID in ipairs(BOUNTIFUL_SAFETY_MAP_IDS) do
        if not seen[mapID] then
            seen[mapID] = true
            candidateMaps[#candidateMaps + 1] = mapID
        end
    end

    local total, active = countFromMapList(candidateMaps)
    if total <= 0 then
        return nil, nil
    end

    if active <= 0 and total > 0 and total <= 4 then
        active = total
    end

    return math.max(0, math.min(4, active)), "poi-map"
end

local function findBountifulRemainingFromHasActiveDelve()
    if not (type(C_DelvesUI) == "table" and type(C_DelvesUI.HasActiveDelve) == "function") then
        return nil, nil
    end

    local checked = 0
    local active = 0

    for _, mapID in ipairs(getKnownDelveMapIDs()) do
        local ok, isActive = pcall(C_DelvesUI.HasActiveDelve, mapID)
        if ok then
            checked = checked + 1
            if isActive then
                active = active + 1
            end
        end
    end

    if checked <= 0 then
        return nil, nil
    end

    return math.max(0, math.min(4, active)), "has-active-delve"
end

local function findBountifulRemainingFromMap()
    local knownRemaining, knownSource = findBountifulRemainingFromKnownMidnightPOIs()
    if knownRemaining ~= nil then
        return knownRemaining, knownSource
    end

    local tableCandidates = {
        { api = C_DelvesUI, fn = "GetBountifulDelvesForMap" },
        { api = C_DelvesUI, fn = "GetBountifulDelvesForMapID" },
        { api = C_DelvesUI, fn = "GetDelvesForMap" },
        { api = C_DelvesUI, fn = "GetDelvesForMapID" },
        { api = C_DelvesUI, fn = "GetDelvesForUiMapID" },
        { api = C_Delves, fn = "GetBountifulDelvesForMap" },
        { api = C_Delves, fn = "GetBountifulDelvesForMapID" },
        { api = C_Delves, fn = "GetDelvesForMap" },
        { api = C_Delves, fn = "GetDelvesForMapID" },
        { api = C_Delves, fn = "GetDelvesForUiMapID" },
    }

    local argSets = buildBountifulArgSets()
    for _, candidate in ipairs(tableCandidates) do
        if type(candidate.api) == "table" and type(candidate.api[candidate.fn]) == "function" then
            for _, args in ipairs(argSets) do
                local ok, result = pcall(candidate.api[candidate.fn], unpack(args))
                if ok then
                    local remaining = countBountifulFromTableResult(result)
                    if remaining ~= nil then
                        return remaining, "map:" .. candidate.fn
                    end
                end
            end
        end
    end

    local poiRemaining, poiSource = findBountifulRemainingFromAreaPOIs()
    if poiRemaining ~= nil then
        return poiRemaining, poiSource
    end

    return findBountifulRemainingFromHasActiveDelve()
end

function DailyPveBountiful.BuildCurrentState(stickyState, character)
    local cap = 4
    if not isBountifulDelvesLevelEligible(character) then
        return {
            completed = 0,
            cap = cap,
            source = "level-gated",
            text = string.format("0/%d", cap),
            status = "locked",
            renownEligible = false,
            tooltipTitle = L.PVE_DAILY_ROW_BOUNTIFUL or "Bountiful Delves",
            tooltipLines = {
                L.PVE_DAILY_BOUNTIFUL_HINT or "Tracks completed bountiful delves for today.",
            },
        }
    end

    local stickyCompleted = math.max(0, math.min(cap, tonumber(stickyState and stickyState.completed) or 0))
    local activeRemaining, source = findBountifulRemainingFromMap()
    local completed = stickyCompleted

    if activeRemaining ~= nil then
        local liveCompleted = math.min(cap, math.max(0, cap - activeRemaining))
        if activeRemaining > 0 then
            completed = liveCompleted
        else
            completed = math.max(stickyCompleted, liveCompleted)
        end
    end

    local tooltipLines = {
        L.PVE_DAILY_BOUNTIFUL_HINT or "Tracks completed bountiful delves for today.",
        string.format(L.PVE_DAILY_BOUNTIFUL_PROGRESS or "Completed today: %d/%d", completed, cap),
    }
    if activeRemaining ~= nil then
        tooltipLines[#tooltipLines + 1] = string.format(
            L.PVE_DAILY_BOUNTIFUL_ACTIVE or "Currently active: %d",
            math.max(0, math.min(cap, activeRemaining))
        )
    end

    return {
        completed = completed,
        cap = cap,
        source = source or "sticky",
        text = string.format("%d/%d", completed, cap),
        status = (completed >= cap and cap > 0) and "complete" or "open",
        renownEligible = true,
        tooltipTitle = L.PVE_DAILY_ROW_BOUNTIFUL or "Bountiful Delves",
        tooltipLines = tooltipLines,
    }
end

DailyPveBountiful.IsLevelEligible = isBountifulDelvesLevelEligible
