---@diagnostic disable: duplicate-set-field
local style = require("core.style")
local DocView = require("core.text.editor")
local config = require("core.config")

-- originally written by luveti

local draw_line_overlay = DocView.draw_line_overlay

--- Return the word under the primary cursor
-- @param doc table text document
-- @return string|nil word
local function word_at_cursor(doc)
	local line, col = doc:get_selection()
	local text = doc.lines[line]

	if not text then
		return nil
	end

	local word

	-- Pattern breakdown: empty captures return match bounds around configured symbol runs
	for start_col, chunk, end_col in text:gmatch("()(" .. config.symbol_pattern .. ")()") do
		if col >= start_col and col <= end_col then
			word = chunk
			break
		end
	end

	return word
end

--- Draw one highlighted match across its visible visual rows
-- @param view table document view
-- @param text string logical line text
-- @param idx number logical line index
-- @param start_col number match start byte column
-- @param end_col number inclusive match end byte column
-- @param first_row number first visible visual row
-- @param last_row number last visible visual row
-- @param x number text origin x
-- @param y number first visible row y
-- @param color table highlight color
local function draw_match(view, text, idx, start_col, end_col, first_row, last_row, x, y, color)
	local match_first_row = view:get_visual_row_for_position(idx, start_col)
	local match_last_row = view:get_visual_row_for_position(idx, end_col + 1, true)
	local draw_first_row = math.max(first_row, match_first_row)
	local draw_last_row = math.min(last_row, match_last_row)
	local line_height = view:get_line_height()

	for row = draw_first_row, draw_last_row do
		local segment_start = start_col
		local segment_end = end_col + 1
		local row_info

		if view:is_wrap_enabled() then
			row_info = view:get_visual_row_info(row)
			segment_start = math.max(segment_start, row_info.start_col)
			segment_end = math.min(segment_end, row_info.end_col)
		end

		if segment_end > segment_start then
			local chunk = text:sub(segment_start, segment_end - 1)
			local x_offset = row_info
				and row_info.indent_x + view:get_col_x_offset(idx, segment_start) - row_info.start_x
				or view:get_col_x_offset(idx, segment_start)
			local x1 = x + x_offset
			local row_y = y + (row - first_row) * line_height + view:get_line_text_y_offset()
			renderer.draw_text(view:get_font(), chunk, x1, row_y, color)
		end
	end
end

--- Draw matching-word highlights through the shared visual-row overlay phase
-- @param idx number logical line index
-- @param first_row number first visible visual row
-- @param last_row number last visible visual row
-- @param x number text origin x
-- @param y number first visible row y
function DocView:draw_line_overlay(idx, first_row, last_row, x, y)
	draw_line_overlay(self, idx, first_row, last_row, x, y)

	local current_line_text = self.doc.lines[idx] or ""

	-- Avoid expensive per-frame substring scans on pathological long lines
	if #current_line_text > (config.long_line_threshold or 5000) then
		return
	end

	local line1, col1, line2, col2 = self.doc:get_selection(true)
	local selected_text

	if config.highlight_selection_matches and line1 == line2 and col1 ~= col2 then
		selected_text = self.doc.lines[line1]:sub(col1, col2 - 1)
	end

	local candidate = selected_text
	local whole_word = false

	-- Skip current word highlighting while find match overlay is active
	if (not candidate or candidate == "") and config.highlight_current_word and not self.doc.match_selection then
		candidate = word_at_cursor(self.doc)
		whole_word = true
	end

	-- Skip selection-driven candidate tint unless find match is active
	if not candidate or candidate == "" or selected_text and not self.doc.match_selection then
		return
	end

	local last_col = 1
	local match_selection = self.doc.match_selection

	while true do
		local start_col, end_col

		if whole_word then
			-- Pattern breakdown: frontier assertions require word boundaries and the capture escapes punctuation
			local word_pattern = "%f[%w_]" .. candidate:gsub("(%W)", "%%%1") .. "%f[^%w_]"
			start_col, end_col = current_line_text:find(word_pattern, last_col)
		else
			start_col, end_col = current_line_text:find(candidate, last_col, true)
		end

		if not start_col then
			break
		end

		local is_active_match = match_selection
			and match_selection[1] == idx
			and match_selection[3] == idx
			and match_selection[2] == start_col
			and match_selection[4] == end_col + 1
		local color = not is_active_match
			and (
				(whole_word and (style.word_highlight or style.candidate))
				or style.candidate
				or style.selection
				or style.syntax.comment
			)

		if color then
			draw_match(self, current_line_text, idx, start_col, end_col, first_row, last_row, x, y, color)
		end

		last_col = end_col + 1
	end
end
