---@diagnostic disable: duplicate-set-field
-- lx-bundle:true
--- Draw the FPS overlay and keep it refreshing during idle redraw gaps
-- @module plugins.fpsoverlay

local common = require("core.common")
local config = require("core.config")
local core = require("core")
local command = require("core.command")
local style = require("core.style")
local WorkspaceView = require("core.workspace.view")
local renderer = require("renderer")

config.plugins.fps_overlay = common.merge({
	enabled = true,
	update_interval = 0.25,
	-- Keep overlay rect changes updating even when no other UI redraw is happening
	update_rect_when_idle = true,
	xmargin = 16,
	ymargin = 4,
	padding = 4,
	background = true,
	position = "top-right",
}, config.plugins.fps_overlay)

local state = {
	last_time = nil,
	next_update = 0,
	accum = 0.0,
	frames = 0,
	text = "-- fps",
	fps = 0.0,
	last_rect = nil,
}

--- Update the sampled FPS text and schedule the next overlay refresh
local function update_fps_state(now)
	if state.last_time then
		local dt = now - state.last_time
		if dt > 0 then
			state.accum = state.accum + dt
			state.frames = state.frames + 1
		end
	end
	state.last_time = now

	if now < state.next_update then
		return
	end

	if state.accum > 0 and state.frames > 0 then
		state.fps = state.frames / state.accum
	end
	state.text = string.format("%.1f fps", state.fps)
	state.accum = 0.0
	state.frames = 0
	state.next_update = now + math.max(0.05, config.plugins.fps_overlay.update_interval)
end

--- Reset FPS timing so the next draw starts from a clean sample window
local function reset_fps_state()
	state.last_time = nil
	state.next_update = 0
	state.accum = 0.0
	state.frames = 0
	state.text = "-- fps"
	state.fps = 0.0
end

--- Return true when both overlay rect tables describe the same bounds
-- @param left table|nil overlay rect
-- @param right table|nil overlay rect
-- @return boolean same bounds
local function rects_equal(left, right)
	if left == right then
		return true
	end

	if not left or not right then
		return false
	end

	return left.x == right.x
		and left.y == right.y
		and left.width == right.width
		and left.height == right.height
end

--- Mark one overlay rect dirty in the renderer cache
-- @param rect table|nil overlay rect
local function invalidate_overlay_rect(rect)
	if not rect then
		return
	end

	renderer.invalidate_rect(rect.x, rect.y, rect.width, rect.height)
end

--- Measure and place the overlay box for one text value
-- @param text string overlay text
-- @return table layout data
local function get_overlay_layout(text)
	local opts = config.plugins.fps_overlay
	local offset_x = math.floor((opts.xmargin or 8) * SCALE)
	local offset_y = math.floor((opts.ymargin or 8) * SCALE)
	local padding = math.floor((opts.padding or 4) * SCALE)
	local font = style.font
	local tw = renderer.measure_transient_text(font, text)
	local th = font:get_height()
	local bw = tw + padding * 2
	local bh = th + padding

	local rw, rh = renderer.get_size()
	local x = offset_x
	local y = offset_y

	if opts.position == "top-right" then
		x = rw - bw - offset_x
		y = offset_y
	elseif opts.position == "bottom-left" then
		x = offset_x
		y = rh - bh - offset_y
	elseif opts.position == "bottom-right" then
		x = rw - bw - offset_x
		y = rh - bh - offset_y
	end

	return {
		font = font,
		padding = padding,
		x = x,
		y = y,
		width = bw,
		height = bh,
	}
end

--- Draw the overlay in the configured corner of the workspace
local function draw_overlay()
	local opts = config.plugins.fps_overlay
	if not opts.enabled then
		invalidate_overlay_rect(state.last_rect)
		state.last_rect = nil
		reset_fps_state()
		return
	end

	local now = core.frame_start or system.get_time()
	update_fps_state(now)

	local layout = get_overlay_layout(state.text)
	local rect = {
		x = layout.x,
		y = layout.y,
		width = layout.width,
		height = layout.height,
	}

	if not rects_equal(state.last_rect, rect) then
		invalidate_overlay_rect(state.last_rect)
		invalidate_overlay_rect(rect)
	end
	state.last_rect = rect

	if opts.background then
		renderer.draw_rect(layout.x, layout.y, layout.width, layout.height, style.background2)
	end
	renderer.draw_transient_text(
		layout.font,
		state.text,
		layout.x + layout.padding,
		layout.y + math.floor(layout.padding * 0.5),
		style.text
	)
end

local _original_draw = WorkspaceView.draw

--- Draw the workspace first, then paint the FPS overlay on top
function WorkspaceView:draw(...)
	_original_draw(self, ...)
	draw_overlay()
end

-- Keep the overlay updating on its own interval even when the UI is otherwise idle
core.add_thread(function()
	while true do
		local opts = config.plugins.fps_overlay
		local interval = math.max(0.05, tonumber(opts.update_interval) or 0.25)

		if opts.enabled and opts.update_rect_when_idle then
			core.redraw = true
		end

		coroutine.yield(interval)
	end
end, state)

command.add({
	["fps-overlay:toggle"] = function()
		config.plugins.fps_overlay.enabled = not config.plugins.fps_overlay.enabled
		core.log("FPS overlay %s", config.plugins.fps_overlay.enabled and "enabled" or "disabled")
		if not config.plugins.fps_overlay.enabled then
			invalidate_overlay_rect(state.last_rect)
			state.last_rect = nil
			reset_fps_state()
		end
		core.redraw = true
	end,
})

return true
