-- lx-bundle:true

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

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

function patterns.rfc3339ish_date_time(token_type, prefix)
	-- 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

	return {
		-- 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 },
	}
end

-- Returns a GUID/UUID value matcher suitable for key=value style syntaxes
-- 8-4-4-4-12 hex groups with hex boundaries
function patterns.guid_value(token_type, prefix)
	return {
		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*",
	}
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 = "number",
		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
