--///////////////// Internal-Use Methods
--//////////////////////////////////////
function methods:InternalDestroy()
	for i, speaker in pairs(self.Speakers) do
		speaker:LeaveChannel(self.Name)
	end

	self.eDestroyed:Fire()

	self.eDestroyed:Destroy()
	self.eMessagePosted:Destroy()
	self.eSpeakerJoined:Destroy()
	self.eSpeakerLeft:Destroy()
	self.eSpeakerMuted:Destroy()
	self.eSpeakerUnmuted:Destroy()
end

function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
	local filtersIterator = self.FilterMessageFunctions:GetIterator()
	for funcId, func, priority in filtersIterator do
		local success, errorMessage = pcall(function()
			func(speakerName, messageObj, channel)
		end)

		if not success then
			warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
		end
	end
end

function methods:InternalDoProcessCommands(speakerName, message, channel)
	local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
	for funcId, func, priority in commandsIterator do
		local success, returnValue = pcall(function()
			local ret = func(speakerName, message, channel)
			if type(ret) ~= "boolean" then
				error("Process command functions must return a bool")
			end
			return ret
		end)

		if not success then
			warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
		elseif returnValue then
			return true
		end
	end

	return false
end

function methods:InternalPostMessage(fromSpeaker, message, extraData)
	if (self:InternalDoProcessCommands(fromSpeaker.Name, message, self.Name)) then return false end

	if (self.Mutes[fromSpeaker.Name:lower()] ~= nil) then
		local t = self.Mutes[fromSpeaker.Name:lower()]
		if (t > 0 and os.time() > t) then
			self:UnmuteSpeaker(fromSpeaker.Name)
		else
			self:SendSystemMessageToSpeaker(ChatLocalization:FormatMessageToSend("GameChat_ChatChannel_MutedInChannel","You are muted and cannot talk in this channel"), fromSpeaker.Name)
			return false
		end
	end

	local messageObj = self:InternalCreateMessageObject(message, fromSpeaker.Name, false, extraData)

	-- allow server to process the unfiltered message string
	messageObj.Message = message
	local processedMessage
	pcall(function()
		processedMessage = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage, messageObj)
	end)
	messageObj.Message = nil

	if processedMessage then

		-- developer server code's choice to mute the message
		if processedMessage.ShouldDeliver == false then
			return false
		end
		messageObj = processedMessage
	end

	message = self:SendMessageObjToFilters(message, messageObj, fromSpeaker)

	local sentToList = {}
	for i, speaker in pairs(self.Speakers) do
		local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
		if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
			table.insert(sentToList, speaker.Name)
			if speaker.Name == fromSpeaker.Name then
				-- Send unfiltered message to speaker who sent the message.
				local cMessageObj = ShallowCopy(messageObj)
				if userShouldMuteUnfilteredMessage then
					local messageLength = messageObj.MessageLengthUtf8 or messageObj.MessageLength
					cMessageObj.Message = string.rep("_", messageLength)
				else
					cMessageObj.Message = message
				end
				cMessageObj.IsFiltered = true
				-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
				speaker:InternalSendMessage(cMessageObj, self.Name)
			else
				speaker:InternalSendMessage(messageObj, self.Name)
			end
		end
	end

	local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
	if not success and err then
		print("Error posting message: " ..err)
	end

	local textFilterContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
	local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
		messageObj.FromSpeaker,
		message,
		textFilterContext
	)
	if (filterSuccess) then
		messageObj.FilterResult = filteredMessage
		messageObj.IsFilterResult = isFilterResult
	else
		if UserFlagRemoveMessageOnTextFilterFailures then
			messageObj.IsFilterResult = false
			messageObj.FilterResult = ""
			messageObj.MessageLength = 0
		else
			return false
		end
	end
	messageObj.IsFiltered = true
	self:InternalAddMessageToHistoryLog(messageObj)

	for _, speakerName in pairs(sentToList) do
		local speaker = self.Speakers[speakerName]
		if (speaker) then
			speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
		end
	end

	-- One more pass is needed to ensure that no speakers do not recieve the message.
	-- Otherwise a user could join while the message is being filtered who had not originally been sent the message.
	local speakersMissingMessage = {}
	for _, speaker in pairs(self.Speakers) do
		local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
		if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
			local wasSentMessage = false
			for _, sentSpeakerName in pairs(sentToList) do
				if speaker.Name == sentSpeakerName then
					wasSentMessage = true
					break
				end
			end
			if not wasSentMessage then
				table.insert(speakersMissingMessage, speaker.Name)
			end
		end
	end

	for _, speakerName in pairs(speakersMissingMessage) do
		local speaker = self.Speakers[speakerName]
		if speaker then
			speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
		end
	end

	return messageObj
end

function methods:InternalAddSpeaker(speaker)
	if (self.Speakers[speaker.Name]) then
		warn("Speaker \"" .. speaker.name .. "\" is already in the channel!")
		return
	end

	self.Speakers[speaker.Name] = speaker
	local success, err = pcall(function() self.eSpeakerJoined:Fire(speaker.Name) end)
	if not success and err then
		print("Error removing channel: " ..err)
	end
end

function methods:InternalRemoveSpeaker(speaker)
	if (not self.Speakers[speaker.Name]) then
		warn("Speaker \"" .. speaker.name .. "\" is not in the channel!")
		return
	end

	self.Speakers[speaker.Name] = nil
	local success, err = pcall(function() self.eSpeakerLeft:Fire(speaker.Name) end)
	if not success and err then
		print("Error removing speaker: " ..err)
	end
end

function methods:InternalRemoveExcessMessagesFromLog()
	local remove = table.remove
	while (#self.ChatHistory > self.MaxHistory) do
		remove(self.ChatHistory, 1)
	end
end

function methods:InternalAddMessageToHistoryLog(messageObj)
	table.insert(self.ChatHistory, messageObj)

	self:InternalRemoveExcessMessagesFromLog()
end

function methods:GetMessageType(message, fromSpeaker)
	if fromSpeaker == nil then
		return ChatConstants.MessageTypeSystem
	end
	return ChatConstants.MessageTypeDefault
end

function methods:InternalCreateMessageObject(message, fromSpeaker, isFiltered, extraData)
	local messageType = self:GetMessageType(message, fromSpeaker)

	local speakerUserId = -1
	local speakerDisplayName = nil
	local speaker = nil

	if fromSpeaker then
		speaker = self.ChatService:GetSpeaker(fromSpeaker)
		if speaker then
			local player = speaker:GetPlayer()
			if player then
				speakerUserId = player.UserId

				if ChatSettings.PlayerDisplayNamesEnabled then
					speakerDisplayName = speaker:GetNameForDisplay()
				end
			else
				speakerUserId = 0
			end
		end
	end

	local messageObj =
	{
		ID = self.ChatService:InternalGetUniqueMessageId(),
		FromSpeaker = fromSpeaker,
		SpeakerDisplayName = speakerDisplayName,
		SpeakerUserId = speakerUserId,
		OriginalChannel = self.Name,
		MessageLength = string.len(message),
		MessageLengthUtf8 = utf8.len(utf8.nfcnormalize(message)),
		MessageType = messageType,
		IsFiltered = isFiltered,
		Message = isFiltered and message or nil,
		--// These two get set by the new API. The comments are just here
		--// to remind readers that they will exist so it's not super
		--// confusing if they find them in the code but cannot find them
		--// here.
		--FilterResult = nil,
		--IsFilterResult = false,
		Time = os.time(),
		ExtraData = {},
	}

	if speaker then
		for k, v in pairs(speaker.ExtraData) do
			messageObj.ExtraData[k] = v
		end
	end

	if (extraData) then
		for k, v in pairs(extraData) do
			messageObj.ExtraData[k] = v
		end
	end

	return messageObj
end

function methods:SetChannelNameColor(color)
	self.ChannelNameColor = color
	for i, speaker in pairs(self.Speakers) do
		speaker:UpdateChannelNameColor(self.Name, color)
	end
end

function methods:GetWelcomeMessageForSpeaker(speaker)
	if self.GetWelcomeMessageFunction then
		local welcomeMessage = self.GetWelcomeMessageFunction(speaker)
		if welcomeMessage then
			return welcomeMessage
		end
	end
	return self.WelcomeMessage
end

function methods:RegisterGetWelcomeMessageFunction(func)
	if type(func) ~= "function" then
		error("RegisterGetWelcomeMessageFunction must be called with a function.")
	end
	self.GetWelcomeMessageFunction = func
end

function methods:UnRegisterGetWelcomeMessageFunction()
	self.GetWelcomeMessageFunction = nil
end