-- lx syntax
local syntax = require("core.syntax")
local quotes = require("core.quotes")

local escape_html_text
local escape_html_attr_double
local escape_html_attr_single
local unescape_html_text
local unescape_html_attr_double
local unescape_html_attr_single

syntax.add({
	name = "Hypertext Markup (HTML)",
	shortname = "HTML",
	author = "liqube",
	extensions = { "htm", "html" },

	headers = {
		"<!doctype",
		"<!DOCTYPE",
		"<html>",
		"<html%s",
	},

	patterns = {
		{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
		-- Highlight full <!DOCTYPE ...> declaration as meta
		{ pattern = "<!doctype[^\n>]*>", type = "meta", caseless = true },
		-- Embedded language blocks
		{ pattern = { "<%s*script[^>\n]*>", "<%s*/%s*script%s*>" }, type = "literal", caseless = true, syntax = ".js" },
		{
			pattern = { "<%s*style[^>\n]*>", "<%s*/%s*style%s*>" },
			type = "literal",
			caseless = true,
			syntax = ".css",
		},
		-- Central document-structure tags as full tokens, including multiline attrs
		{ pattern = { "</?html%f[%s>/]", ">" }, type = "object", caseless = true },
		{ pattern = { "</?head%f[%s>/]", ">" }, type = "object", caseless = true },
		{ pattern = { "</?body%f[%s>/]", ">" }, type = "object", caseless = true },
		--  { pattern = { "</?title%f[%s>/]", ">" }, type = "function", caseless = true },
		{ pattern = { "</?main%f[%s>/]", ">" }, type = "object", caseless = true },
		{ pattern = { "</?article%f[%s>/]", ">" }, type = "object", caseless = true },
		{ pattern = { "<b>", "</b>", "" }, type = "bold", delim_type = "operator", caseless = true },
		{ pattern = { "<strong>", "</strong>", "" }, type = "bold", delim_type = "operator", caseless = true },
		{ pattern = { "<i>", "</i>", "" }, type = "italic", delim_type = "operator", caseless = true },
		--  { pattern = { "<u>", "</u>", "" },      type = "underline", delim_type = "operator", caseless = true },
		-- Keep other tag names as function
		{ pattern = "</[%a][%w:-]*", type = "function" },
		{ pattern = "<[%a][%w:-]*", type = "function" },
		-- Keep '=' as operator and highlight only quoted attribute value text as string
		{ pattern = '"[^\n"]*"', type = "string" },
		{ pattern = "'[^'\n]*'", type = "string" },
		{ pattern = "[/<>=]", type = "operator" },
		{ pattern = "&[%a#][%w#]*;", type = "literal" },
	},

	block_comment = {
		block_open = "<!--",
		block_close = "-->",
		block_line_prefix = "",
		close_line_prefix = "",
		open_on_own_line = false,
		close_on_own_line = true,
		strip_line_prefix = "",
	},

	quotes = {
		primary = "text",
		secondary = "attr_double",
		profiles = {
			text = {
				input_multiline = true,
				output_multiline = true,
				wrap = function(text)
					return escape_html_text(text), nil
				end,
				unwrap = function(text)
					return unescape_html_text(text)
				end,
				escape = function(text)
					return escape_html_text(text), nil
				end,
				unescape = unescape_html_text,
			},
			attr_double = {
				input_multiline = true,
				output_multiline = false,
				delimiters = '"',
				escape = function(text)
					return escape_html_attr_double(text), nil
				end,
				unescape = unescape_html_attr_double,
			},
			attr_single = {
				input_multiline = true,
				output_multiline = false,
				delimiters = "'",
				escape = function(text)
					return escape_html_attr_single(text), nil
				end,
				unescape = unescape_html_attr_single,
			},
		},
	},

	symbols = {
		-- Document Structure & Metadata
		["html"] = "function", -- Root element
		["head"] = "function", -- Metadata container
		["body"] = "function", -- Document body
		["title"] = "function", -- Document title
		["meta"] = "function", -- Metadata properties
		["link"] = "function", -- External resource links
		["style"] = "function", -- Internal CSS
		["script"] = "function", -- JavaScript inclusion
		["noscript"] = "function", -- Fallback for disabled scripts

		-- Semantic Sections
		["header"] = "function", -- Intro/nav container
		["nav"] = "function", -- Navigation links
		["main"] = "function", -- Primary content
		["section"] = "function", -- Generic section
		["article"] = "function", -- Independent content
		["aside"] = "function", -- Sidebars/indirect content
		["footer"] = "function", -- Page or section footer
		["details"] = "function", -- Disclosure widget
		["summary"] = "function", -- Label for <details>

		-- Text Content & Formatting
		["h1"] = "function", -- Heading levels 1-6
		["h2"] = "function",
		["h3"] = "function",
		["h4"] = "function",
		["h5"] = "function",
		["h6"] = "function",
		["p"] = "function", -- Paragraph
		["div"] = "function", -- Block container
		["span"] = "function", -- Inline container
		["a"] = "function", -- Anchor/link
		["li"] = "function", -- List item
		["ol"] = "function", -- Ordered list
		["ul"] = "function", -- Unordered list
		["br"] = "function", -- Line break
		["hr"] = "function", -- Horizontal rule

		-- Forms & Input
		["button"] = "function", -- Clickable button
		["form"] = "function", -- Input container
		["input"] = "function", -- Multi-type input field
		["label"] = "function", -- Input caption
		["option"] = "function", -- Selection item
		["select"] = "function", -- Dropdown menu
		["textarea"] = "function", -- Multi-line text input
		["fieldset"] = "function", -- Form group
		["legend"] = "function", -- Fieldset caption

		-- Multimedia & Graphics
		["audio"] = "function", -- Sound content
		["video"] = "function", -- Movie content
		["img"] = "function", -- Image
		["canvas"] = "function", -- Scriptable graphics
		["svg"] = "function", -- Vector graphics
		["source"] = "function", -- Media resource for audio/video

		-- Tables
		["table"] = "function", -- Table container
		["thead"] = "function", -- Header group
		["tbody"] = "function", -- Body group
		["tfoot"] = "function", -- Footer group
		["tr"] = "function", -- Table row
		["th"] = "function", -- Header cell
		["td"] = "function", -- Data cell

		-- Attributes (keyword)
		["alt"] = "keyword", -- Image fallback text
		["aria-label"] = "keyword", -- Accessibility label
		["charset"] = "keyword", -- Encoding (meta)
		["class"] = "keyword", -- CSS class
		["content"] = "keyword", -- Meta content attribute
		["data-"] = "keyword", -- Custom data attributes
		["disabled"] = "keyword", -- Form disabled state
		["for"] = "keyword", -- Label association
		["href"] = "keyword", -- Link destination
		["id"] = "keyword", -- Unique identifier
		["lang"] = "keyword", -- Language code
		["name"] = "keyword", -- Element name (forms/meta)
		["placeholder"] = "keyword", -- Hint text
		["rel"] = "keyword", -- Relationship (link)
		["required"] = "keyword", -- Form validation constraint
		["src"] = "keyword", -- Resource source path
		["target"] = "keyword", -- Link target window
		["type"] = "keyword", -- Input or script type
		["value"] = "keyword", -- Form element value

		-- Special Declarations (keyword2)
		["<!DOCTYPE"] = "keyword2", -- Document type declaration
		--  ["!--"]          = "keyword2", -- Comment start
		--  ["-->"]          = "keyword2", -- Comment end
	},
})

local TEXT_ESC_MAP = {
	["&"] = "&amp;",
	["<"] = "&lt;",
	[">"] = "&gt;",
}

local ATTR_DOUBLE_ESC_MAP = {
	["&"] = "&amp;",
	["<"] = "&lt;",
	[">"] = "&gt;",
	['"'] = "&quot;",
}

local ATTR_SINGLE_ESC_MAP = {
	["&"] = "&amp;",
	["<"] = "&lt;",
	[">"] = "&gt;",
	["'"] = "&#39;",
}

escape_html_text = function(text)
	return (tostring(text or ""):gsub("[&<>]", TEXT_ESC_MAP))
end

escape_html_attr_double = function(text)
	return (tostring(text or ""):gsub('[&<>"]', ATTR_DOUBLE_ESC_MAP))
end

escape_html_attr_single = function(text)
	return (tostring(text or ""):gsub("[&<>']", ATTR_SINGLE_ESC_MAP))
end

-- Strict profile-local decoders:
-- each context decodes only the entities produced by its own encoder
unescape_html_text = function(text)
	local s = tostring(text or "")
	s = s:gsub("&lt;", "<")
	s = s:gsub("&gt;", ">")
	s = s:gsub("&amp;", "&")
	return s, nil
end

unescape_html_attr_double = function(text)
	local s = tostring(text or "")
	s = s:gsub("&quot;", '"')
	s = s:gsub("&lt;", "<")
	s = s:gsub("&gt;", ">")
	s = s:gsub("&amp;", "&")
	return s, nil
end

unescape_html_attr_single = function(text)
	local s = tostring(text or "")
	s = s:gsub("&#39;", "'")
	s = s:gsub("&apos;", "'")
	s = s:gsub("&lt;", "<")
	s = s:gsub("&gt;", ">")
	s = s:gsub("&amp;", "&")
	return s, nil
end
