-- lx-bundle:true

---@diagnostic disable: param-type-mismatch
local core = require("core")
local common = require("core.common")
local keymap = require("core.keymap")
local CommandView = require("core.command.view")
local StatusView = require("core.status.view")
local View = require("core.ui.view")
local Node = require("core.workspace.node")

-- Copies position and size coordinates from one view-like object to another
local function copy_position_and_size(dst, src)
	dst.position.x, dst.position.y = src.position.x, src.position.y
	dst.size.x, dst.size.y = src.size.x, src.size.y
end

local WorkspaceView = View:extend()

-- Initializes the root view state and creates the initial layout node
function WorkspaceView:new()
	WorkspaceView.super.new(self)
	self.root_node = Node()
	self.deferred_draws = {}
	self.mouse = { x = 0, y = 0 }
end

--- Build the default workspace shell used at startup
-- Creates the workspace, command, and status views and installs the live-resize draw path
-- @return table shell
function WorkspaceView.build_default_shell()
	local workspace_view = WorkspaceView()
	local command_view = CommandView()
	local status_view = StatusView()

	workspace_view.root_node:split("down", command_view, true)
	workspace_view.root_node.b:split("down", status_view, true)

	-- Keep Windows modal resize drawing on the normal Lua render path so layout stays live while dragging the frame
	system.set_live_resize_callback(function()
		local width, height = renderer.get_size()

		workspace_view.size.x, workspace_view.size.y = width, height
		workspace_view:update()

		renderer.begin_frame()
		core.clip_rect_stack[1] = { 0, 0, width, height }
		renderer.set_clip_rect(table.unpack(core.clip_rect_stack[1]))
		workspace_view:draw()
		renderer.end_frame()
	end)

	return {
		workspace_view = workspace_view,
		command_view = command_view,
		status_view = status_view,
	}
end

-- Apply mouse cursor shape for current pointer position and active view state
function WorkspaceView:sync_mouse_cursor()
	if core.startup_cursor_lock == true then
		return
	end
	local x, y = self.mouse.x, self.mouse.y
	local ok_node, node = pcall(self.root_node.get_child_overlapping_point, self.root_node, x, y)
	if not ok_node then
		system.set_mouse_cursor("arrow")
		return
	end
	if not node then
		system.set_mouse_cursor("arrow")
		return
	end
	local ok_div, div = pcall(self.root_node.get_divider_overlapping_point, self.root_node, x, y)
	if not ok_div then
		system.set_mouse_cursor("arrow")
		return
	end
	if div then
		system.set_mouse_cursor(div.type == "hsplit" and "sizeh" or "sizev")
	elseif node:get_tab_overlapping_point(x, y) then
		system.set_mouse_cursor("arrow")
	else
		system.set_mouse_cursor(node.active_view.mouse_cursor)
	end
end

-- Queues a draw callback to run after the normal root draw pass
function WorkspaceView:defer_draw(fn, ...)
	table.insert(self.deferred_draws, 1, { fn = fn, ... })
end

-- Returns the layout node that contains the active view
function WorkspaceView:get_active_node()
	return self.root_node:get_node_for_view(core.active_view)
end

--- Opens or focuses a document in the active layout node
-- @param doc table document to show
-- @param opts table|nil view construction options
function WorkspaceView:open_doc(doc, opts)
	if not doc then
		return nil
	end
	local node = self:get_active_node()
	if node.locked and core.last_active_view then
		core.set_active_view(core.last_active_view)
		node = self:get_active_node()
	end
	assert(not node.locked, "Cannot open doc on locked node")
	for i, view in ipairs(node.views) do
		if view.doc == doc then
			node:set_active_view(node.views[i])
			return view
		end
	end
	-- Build the view through the core factory so plugins can choose view class
	local view = core.create_doc_view(doc, opts)
	node:add_view(view)
	self.root_node:update_layout()
	if view.scroll_to_line and view.doc and view.doc.get_selection then
		view:scroll_to_line(view.doc:get_selection(), true, true)
	end
	return view
end

-- Handles divider dragging, tab activation, tab middle-close, and view clicks
function WorkspaceView:on_mouse_down(button, x, y, clicks)
	local div = self.root_node:get_divider_overlapping_point(x, y)
	if div then
		self.dragged_divider = div
		return
	end
	local node = self.root_node:get_child_overlapping_point(x, y)
	local idx = node:get_tab_overlapping_point(x, y)
	if idx then
		node:set_active_view(node.views[idx])
		if button == "middle" then
			node:close_active_view(self.root_node)
		end
	else
		core.set_active_view(node.active_view)
		node.active_view:on_mouse_down(button, x, y, clicks)
	end
end

-- Stops divider dragging and forwards mouse-release handling to the layout tree
function WorkspaceView:on_mouse_up(...)
	if self.dragged_divider then
		self.dragged_divider = nil
	end
	self.root_node:on_mouse_up(...)
end

-- Updates divider drag state, hover routing, and mouse cursor shape
function WorkspaceView:on_mouse_move(x, y, dx, dy)
	if self.dragged_divider then
		local node = self.dragged_divider
		if node.type == "hsplit" then
			node.divider = node.divider + dx / node.size.x
		else
			node.divider = node.divider + dy / node.size.y
		end
		node.divider = common.clamp(node.divider, 0.01, 0.99)
		return
	end

	self.mouse.x, self.mouse.y = x, y
	self.root_node:on_mouse_move(x, y, dx, dy)
	self:sync_mouse_cursor()
end

-- Routes wheel input through keymap bindings or the view under the mouse
function WorkspaceView:on_mouse_wheel(...)
	-- Allow keymap bindings like ctrl+wheelup / ctrl+wheeldown
	if keymap.on_mouse_wheel(...) then
		return
	end
	local x, y = self.mouse.x, self.mouse.y
	local node = self.root_node:get_child_overlapping_point(x, y)
	node.active_view:on_mouse_wheel(...)
end

function WorkspaceView:on_mouse_left(...)
	self.root_node:on_mouse_left(...)
	if core.startup_cursor_lock == true then
		return
	end
	system.set_mouse_cursor("arrow")
end

-- Forwards text input to the currently active view
function WorkspaceView:on_text_input(...)
	core.active_view:on_text_input(...)
end

-- Synchronizes root layout bounds and updates the layout tree
function WorkspaceView:update()
	copy_position_and_size(self.root_node, self)
	self.root_node:update()
	self.root_node:update_layout()
end

-- Draws the layout tree and then runs deferred draw callbacks
function WorkspaceView:draw()
	self.root_node:draw()
	while #self.deferred_draws > 0 do
		local t = table.remove(self.deferred_draws)
		t.fn(table.unpack(t))
	end
end

return WorkspaceView
