local _, GF = ...

GF.ApplicantModel = {}
local AM = GF.ApplicantModel

local LABEL_GOLD = { r = 1, g = 0.82, b = 0 }
local IL_VALUE = { r = 0.1, g = 1, b = 0.1 }

local STATUS_GRAY = { r = 0.5, g = 0.5, b = 0.5 }
local STATUS_GREEN = GREEN_FONT_COLOR or { r = 0, g = 1, b = 0 }
local MAX_APPLICANT_API_ID = 4294967295

local GRAYED_STATUSES = {
	failed = true,
	cancelled = true,
	declined = true,
	declined_full = true,
	declined_delisted = true,
	invitedeclined = true,
	timedout = true,
	inviteaccepted = true,
}

local function getActivityInfo()
	if not C_LFGList or not C_LFGList.GetActiveEntryInfo then
		return nil
	end
	local entry = C_LFGList.GetActiveEntryInfo()
	if not entry or not entry.activityIDs or not entry.activityIDs[1] then
		return nil
	end
	if C_LFGList.GetActivityInfoTable then
		return C_LFGList.GetActivityInfoTable(entry.activityIDs[1], entry.questID)
	end
	return nil
end

local function isGrayedOut(appInfo)
	if not appInfo then
		return true
	end
	if appInfo.pendingApplicationStatus then
		return false
	end
	local status = appInfo.applicationStatus
	return status and GRAYED_STATUSES[status] or false
end

local function getBlocklistMatch(playerName)
	local bl = GF.Blocklist
	if not playerName or playerName == "" or not bl then
		return nil
	end
	if bl.IsEnabled and not bl:IsEnabled() then
		return nil
	end
	if bl.FindPlayerMatch then
		return bl:FindPlayerMatch(playerName)
	end
	return nil
end

local function getSpecName(specID)
	if specID and PlayerUtil and PlayerUtil.GetSpecNameBySpecID then
		local specName = PlayerUtil.GetSpecNameBySpecID(specID)
		if specName and specName ~= "" then
			return specName
		end
	end
	return nil
end

local function specDisplayName(specID, localizedClass)
	local className = localizedClass or ""
	local specName = getSpecName(specID)
	if specName then
		return specName
	end
	return className
end

local function memberItemLevel(activityInfo, itemLevel, pvpItemLevel)
	if activityInfo and activityInfo.isPvpActivity then
		return math.floor(pvpItemLevel or 0)
	end
	return math.floor(itemLevel or 0)
end

local function getListingDungeonScore(applicantID, memberIdx)
	if not C_LFGList or not C_LFGList.GetApplicantDungeonScoreForListing then
		return nil
	end
	local entry = C_LFGList.GetActiveEntryInfo()
	local actID = entry and entry.activityIDs and entry.activityIDs[1]
	if not actID then
		return nil
	end
	return C_LFGList.GetApplicantDungeonScoreForListing(applicantID, memberIdx, actID)
end

local function getBestDungeonScore(applicantID, memberIdx)
	if not C_LFGList or not C_LFGList.GetApplicantBestDungeonScore then
		return nil
	end
	local ok, scoreInfo = pcall(C_LFGList.GetApplicantBestDungeonScore, applicantID, memberIdx)
	return ok and scoreInfo or nil
end

local function memberRatingText(applicantID, memberIdx, level, dungeonScore, activityInfo)
	local maxLevel = (GetMaxLevelForPlayerExpansion and GetMaxLevelForPlayerExpansion()) or (MAX_PLAYER_LEVEL or 80)
	if level and level < maxLevel then
		return "level", level, nil, nil
	end
	if activityInfo and activityInfo.isRatedPvpActivity and C_LFGList.GetApplicantPvpRatingInfoForListing then
		local entry = C_LFGList.GetActiveEntryInfo()
		local actID = entry and entry.activityIDs and entry.activityIDs[1]
		if actID then
			local pvp = C_LFGList.GetApplicantPvpRatingInfoForListing(applicantID, memberIdx, actID)
			if pvp and pvp.rating then
				return "rating", pvp.rating, HIGHLIGHT_FONT_COLOR, pvp
			end
		end
	end
	if activityInfo and activityInfo.isMythicPlusActivity then
		local overall = (dungeonScore and dungeonScore > 0) and dungeonScore or nil
		local listing = getListingDungeonScore(applicantID, memberIdx)
		local bestOverall = getBestDungeonScore(applicantID, memberIdx)
		return "mplus", nil, nil, {
			overall = overall,
			mapScore = listing and listing.mapScore,
			bestRunLevel = listing and listing.bestRunLevel,
			finishedSuccess = listing and listing.finishedSuccess,
			bestLevelIncrement = listing and listing.bestLevelIncrement,
			mapName = listing and listing.mapName,
			currentDungeon = listing,
			bestOverallScore = bestOverall,
		}
	end
	local score = dungeonScore
	if score and score > 0 then
		local color = (C_ChallengeMode and C_ChallengeMode.GetDungeonScoreRarityColor and C_ChallengeMode.GetDungeonScoreRarityColor(score))
			or HIGHLIGHT_FONT_COLOR
		return "rating", score, color, nil
	end
	return "rating", nil, nil, nil
end

local function buildRoles(tank, healer, damage)
	local role1 = tank and "TANK" or (healer and "HEALER" or (damage and "DAMAGER"))
	local role2 = (tank and healer and "HEALER") or ((tank or healer) and damage and "DAMAGER")
	local role3 = (tank and healer and damage and "DAMAGER")
	return role1, role2, role3
end

local function normalizeApplicantAPIID(applicantID)
	applicantID = tonumber(applicantID)
	if not applicantID or applicantID < 0 or applicantID > MAX_APPLICANT_API_ID then
		return nil
	end
	return applicantID
end

function AM:GetSortedApplicantIDs()
	local ids = C_LFGList.GetApplicants() or {}
	if LFGListUtil_SortApplicants then
		LFGListUtil_SortApplicants(ids)
	end
	if GF.ApplicantTestData and GF.ApplicantTestData.IsEnabled and GF.ApplicantTestData:IsEnabled() then
		local testIDs = GF.ApplicantTestData:GetApplicantIDs()
		for _, applicantID in ipairs(testIDs) do
			ids[#ids + 1] = applicantID
		end
	end
	return ids
end

function AM:BuildMember(applicantID, memberIdx, appInfo, activityInfo)
	local name, class, localizedClass, level, itemLevel, honorLevel, tank, healer, damage, assignedRole, relationship, dungeonScore, pvpItemLevel, factionGroup, _, specID, isLeaver =
		C_LFGList.GetApplicantMemberInfo(applicantID, memberIdx)

	local grayed = isGrayedOut(appInfo)
	local role1, role2, role3 = buildRoles(tank, healer, damage)
	local ratingKind, ratingValue, ratingColor, ratingDetail = memberRatingText(applicantID, memberIdx, level, dungeonScore, activityInfo)
	local blacklistEntry = getBlocklistMatch(name)
	local specName = getSpecName(specID)

	local playerFaction = UnitFactionGroup("player")
	local showFaction = false
	if factionGroup and playerFaction and PLAYER_FACTION_GROUP and PLAYER_FACTION_GROUP[factionGroup] then
		showFaction = playerFaction ~= PLAYER_FACTION_GROUP[factionGroup]
	end

	return {
		memberIdx = memberIdx,
		name = name,
		displayName = name and Ambiguate(name, "short") or nil,
		class = class,
		localizedClass = localizedClass,
		specID = specID,
		specName = specName,
		specText = specName or specDisplayName(specID, localizedClass),
		activityInfo = activityInfo,
		level = level,
		honorLevel = honorLevel,
		ilvl = memberItemLevel(activityInfo, itemLevel, pvpItemLevel),
		role1 = role1,
		role2 = role2,
		role3 = role3,
		assignedRole = assignedRole,
		relationship = relationship,
		isLeaver = isLeaver,
		isBlacklisted = blacklistEntry ~= nil,
		blacklistEntry = blacklistEntry,
		factionGroup = factionGroup,
		showFactionIcon = showFaction,
		grayed = grayed,
		noTouchy = appInfo.applicationStatus == "invited",
		ratingKind = ratingKind,
		ratingValue = ratingValue,
		ratingColor = ratingColor,
		ratingDetail = ratingDetail,
		comment = appInfo.comment or "",
	}
end

function AM:BuildApplicant(applicantID)
	if GF.ApplicantTestData and GF.ApplicantTestData.IsTestApplicantID
		and GF.ApplicantTestData:IsTestApplicantID(applicantID) then
		return GF.ApplicantTestData:BuildApplicant(applicantID)
	end
	applicantID = normalizeApplicantAPIID(applicantID)
	if not applicantID then
		return nil
	end
	local appInfo = C_LFGList.GetApplicantInfo(applicantID)
	if not appInfo then
		return nil
	end
	local activityInfo = getActivityInfo()
	local numMembers = appInfo.numMembers or 1
	local members = {}
	for i = 1, numMembers do
		members[i] = self:BuildMember(applicantID, i, appInfo, activityInfo)
	end

	local status = appInfo.applicationStatus
	local statusText, statusColor
	if appInfo.applicantInfo then
		statusText = nil
	elseif status then
		statusText = GF.Listing:GetApplicantStatusMessage(status)
		if status == "invited" or status == "inviteaccepted" then
			statusColor = STATUS_GREEN
		elseif status == "failed" or status == "cancelled" or status == "declined"
			or status == "declined_full" or status == "declined_delisted"
			or status == "timedout" or status == "invitedeclined" then
			statusColor = STATUS_GRAY
		end
	end

	local showActions = not appInfo.applicantInfo
		and status == "applied"

	local declineIsAck = status ~= "applied" and status ~= "invited"
	local useCompactInvite = activityInfo and (activityInfo.isMythicPlusActivity or activityInfo.isRatedPvpActivity)

	return {
		applicantID = applicantID,
		appInfo = appInfo,
		members = members,
		numMembers = numMembers,
		comment = appInfo.comment or "",
		loading = appInfo.applicantInfo ~= nil,
		isNew = appInfo.isNew,
		status = status,
		statusText = statusText,
		statusColor = statusColor,
		grayed = isGrayedOut(appInfo),
		showInvite = showActions,
		showDecline = showActions,
		declineIsAck = declineIsAck,
		canInvite = showActions and GF.Listing:CanInviteApplicant(applicantID),
		canDecline = showActions and GF.Listing:CanDeclineApplicant(applicantID),
		useCompactInvite = useCompactInvite,
		activityInfo = activityInfo,
	}
end

function AM.GetLabelGold()
	return LABEL_GOLD.r, LABEL_GOLD.g, LABEL_GOLD.b
end

function AM.GetIlvlValueColor()
	return IL_VALUE.r, IL_VALUE.g, IL_VALUE.b
end
