local discordia = require("discordia")
local endpoints = require('./endpoints')
local f = string.format
local Snowflake_m = discordia.class.classes.Snowflake
local AC, ACgetters = discordia.class('ApplicationCommand', Snowflake_m)

local function recursiveOptionsMap(t)
	local map = {}

	for _, v in ipairs(t) do
		local name = string.lower(v.name)
		v.name = name
		map[name] = v

		if v.options then
			v.mapoptions = recursiveOptionsMap(v.options)
		end
	end

	return map
end

function AC:__init(data, parent)
	self._id = data.id
	self._parent = parent
	self._name = data.name
	self._description = data.description
	self._default_permission = data.default_permission
	self._version = data.version
	self._callback = data.callback
	self._guild = parent._id and parent

	if not self._options then
		self._options = data.options or {}
	end
end

function AC:publish()
	if self._id then return self:edit() end
	local g = self._guild

	if not g then
		local res, err = self.client._api:request('POST', f(endpoints.COMMANDS, self.client._slashid), {
			name = self._name,
			description = self._description,
			options = self._options,
			default_permission = self._default_permission
		})

		if not res then
			return nil, err
		else
			self._id = res.id

			return self
		end
	else
		local res, err = self.client._api:request('POST', f(endpoints.COMMANDS_GUILD, self.client._slashid, g._id), {
			name = self._name,
			description = self._description,
			options = self._options,
			default_permission = self._default_permission
		})

		if not res then
			return nil, err
		else
			self._id = res.id

			return true
		end
	end
end

function AC:edit()
	local g = self._guild

	if not g then
		local res, err = self.client._api:request('PATCH', f(endpoints.COMMANDS_MODIFY, self.client._slashid, self._id), {
			name = self._name,
			description = self._description,
			options = self._options,
			default_permission = self._default_permission
		})

		if not res then
			return nil, err
		else
			return true
		end
	else
		local res, err = self.client._api:request('PATCH', f(endpoints.COMMANDS_MODIFY_GUILD, self.client._slashid, g._id, self._id), {
			name = self._name,
			description = self._description,
			options = self._options,
			default_permission = self._default_permission
		})

		if not res then
			return nil, err
		else
			return true
		end
	end
end

function AC:setName(name)
	self._name = name
end

function AC:setDescription(description)
	self._description = description
end

function AC:setOptions(options)
	self._options = options
end

function AC:setCallback(callback)
	self._callback = callback
end

function AC:delete()
	local g = self._guild

	if not g then
		self.client._api:request('DELETE', f(endpoints.COMMANDS_MODIFY, self.client._slashid, self._id))
		self.client._globalCommands:_delete(self._id)
	else
		self.client._api:request('DELETE', f(endpoints.COMMANDS_MODIFY_GUILD, self.client._slashid, g._id, self._id))
		g._slashCommands:_delete(self._id)
	end
end

function AC:getPermissions(g)
	g = self._guild or g

	if not g then
		error("Guild is required")
	end

	local stat, err = self.client._api:request('GET', f(endpoints.COMMAND_PERMISSIONS_MODIFY, self.client._slashid, g._id, self._id))

	if stat then
		return stat.permissions
	else
		return stat, err
	end
end

function AC:addPermission(perm, g)
	g = self._guild or g

	if not g then
		error("Guild is required")
	end

	if not self._permissions then
		self._permissions = self:getPermissions(g) or {}
	end

	for k, v in ipairs(self._permissions) do
		if v.id == perm.id and v.type == perm.type then
			if v.permission == perm.permission then return end
			self._permissions[k] = perm
			goto found
		end
	end

	do
		self._permissions[#self._permissions + 1] = perm
	end

	::found::

	return self.client._api:request('PUT', f(endpoints.COMMAND_PERMISSIONS_MODIFY, self.client._slashid, g._id, self._id), {
		permissions = self._permissions
	})
end

function AC:removePermission(perm, g)
	g = self._guild or g

	if not g then
		error("Guild is required")
	end

	if not self._permissions then
		self._permissions = self:getPermissions(g) or {}
	end

	for k, v in ipairs(self._permissions) do
		if v.id == perm.id and v.type == perm.type then
			table.remove(self._permissions, k)
			return
		end
	end

	return self.client._api:request('PUT', f(endpoints.COMMAND_PERMISSIONS_MODIFY, self.client._slashid, g._id, self._id), {
		permissions = self._permissions
	})
end

local function recursiveCompare(a, b, checked)
	checked = checked or {}
	if checked[a] or checked[b] then return true end
	local inner_checked = {}

	for k, v in pairs(a) do
		if type(v) == "table" and type(b[k]) == "table" then
			if not recursiveCompare(v, b[k], checked) then return false end
		elseif v ~= b[k] then
			print("k: ", k, "a[k]:", v, "b[k]: ", b[k])

			return false
		else
			inner_checked[k] = true
		end
	end

	for k, v in pairs(b) do
		if inner_checked[k] then
			goto skip
		end

		if type(v) == "table" and type(a[k]) == "table" then
			if not recursiveCompare(v, a[k], checked) then return false end
		elseif v ~= a[k] then
			print("k: ", k, "a[k]:", a[k], "b[k]: ", v)

			return false
		end

		::skip::
	end

	checked[a], checked[b] = true, true

	return true
end

function AC:_compare(cmd)
	if self._name ~= cmd._name or self._description ~= cmd._description or self._default_permission ~= cmd._default_permission then return false end
	if not self._options and cmd._options then return false end
	if not recursiveCompare(self._options, cmd._options) then return false end

	return true
end

function AC:_merge(cmd)
	self._name = cmd._name
	self._description = cmd._description
	self._options = cmd._options
	self._callback = cmd._callback
	self._default_permission = cmd._default_permission
	self:edit()
end

function ACgetters:name()
	return self._name
end

function ACgetters:description()
	return self._description
end

function ACgetters:options()
	return self._options
end

function ACgetters:guild()
	return self._guild
end

function ACgetters:callback()
	return self._callback
end

function ACgetters:version()
	return self._version
end

return AC