-- Community.lua
-- War Board community (BattleNet club) membership: the gate for syncing with the
-- rest of the War Board network. Holds the invite link, a membership check, a
-- login reminder, and a helper to open Blizzard's join-community dialog.

local addonName, WB = ...

WB.Community = WB.Community or {}
local C = WB.Community

-- The War Board community. Find a club's id with /dump C_Club.GetSubscribedClubs()
C.CLUB_ID     = 501627671
C.INVITE_CODE = "LqmOb27i5aL"
C.INVITE_URL  = "https://www.worldofwarcraft.com/invite/LqmOb27i5aL?region=US&faction=Horde"

-- Is the player subscribed to the War Board community?
function C.IsMember()
    if not (C_Club and C_Club.GetSubscribedClubs) then return false end
    local clubs = C_Club.GetSubscribedClubs()
    if not clubs then return false end
    for _, club in ipairs(clubs) do
        if club.clubId == C.CLUB_ID then return true end
    end
    return false
end

-- Normalize a "Name" or "Name-Realm" into a comparable lowercase key (for membership matching).
function C.NameKey(name, realm)
    if type(name) ~= "string" or name == "" then return nil end
    name = name:gsub("%s+$", "")
    if name:find("-", 1, true) then
        return name:lower()
    elseif realm and realm ~= "" then
        return (name .. "-" .. realm):lower()
    end
    return name:lower()
end

-- Snapshot of the CURRENT community (club) membership, used to gate who can be bountied.
-- Returns byGuid[guid]=true and byName[namekey]=true, plus a `ready` flag (false when the
-- club's member list isn't populated yet, so callers can avoid over-filtering). Members are
-- keyed by GUID (reliable when resolvable) and name (fallback for offline members).
function C.GetMemberSet()
    local byGuid, byName, count = {}, {}, 0
    if C.CLUB_ID and C_Club and C_Club.GetClubMembers then
        local ok, members = pcall(C_Club.GetClubMembers, C.CLUB_ID)
        if ok and type(members) == "table" then
            for _, mid in ipairs(members) do
                local ok2, info = pcall(C_Club.GetMemberInfo, C.CLUB_ID, mid)
                if ok2 and info and info.name then
                    if info.guid then byGuid[info.guid] = true end
                    local key = C.NameKey(info.name)
                    if key then byName[key] = true end
                    count = count + 1
                end
            end
        end
    end
    return byGuid, byName, (count > 0)
end

-- Open Blizzard's Guilds & Communities frame and the "Join or Create Community"
-- (Add Community) flow over it, so the player can paste the invite link.
function C.OpenJoinCommunityDialog()
    local inInstance, instanceType = IsInInstance()
    if inInstance and (instanceType == "arena" or instanceType == "pvp") then return end
    if InCombatLockdown() then return end

    -- Defer a frame so we're clear of the click handler / any taint context.
    C_Timer.After(0, function()
        if InCombatLockdown() then return end

        -- Load the Blizzard Communities UI if it isn't up yet.
        if C_AddOns and C_AddOns.LoadAddOn then
            pcall(C_AddOns.LoadAddOn, "Blizzard_Communities")
        elseif UIParentLoadAddOn then
            pcall(UIParentLoadAddOn, "Blizzard_Communities")
        end

        -- Make sure the Communities frame itself is open.
        if ToggleCommunitiesFrame and (not CommunitiesFrame or not CommunitiesFrame:IsShown()) then
            securecall(ToggleCommunitiesFrame)
        end

        -- Open the Add/Join Community flow (skip if it's already showing).
        if AddCommunitiesFlow_IsShown and AddCommunitiesFlow_IsShown() then return end
        if AddCommunitiesFlow_Toggle then
            securecall(AddCommunitiesFlow_Toggle)
        end
    end)
end

-- One-time login reminder if the player isn't in the War Board community.
local function CheckCommunityReminder()
    if not (C_Club and C_Club.GetSubscribedClubs) then return end
    if C.IsMember() then return end
    print(WB.TAG.." You are not in the War Board community (ID: " .. C.CLUB_ID .. ").")
    print(WB.TAG.." Join the War Board community to sync and properly use the addon. Open the War Board (/wb) and click \"Community\" for the invite link.")
end

-- ── Community roster warming ───────────────────────────────────────────────────
-- C_Club's member list often isn't populated until the player opens the Communities UI, so
-- right after login or a loading screen our peer discovery (Comm.GetOnlinePeers →
-- C_Club.GetClubMembers) can come back empty and sync stalls. We warm it ourselves the way the
-- Communities UI would — subscribe to presence + focus the member list — WITHOUT opening any
-- UI, then poll until the roster is ready and kick a fresh announce so we sync the moment peers
-- are visible. Stands down inside instances (we don't sync there). All C_Club calls are pcall'd
-- so a missing/changed API can never error.

-- True once the club's member list is populated. Prefers AreMembersReady; falls back to a
-- non-empty GetClubMembers for client builds that don't expose it.
local function MembersReady(clubId)
    if C_Club and C_Club.AreMembersReady then
        local ok, ready = pcall(C_Club.AreMembersReady, clubId)
        if ok and ready then return true end
    end
    if C_Club and C_Club.GetClubMembers then
        local ok, members = pcall(C_Club.GetClubMembers, clubId)
        if ok and type(members) == "table" and #members > 0 then return true end
    end
    return false
end

local warming          = false   -- a warm cycle is in flight (prevents overlapping polls)
local lastWarmAnnounce = 0       -- GetTime() of the last successful warm (throttles quick re-fires)

function C.WarmRoster()
    if not C_Club then return end
    if WB.InInstance and WB.InInstance() then return end          -- no sync in instances
    if warming then return end
    if (GetTime() - lastWarmAnnounce) < 60 then return end         -- already warm; skip rapid loading screens
    warming = true

    local clubId = C.CLUB_ID
    -- Request the roster the way the Communities UI would (no UI opened).
    if C_Club.SetClubPresenceSubscription then pcall(C_Club.SetClubPresenceSubscription, clubId) end
    if C_Club.FocusMembers              then pcall(C_Club.FocusMembers, clubId) end

    local attempts, MAX_ATTEMPTS = 0, 8
    local function poll()
        attempts = attempts + 1
        if WB.InInstance and WB.InInstance() then warming = false; return end   -- zoned into an instance mid-warm
        if MembersReady(clubId) then
            warming = false
            lastWarmAnnounce = GetTime()
            if WB.Comm and WB.Comm.Announce        then pcall(WB.Comm.Announce) end
            if WB.Comm and WB.Comm.AnnounceProfile then pcall(WB.Comm.AnnounceProfile) end
            -- The bounty picker filters its candidate list against club membership, so re-render
            -- it now that the member roster is populated.
            if WB.UI and WB.UI.RefreshBountyPicker then pcall(WB.UI.RefreshBountyPicker) end
            return
        end
        if attempts >= MAX_ATTEMPTS then warming = false; return end  -- give up; a later event re-warms
        if C_Club.FocusMembers then pcall(C_Club.FocusMembers, clubId) end   -- focus can lapse before it fills
        if C_Timer then C_Timer.After(2, poll) end
    end
    if C_Timer then C_Timer.After(1, poll) else poll() end
end

do
    local f = CreateFrame("Frame")
    f:RegisterEvent("PLAYER_LOGIN")
    f:RegisterEvent("PLAYER_ENTERING_WORLD")   -- also fires after every loading screen
    f:SetScript("OnEvent", function(_, event)
        if event == "PLAYER_LOGIN" then
            -- Club data isn't ready instantly at login; give it a moment.
            C_Timer.After(10, CheckCommunityReminder)
        end
        -- Warm the roster on login and after loading screens so peers are discoverable for sync.
        C.WarmRoster()
    end)
end
