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

-- This file is part of Lx, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

--[[
- Draws thin horizontal "section header" markers on lines that match the active
  format'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 outlines are resolved:
- Uses format-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 config = require("core.config")
local style = require("core.style")
local DocView = require("core.text.editor")
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

local draw_line_overlay = DocView.draw_line_overlay
--- Return the optional divider profile for one view
-- @param view table document view
-- @return table|nil profile active phase counters
local function get_profile(view)
	local profile = view._outlinedividers_profile

	if not profile then
		profile = {
			calls = 0,
			matches = 0,
			drawn = 0,
			geometry = 0,
			match = 0,
			draw = 0,
		}
		view._outlinedividers_profile = profile
	end

	return profile
end

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

--- Return the final visible-row x position after one logical line's text
-- @param view table document view
-- @param idx number logical line index
-- @param len number logical text length
-- @param first_row number first visible visual row
-- @param last_row number last visible visual row
-- @param x number text origin x
-- @return number|nil text_end_x text end screen position
-- @return number|nil marker_row final displayed row
local function get_text_end_position(view, idx, len, first_row, last_row, x)
	local line_width = view:get_col_x_offset(idx, len + 1)

	if not view:is_wrap_enabled() then
		return x + line_width, idx
	end

	local marker_row, row_info = view:get_visual_row_for_position(idx, len + 1, true)

	if marker_row < first_row or marker_row > last_row then
		return nil, nil
	end

	return x + row_info.indent_x + line_width - row_info.start_x, marker_row
end

--- Return divider geometry for one visible logical line
-- @param view table 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
-- @return number|nil x1 divider left edge
-- @return number|nil x2 divider right edge
-- @return number|nil marker_row displayed row
-- @return number|nil line_height displayed line height
local function get_divider_geometry(view, idx, first_row, last_row, x)
	local lh = view:get_line_height()
	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))
	-- Shared viewport edge already excludes every active right-side lane
	local x2 = view:get_overlay_right_lane_limit() - 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 = view:get_line_screen_position(1) + view:get_font():get_width("n") * config.line_limit
	x2 = math.min(x2, guide_x - guide_pad)

	-- Lines without drawable marker room do not need text measurement
	if x2 - left_gap <= x then
		return nil
	end

	local text = view.doc.lines[idx] or ""
	local len = #text

	if text:sub(-1) == "\n" then
		len = len - 1
	end

	local text_end_x, marker_row = get_text_end_position(view, idx, len, first_row, last_row, x)

	if not text_end_x then
		return nil
	end

	local x1 = text_end_x + left_gap

	if x1 >= x2 then
		return nil
	end

	return x1, x2, marker_row, lh
end

--- Draw outline dividers 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)

	if config.outlinedividers_enabled and self.doc and self.doc.format then
		-- Formats without outline rules have no possible divider lines
		local outline_patterns = self.doc.format.outline_patterns
		if type(outline_patterns) ~= "table" or #outline_patterns == 0 then
			return
		end

		-- Keep timing out of the normal path unless the benchmark explicitly enables it
		local profile = config.text_wrap_profile_enabled == true and get_profile(self) or nil
		if profile then
			profile.calls = profile.calls + 1
		end

		-- Reject ordinary lines before measuring text or resolving wrapped-row geometry
		local match_start_time = profile and system.get_time() or nil
		local outliner = Outliner.for_doc(self.doc)
		local is_outline_line, outline_level = outliner:match_line("divider", idx)
		if profile then
			profile.match = profile.match + system.get_time() - match_start_time
		end
		if not is_outline_line then
			return
		end
		if profile then
			profile.matches = profile.matches + 1
		end

		local geometry_start_time = profile and system.get_time() or nil
		local x1, x2, marker_row, lh = get_divider_geometry(self, idx, first_row, last_row, x)
		if profile then
			profile.geometry = profile.geometry + system.get_time() - geometry_start_time
		end
		if not x1 then
			return
		end

		local draw_start_time = profile and system.get_time() or nil
		local w = math.floor(x2 - x1)
		if w <= 0 then
			if profile then
				profile.draw = profile.draw + system.get_time() - draw_start_time
			end
			return
		end
		local h = math.max(1, math.floor(SCALE + 0.5))
		local color = resolve_divider_color(outline_level)
		local y_mid = y + (marker_row - first_row) * lh + math.floor(lh / 2)
		renderer.draw_rect(x1, y_mid, w, h, color)
		if profile then
			profile.draw = profile.draw + system.get_time() - draw_start_time
			profile.drawn = profile.drawn + 1
		end
	end
end
