-- lx-bundle:true

-- This file is part of Lx and Qui, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

--- Provide reusable Form-local actions without replacing direct Control callbacks
-- @module qui.command

local Shortcut = require("qui.shortcut")

local Command = {}
local command_states = setmetatable({}, { __mode = "k" })	-- Private listeners keep authored Command tables uncomplicated

--- Tell every attached Control or Menu item that one shared state value changed
-- @param command table live Command
-- @param field string changed field name
local function notify(command, field)
	local state = assert(command_states[command], "Command is not registered")
	for target in pairs(state.targets) do
		target:command_changed(command, field)
	end
end

--- Create one reusable action for a single Form
-- Local-only behavior should continue to use a Control callback instead
-- @param options table name, text, shortcut, state, and execute callback
-- @return table command reusable authored Command
function Command.new(options)
	assert(type(options) == "table", "qui.command expects a table")
	assert(type(options.name) == "string" and options.name ~= "", "Command name must be a non-empty string")
	assert(options.text == nil or type(options.text) == "string", "Command text must be a string")
	assert(options.shortcut == nil or type(options.shortcut) == "string", "Command shortcut must be a string")
	assert(options.enabled == nil or type(options.enabled) == "boolean", "Command enabled must be a boolean")
	assert(options.checked == nil or type(options.checked) == "boolean", "Command checked must be a boolean")
	assert(type(options.execute) == "function", "Command execute must be a function")
	assert(options.run == nil and options.set_enabled == nil and options.set_checked == nil,
		"Command live-method fields are reserved")

	local command = options
	command.text = command.text or command.name
	command.shortcut = command.shortcut and Shortcut.parse(command.shortcut) or nil
	command.enabled = command.enabled ~= false
	command.checked = command.checked == true
	command_states[command] = {
		targets = setmetatable({}, { __mode = "k" }),
	}

	--- Run this Command when it is available
	-- @return boolean executed
	function command:run()
		if not self.enabled then
			return false
		end

		self:execute()
		return true
	end

	--- Change availability for every attached Control and Menu item
	-- @param enabled boolean new availability
	-- @return boolean changed
	function command:set_enabled(enabled)
		assert(type(enabled) == "boolean", "Command enabled state must be a boolean")
		if self.enabled == enabled then
			return true
		end

		self.enabled = enabled
		notify(self, "enabled")
		return true
	end

	--- Change checked state for every attached Control and Menu item
	-- @param checked boolean new checked state
	-- @return boolean changed
	function command:set_checked(checked)
		assert(type(checked) == "boolean", "Command checked state must be a boolean")
		if self.checked == checked then
			return true
		end

		self.checked = checked
		notify(self, "checked")
		return true
	end

	return command
end

--- Report whether a value was created by 'qui.command'
-- @param value any value to inspect
-- @return boolean command
function Command.is(value)
	return type(value) == "table" and command_states[value] ~= nil
end

--- Connect one Control or Menu item to shared Command state
-- This stays module-level so authored Command tables expose only useful application methods
-- @param command table live Command
-- @param target table object providing 'command_changed'
function Command.bind(command, target)
	local state = assert(command_states[command], "expected a qui.command")
	assert(type(target) == "table" and type(target.command_changed) == "function",
		"Command target must provide command_changed")
	state.targets[target] = true
	target:command_changed(command, "all")
end

-- Callable module syntax keeps declarations readable as 'Command { ... }'
setmetatable(Command, {
	__call = function(_, options)
		return Command.new(options)
	end,
})

return Command
