local _, GF = ...

GF.FindGroup = {}
local FG = GF.FindGroup

local function readSearchResultPlayer(resultID, memberIndex)
	local getter = C_LFGList and C_LFGList.GetSearchResultPlayerInfo
	if type(getter) ~= "function" then
		return nil, false
	end
	local diagnostics = GF.SearchMemoryDiagnostics
	if diagnostics and diagnostics.current then
		diagnostics:Add("playerInfoReads", 1)
		if diagnostics.MeasureProtectedFirst then
			return diagnostics:MeasureProtectedFirst(
				"playerInfo", getter, resultID, memberIndex)
		end
	end
	local ok, player = pcall(getter, resultID, memberIndex)
	return ok and player or nil, ok == true
end

function FG:IsSearchableSelection(node)
	if type(node) ~= "table" then
		return false
	end
	local view = GF.LFGWorkspaceView
	local allowsNode = view and view.IsNodeAllowed
	if allowsNode and allowsNode(view, node) == false then
		return false
	end
	local getWorkspace = view and view.GetWorkspaceID
	local workspaceID = getWorkspace and getWorkspace(view)
	local policy = GF.LFGWorkspacePolicy
	local policyAllows = policy and policy.IsSearchableNode
	if policyAllows and policyAllows(policy, workspaceID, node) == false then
		return false
	end
	local isSearchable = GF.NavData and GF.NavData.IsSearchable
	if isSearchable then
		return isSearchable(node) == true
	end
	return node.categoryID ~= nil
end

function FG:GetSelectionKey(node)
	return node and (node.searchKey or node.key) or nil
end

function FG:GetSearchCooldownRemaining(lastSearchAt)
	if not lastSearchAt or not GetTime then
		return 0
	end
	local remain = (GF.SEARCH_COOLDOWN or 3) - (GetTime() - lastSearchAt)
	if remain <= 0 then
		return 0
	end
	return remain
end

function FG:IsFriendListing(info, resultID)
	local socialType = GF.GetSearchResultSocialType and GF.GetSearchResultSocialType(info, resultID)
	return socialType == GF.SOCIAL_TYPE_BNET or socialType == GF.SOCIAL_TYPE_FRIEND
end

function FG:IsCurrentGroupListing(info, resultID)
	return GF.IsCurrentGroupSearchResult
		and GF.IsCurrentGroupSearchResult(info, resultID) == true
end

function FG:IsGuildListing(info, resultID)
	return GF.GetSearchResultSocialType
		and GF.GetSearchResultSocialType(info, resultID) == GF.SOCIAL_TYPE_GUILD
end

function FG:IsSocialListing(info, resultID)
	if GF.IsSocialSearchResult then
		return GF.IsSocialSearchResult(info, resultID)
	end
	return self:IsFriendListing(info, resultID) or self:IsGuildListing(info, resultID)
end

local function getBlocklist()
	local bl = GF.Blocklist
	if not bl or not bl.IsEnabled or not bl:IsEnabled() or not bl.FindPlayerMatch then
		return nil
	end
	return bl
end

local function readableMemberName(member)
	if type(member) ~= "table" then
		return nil
	end
	local ok, name = pcall(function()
		return member.name
	end)
	if not ok or type(name) ~= "string" or name == "" then
		return nil
	end
	if type(issecretvalue) == "function" then
		local secretOK, secret = pcall(issecretvalue, name)
		if secretOK and secret == true then
			return nil
		end
	end
	return name
end

local function testBlockedMember(bl, member)
	local name = readableMemberName(member)
	if not name then
		return nil, nil, false
	end
	local row = bl:FindPlayerMatch(name)
	if row then
		return member, row, true
	end
	return nil, nil, true
end

local function scanBlockedMembers(bl, members, complete)
	if type(members) ~= "table" then
		return nil, nil, false
	end
	local lengthOK, length = pcall(function()
		return #members
	end)
	if not lengthOK or type(length) ~= "number" then
		return nil, nil, false
	end
	for index = 1, length do
		local memberOK, member = pcall(function()
			return members[index]
		end)
		local blockedMember, blockedRow, readable
		if memberOK then
			blockedMember, blockedRow, readable = testBlockedMember(bl, member)
		end
		if blockedMember then
			return blockedMember, blockedRow, true
		end
		complete = complete and memberOK and readable
	end
	return nil, nil, complete == true
end

function FG:FindBlockedMember(resultID, info, entry)
	local bl = getBlocklist()
	if not bl or not resultID or not info then
		return nil, nil
	end
	local countOK, numMembers = pcall(function()
		return tonumber(info.numMembers)
	end)
	if countOK and numMembers ~= nil and numMembers >= 0 then
		numMembers = math.floor(numMembers + 0.0001)
	else
		numMembers = nil
	end
	local snapshotOK, snapshotPlayers, snapshotComplete = pcall(function()
		return info._gfBlocklistPlayers,
			info._gfBlocklistPlayersComplete == true
	end)
	local hasPlayerSnapshot = snapshotOK and type(snapshotPlayers) == "table"
	if hasPlayerSnapshot then
		local blockedMember, blockedRow, complete = scanBlockedMembers(
			bl, snapshotPlayers, snapshotComplete)
		if blockedMember then
			return blockedMember, blockedRow, true
		end
		if complete then
			return nil, nil, true
		end
	end
	if entry and type(entry.players) == "table" then
		local lengthOK, length = pcall(function()
			return #entry.players
		end)
		local blockedMember, blockedRow, complete = scanBlockedMembers(
			bl, entry.players,
			numMembers ~= nil and lengthOK and type(length) == "number"
				and length >= numMembers)
		if blockedMember then
			return blockedMember, blockedRow, true
		end
		if complete then
			return nil, nil, true
		end
	end
	-- A compact snapshot marks a result from an earlier aggregate scope. The
	-- native result store now belongs to a later scope, so an incomplete snapshot
	-- must fail open instead of reading unrelated player slots by result ID.
	if hasPlayerSnapshot then
		return nil, nil, false
	end
	if not (C_LFGList and C_LFGList.GetSearchResultPlayerInfo) then
		return nil, nil, false
	end
	if numMembers == nil then
		return nil, nil, false
	end
	local complete = true
	for i = 1, numMembers do
		local member, ok = readSearchResultPlayer(resultID, i)
		if ok then
			local blockedMember, blockedRow, readable = testBlockedMember(bl, member)
			if blockedMember then
				return blockedMember, blockedRow, true
			end
			complete = complete and readable
		else
			complete = false
		end
	end
	return nil, nil, complete
end

function FG:FindBlocklistMatch(resultID, info, entry)
	local bl = getBlocklist()
	if not bl or not resultID or not info then
		return nil, nil, nil, false
	end
	local kind, row = bl:FindMatch(resultID, info)
	if kind then
		return kind, row, nil, true
	end
	local member, memberRow, complete = self:FindBlockedMember(resultID, info, entry)
	if member then
		return "member", memberRow, member, true
	end
	return nil, nil, nil, complete
end

function FG:ShouldHideBlockedResult(resultID, info, entry)
	return self:FindBlocklistMatch(resultID, info, entry) ~= nil
end

function FG:GetResultType(info, entry, resultID)
	if not info then
		return nil
	end
	if entry and entry.hasLeaver == true then
		return "leaver"
	end
	if self:IsCurrentGroupListing(info, resultID) then
		return GF.RESULT_TYPE_CURRENT_GROUP
	end
	local socialType = GF.GetSearchResultSocialType and GF.GetSearchResultSocialType(info, resultID)
	if socialType then
		return socialType
	end
	return nil
end

function FG:RunSearch(selection, context)
	if not GF.Search or not GF.Search.Run then
		return false
	end
	return GF.Search:Run(selection, context)
end
