-- lx-bundle:true

--- Base view for native rendered-document previews
-- Format adapters supply parsing, layout details, design, and specialized content
-- @module core.preview.view

local preview_layout = require("core.preview.layout")
local preview_render = require("core.preview.render")
local config = require("core.config")
local core = require("core")
local DocView = require("core.doc.view")

local PreviewView = DocView:extend()

PreviewView.is_document_preview = true

-- Direct bindings keep shared hot paths free of forwarding calls
PreviewView.add_layout_row = preview_layout.add_row
PreviewView.get_first_visible_layout_row = preview_layout.find_first_visible_row
PreviewView.get_visible_source_line = preview_layout.get_visible_source_line
PreviewView.capture_layout_anchor = preview_layout.capture_anchor
PreviewView.restore_layout_anchor = preview_layout.restore_anchor
PreviewView.draw_code_block_background = preview_render.draw_code_block_background
PreviewView.draw_document = preview_render.draw_document

--- Return key binding contexts for rendered-document previews
-- @return table contexts ordered from most specific to broadest
function PreviewView:get_keymap_contexts()
	return { "document_preview", "doc_view", "global" }
end

--- Scroll vertically by one rendered body row
-- @param direction number negative for up, positive for down
function PreviewView:scroll_vertical_step(direction)
	self.scroll.to.y = (self.scroll.to.y or self.scroll.y or 0)
		+ direction * self:get_line_height()
	self:clamp_scroll_position()
	core.redraw = true
end

--- Scroll vertically by one visible page
-- @param direction number negative for up, positive for down
function PreviewView:scroll_page(direction)
	local viewport_height = self:get_scroll_viewport_height()
	local line_height = self:get_line_height()
	local step = math.max(line_height, viewport_height - line_height)

	self.scroll.to.y = (self.scroll.to.y or self.scroll.y or 0) + direction * step
	self:clamp_scroll_position()

	if config.animate_page_scroll ~= true then
		self.scroll.y = self.scroll.to.y
	end

	core.redraw = true
end

--- Return one rendered text link under a pointer position
-- Uses measured row widths so link clicks follow the exact painted text
-- @param x number pointer x
-- @param y number pointer y
-- @return string|nil target adapter-owned link target
function PreviewView:get_link_at(x, y)
	if type(x) ~= "number"
		or type(y) ~= "number"
		or x < self.position.x
		or y < self.position.y
		or x >= self.position.x + self.size.x
		or y >= self.position.y + self.size.y
		or self:scrollbar_overlaps_point(x, y)
	then
		return nil
	end

	self:rebuild_layout()

	local design = self.preview_design
	local document_design = design and design.document

	if not document_design then
		return nil
	end

	local content_x = self.position.x
		+ document_design.padding_horizontal * (SCALE or 1)
	local content_top = self.position.y
		+ document_design.padding_vertical * (SCALE or 1)
		- (self.scroll.y or 0)
	local layout_y = y - content_top
	local row_index = self:get_first_visible_layout_row(layout_y)
	local row = self.layout_rows[row_index]

	if not row or layout_y < row.y or layout_y > row.y + row.height then
		return nil
	end

	local run_x = content_x + row.x_offset

	for run_index = 1, #row.runs do
		local run = row.runs[run_index]
		local options = run.options or {}
		local link_x = run_x + (run.link_offset or 0)
		local link_width = run.link_width or run.width

		if options.link and x >= link_x and x <= link_x + link_width then
			return options.link
		end

		run_x = run_x + run.width
	end

	return nil
end

--- Open a rendered link through its format adapter
-- @param x number pointer x
-- @param y number pointer y
-- @return boolean handled true when a link consumed the click
function PreviewView:open_link_at(x, y)
	local target = self:get_link_at(x, y)

	return target and type(self.open_link) == "function" and self:open_link(target) or false
end

return PreviewView
