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

---@class druid.tiling_node: druid.component
---@field animation table
---@field node node
---@field params vector4
---@field time number
local M = component.create("tiling_node")

M.PROP_SIZE_X = hash("size.x")
M.PROP_SIZE_Y = hash("size.y")
M.PROP_SCALE_X = hash("scale.x")
M.PROP_SCALE_Y = hash("scale.y")


---@param node node|string
function M:init(node)
	self.node = self:get_node(node)
	self.animation = nil
	self.time = 0
	self.margin = 0

	self.params = gui.get(self.node, "params") --[[@as vector4]]

	self.timer_no_init = timer.delay(0.1, false, function()
		print("The druid.script is not found, please add it nearby to the GUI collection", msg.url())
	end)

	defer.push("druid.get_atlas_path", {
		texture_name = gui.get_texture(self.node),
		sender = msg.url(),
	}, self.on_get_atlas_path, self)
end


---@param atlas_path string
function M:on_get_atlas_path(atlas_path)
	timer.cancel(self.timer_no_init)
	self.is_inited = self:init_tiling_animation(atlas_path)
	local repeat_x, repeat_y = self:get_repeat_count_from_node()
	self:start_animation(repeat_x, repeat_y)
end


---@param node node
---@param property string
function M:on_node_property_changed(node, property)
	if not self.is_inited or node ~= self.node then
		return
	end

	if property == "size" or property == "scale" then
		local repeat_x, repeat_y = self:get_repeat_count_from_node()
		self:set_repeat(repeat_x, repeat_y)
	end
end


function M:get_repeat_count_from_node()
	if not self.is_inited then
		return 1, 1
	end
	local size_x = gui.get(self.node, M.PROP_SIZE_X)
	local size_y = gui.get(self.node, M.PROP_SIZE_Y)
	local scale_x = gui.get(self.node, M.PROP_SCALE_X)
	local scale_y = gui.get(self.node, M.PROP_SCALE_Y)

	local repeat_x = (size_x / self.animation.width) / scale_x
	local repeat_y = (size_y / self.animation.height) / scale_y

	return repeat_x, repeat_y
end


---@param atlas_path string
---@return boolean
function M:init_tiling_animation(atlas_path)
	if not atlas_path then
		print("No atlas path found for node", gui.get_id(self.node), gui.get_texture(self.node))
		print("Probably you should add druid.script at window collection to access resources")
		return false
	end

	self.animation = helper.get_animation_data_from_node(self.node, atlas_path)
	return true
end


-- Start our repeat shader work
---@param repeat_x number X factor
---@param repeat_y number Y factor
function M:start_animation(repeat_x, repeat_y)
	if not self.is_inited then
		return
	end

	self:set_repeat(repeat_x, repeat_y)

	local node = self.node
	local animation = self.animation
	local frame = animation.frames[1]
	gui.set(node, "uv_coord", frame.uv_coord)

	if #animation.frames > 1 and animation.fps > 0 then
		animation.handle =
		timer.delay(1/animation.fps, true, function(_, handle, time_elapsed)
			local next_rame = animation.frames[animation.current_frame]
			gui.set(node, "uv_coord", next_rame.uv_coord)

			animation.current_frame = animation.current_frame + 1
			if animation.current_frame > #animation.frames then
				animation.current_frame = 1
			end
		end)
	end
end


function M:final()
	local animation = self.animation
	if animation.handle then
		timer.cancel(animation.handle)
		animation.handle = nil
	end
end


---Update repeat factor values
---@param repeat_x number? X factor
---@param repeat_y number? Y factor
function M:set_repeat(repeat_x, repeat_y)
	local animation = self.animation
	animation.v.x = repeat_x or animation.v.x
	animation.v.y = repeat_y or animation.v.y

	local anchor = helper.get_pivot_offset(gui.get_pivot(self.node))
	animation.v.z = anchor.x
	animation.v.w = anchor.y

	gui.set(self.node, "uv_repeat", animation.v)
end


---@param offset_perc_x number? X offset
---@param offset_perc_y number? Y offset
function M:set_offset(offset_perc_x, offset_perc_y)
	self.params.z = offset_perc_x or self.params.z
	self.params.w = offset_perc_y or self.params.w
	gui.set(self.node, "params", self.params)
	return self
end


---@param margin_x number? X margin
---@param margin_y number? Y margin
function M:set_margin(margin_x, margin_y)
	self.params.x = margin_x or self.params.x
	self.params.y = margin_y or self.params.y
	gui.set(self.node, "params", self.params)
	return self
end


---@param scale number
function M:set_scale(scale)
	local current_scale_x = gui.get(self.node, M.PROP_SCALE_X)
	local current_scale_y = gui.get(self.node, M.PROP_SCALE_Y)
	local current_size_x = gui.get(self.node, M.PROP_SIZE_X)
	local current_size_y = gui.get(self.node, M.PROP_SIZE_Y)

	local delta_scale_x = scale / current_scale_x
	local delta_scale_y = scale / current_scale_y
	gui.set(self.node, M.PROP_SCALE_X, scale)
	gui.set(self.node, M.PROP_SCALE_Y, scale)
	gui.set(self.node, M.PROP_SIZE_X, current_size_x / delta_scale_x)
	gui.set(self.node, M.PROP_SIZE_Y, current_size_y / delta_scale_y)

	return self
end


return M
