local event = require("event.event")
local const = require("druid.const")
local helper = require("druid.helper")
local component = require("druid.component")

---@class druid.swipe.style
---@field SWIPE_TIME number|nil Maximum time for swipe trigger. Default: 0.4
---@field SWIPE_THRESHOLD number|nil Minimum distance for swipe trigger. Default: 50
---@field SWIPE_TRIGGER_ON_MOVE boolean|nil If true, trigger on swipe moving, not only release action. Default: false

---The component to manage swipe events over a node
---@class druid.swipe: druid.component
---@field node node The node to manage the swipe
---@field on_swipe event fun(context, side, dist, dt) The event triggered when a swipe is detected
---@field style druid.swipe.style The style of the swipe
---@field click_zone node The click zone of the swipe
---@field private _trigger_on_move boolean True if the swipe should trigger on move
---@field private _swipe_start_time number The time the swipe started
---@field private _start_pos vector3 The start position of the swipe
---@field private _is_enabled boolean True if the swipe is enabled
---@field private _is_mobile boolean True if the swipe is on a mobile device
local M = component.create("swipe")


---@param node_or_node_id node|string
---@param on_swipe_callback function
function M:init(node_or_node_id, on_swipe_callback)
	self._trigger_on_move = self.style.SWIPE_TRIGGER_ON_MOVE
	self.node = self:get_node(node_or_node_id)

	self._swipe_start_time = 0
	self._start_pos = vmath.vector3(0)

	self.click_zone = nil
	self.on_swipe = event.create(on_swipe_callback)
end


---@private
function M:on_late_init()
	if not self.click_zone then
		local stencil_node = helper.get_closest_stencil_node(self.node)
		if stencil_node then
			self:set_click_zone(stencil_node)
		end
	end
end


---@private
---@param style druid.swipe.style
function M:on_style_change(style)
	self.style = {
		SWIPE_TIME = style.SWIPE_TIME or 0.4,
		SWIPE_THRESHOLD = style.SWIPE_THRESHOLD or 50,
		SWIPE_TRIGGER_ON_MOVE = style.SWIPE_TRIGGER_ON_MOVE or false,
	}
end


---@private
---@param action_id hash The action id
---@param action action The action table
---@return boolean is_consumed True if the input was consumed
function M:on_input(action_id, action)
	if action_id ~= const.ACTION_TOUCH then
		return false
	end

	if not gui.is_enabled(self.node, true) then
		return false
	end

	local is_pick = helper.pick_node(self.node, action.x, action.y, self.click_zone)
	if not is_pick then
		self:_reset_swipe()
		return false
	end

	if self._swipe_start_time ~= 0 and (self._trigger_on_move or action.released) then
		self:_check_swipe(action)
	end

	if action.pressed then
		self:_start_swipe(action)
	end

	if action.released then
		self:_reset_swipe()
	end

	return true
end


---@private
function M:on_input_interrupt()
	self:_reset_swipe()
end


---Set the click zone for the swipe, useful for restricting events outside stencil node
---@param zone node|string|nil Gui node
function M:set_click_zone(zone)
	if not zone then
		self.click_zone = nil
		return
	end

	self.click_zone = self:get_node(zone)
end


---Start swipe event
---@param action action The action table
function M:_start_swipe(action)
	self._swipe_start_time = socket.gettime()
	self._start_pos.x = action.x
	self._start_pos.y = action.y
end


---Reset swipe event
function M:_reset_swipe()
	self._swipe_start_time = 0
end


---Check swipe event
---@param self druid.swipe
---@param action action
function M:_check_swipe(action)
	local dx = action.x - self._start_pos.x
	local dy = action.y - self._start_pos.y
	local dist = helper.distance(self._start_pos.x, self._start_pos.y, action.x, action.y)
	local delta_time = socket.gettime() - self._swipe_start_time
	local is_swipe = self.style.SWIPE_THRESHOLD <= dist and delta_time <= self.style.SWIPE_TIME

	if is_swipe then
		local is_x_swipe = math.abs(dx) >= math.abs(dy)
		local swipe_side = "undefined"

		if is_x_swipe and dx > 0 then
			swipe_side = "right"
		end
		if is_x_swipe and dx < 0 then
			swipe_side = "left"
		end
		if not is_x_swipe and dy > 0 then
			swipe_side = "up"
		end
		if not is_x_swipe and dy < 0 then
			swipe_side = "down"
		end

		self.on_swipe:trigger(self:get_context(), swipe_side, dist, delta_time)
		self:_reset_swipe()
	end
end


return M
