-- lx-bundle:true

--- Markdown reference-style link definitions and lookups
-- @module plugins.mdpreview.references

local markdown_escapes = require("plugins.mdpreview.escapes")

local references = {}

--- Normalize one reference label using Markdown's case and whitespace rules
-- @param label string source reference label
-- @return string key normalized lookup key
local function normalize_label(label)
	-- Pattern breakdown:
	-- '^%s*(.-)%s*$' removes outer whitespace
	-- '%s+' collapses every internal whitespace run to one space
	return markdown_escapes.decode(label):match("^%s*(.-)%s*$"):gsub("%s+", " "):lower()
end

--- Read one link definition with up to three leading spaces
-- Optional titles remain ignored because preview links currently expose their target
-- @param line string source line
-- @return string|nil key normalized reference label
-- @return string|nil target decoded link target
local function read_definition(line)
	-- Pattern breakdown: '^(%s*)' captures indentation before the opening bracket
	local indent = line:match("^(%s*)") or ""

	if #indent > 3 or indent:find("\t", 1, true) then
		return nil
	end

	local label_start = #indent + 1

	if line:sub(label_start, label_start) ~= "[" then
		return nil
	end

	local label_end = markdown_escapes.find_unescaped(line, "]:", label_start + 1)

	if not label_end then
		return nil
	end

	local label = line:sub(label_start + 1, label_end - 1)
	local remainder = line:sub(label_end + 2):match("^%s*(.-)%s*$")

	if label == "" or remainder == "" then
		return nil
	end

	local target

	if remainder:sub(1, 1) == "<" then
		local target_end = markdown_escapes.find_unescaped(remainder, ">", 2)
		target = target_end and remainder:sub(2, target_end - 1) or nil
	else
		-- Pattern breakdown: '^(%S+)' captures the destination before an optional title
		target = remainder:match("^(%S+)")
	end

	if not target or target == "" then
		return nil
	end

	return normalize_label(label), markdown_escapes.decode(target)
end

--- Collect reference definitions outside fenced code blocks
-- The first definition wins, matching ordinary Markdown behavior
-- @param lines table raw document lines
-- @param strip_line_ending function shared line-ending helper
-- @param read_fence function shared fence recognizer
-- @return table definitions normalized labels to targets
-- @return table definition_lines source lines hidden from the preview
function references.scan(lines, strip_line_ending, read_fence)
	local definitions = {}
	local definition_lines = {}
	local fence_marker

	for line_index = 1, #lines do
		local line = strip_line_ending(lines[line_index])
		local possible_fence = read_fence(line)

		if fence_marker then
			local remainder = possible_fence and line:gsub("^%s*" .. possible_fence, "", 1) or line
			local closes_fence = possible_fence
				and possible_fence:sub(1, 1) == fence_marker:sub(1, 1)
				and #possible_fence >= #fence_marker
				and remainder:match("^%s*$") ~= nil

			if closes_fence then
				fence_marker = nil
			end
		elseif possible_fence then
			fence_marker = possible_fence
		else
			local key, target = read_definition(line)

			if key then
				if definitions[key] == nil then
					definitions[key] = target
				end
				definition_lines[line_index] = true
			end
		end
	end

	return definitions, definition_lines
end

--- Read one resolved full or collapsed reference link
-- @param text string Markdown inline source
-- @param position number opening bracket byte
-- @param definitions table normalized labels to targets
-- @return string|nil label visible link label
-- @return string|nil target resolved target
-- @return number|nil next_position first byte after the complete reference
function references.read_link(text, position, definitions)
	if text:sub(position, position) ~= "[" then
		return nil
	end

	local label_end = markdown_escapes.find_unescaped(text, "]", position + 1)

	if not label_end or text:sub(label_end + 1, label_end + 1) ~= "[" then
		return nil
	end

	local reference_end = markdown_escapes.find_unescaped(text, "]", label_end + 2)

	if not reference_end then
		return nil
	end

	local label = text:sub(position + 1, label_end - 1)
	local reference_label = text:sub(label_end + 2, reference_end - 1)

	if label == "" then
		return nil
	end

	local target = definitions[normalize_label(reference_label ~= "" and reference_label or label)]

	if not target then
		return nil
	end

	return label, target, reference_end + 1
end

return references
