voiceData = {}
radioData = {}
callData = {}

local mappedChannels = {}
function firstFreeChannel()
	for i = 1, 2048 do
		if not mappedChannels[i] then
			return i
		end
	end

	return 0
end

function defaultTable(source)
	handleStateBagInitilization(source)
	return {
		radio = 0,
		call = 0,
		lastRadio = 0,
		lastCall = 0
	}
end

function handleStateBagInitilization(source)
	local plyState = Player(source).state
	if not plyState.pmaVoiceInit then
		plyState:set('radio', GetConvarInt('voice_defaultRadioVolume', 30), true)
		plyState:set('call', GetConvarInt('voice_defaultCallVolume', 60), true)
		plyState:set('submix', nil, true)
		plyState:set('proximity', {}, true)
		plyState:set('callChannel', 0, true)
		plyState:set('radioChannel', 0, true)
		plyState:set('voiceIntent', 'speech', true)
		-- We want to save voice inits because we'll automatically reinitalize calls and channels
		plyState:set('pmaVoiceInit', true, false)
	end

	local assignedChannel = firstFreeChannel()
	plyState:set('assignedChannel', assignedChannel, true)
	if assignedChannel ~= 0 then
		mappedChannels[assignedChannel] = source
		logger.verbose('[reuse] Assigned %s to channel %s', source, assignedChannel)
	else
		logger.error('[reuse] Failed to find a free channel for %s', source)
	end
end

CreateThread(function()
	local plyTbl = GetPlayers()
	for i = 1, #plyTbl do
		local ply = tonumber(plyTbl[i])
		voiceData[ply] = defaultTable(ply)
	end

	Wait(5000)

	local nativeAudio = GetConvar('voice_useNativeAudio', 'not-set')
	local _3dAudio = GetConvar('voice_use3dAudio', 'not-set')
	local _2dAudio = GetConvar('voice_use2dAudio', 'not-set')
	local sendingRangeOnly = GetConvar('voice_useSendingRangeOnly', 'not-set')
	local gameVersion = GetConvar('gamename', 'fivem')

	-- handle no convars being set (default drag n' drop)
	if
		nativeAudio == 'not-set'
		and _3dAudio == 'not-set'
		and _2dAudio == 'not-set'
	then
		SetConvarReplicated('voice_useNativeAudio', 'true')
		logger.info('No voice mod detected, defaulting to \'setr voice_useNativeAudio true\'')
	elseif sendingRangeOnly == 'true' then
		logger.warn(
			"It's recommended to have 'voice_useSendingRangeOnly' set to false, you can do that with 'setr voice_useSendingRangeOnly false', this makes clients ignore the position information sent from the client.")
	end

	local radioVolume = GetConvarInt("voice_defaultRadioVolume", 30)
	local callVolume = GetConvarInt("voice_defaultCallVolume", 60)

	-- When casted to an integer these get set to 0 or 1, so warn on these values that they don't work
	if
		radioVolume == 0 or radioVolume == 1 or
		callVolume == 0 or callVolume == 1
	then
		SetConvarReplicated("voice_defaultRadioVolume", 30)
		SetConvarReplicated("voice_defaultCallVolume", 60)
		for i = 1, 5 do
			Wait(5000)
			logger.warn(
				"`voice_defaultRadioVolume` or `voice_defaultCallVolume` have their value set as a float, this is going to automatically be fixed but please update your convars.")
		end
	end
end)

AddEventHandler('playerJoining', function()
	if not voiceData[source] then
		voiceData[source] = defaultTable(source)
	end
end)

AddEventHandler("playerDropped", function()
	local source = source
	local mappedChannel = Player(source).state.assignedChannel

	if voiceData[source] then
		local plyData = voiceData[source]

		if plyData.radio ~= 0 then
			removePlayerFromRadio(source, plyData.radio)
		end

		if plyData.call ~= 0 then
			removePlayerFromCall(source, plyData.call)
		end

		voiceData[source] = nil
	end

	if mappedChannel then
		mappedChannels[mappedChannel] = nil
		logger.verbose('[reuse] Unassigned %s from channel %s', source, mappedChannel)
	end
end)

if GetConvarInt('voice_externalDisallowJoin', 0) == 1 then
	AddEventHandler('playerConnecting', function(_, _, deferral)
		deferral.defer()
		Wait(0)
		deferral.done('This server is not accepting connections.')
	end)
end

-- only meant for internal use so no documentation
function isValidPlayer(source)
	return voiceData[source]
end

exports('isValidPlayer', isValidPlayer)

function getPlayersInRadioChannel(channel)
	local returnChannel = radioData[channel]
	if returnChannel then
		return returnChannel
	end
	-- channel doesnt exist
	return {}
end

exports('getPlayersInRadioChannel', getPlayersInRadioChannel)
exports('GetPlayersInRadioChannel', getPlayersInRadioChannel)
