---@diagnostic disable: duplicate-set-field,param-type-mismatch
-- lx-bundle:true

local common = require("core.common")
local config = require("core.config")
local DocView = require("core.text.editor")
local style = require("core.style")

local white = common.color("#ffffff")
local black = common.color("#000000")
local tmp = {}

-- Choose readable text color for an RGB background (luminance based)
local function text_color_for_rgb(r, g, b)
	local luma = 0.2126 * r + 0.7152 * g + 0.0722 * b
	return (luma < 140) and white or black
end
-- Draw mode for color previews: "bg" or "alt"
local draw_mode = "alt"

--- Draw one color token segment on a visible visual row
-- @param view table active document view
-- @param text string full logical line text
-- @param line number logical line index
-- @param start_col number segment start byte column
-- @param end_col number segment end byte column
-- @param x number text origin x
-- @param y number visual row y
-- @param r number red channel
-- @param g number green channel
-- @param b number blue channel
local function draw_color_preview_segment(view, text, line, start_col, end_col, x, y, r, g, b)
	local x1 = x + view:get_visual_position_x(line, start_col)
	local x2 = x + view:get_visual_position_x(line, end_col, true)
	if x2 <= x1 then
		return
	end

	local str = text:sub(start_col, end_col - 1)
	local oy = view:get_line_text_y_offset()
	local text_color = text_color_for_rgb(r, g, b)
	local h = view:get_line_height()

	tmp[1], tmp[2], tmp[3] = r, g, b

	if draw_mode == "alt" then
		-- Alt mode fills background, 1px border, inverted text color
		renderer.draw_rect(x1, y, x2 - x1, h, tmp)
		local border = 1 * SCALE
		renderer.draw_rect(x1, y, x2 - x1, border, tmp)
		renderer.draw_rect(x1, y + h - border, x2 - x1, border, tmp)
		renderer.draw_rect(x1, y, border, h, tmp)
		renderer.draw_rect(x2 - border, y, border, h, tmp)
		renderer.draw_text(view:get_font(), str, x1, y + oy, text_color)
	else
		renderer.draw_rect(x1, y, x2 - x1, h, tmp)
		renderer.draw_text(view:get_font(), str, x1, y + oy, text_color)
	end
end

--- Return whether a selection suppresses color previews on one logical line
-- @param view table active document view
-- @param line number logical line index
-- @return boolean hidden whether the preview should be hidden
local function selection_hides_color_preview(view, line)
	if not view.doc:has_selection() then
		return false
	end

	local line1, _, line2 = view.doc:get_selection(true)

	return line >= line1 and line <= line2
end

--- Draw recognized color tokens across their visible visual rows
-- @param self table active document view
-- @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
-- @param ptn string color token pattern
-- @param base number numeric channel base
-- @param nibbles boolean|nil whether channels use one hex digit
-- @param opts table|nil matching options
local function draw_color_previews(self, idx, first_row, last_row, x, y, ptn, base, nibbles, opts)
	opts = opts or {}
	local text = self.doc.lines[idx]
	local s, e = 0, 0
	local line_height = self:get_line_height()

	while true do
		s, e = text:find(ptn, e + 1)
		if not s then
			break
		end

		-- Require non-word boundaries around color tokens (avoid #define)
		local prev_char = (s > 1) and text:sub(s - 1, s - 1) or ""
		local next_char = text:sub(e + 1, e + 1)
		local prev_ok = (prev_char == "") or not prev_char:match("[%w_]")
		if opts.skip_entity_prefix and prev_char == "&" then
			prev_ok = false
		end
		if opts.skip_bracket_prefix and prev_char == "[" then
			prev_ok = false
		end
		local next_ok = (next_char == "") or not next_char:match("[%w_]")
		local in_url_fragment = false
		if opts.skip_url_fragments and prev_ok and next_ok then
			local tok_start = s - 1
			while tok_start >= 1 and not text:sub(tok_start, tok_start):match("%s") do
				tok_start = tok_start - 1
			end
			local token_prefix = text:sub(tok_start + 1, s - 1)
			in_url_fragment = token_prefix:find("://", 1, true) ~= nil
		end
		if prev_ok and next_ok and not in_url_fragment and not selection_hides_color_preview(self, idx) then
			local str = text:sub(s, e)
			local r, g, b = str:match(ptn)
			r, g, b = tonumber(r, base), tonumber(g, base), tonumber(b, base)

			-- #123 becomes #112233
			if nibbles then
				r = r * 16
				g = g * 16
				b = b * 16
			end

			local token_first_row = self:get_visual_row_for_position(idx, s)
			local token_last_row = self:get_visual_row_for_position(idx, e + 1, true)
			local draw_first_row = math.max(first_row, token_first_row)
			local draw_last_row = math.min(last_row, token_last_row)

			for row = draw_first_row, draw_last_row do
				local start_col = s
				local end_col = e + 1

				if self:is_wrap_enabled() then
					local row_info = self:get_visual_row_info(row)
					start_col = math.max(start_col, row_info.start_col)
					end_col = math.min(end_col, row_info.end_col)
				end

				if end_col > start_col then
					draw_color_preview_segment(
						self,
						text,
						idx,
						start_col,
						end_col,
						x,
						y + (row - first_row) * line_height,
						r,
						g,
						b
					)
				end
			end
		end
	end
end

local draw_line_overlay = DocView.draw_line_overlay

--- Draw inline color previews 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)

	-- Skip expensive color token scans on very long lines
	if #(self.doc.lines[idx] or "") > (config.long_line_threshold or 5000) then
		return
	end

	-- Pattern breakdown: '#' plus two hex digits per RGB channel and a following non-hex frontier
	draw_color_previews(
		self, idx, first_row, last_row, x, y, "#(%x%x)(%x%x)(%x%x)%f[%X]", 16, nil,
		{ skip_entity_prefix = true, skip_bracket_prefix = true, skip_url_fragments = true }
	)

	-- Pattern breakdown: '#' plus one hex digit per RGB channel and a following non-hex frontier
	draw_color_previews(
		self, idx, first_row, last_row, x, y, "#(%x)(%x)(%x)%f[%X]", 16, true,
		{ skip_entity_prefix = true, skip_bracket_prefix = true, skip_url_fragments = true }
	)

	-- Pattern breakdown: '#' plus two hex digits per RGB channel, two alpha digits, and a non-hex frontier
	draw_color_previews(
		self, idx, first_row, last_row, x, y, "#(%x%x)(%x%x)(%x%x)%x%x%f[%X]", 16, nil,
		{ skip_entity_prefix = true, skip_bracket_prefix = true, skip_url_fragments = true }
	)

	-- Pattern breakdown: 'rgb' or 'rgba', three decimal channels separated by non-digits, then the closing ')'
	draw_color_previews(self, idx, first_row, last_row, x, y, "rgba?%((%d+)%D+(%d+)%D+(%d+).-%)", 10)
end
