-- 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.

--- Define the small DIP metric catalog used by new UI themes
-- QUI supplies one normal default set, while Themes may replace only the values they need
-- @module qui.metrics

local metrics = {}
local catalog = {}
local selectors = {}

--- Build one compact identifier for a selector metric
-- @param selector string shared family or concrete Control selector
-- @param name string metric name
-- @return string identifier
local function identifier_name(selector, name)
	return selector .. "_" .. name
end

--- Add one checked metric and its automatic family fallback
-- @param selector string owner selector
-- @param name string metric name
-- @param fallback string|nil shared family identifier
local function define(selector, name, fallback)
	local identifier = identifier_name(selector, name)
	assert(catalog[identifier] == nil, "duplicate qui metric " .. identifier)

	catalog[identifier] = {
		fallback = fallback,
	}
	metrics[identifier] = identifier
	selectors[selector][name] = identifier
end

--- Register one selector without repeating its family relationship for every metric
-- @param selector string shared family or concrete Control selector
-- @param family string|nil shared selector
-- @param names table metric names supported by this selector
local function define_selector(selector, family, names)
	assert(selectors[selector] == nil, "duplicate qui metric selector " .. selector)
	assert(family == nil or selectors[family] ~= nil, "unknown qui metric family " .. tostring(family))
	selectors[selector] = {}

	for name in pairs(names) do
		local family_entry = family and selectors[family] or nil
		local fallback = family_entry and family_entry[name] or nil
		define(selector, name, fallback)
	end
end

define_selector("input", nil, {
	padding_x = true,
	padding_y = true,
	indicator_size = true,
	compact_indicator_size = true,
	indicator_gap = true,
	checkmark_padding = true,
})
define_selector("accelerator", nil, {
	underline_min_width = true,
	underline_thickness = true,
	underline_inset = true,
})
define_selector("panel", nil, {
	frame_width = true,
})
define_selector("separator", nil, {
	thickness = true,
})
define_selector("section", nil, {
	lead_width = true,
	title_gap = true,
	content_gap = true,
})
define_selector("button", "input", {
	padding_x = true,
	padding_y = true,
})
define_selector("textbox", "input", {
	padding_x = true,
	padding_y = true,
})
define_selector("checkbox", "input", {
	indicator_size = true,
	compact_indicator_size = true,
	indicator_gap = true,
	checkmark_padding = true,
})
define_selector("radio", "input", {
	indicator_size = true,
	compact_indicator_size = true,
	indicator_gap = true,
	checkmark_padding = true,
})
define_selector("listbox", "input", {
	padding_x = true,
	padding_y = true,
})
define_selector("tree", "input", {
	padding_x = true,
	padding_y = true,
	indent = true,
	indicator_size = true,
})
define_selector("scrollbar", "input", {
	thickness = true,
	minimum_thumb = true,
	natural_length = true,
})
define_selector("combobox", "button", {
	padding_x = true,
	padding_y = true,
	arrow_width = true,
	overlay_gap = true,
})
define_selector("menu", nil, {
	padding_x = true,
	padding_y = true,
	item_height = true,
	separator_height = true,
	detail_gap = true,
	indicator_width = true,
	indicator_size = true,
	indicator_gap = true,
	submenu_width = true,
	submenu_size = true,
	submenu_gap = true,
})

--- Return the identifier represented by one readable Theme metric path
-- @param selector string top-level Theme selector
-- @param name string metric name
-- @return string|nil identifier
function metrics.identifier(selector, name)
	local selector_entry = selectors[selector]

	return selector_entry and selector_entry[name] or nil
end

--- Report whether a top-level Theme selector may contain metrics
-- @param selector string requested selector
-- @return boolean known
function metrics.has_selector(selector)
	return selectors[selector] ~= nil
end

--- Check partial metric values before preparing defaults or a Theme
-- @param values table values indexed by metric identifier
local function check_values(values)
	assert(type(values) == "table", "qui theme metrics must be a table")
	for identifier, value in pairs(values) do
		assert(type(identifier) == "string" and catalog[identifier] ~= nil,
			"unknown qui theme metric " .. tostring(identifier))
		assert(type(value) == "number" and value >= 0 and value % 1 == 0,
			"theme metric " .. identifier .. " must be a non-negative integer")
	end
end

--- Prepare one metric layer and use its selector families before the supplied defaults
-- @param values table partial values indexed by metric identifier
-- @param defaults table|nil complete shared defaults
-- @param defaults_layer boolean whether this call is creating the shared defaults
-- @return table prepared complete metric snapshot
local function prepare(values, defaults, defaults_layer)
	check_values(values)
	assert(defaults == nil or type(defaults) == "table", "qui default metrics must be a table")

	if defaults ~= nil then
		for identifier in pairs(catalog) do
			assert(type(defaults[identifier]) == "number", "qui default metric is missing " .. identifier)
		end
	end

	local prepared = {}
	local resolving = {}
	local function resolve_configured(identifier)
		local value = values[identifier]
		if value ~= nil then
			return value
		end
		assert(not resolving[identifier], "recursive qui theme metric " .. identifier)
		resolving[identifier] = true

		local entry = catalog[identifier]
		if entry.fallback ~= nil then
			value = resolve_configured(entry.fallback)
		end
		resolving[identifier] = nil

		return value
	end

	for identifier in pairs(catalog) do
		local value = resolve_configured(identifier)
		if value == nil and defaults ~= nil then
			value = defaults[identifier]
		end
		assert(value ~= nil,
			(defaults_layer and "qui default metric is missing " or "qui theme metric has no default ") .. identifier)
		prepared[identifier] = value
	end

	return prepared
end

--- Prepare the one complete metric baseline supplied by QUI
-- A concrete selector may use a value from its shared family
-- @param values table default values indexed by metric identifier
-- @return table prepared complete metric snapshot
function metrics.prepare_defaults(values)
	return prepare(values, nil, true)
end

--- Prepare a complete metric snapshot from partial Theme values
-- A Theme family value wins only when the concrete selector did not supply its own value
-- @param values table partial values indexed by metric identifier
-- @param defaults table complete shared defaults
-- @return table prepared complete metric snapshot
function metrics.prepare(values, defaults)
	assert(defaults ~= nil, "qui Theme metrics require the shared defaults")

	return prepare(values, defaults, false)
end

--- Resolve one catalog identifier through a prepared Theme snapshot
-- @param identifier string central metric identifier
-- @param prepared table complete snapshot returned by 'prepare'
-- @return integer dips
function metrics.resolve(identifier, prepared)
	assert(type(identifier) == "string" and catalog[identifier] ~= nil,
		"unknown qui metric " .. tostring(identifier))
	assert(type(prepared) == "table" and prepared[identifier] ~= nil,
		"prepared qui metric is missing " .. identifier)

	return prepared[identifier]
end

return metrics
