-- lx-bundle:true

--- Central visual design for rendered Markdown previews
-- @module plugins.mdpreview.design

local core_style = require("core.style")

local MINIMUM_VISIBLE_SEPARATOR_OPACITY = 0.05

local markdown_design = {
	-- Keep all font choices together so heading hierarchy is easy to tune
	typography = {
		font_size = 16,	-- Main baseline for every preview font scale
		fixed_font_scale = 0.9,	-- Compensate for visually larger fixed-width faces
		line_height_scale = 1.2,
		-- Heading font scales are em-like multiples of the main font size
		heading_levels = {
			-- h1
			{
				font_scale = 2,
				font_style = "bold",
				gap_above = 18,
				separator_width = 2,
				separator_opacity = 0.5,
			},
			-- h2
			{
				font_scale = 1.7,
				font_style = "regular",
				gap_above = 10,
				separator_width = 2,
				separator_opacity = 0.25,
			},
			-- h3
			{
				font_scale = 1.5,
				font_style = "regular",
				gap_above = 14,
			},
			-- h4
			{
				font_scale = 1.4,
				font_style = "light",
--				font_embolden = 0.5,
				gap_above = 11,
			},
			-- h5
			{
				font_scale = 1.25,
				font_style = "light",
--				font_embolden = 0.5,
				gap_above = 9,
			},
			-- h6
			{
				font_scale = 1.1,
				font_style = "light",
--				font_embolden = 0.5,
				gap_above = 8,
			},
		},
	},

	-- Document measurements use logical pixels and are scaled by the view
	document = {
		skip_initial_blank_lines = true,	-- Let source padding remain invisible before the first block
		minimum_content_width = 80,
		padding_horizontal = 32,
		padding_vertical = 14,
	},
	-- The heading outline is a fixed local panel rather than zoomed document content
	outline = {
		visible = true,
		width = 300,
		minimum_width = 160,
		padding_horizontal = 12,
		padding_vertical = 12,
		indent_width = 12,
		item_gap = 3,
		font_scale = 0.9,
		line_height_scale = 1.1,
		separator_width = 1,
		separator_opacity = 0.18,
		text_opacity = 0.68,
		active_text_opacity = 1,
		background_darken = 0.2,
		active_background_darken = -0.5,
	},
	paragraphs = {
		paragraph_gap = 14,
		source_line_gap = 0,
		wrapped_line_gap = 0,
	},
	headings = {
		gap_above = 10,
		gap_below = 8,
		separator_width = 1,
		separator_opacity = 0.15,
	},

	-- Inline and fenced code deliberately have separate backgrounds
	inline_code = {
		padding_horizontal = 6,
		padding_vertical = 1,
		background_darken = -0.8,
	},
	math = {
		-- Inline math size relative to the main font size
		font_scale = 1.15,
		-- Separate math blocks need more visual weight
		display_font_scale = 1.5,
		script_scale = 0.7,
		display_operator_scale = 1.45,
		display_limit_gap_scale = 0.12,
		-- Use the same gap as fenced code between consecutive blocks
		display_gap_above = 10,
		display_padding_horizontal = 16,
		display_padding_vertical = 12,
		background_darken = 0.18,
	},
	fenced_code = {
		-- Use the same gap as display math between consecutive blocks
		gap_above = 10,
		padding_horizontal = 16,
		padding_vertical = 12,
		background_darken = 0.3,
		header = {
			enabled = false,
			font_scale = 0.75,
			padding_horizontal = 16,
			padding_vertical = 1,
			background_darken = -0.3,
			text_opacity = 0.9,
		},
		badge = {
			enabled = true,
			background_enabled = false,
			font_scale = 0.75,
			padding_horizontal = 6,
			padding_vertical = 2,
			margin_top = 6,
			margin_right = 6,
			background_darken = 1.0,
			text_opacity = 0.4,
		},
	},

	lists = {
		base_indent = 24,
		nested_indent_scale = 1,
		item_gap = 1,
		gap_below = 8,	-- Separate a completed list from the following non-list block
		unordered_marker_spaces = 2,
		ordered_marker_spaces = 1,
	},
	quotes = {
		guide_width = 3,
		padding_vertical = 8,
		base_indent_spaces = 2,
		block_offset_scale = 1.5,
		text_offset_scale = 3,
	},
	tables = {
		column_grid_units = 24,		-- Snap column widths to a stable shared grid
		column_length_percentile = 0.75,	-- Ignore the longest quarter of cell-length outliers
		minimum_column_units = 2,	-- Keep every ordinary column readable
		maximum_column_units = 16,	-- Stop one column from consuming the whole table
		minimum_cell_padding_horizontal = 4,
		cell_padding_horizontal_scale = 0.5,
		cell_padding_vertical = 6,
		theme_gap_above_scale = 0.5,
		adjacent_table_gap = 8,
		frame_width = 1,
		frame_opacity = 0.5,
		inner_line_opacity = 0.18,
	},
	horizontal_rules = {
		width = 1,
		opacity = 0.34,
	},
	links = {
		underline_width = 1,
		underline_offset = 2,
	},
	images = {
		fallback_height = 48,
		block_gap = 8,
		placeholder_border_width = 1,
		placeholder_border_opacity = 0.32,
		placeholder_cell_padding_horizontal_scale = 1,
		placeholder_padding_vertical = 8,
	},
}

--- Return the live application background color
-- @return table color document background
function markdown_design.get_document_background_color()
	return core_style.background
end

--- Resolve one optional syntax role to its live foreground color
-- @param syntax_type string|nil tokenizer color role
-- @return table color rendered text color
function markdown_design.get_text_color(syntax_type)
	local syntax_style = syntax_type and core_style.syntax and core_style.syntax[syntax_type]

	if type(syntax_style) == "table" and (syntax_style.fg or syntax_style.bg) then
		return syntax_style.fg or core_style.text
	end

	return syntax_style or core_style.text
end

--- Return the live link text color
-- @return table color link color
function markdown_design.get_link_color()
	return core_style.accent or core_style.text
end

--- Return the heading-outline panel background
-- @return table color outline background
function markdown_design.get_outline_background_color()
	return core_style.darken(core_style.background, markdown_design.outline.background_darken)
end

--- Return the heading-outline divider color
-- @return table color outline separator
function markdown_design.get_outline_separator_color()
	return core_style.transparent(core_style.text, markdown_design.outline.separator_opacity)
end

--- Return the active heading background inside the outline
-- @return table color active heading background
function markdown_design.get_outline_active_background_color()
	return core_style.darken(core_style.background, markdown_design.outline.active_background_darken)
end

--- Return heading-outline text color
-- @param active boolean true for the current section
-- @return table color outline text
function markdown_design.get_outline_text_color(active)
	local opacity = active
		and markdown_design.outline.active_text_opacity
		or markdown_design.outline.text_opacity

	return core_style.transparent(core_style.text, opacity)
end

--- Return the lighter background reserved for small inline code spans
-- @return table color inline code background
function markdown_design.get_inline_code_background_color()
	return core_style.darken(core_style.background, markdown_design.inline_code.background_darken)
end

--- Return the background reserved for display math
-- @return table color display-math background
function markdown_design.get_math_background_color()
	return core_style.darken(core_style.background, markdown_design.math.background_darken)
end

--- Return the darker background reserved for complete fenced code blocks
-- @return table color fenced code background
function markdown_design.get_fenced_code_background_color()
	return core_style.darken(
		core_style.background,
		markdown_design.fenced_code.background_darken
	)
end

--- Return the lighter background reserved for fenced-code language headers
-- @return table color fenced-code header background
function markdown_design.get_fenced_code_header_background_color()
	return core_style.darken(
		core_style.background,
		markdown_design.fenced_code.header.background_darken
	)
end

--- Return the muted text color used by fenced-code language headers
-- @return table color fenced-code header text
function markdown_design.get_fenced_code_header_text_color()
	return core_style.transparent(
		core_style.text,
		markdown_design.fenced_code.header.text_opacity
	)
end

--- Return the compact background reserved for fenced-code language badges
-- @return table color fenced-code badge background
function markdown_design.get_fenced_code_badge_background_color()
	return core_style.darken(
		core_style.background,
		markdown_design.fenced_code.badge.background_darken
	)
end

--- Return the text color used by fenced-code language badges
-- @return table color fenced-code badge text
function markdown_design.get_fenced_code_badge_text_color()
	return core_style.transparent(
		core_style.text,
		markdown_design.fenced_code.badge.text_opacity
	)
end

--- Return the live quote-panel background color
-- @return table color quote background
function markdown_design.get_quote_background_color()
	return core_style.background2 or core_style.line_highlight or core_style.background
end

--- Return the live table-header background color
-- @return table color table header background
function markdown_design.get_table_header_background_color()
	return core_style.background2 or core_style.line_highlight or core_style.background
end

--- Return the live failed-image placeholder background color
-- @return table color placeholder background
function markdown_design.get_image_placeholder_background_color()
	return core_style.background2 or core_style.line_highlight or core_style.background
end

--- Return the live quote guide color
-- @return table color quote guide
function markdown_design.get_quote_guide_color()
	return core_style.text
end

--- Return the table outer-frame color
-- @return table color table frame
function markdown_design.get_table_frame_color()
	return core_style.transparent(core_style.text, markdown_design.tables.frame_opacity)
end

--- Return the scaled table outer-frame thickness
-- @return number width scaled screen pixels
function markdown_design.get_table_frame_width()
	return math.max(1, markdown_design.tables.frame_width * (SCALE or 1))
end

--- Return the table inner-line color
-- @return table color table inner line
function markdown_design.get_table_inner_line_color()
	return core_style.transparent(core_style.text, markdown_design.tables.inner_line_opacity)
end

--- Return the horizontal-rule color
-- @return table color horizontal rule
function markdown_design.get_horizontal_rule_color()
	return core_style.transparent(core_style.text, markdown_design.horizontal_rules.opacity)
end

--- Return the scaled horizontal-rule thickness
-- @return number width scaled screen pixels
function markdown_design.get_horizontal_rule_width()
	return math.max(1, markdown_design.horizontal_rules.width * (SCALE or 1))
end

--- Resolve one heading value through its optional level override
-- @param heading_level number heading level
-- @param field string design field
-- @return any value resolved design value
local function get_heading_value(heading_level, field)
	local heading = markdown_design.typography.heading_levels[heading_level]
	local value = heading and heading[field]

	if value == nil then
		value = markdown_design.headings[field]
	end

	return value
end

--- Return one heading level's body-relative logical font size
-- @param heading_level number heading level
-- @return number size logical font size
function markdown_design.get_heading_font_size(heading_level)
	local heading = markdown_design.typography.heading_levels[heading_level]

	return markdown_design.typography.font_size * heading.font_scale
end

--- Return one heading level's gap above
-- @param heading_level number heading level
-- @return number gap logical pixels
function markdown_design.get_heading_gap_above(heading_level)
	return get_heading_value(heading_level, "gap_above")
end

--- Return one heading level's gap below
-- @param heading_level number heading level
-- @return number gap logical pixels
function markdown_design.get_heading_gap_below(heading_level)
	return get_heading_value(heading_level, "gap_below")
end

--- Return whether one heading level draws a separator
-- @param heading_level number heading level
-- @return boolean enabled true when the separator is visible
function markdown_design.is_heading_separator_enabled(heading_level)
	local heading = markdown_design.typography.heading_levels[heading_level]
	local opacity = get_heading_value(heading_level, "separator_opacity")

	-- Near-transparent separators are treated as disabled instead of drawing invisible work
	if opacity < MINIMUM_VISIBLE_SEPARATOR_OPACITY then
		return false
	end

	if heading and heading.separator ~= nil then
		return heading.separator == true
	end

	return true
end

--- Return one heading level's separator color
-- @param heading_level number heading level
-- @return table color heading separator
function markdown_design.get_heading_separator_color(heading_level)
	return core_style.transparent(
		core_style.text,
		get_heading_value(heading_level, "separator_opacity")
	)
end

--- Return one heading level's scaled separator thickness
-- @param heading_level number heading level
-- @return number width scaled screen pixels
function markdown_design.get_heading_separator_width(heading_level)
	return math.max(
		1,
		get_heading_value(heading_level, "separator_width") * (SCALE or 1)
	)
end

--- Return the failed-image placeholder border color
-- @return table color placeholder border
function markdown_design.get_image_placeholder_border_color()
	return core_style.transparent(core_style.text, markdown_design.images.placeholder_border_opacity)
end

--- Return the failed-image placeholder text color
-- @return table color placeholder text
function markdown_design.get_image_placeholder_text_color()
	return core_style.dim or core_style.text
end

--- Return theme-aware horizontal padding for table cells
-- @return number padding scaled screen pixels
function markdown_design.get_table_cell_padding_horizontal()
	return math.max(
		markdown_design.tables.minimum_cell_padding_horizontal * (SCALE or 1),
		core_style.padding.x * markdown_design.tables.cell_padding_horizontal_scale
	)
end

--- Return theme-aware spacing above a table
-- @return number gap scaled screen pixels
function markdown_design.get_table_gap_above()
	return core_style.padding.y * markdown_design.tables.theme_gap_above_scale
end

--- Return theme-aware text padding inside a failed-image placeholder
-- @return number padding scaled screen pixels
function markdown_design.get_image_placeholder_padding_horizontal()
	return core_style.padding.x * markdown_design.images.placeholder_cell_padding_horizontal_scale
end

return markdown_design
