--- Build strict file-reference rules for Rust source files
-- @module formats.rust.references

local rust_references = {}

-- Pattern breakdown: macro names stay standalone and end before their required bang and opening parenthesis
local include_prefix = "%f[%w_%.:]include%f[^%w_]%s*!%s*%(%s*"
local include_string_prefix = "%f[%w_%.:]include_str%f[^%w_]%s*!%s*%(%s*"
local include_bytes_prefix = "%f[%w_%.:]include_bytes%f[^%w_]%s*!%s*%(%s*"
-- Pattern breakdown: an outer Rust path attribute ends immediately before its quoted path
local path_attribute_prefix = "#%s*%[%s*path%s*=%s*"

--- Return whether one module name belongs to an external declaration ending in a semicolon
-- @param reference table parsed module reference
-- @param line_text string complete source line
-- @return boolean accepted
local function is_external_module(reference, line_text)
	local first_byte = reference.text:byte(1)
	local valid_start = reference.text:match("^[%a_]") ~= nil
		or first_byte and first_byte >= 128

	return valid_start and line_text:sub(reference.col2):match("^%s*;") ~= nil
end

--- Convert one module declaration into filenames relative to its containing module
-- @param reference table parsed module reference
-- @param source_filename string|nil current Rust source filename
-- @return table candidates
local function module_candidates(reference, source_filename)
	local source = type(source_filename) == "string" and source_filename:gsub("\\", "/") or ""
	local basename = source:match("([^/]+)$") or ""
	local stem = basename:match("^(.*)%.rs$")
	local module_path = reference.path

	-- Crate roots and 'mod.rs' own sibling modules; other files own a same-named module directory
	if stem and stem ~= "lib" and stem ~= "main" and stem ~= "mod" then
		module_path = stem .. PATHSEP .. module_path
	end

	return {
		module_path .. ".rs",
		module_path .. PATHSEP .. "mod.rs",
	}
end

--- Return strict file-reference rules for Rust source files
-- @return table rules
function rust_references.new()
	local rules = {
		{ prefix = include_prefix, open = "\"", close = "\"" },
		{ prefix = include_string_prefix, open = "\"", close = "\"" },
		{ prefix = include_bytes_prefix, open = "\"", close = "\"" },
		{ prefix = path_attribute_prefix, open = "\"", close = "\"" },
	}

	-- External module declarations may be private, public, or visibility-limited
	for _, prefix in ipairs({
		"^%s*mod%s+",
		"^%s*pub%s+mod%s+",
		"^%s*pub%s*%b()%s+mod%s+",
	}) do
		rules[#rules + 1] = {
			prefix = prefix,
			chars = "[%w_]",
			validate = is_external_module,
			candidates = module_candidates,
		}
		-- Raw identifiers use the same filename without their source-only 'r#' marker
		rules[#rules + 1] = {
			prefix = prefix .. "r#",
			chars = "[%w_]",
			validate = is_external_module,
			candidates = module_candidates,
		}
	end

	return rules
end

return rust_references
