-- lx-bundle:true

---@diagnostic disable: assign-type-mismatch
-- workspace
local core = require("core")
local style = require("core.style")
local command = require("core.command")
local common = require("core.common")

-- Returns true for any non-command document-backed view
local function is_document_view(v)
	return v and v.doc ~= nil
end

local root = {
	-- Closes the currently active view in the active pane
	["workspace:close"] = function()
		local node = core.workspace_view:get_active_node()
		node:close_active_view(core.workspace_view.root_node)
	end,

	-- Activates the previous tab in the active pane, wrapping at the start
	["workspace:switch-to-previous-tab"] = function()
		local node = core.workspace_view:get_active_node()
		local idx = node:get_view_idx(core.active_view)
		idx = idx - 1
		if idx < 1 then
			idx = #node.views
		end
		node:set_active_view(node.views[idx])
	end,

	-- Activates the next tab in the active pane, wrapping at the end
	["workspace:switch-to-next-tab"] = function()
		local node = core.workspace_view:get_active_node()
		local idx = node:get_view_idx(core.active_view)
		idx = idx + 1
		if idx > #node.views then
			idx = 1
		end
		node:set_active_view(node.views[idx])
	end,

	-- Moves the active tab one position to the left in the tab list
	["workspace:move-tab-left"] = function()
		local node = core.workspace_view:get_active_node()
		local idx = node:get_view_idx(core.active_view)
		if idx > 1 then
			table.remove(node.views, idx)
			table.insert(node.views, idx - 1, core.active_view)
		end
	end,

	-- Moves the active tab one position to the right in the tab list
	["workspace:move-tab-right"] = function()
		local node = core.workspace_view:get_active_node()
		local idx = node:get_view_idx(core.active_view)
		if idx < #node.views then
			table.remove(node.views, idx)
			table.insert(node.views, idx + 1, core.active_view)
		end
	end,

	-- Shrinks the active pane by adjusting its parent split divider
	["workspace:shrink"] = function()
		local node = core.workspace_view:get_active_node()
		local parent = node:get_parent_node(core.workspace_view.root_node)
		if not parent or not parent:is_divider_resizable() then
			return false
		end
		local n = (parent.a == node) and -0.1 or 0.1
		parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
		return true
	end,

	-- Grows the active pane by adjusting its parent split divider
	["workspace:grow"] = function()
		local node = core.workspace_view:get_active_node()
		local parent = node:get_parent_node(core.workspace_view.root_node)
		if not parent or not parent:is_divider_resizable() then
			return false
		end
		local n = (parent.a == node) and 0.1 or -0.1
		parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
		return true
	end,
}

-- Collect leaf nodes (panes) in a stable order
local function get_leaf_nodes(node, t)
	t = t or {}
	if node.type == "leaf" then
		table.insert(t, node)
	else
		get_leaf_nodes(node.a, t)
		get_leaf_nodes(node.b, t)
	end
	return t
end

-- Collect panes that contain at least one document view
local function get_doc_panes()
	local panes = {}
	for _, node in ipairs(get_leaf_nodes(core.workspace_view.root_node)) do
		for _, view in ipairs(node.views) do
			if is_document_view(view) then
				table.insert(panes, node)
				break
			end
		end
	end
	return panes
end

-- Prefer a document view inside the pane if available
local function activate_pane(node)
	for _, view in ipairs(node.views) do
		if is_document_view(view) then
			node:set_active_view(view)
			return
		end
	end
	core.set_active_view(node.active_view)
end

-- Add pane switching commands (1..9)
for i = 1, 9 do
	-- Switches focus to pane 'i' (prefers panes containing document views)
	root["workspace:switch-to-pane-" .. i] = function()
		local panes = get_doc_panes()
		if #panes == 0 then
			panes = get_leaf_nodes(core.workspace_view.root_node)
		end
		local node = panes[i]
		if node then
			activate_pane(node)
		end
	end
end

for i = 1, 9 do
	-- Switches to tab 'i' within the active pane
	root["workspace:switch-to-tab-" .. i] = function()
		local node = core.workspace_view:get_active_node()
		local view = node.views[i]
		if view then
			node:set_active_view(view)
		end
	end
end

for _, dir in ipairs({ "left", "right", "up", "down" }) do
	-- Splits the active pane toward 'dir', duplicating the active doc when present
	root["workspace:split-" .. dir] = function()
		local node = core.workspace_view:get_active_node()
		local av = node.active_view
		node:split(dir)
		if is_document_view(av) then
			core.workspace_view:open_doc(av.doc)
		end
	end

	-- Moves focus to the adjacent pane in 'dir' when the target pane is not locked
	root["workspace:switch-to-" .. dir] = function()
		local node = core.workspace_view:get_active_node()
		local x, y
		if dir == "left" or dir == "right" then
			y = node.position.y + node.size.y / 2
			x = node.position.x + (dir == "left" and -1 or node.size.x + style.divider_size)
		else
			x = node.position.x + node.size.x / 2
			y = node.position.y + (dir == "up" and -1 or node.size.y + style.divider_size)
		end
		local node = core.workspace_view.root_node:get_child_overlapping_point(x, y)
		if not node:get_locked_size() then
			core.set_active_view(node.active_view)
		end
	end
end

--- Return true when the active workspace node is unlocked
-- @return boolean resizable
local function root_active_node_resizable()
	local node = core.workspace_view:get_active_node()
	return not node:get_locked_size()
end

--- Return true when the active pane belongs to a movable split
-- @return boolean resizable
local function active_node_parent_resizable()
	local node = core.workspace_view:get_active_node()
	local parent = node:get_parent_node(core.workspace_view.root_node)

	return parent ~= nil and parent:is_divider_resizable()
end

for command_name, command_handler in pairs(root) do
	local predicate = root_active_node_resizable
	if command_name == "workspace:grow" or command_name == "workspace:shrink" then
		predicate = active_node_parent_resizable
	end

	root[command_name] = {
		-- Split-lock state is dynamic and has no equivalent requires token yet
		predicate = predicate,
		perform = command_handler,
	}
end
command.register(root)
