-- lx-bundle:true

---@diagnostic disable: need-check-nil
local patterns = {}

--- Wrap one value rule in matching quote captures without losing its inner color roles
-- @param rule table pattern rule
-- @param quote_pattern string Lua pattern matching one quote delimiter
-- @param delimiter_type string color role for both quote delimiters
-- @return table rule quoted pattern rule
local function wrap_quoted_rule(rule, quote_pattern, delimiter_type)
	local value_types = type(rule.type) == "table" and rule.type or { rule.type }
	local quoted_types = { delimiter_type }
	local value_pattern = rule.pattern

	for _, value_type in ipairs(value_types) do
		quoted_types[#quoted_types + 1] = value_type
	end
	quoted_types[#quoted_types + 1] = delimiter_type

	if type(rule.type) ~= "table" then
		value_pattern = "(" .. value_pattern .. ")"
	end

	-- Pattern breakdown: capture the opening quote, existing value parts, and closing quote
	rule.pattern = "(" .. quote_pattern .. ")" .. value_pattern .. "(" .. quote_pattern .. ")"
	rule.type = quoted_types

	return rule
end

-- Escapes Lua pattern magic chars for literal matching
local function escape_lua_pattern(text)
	return (text:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"))
end

-- Applies optional key/value overrides to a pattern table
local function with_opts(rule, opts)
	if type(opts) ~= "table" then
		return rule
	end
	for k, v in pairs(opts) do
		rule[k] = v
	end
	return rule
end

--- Return RFC3339-like date and time rules, optionally including surrounding quotes
-- @param token_type string|table|nil color roles for date, time, and separator parts
-- @param prefix string|nil required line prefix before the value
-- @param opts table|nil optional 'quoted' and 'delimiter_type' choices
-- @return table rules
function patterns.rfc3339ish_date_time(token_type, prefix, opts)
	-- Returns date/time matcher bundle for RFC3339-like timestamps and standalone date/time values
	local date_type = "date"
	local time_type = "time"
	local operator_type = "operator"
	if type(token_type) == "table" then
		date_type = token_type.date or date_type
		time_type = token_type.time or time_type
		operator_type = token_type.operator or operator_type
	elseif type(token_type) == "string" then
		date_type = token_type
		time_type = token_type
	end

	local rules = {
		-- Datetime: 2020-02-29T23:59:59Z or 2020-02-29T23:59:59.123Z
		{
			pattern = "(%d%d%d%d%-%d%d%-%d%d)([Tt ])(%d%d:%d%d:%d%d%.%d+)([Zz]?)",
			type = { date_type, operator_type, time_type, operator_type },
			line_prefix = prefix,
		},
		{
			pattern = "(%d%d%d%d%-%d%d%-%d%d)([Tt ])(%d%d:%d%d:%d%d)([Zz]?)",
			type = { date_type, operator_type, time_type, operator_type },
			line_prefix = prefix,
		},
		-- Datetime with numeric offset: +01:00 or -05:30
		{
			pattern = "(%d%d%d%d%-%d%d%-%d%d)([Tt ])(%d%d:%d%d:%d%d%.%d+)([%+%-]%d%d:%d%d)",
			type = { date_type, operator_type, time_type, operator_type },
			line_prefix = prefix,
		},
		{
			pattern = "(%d%d%d%d%-%d%d%-%d%d)([Tt ])(%d%d:%d%d:%d%d)([%+%-]%d%d:%d%d)",
			type = { date_type, operator_type, time_type, operator_type },
			line_prefix = prefix,
		},
		-- Date only: 2020-02-29
		{ pattern = "%d%d%d%d%-%d%d%-%d%d", type = date_type, line_prefix = prefix },
		-- Time only: 23:59:59 or 23:59:59.123
		{ pattern = "%d%d:%d%d:%d%d%.%d+", type = time_type, line_prefix = prefix },
		{ pattern = "%d%d:%d%d:%d%d", type = time_type, line_prefix = prefix },
	}

	if opts and opts.quoted == true then
		for _, rule in ipairs(rules) do
			wrap_quoted_rule(rule, '"', opts.delimiter_type or "operator")
		end
	end

	return rules
end

--- Return a GUID/UUID value rule suitable for key/value syntaxes
-- @param token_type string|nil color role for the UUID
-- @param prefix string|nil required line prefix before the value
-- @param opts table|nil optional 'quoted' and 'delimiter_type' choices
-- @return table rule
function patterns.guid_value(token_type, prefix, opts)
	local rule = {
		-- Pattern breakdown: five hexadecimal groups in the standard 8-4-4-4-12 shape
		pattern = "%f[%x]%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x%f[^%x]",
		type = token_type or "hex",
		line_prefix = prefix or "%s*[^=;\n]+=%s*",
	}

	if opts and opts.quoted == true then
		wrap_quoted_rule(rule, '"', opts.delimiter_type or "operator")
	end

	return rule
end

-- Returns a braced GUID/UUID matcher: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
function patterns.guid_braced(token_type)
	return {
		pattern = "(%{)(%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x)(%})",
		type = token_type or { "operator", "hex", "operator" },
	}
end

-- Returns a bounded integer/float/exponent numeric token matcher
function patterns.number_token(opts)
	return with_opts({
		pattern = "-?%d+[%d%.eE]*f?",
		type = "number",
		not_preceded_by = "[%w_]",
		not_followed_by = "[%w_]",
	}, opts)
end

-- Returns a bounded float token matcher
function patterns.float_token(opts)
	return with_opts({
		pattern = "-?%.?%d+f?",
		type = "number",
		not_preceded_by = "[%w_]",
		not_followed_by = "[%w_]",
	}, opts)
end

-- Returns a bounded hex token matcher (e.g. 0xFFAA12)
function patterns.hex_token(opts)
	return with_opts({
		pattern = "0x[%da-fA-F]+",
		type = "hex",
		not_preceded_by = "[%w_]",
		not_followed_by = "[%w_]",
	}, opts)
end

-- Returns a double-quoted string matcher with operator delimiters
function patterns.double_quoted_string(token_type, delim_type, opts)
	return with_opts({
		pattern = { '"', '"', "\\" },
		type = token_type or "string",
		delim_type = delim_type or "operator",
		-- Avoid opening quote state on stray standalone " with no same-line closer
		followed_by = '[^"\n]-"',
	}, opts)
end

-- Returns a single-quoted string matcher with operator delimiters
function patterns.single_quoted_string(token_type, delim_type, opts)
	return with_opts({
		pattern = { "'", "'", "\\" },
		type = token_type or "string",
		delim_type = delim_type or "operator",
		-- Avoid opening quote state on stray standalone ' with no same-line closer
		followed_by = "[^'\n]-'",
		-- Prevent apostrophes inside words (e.g. shouldn't) from opening/closing strings
		not_preceded_by = "[%w_]",
		not_followed_by = "[%w_]",
	}, opts)
end

-- Returns a single-line comment matcher for a literal comment prefix
function patterns.line_comment(prefix, opts)
	local p = escape_lua_pattern(prefix or "//") .. ".-\n"
	return with_opts({
		pattern = p,
		type = "comment",
	}, opts)
end

-- Returns a block comment matcher from start/end Lua patterns
function patterns.block_comment(open_pat, close_pat, opts)
	return with_opts({
		pattern = { open_pat or "/%*", close_pat or "%*/" },
		type = "comment",
	}, opts)
end

function patterns.shebang_line(token_type, opts)
	return with_opts({
		pattern = "#![^\n]*[ /][%w._+-]+",
		type = token_type or "meta",
		line_start = true,
	}, opts)
end

return patterns
