local addonName, RF = ...

-------------------------------------------------------------------------------
-- Roster Cache: Fetches guild roster data and builds enriched member array
-- All collected data persists in guildDB.memberCache across sessions.
-------------------------------------------------------------------------------

RF.rosterData = {}
RF.rosterByName = {}  -- name -> data entry (for quick lookup)

local lastRefreshTime = 0

-------------------------------------------------------------------------------
-- Member Cache: Persist last-known data for every member
-------------------------------------------------------------------------------
local function GetMemberCache()
    local guildDB = RF:GetGuildDB()
    if not guildDB then return nil end
    return guildDB.memberCache
end

local function SaveToCache(name, entry)
    local cache = GetMemberCache()
    if not cache then return end

    if not cache[name] then
        cache[name] = {}
    end
    local c = cache[name]

    -- Always update what we know
    c.classFile        = entry.classFile
    c.classDisplayName = entry.classDisplayName
    c.rankName         = entry.rankName
    c.rankIndex        = entry.rankIndex
    c.level            = entry.level
    c.lastSeen         = time()

    -- Only update these from online/fresh data
    if entry.isOnline and entry.zone ~= "" then
        c.lastZone = entry.zone
    end
    if entry.mplusRating and entry.mplusRating > 0 then
        c.mplusRating = entry.mplusRating
        c.mplusRatingColor = entry.mplusRatingColor
    end
    if entry.achievementPoints and entry.achievementPoints > 0 then
        c.achievementPoints = entry.achievementPoints
    end
    if entry.profession1Name and entry.profession1Name ~= "" then
        c.profession1Name = entry.profession1Name
        c.profession1Rank = entry.profession1Rank
    end
    if entry.profession2Name and entry.profession2Name ~= "" then
        c.profession2Name = entry.profession2Name
        c.profession2Rank = entry.profession2Rank
    end
end

local function RestoreFromCache(name, entry)
    local cache = GetMemberCache()
    if not cache or not cache[name] then return end
    local c = cache[name]

    -- Restore M+ rating if we don't have a live one
    if (not entry.mplusRating or entry.mplusRating == 0) and c.mplusRating then
        entry.mplusRating = c.mplusRating
        entry.mplusRatingColor = c.mplusRatingColor
    end

    -- Restore achievement points
    if (not entry.achievementPoints or entry.achievementPoints == 0) and c.achievementPoints then
        entry.achievementPoints = c.achievementPoints
    end

    -- Restore profession data
    if (not entry.profession1Name or entry.profession1Name == "") and c.profession1Name then
        entry.profession1Name = c.profession1Name
        entry.profession1Rank = c.profession1Rank or 0
    end
    if (not entry.profession2Name or entry.profession2Name == "") and c.profession2Name then
        entry.profession2Name = c.profession2Name
        entry.profession2Rank = c.profession2Rank or 0
    end
end

-------------------------------------------------------------------------------
-- Build roster data from guild API
-------------------------------------------------------------------------------
function RF:RefreshRosterData()
    if not IsInGuild() then
        RF.rosterData = {}
        RF.rosterByName = {}
        return
    end

    local numTotal, numOnline = GetNumGuildMembers()

    -- Server hasn't responded yet — don't overwrite with empty data
    if numTotal == 0 then return end

    -- Throttle refreshes, but always allow if we have no data yet
    local now = GetTime()
    if #RF.rosterData > 0 and now - lastRefreshTime < RF.ROSTER_THROTTLE_SEC then
        return -- throttle
    end
    lastRefreshTime = now
    local newData = {}
    local byName = {}

    for i = 1, numTotal do
        local fullName, rankName, rankIndex, level, classDisplayName,
              zone, note, officerNote, isOnline, status, classFile,
              achievementPoints, achievementRank, isMobile, canSoR,
              repStanding, guid = GetGuildRosterInfo(i)

        if fullName then
            local name = Ambiguate(fullName, "guild")
            local years, months, days, hours = GetGuildRosterLastOnline(i)

            -- Determine status
            local memberStatus
            if isMobile and not isOnline then
                memberStatus = "MOBILE"
            elseif not isOnline then
                memberStatus = "OFFLINE"
            elseif status == 1 then
                memberStatus = "AWAY"
            elseif status == 2 then
                memberStatus = "DND"
            else
                memberStatus = "ONLINE"
            end

            local entry = {
                index           = i,
                fullName        = fullName,
                name            = name,
                rankName        = rankName,
                rankIndex       = rankIndex,
                level           = level,
                classDisplayName= classDisplayName,
                classFile       = classFile,
                zone            = zone or "",
                note            = note or "",
                officerNote     = officerNote or "",
                isOnline        = isOnline,
                isMobile        = isMobile,
                status          = memberStatus,
                achievementPoints = achievementPoints,
                repStanding     = repStanding,
                guid            = guid,
                -- Last online
                lastOnlineYears  = years,
                lastOnlineMonths = months,
                lastOnlineDays   = days,
                lastOnlineHours  = hours,
                lastOnlineText   = isOnline and "Online" or
                    RF:FormatTimeOffline(years, months, days, hours),
                lastOnlineSeconds = isOnline and 0 or
                    RF:TimeToSeconds(years, months, days, hours),
                -- M+ Rating (populated by FetchMythicPlusRatings or cache)
                mplusRating     = 0,
                mplusRatingColor= nil,
                -- Profession data (populated by FetchClubMemberData)
                profession1Name = "",
                profession1Rank = 0,
                profession2Name = "",
                profession2Rank = 0,
                -- Enriched fields (filled by EnrichRosterData)
                joinDate        = nil,
                altMain         = nil,
                extendedNote    = nil,
            }

            tinsert(newData, entry)
            byName[name] = entry
            byName[fullName] = entry
        end
    end

    RF.rosterData = newData
    RF.rosterByName = byName

    -- Fetch M+ ratings from Communities API
    RF:FetchMythicPlusRatings()

    -- Restore cached data for members missing live data, then save fresh data
    for _, entry in ipairs(RF.rosterData) do
        RestoreFromCache(entry.name, entry)
        SaveToCache(entry.name, entry)
    end

    -- Enrich with tracking/alt/notes data
    RF:EnrichRosterData()

    RF:FireCallbacks("ROSTER_REFRESHED", #newData, numOnline)
    RF._lastRosterRefresh = GetTime()
end

-------------------------------------------------------------------------------
-- Fetch Mythic Plus Ratings via C_Club (Communities API)
-------------------------------------------------------------------------------
function RF:FetchMythicPlusRatings()
    local clubId = C_Club and C_Club.GetGuildClubId and C_Club.GetGuildClubId()
    if not clubId then return end

    -- C_Club.GetClubMembers can return tainted "secret" values (especially near
    -- combat transitions) that pass nil/type checks but explode in ipairs.
    -- Wrapping the entire block in pcall makes this bulletproof.
    pcall(function()
        local members = C_Club.GetClubMembers(clubId)
        if not members then return end
        for _, memberId in ipairs(members) do
            local info = C_Club.GetMemberInfo(clubId, memberId)
            if info and info.name then
                local name = Ambiguate(info.name, "guild")
                local entry = RF.rosterByName and RF.rosterByName[name]
                if entry then
                    entry.mplusRating = info.overallDungeonScore or 0
                    -- Color the rating using Blizzard's helper if available
                    if info.overallDungeonScore and info.overallDungeonScore > 0
                       and C_ChallengeMode and C_ChallengeMode.GetDungeonScoreRarityColor then
                        local color = C_ChallengeMode.GetDungeonScoreRarityColor(info.overallDungeonScore)
                        if color then
                            entry.mplusRatingColor = { r = color.r, g = color.g, b = color.b }
                        end
                    end
                    -- Profession data from ClubMemberInfo
                    entry.profession1Name = info.profession1Name or ""
                    entry.profession1Rank = info.profession1Rank or 0
                    entry.profession2Name = info.profession2Name or ""
                    entry.profession2Rank = info.profession2Rank or 0

                end
            end
        end
    end)
end

-------------------------------------------------------------------------------
-- Enrich roster entries with data from TrackingEngine, AltManager, NotesManager
-------------------------------------------------------------------------------
function RF:EnrichRosterData()
    local guildDB = RF:GetGuildDB()
    if not guildDB then return end

    for _, entry in ipairs(RF.rosterData) do
        local name = entry.name

        -- Join date from tracking
        local memberData = guildDB.tracking.members[name]
        if memberData and memberData.joinDate then
            entry.joinDate = RF:FormatTimestamp(memberData.joinDate)
            entry.joinDateRaw = memberData.joinDate
        end

        -- Alt/Main linking
        local altLink = guildDB.alts.links[name]
        if altLink then
            entry.altMain = "Alt of " .. altLink
            entry.mainName = altLink
        elseif guildDB.alts.mains[name] then
            local alts = guildDB.alts.mains[name]
            entry.altMain = "Main (" .. #alts .. " alts)"
            entry.isMain = true
        end

        -- Extended notes
        if guildDB.notes[name] then
            entry.extendedNote = guildDB.notes[name]
        end

        -- Favorites
        if guildDB.favorites and guildDB.favorites[name] then
            entry.isFavorite = true
        end

    end
end

-------------------------------------------------------------------------------
-- Get member data by name
-------------------------------------------------------------------------------
function RF:GetMemberByName(name)
    local ok, result = pcall(function() return RF.rosterByName[name] end)
    if ok then return result end
    return nil
end

-------------------------------------------------------------------------------
-- Roster Stats
-------------------------------------------------------------------------------
function RF:GetRosterStats()
    local total = #RF.rosterData
    local online = 0
    local classCounts = {}

    for _, entry in ipairs(RF.rosterData) do
        if entry.isOnline then
            online = online + 1
        end
        local cf = entry.classFile
        classCounts[cf] = (classCounts[cf] or 0) + 1
    end

    return {
        total = total,
        online = online,
        offline = total - online,
        classCounts = classCounts,
    }
end

-------------------------------------------------------------------------------
-- Clean stale cache entries (members no longer in guild)
-------------------------------------------------------------------------------
function RF:CleanMemberCache()
    local cache = GetMemberCache()
    if not cache then return end

    local currentNames = {}
    for _, entry in ipairs(RF.rosterData) do
        currentNames[entry.name] = true
    end

    for name in pairs(cache) do
        if not currentNames[name] then
            cache[name] = nil
        end
    end
end

-------------------------------------------------------------------------------
-- Hook into GUILD_ROSTER_UPDATE
-------------------------------------------------------------------------------
RF:RegisterCallback("GUILD_ROSTER_UPDATE", function()
    RF:RefreshRosterData()
end)

-- Initial load after addon init
RF:RegisterCallback("ADDON_LOADED", function()
    if IsInGuild() then
        C_Timer.After(3, function()
            RF:RefreshRosterData()
        end)
    end
end)

-- Clean stale cache periodically
RF:RegisterCallback("PLAYER_ENTERING_WORLD", function()
    C_Timer.After(10, function()
        RF:CleanMemberCache()
    end)
end)
