-- lx-bundle:true

--- Build modern titled sections from ordinary QUI declarations
-- Section fills the HTML/CSS fieldset role and, in the broadest sense, is a classic GroupBox
-- It is a construction-time meta control rather than an actual Control retained by the live Form
-- @module qui.section

local qui = require("qui")
local colors = require("qui.colors")
local Label = require("qui.label")
local metrics = require("qui.metrics")
local Separator = require("qui.separator")
local theme = require("qui.theme")

local Section = {}

-- Only normal Pane sizing belongs to the outer declaration produced by Section
local outer_pane_fields = {
	"name",
	"width",
	"height",
	"length",
	"weight",
	"minimum_width",
	"minimum_height",
	"maximum_width",
	"maximum_height",
	"collapsed",
}
local section_fields = {
	name = true,
	title = true,
	layout = true,
	title_gap = true,
	content_gap = true,
	lead_width = true,
}
for _, field in ipairs(outer_pane_fields) do
	section_fields[field] = true
end

--- Keep one Section spacing value inside a non-negative whole-DIP range
-- @param value number|nil requested override
-- @param fallback integer Theme value
-- @param name string readable option name
-- @return integer dips
local function dip_value(value, fallback, name)
	value = value == nil and fallback or value
	assert(type(value) == "number" and value >= 0 and value < math.huge,
		name .. " must be a non-negative number")

	return math.floor(value + 0.5)
end

--- Give generated declarations readable names only when the Section itself was named
-- @param name string|nil Section name
-- @param suffix string generated child role
-- @return string|nil child_name
local function child_name(name, suffix)
	return name and name .. "_" .. suffix or nil
end

--- Return the Section frame color through an ordinary Separator callback
-- @param self table generated Separator
-- @param type string requested visual type
-- @return string color_identifier
local function section_frame_color(self, type)
	assert(type == "frame", "Section divider did not handle " .. tostring(type))

	return colors.section_frame
end

--- Build one ordinary Pane subtree that groups related form inputs under a titled divider
-- The returned Pane may be placed anywhere another 'qui.pane' declaration is accepted
-- @param options table required title and layout plus normal Pane sizing and section spacing
-- @return table declaration ordinary Pane declaration
function Section.new(options)
	assert(type(options) == "table", "Section expects a table")
	for field in next, options do
		assert(section_fields[field] == true, "unknown Section option " .. tostring(field))
	end
	assert(type(options.title) == "string" and options.title ~= "", "Section title must be a non-empty string")
	assert(type(options.layout) == "table", "Section layout must be qui.rows or qui.cols")

	local name = options.name
	assert(name == nil or type(name) == "string" and name ~= "", "Section name must be a non-empty string")
	local title_gap = dip_value(
		options.title_gap, theme.resolve_metric(metrics.section_title_gap), "Section title_gap")
	local content_gap = dip_value(
		options.content_gap, theme.resolve_metric(metrics.section_content_gap), "Section content_gap")
	local lead_width = dip_value(
		options.lead_width, theme.resolve_metric(metrics.section_lead_width), "Section lead_width")
	local title = Label {
		name = child_name(name, "title"),
		text = options.title,
		color = colors.section_text,
		placement = {
			horizontal = "left",
			vertical = "center",
		},
	}
	local leading_line = Separator {
		name = child_name(name, "leading_line"),
		on_color = section_frame_color,
	}
	local trailing_line = Separator {
		name = child_name(name, "trailing_line"),
		on_color = section_frame_color,
	}

	-- The heading is itself ordinary layout: short line, title, then a line using the remaining width
	local heading = qui.cols {
		name = child_name(name, "heading"),
		gap = title_gap,

		qui.pane {
			name = child_name(name, "leading_line_pane"),
			width = lead_width,
			controls = { leading_line },
		},

		qui.pane {
			name = child_name(name, "title_pane"),
			length = "content",
			controls = { title },
		},

		qui.pane {
			name = child_name(name, "trailing_line_pane"),
			length = "weighted",
			weight = 1,
			controls = { trailing_line },
		},
	}
	local section_layout = qui.rows {
		name = child_name(name, "section_rows"),
		gap = content_gap,

		qui.pane {
			name = child_name(name, "heading_pane"),
			length = "content",
			layout = heading,
		},

		qui.pane {
			name = child_name(name, "content_pane"),
			length = "content",
			layout = options.layout,
		},
	}
	local pane = {
		layout = section_layout,
	}
	for _, field in ipairs(outer_pane_fields) do
		pane[field] = options[field]
	end

	return qui.pane(pane)
end

-- A callable module also supports explicit 'local Section = require("qui.section")' use
setmetatable(Section, {
	__call = function(_, options)
		return Section.new(options)
	end,
})

return Section
