local discordia = require("discordia")
local endpoints = require('./endpoints')
local f = string.format
local AC = require('./ApplicationCommand')
local IA = require('./Interaction')
local client_m = discordia.Client
local guild_m = discordia.class.classes.Guild
local cache_m = discordia.class.classes.Cache
local enums = require('./enums')

local typeConverter = {
	[enums.optionType.string] = function(val) return val end,
	[enums.optionType.integer] = function(val) return val end,
	[enums.optionType.boolean] = function(val) return val end,
	[enums.optionType.user] = function(val, args) return args:getMember(val) end,
	[enums.optionType.channel] = function(val, args) return args:getChannel(val) end,
	[enums.optionType.role] = function(val, args) return args:getRole(val) end,
}

local subCommand = enums.optionType.subCommand
local subCommandGroup = enums.optionType.subCommandGroup

local function makeParams(data, guild, output)
	output = output or {}

	for k, v in ipairs(data) do
		if v.type == subCommand or v.type == subCommandGroup then
			local t = {}
			output[v.name] = t
			makeParams(v.options, guild, t)
		else
			output[v.name] = typeConverter[v.type](v.value, guild)
		end
	end

	return output
end

function client_m:useSlashCommands()
	self._slashCommandsInjected = true

	function self._events.INTERACTION_CREATE(args, client)
		local data = args.data
		local cmd = client:getSlashCommand(data.id)
		if not cmd then return client:warning('Uncached slash command (%s) on INTERACTION_CREATE', data.id) end
		if data.name ~= cmd._name then return client:warning('Slash command %s "%s" name doesn\'t match with interaction response, got "%s"! Guild %s, channel %s, member %s', cmd._id, cmd._name, data.name, args.guild_id, args.channel_id, args.member.user.id) end
		local ia = IA(args, client)
		local params = makeParams(data.options, ia.guild)
		local cb = cmd._callback
		if not cb then return client:warning('Unhandled slash command interaction: %s "%s" (%s)!', cmd._id, cmd._name, cmd._guild and "Guild " .. cmd._guild.id or "Global") end
		cb(ia, params, cmd)
	end

	self:once("ready", function()
		local id = self:getApplicationInformation().id
		self._slashid = id
		self._globalCommands = {}
		self._guildCommands = {}
		self:getSlashCommands()
		self:emit("slashCommandsReady")
	end)

	return self
end

function client_m:slashCommand(data)
	local found

	if not self._globalCommands then
		self:getSlashCommands()
	end

	do
		local name = data.name

		for _, v in pairs(self._globalCommands) do
			if v._name == name then
				found = v
				break
			end
		end
	end

	local cmd = AC(data, self)

	if found then
		if not found:_compare(cmd) then
			found:_merge(cmd)
		elseif not found._callback then
			found._callback = cmd._callback
		end

		return found
	else
		if cmd:publish() then
			self._globalCommands:_insert(cmd)
		else
			return nil
		end
	end

	return cmd
end

function guild_m:slashCommand(data)
	local found

	if not self._slashCommands then
		self:getSlashCommands()
	end

	do
		local name = data.name

		for _, v in pairs(self._slashCommands) do
			if v._name == name then
				found = v
				break
			end
		end
	end

	local cmd = AC(data, self)

	if found then
		if not found:_compare(cmd) then
			found:_merge(cmd)
		elseif not found._callback then
			found._callback = cmd._callback
		end

		return found
	else
		if cmd:publish() then
			self._slashCommands:_insert(cmd)
		else
			return nil
		end
	end

	return cmd
end

function client_m:getSlashCommands()
	local list, err = self._api:request('GET', f(endpoints.COMMANDS, self._slashid))
	if not list then return nil, err end
	local cache = cache_m(list, AC, self)
	self._globalCommands = cache

	return cache
end

function guild_m:getSlashCommands()
	local list, err = self.client._api:request('GET', f(endpoints.COMMANDS_GUILD, self.client._slashid, self.id))
	if not list then return nil, err end
	local cache = cache_m(list, AC, self)
	self._slashCommands = cache
	self.client._guildCommands[self] = cache

	return cache
end

function client_m:getSlashCommand(id)
	if not self._globalCommands then
		self:getSlashCommands()
	end

	local g = self._globalCommands:get(id)
	if g then return g end

	for _, v in pairs(self._guildCommands) do
		g = v:get(id)
		if g then return g end
	end

	return nil
end