---@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 function draw_box(x, y, w, h, color)
	local r = renderer.draw_rect
	local s = math.ceil(SCALE)
	r(x, y, w, s, color)
	r(x, y + h - s, w, s, color)
	r(x, y + s, s, h - s * 2, color)
	r(x + w - s, y + s, s, h - s * 2, color)
end

local draw_line_body = DocView.draw_line_body

-- Find word under cursor for match highlighting
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
	for s, chunk, e in text:gmatch("()(" .. config.symbol_pattern .. ")()") do
		if col >= s and col <= e then
			word = chunk
			break
		end
	end
	return word
end

function DocView:draw_line_body(idx, 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
		draw_line_body(self, idx, x, y)
		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
		local lh = self:get_line_height()
		selected_text = self.doc.lines[line1]:sub(col1, col2 - 1)
		-- Toggle candidate outline box rendering
		-- Only draw selection candidates when driven by find match selection
		local draw_candidate_box = self.doc.match_selection and false
		local last_col = 1
		while true do
			local start_col, end_col = current_line_text:find(selected_text, last_col, true)
			if start_col == nil then
				break
			end
			local x1 = x + self:get_col_x_offset(idx, start_col)
			local x2 = x + self:get_col_x_offset(idx, end_col + 1)
			local color = style.candidate or style.selection or style.syntax.comment
			if draw_candidate_box then
				-- Pad candidate box so outline doesn't overlap text
				local pad = math.max(4, math.ceil(SCALE))
				draw_box(x1 - pad, y, (x2 - x1) + pad * 2, lh, color)
			end
			last_col = end_col + 1
		end
	end
	draw_line_body(self, idx, x, y)
	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
		-- Use word under cursor when there is no selection
		candidate = word_at_cursor(self.doc)
		whole_word = true
	end
	-- Skip selection-driven candidate tint unless find match is active
	if candidate and candidate ~= "" and (not selected_text or self.doc.match_selection) then
		local last_col = 1
		-- Avoid tinting the active match; handled by match overlay
		local ms = self.doc.match_selection
		while true do
			local start_col, end_col
			if whole_word then
				-- Enforce whole-word matches using symbol boundaries
				local word_re = "%f[%w_]" .. candidate:gsub("(%W)", "%%%1") .. "%f[^%w_]"
				start_col, end_col = current_line_text:find(word_re, last_col)
			else
				-- Selection matches use plain substring search
				start_col, end_col = current_line_text:find(candidate, last_col, true)
			end
			if start_col == nil then
				break
			end
			local is_active_match = ms
				and ms[1] == idx
				and ms[3] == idx
				and ms[2] == start_col
				and ms[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
				)
			local x1 = x + self:get_col_x_offset(idx, start_col)
			local y1 = y + self:get_line_text_y_offset()
			local chunk = current_line_text:sub(start_col, end_col)
			if #chunk > 0 and color then
				renderer.draw_text(self:get_font(), chunk, x1, y1, color)
			end
			last_col = end_col + 1
		end
	end
end
