---@diagnostic disable: duplicate-set-field,need-check-nil
-- lx-bundle:true

--[[
Outlinedividers plugin

What this plugin does:
- Draws thin horizontal "section header" markers on lines that match the active
  syntax's 'outline_patterns'.
- Each marker starts after the rendered line text and extends rightward.
- Marker length is constrained so it does not overlap right-side UI overlays:
  it stops before scrollbar/minimap space, and (when configured) before the
  vertical ruler position ('config.line_limit').

How outline lines are resolved:
- Uses syntax-provided 'outline_patterns' (same source used by outline commands).
- Uses line-local outline matching so visible-line redraws do not trigger
  full-document outline rescans after every edit

Performance model:
- Outline matching and caching reuse core.text.outliner shared logic
- Divider rendering only queries the current visible line state
- No arbitrary long-line cutoffs are applied by this plugin
]]

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

-- New config keys
config.outlinedividers_enabled = config.outlinedividers_enabled
if config.outlinedividers_enabled == nil then
	config.outlinedividers_enabled = true
end

-- Pixels between last text character and separator start
config.outlinedividers_left_gap = config.outlinedividers_left_gap or 16

-- Pixels between separator end and right-side minimap/scrollbar area
config.outlinedividers_right_gap = config.outlinedividers_right_gap or 16

-- Extra pixels before the right guide line
config.outlinedividers_guide_padding = config.outlinedividers_guide_padding or 8

-- Compares two paths after normalization (slashes, trailing slash, drive case)
local function same_path(a, b)
	if not (a and b) then
		return false
	end
	local pa = tostring(system.absolute_path(a) or a):gsub("\\", "/"):gsub("/+", "/")
	local pb = tostring(system.absolute_path(b) or b):gsub("\\", "/"):gsub("/+", "/")
	if #pa > 1 then
		pa = pa:gsub("/$", "")
	end
	if #pb > 1 then
		pb = pb:gsub("/$", "")
	end
	if pa:match("^%a:/") then
		pa = pa:lower()
	end
	if pb:match("^%a:/") then
		pb = pb:lower()
	end
	return pa == pb
end

-- Returns true when minimap is active and visible for this doc view
local function minimap_visible_for_view(dv)
	if not package.loaded["plugins.minimap"] then
		return false
	end
	if config.minimap_enabled == false then
		return false
	end
	local min_lines = config.minimap_min_file_lines or 30
	if not (dv and dv.doc and dv.doc.lines and #dv.doc.lines >= min_lines) then
		return false
	end
	-- In diff mode minimap is shown only on the right side
	local ctx = core.diff_context
	if ctx and ctx.enabled and dv.doc and dv.doc.filename and ctx.left then
		if same_path(dv.doc.filename, ctx.left) then
			return false
		end
	end
	return true
end

-- Computes right-side width reserved for scrollbar/minimap overlays
local function get_right_reserved_width(dv)
	local reserved = style.scrollbar_size or 0
	if minimap_visible_for_view(dv) then
		local mw = math.floor((config.minimap_width or 112) * SCALE + 0.5)
		mw = common.clamp(mw, style.scrollbar_size + 2, math.max(style.scrollbar_size + 2, math.floor(dv.size.x * 0.5)))
		reserved = math.max(reserved, mw)
	end
	return reserved
end

local draw_line_body = DocView.draw_line_body

local function resolve_divider_color(level)
	local base = style.outline or style.line_number or style.guide or style.selection
	local n = tonumber(level)
	if n and n > 0 then
		n = math.floor(n)
		local level_color = style["outline" .. tostring(n)]
		if level_color then
			return level_color
		end
	end
	return base
end

-- Draws outline separator markers on lines that match syntax outline points
function DocView:draw_line_body(idx, x, y)
	draw_line_body(self, idx, x, y)

	if config.outlinedividers_enabled and self.doc and self.doc.syntax then
		local lh = self:get_line_height()
		local y_mid = y + math.floor(lh / 2)
		local left_gap = math.max(0, math.floor((config.outlinedividers_left_gap or 16) * SCALE + 0.5))
		local right_gap = math.max(0, math.floor((config.outlinedividers_right_gap or 16) * SCALE + 0.5))
		local x2 = self.position.x + self.size.x - get_right_reserved_width(self) - right_gap
		-- Stop before the vertical guide line when available
		local guide_pad = math.max(0, math.floor((config.outlinedividers_guide_padding or 8) * SCALE + 0.5))
		local guide_x = self:get_line_screen_position(1) + self:get_font():get_width("n") * config.line_limit
		x2 = math.min(x2, guide_x - guide_pad)

		-- If this line cannot fully fit in the drawable marker area, never draw marker
		-- Early-return before outline cache lookup/scanning
		local max_text_x = x2 - left_gap
		if max_text_x <= x then
			return
		end
		local text = self.doc.lines[idx] or ""
		local len = #text
		if text:sub(-1) == "\n" then
			len = len - 1
		end
		local max_col_fit = self:get_x_offset_col(idx, max_text_x - x)
		if max_col_fit <= len then
			return
		end

		-- Query only the visible line instead of rebuilding a full file-wide divider set
		local outliner = Outliner.for_doc(self.doc)
		local is_outline_line, outline_level = outliner:match_line("divider", idx)
		if not is_outline_line then
			return
		end

		local line_w = self:get_col_x_offset(idx, len + 1)
		local x1 = x + line_w + left_gap
		local w = math.floor(x2 - x1)
		if w <= 0 then
			return
		end
		local h = math.max(1, math.floor(SCALE + 0.5))
		local color = resolve_divider_color(outline_level)
		renderer.draw_rect(x1, y_mid, w, h, color)
	end
end
