-- lx-bundle:true

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

--- Central visual design for the Markdown renderer
-- @module plugins.mdpreview.design

local core_style = require("core.style")

local MINIMUM_VISIBLE_SEPARATOR_OPACITY = 0.05

local markdown_design = {
	-- Font choice hierarchy
	typography = {

		-- Main baseline for every preview font scale
		font_size = 16,
		line_height_scale = 1.2,

		-- Heading font scales are em-like multiples of 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,
			},
		},
	},

	-- Monospace face shared by code spans, code blocks, and detected drawings
	monospace = {
		-- Compensate for visually larger fixed-width faces
		font_scale = 0.9,
	},

	-- Document measurements use logical pixels and are scaled by view
	document = {
		-- Let source padding remain invisible before first block
		skip_initial_blank_lines = true,
		minimum_content_width = 80,
		padding_horizontal = 32,
		padding_vertical = 14,
	},

	-- 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,
	},

	-- Code spans keep the surrounding paragraph's row height
	code_spans = {
		padding_horizontal = 6,
		padding_vertical = 1,
		background_darken = -0.8,
	},

	math = {
		-- Inline math size relative to 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 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 and indented blocks share one compact monospace layout
	code_blocks = {
		line_height_scale = 1.2,

		-- Use 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,
		},
	},

	-- Temporary source detection preserves connected box-drawing characters
	box_drawings = {
		auto_detect = true,
		scan_character_limit = 64,
	},

	lists = {
		base_indent = 24,
		nested_indent_scale = 1,
		item_gap = 1,
		-- Separate a completed list from following non-list block
		gap_below = 8,
		unordered_marker_spaces = 2,
		ordered_marker_spaces = 1,
	},

	quotes = {
		guide_width = 3,
		padding_vertical = 8,
		-- Keep following content clear of completed quote panel
		gap_below = 16,
		base_indent_spaces = 2,
		block_offset_scale = 1.5,
		text_offset_scale = 3,
	},

	tables = {
		-- Snap column widths to a stable shared grid
		column_grid_units = 24,
		-- Ignore longest quarter of cell-length outliers
		column_length_percentile = 0.75,
		-- Keep every ordinary column readable
		minimum_column_units = 2,
		-- Stop one column from consuming whole table
		maximum_column_units = 16,
		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 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 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 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 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 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 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 lighter background reserved for small code spans
-- @return table color code span background
function markdown_design.get_code_span_background_color()
	return core_style.darken(core_style.background, markdown_design.code_spans.background_darken)
end

--- Return 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 darker background reserved for complete code blocks
-- @return table color code block background
function markdown_design.get_code_block_background_color()
	return core_style.darken(
		core_style.background,
		markdown_design.code_blocks.background_darken
	)
end

--- Return lighter background reserved for code-block language headers
-- @return table color code-block header background
function markdown_design.get_code_block_header_background_color()
	return core_style.darken(
		core_style.background,
		markdown_design.code_blocks.header.background_darken
	)
end

--- Return muted text color used by code-block language headers
-- @return table color code-block header text
function markdown_design.get_code_block_header_text_color()
	return core_style.transparent(
		core_style.text,
		markdown_design.code_blocks.header.text_opacity
	)
end

--- Return compact background reserved for code-block language badges
-- @return table color code-block badge background
function markdown_design.get_code_block_badge_background_color()
	return core_style.darken(
		core_style.background,
		markdown_design.code_blocks.badge.background_darken
	)
end

--- Return text color used by code-block language badges
-- @return table color code-block badge text
function markdown_design.get_code_block_badge_text_color()
	return core_style.transparent(
		core_style.text,
		markdown_design.code_blocks.badge.text_opacity
	)
end

--- Return 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 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 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 live quote guide color
-- @return table color quote guide
function markdown_design.get_quote_guide_color()
	return core_style.text
end

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