-- lx-bundle:true

--- Generic pane binding for document-backed views
-- @module core.doc.view

local core = require("core")
local monitor = require("core.doc.monitor")
local config = require("core.config")
local keymap = require("core.keymap")
local style = require("core.style")
local View = require("core.ui.view")

local DocView = View:extend()
DocView.LaneIDs = View.LaneIDs
DocView.LaneOrders = View.LaneOrders

--- Build one generic document pane
-- @param doc table document model
function DocView:new(doc)
	DocView.super.new(self)
	self.doc = assert(doc)
	self.scrollable = true
	self.mouse_cursor = "arrow"
end

--- Return tab label with dirty marker when needed
-- @return string name tab display name
function DocView:get_name()
	local post = self.doc:is_dirty() and "*" or ""
	local name = self.doc:get_name()
	local state = monitor.get_state and monitor.get_state(self.doc)
	local deleted_suffix = (state and state.deleted) and " [deleted]" or ""
	-- Pattern breakdown:
	-- '[^/%\\]*' captures trailing chars that are not '/' or '\'
	-- '$' anchors to end, yielding basename without directories
	return name:match("[^/%\\]*$") .. deleted_suffix .. post
end

--- Check whether scroll state is still animating toward its target
-- @return boolean scrolling true when either axis has remaining delta
function DocView:is_scrolling()
	local sx = math.abs((self.scroll.to.x or 0) - (self.scroll.x or 0))
	local sy = math.abs((self.scroll.to.y or 0) - (self.scroll.y or 0))

	return sx > 0.01 or sy > 0.01
end

--- Return the default scrollable size for document panes
-- Concrete surfaces override this when they render content rows
-- @return number size scrollable height
function DocView:get_scrollable_size()
	return 0
end

--- Return per-view content padding for document panes
-- Views can override this to tighten or expand their draw origin
-- @return table padding with 'x' and 'y'
function DocView:get_content_padding()
	return style.padding
end

--- Return key binding contexts for this document view
-- More specific surfaces prepend their own context before the shared 'doc_view' and 'global' scopes
-- @return table contexts ordered from most specific to broadest
function DocView:get_keymap_contexts()
	return { "doc_view", "global" }
end

--- Return per-view zoom levels for deterministic zoom stepping
-- Views can override this to constrain visible zoom choices without touching global defaults
-- @return table zoom levels in percent
function DocView:get_zoom_levels()
	return config.zoom_levels or {}
end

--- Return per-view default zoom level for reset behavior
-- Standard document views treat 100 percent as neutral content zoom
-- @return number zoom level in percent
function DocView:get_default_zoom_level()
	return 100
end

--- Return per-view intrinsic zoom multiplier
-- Special surfaces can expose built-in enlargement without changing the shared zoom baseline
-- @return number zoom multiplier
function DocView:get_zoom_multiplier()
	return 1
end

--- Apply one per-view intrinsic zoom multiplier
-- Special views can react to visible zoom changes without forcing shared text zoom math
-- @param _zoom_multiplier number requested zoom multiplier
function DocView:set_zoom_multiplier(_zoom_multiplier)
end

--- Reset zoom from a control-modified middle click when this view supports content zoom
-- Shared document panes use this before starting any middle-button pan gesture
-- @param button string mouse button
-- @return boolean handled true when the reset consumed the click
function DocView:try_middle_click_zoom_reset(button)
	if button ~= "middle" or not keymap.modkeys["ctrl"] then
		return false
	end

	local zoom = package.loaded["core.zoom"] or require("core.zoom")

	return zoom.reset() == true
end

--- Handle generic document mouse presses before subclass-specific pointer logic
-- Control-modified middle click resets zoom for document-backed views
-- @param button string mouse button
-- @param x number pointer x
-- @param y number pointer y
-- @param clicks number click count
-- @return boolean|nil handled
function DocView:on_mouse_down(button, x, y, clicks)
	if self:try_middle_click_zoom_reset(button) then
		return true
	end

	return DocView.super.on_mouse_down(self, button, x, y, clicks)
end

--- Request close confirmation and optional save for dirty documents
-- @param do_close function callback executed when close is approved
function DocView:try_close(do_close)
	if self.doc:is_dirty() and #core.get_views_referencing_doc(self.doc) == 1 then
		-- Use selectable menu options for unsaved changes prompt
		--- Handle unsaved-changes choice submit callback
		-- @param _ any unused placeholder
		-- @param item table|nil selected menu item
		core.command_view:enter({
			caption = "You have unsaved changes",
			submit = function(_, item)
				local choice = item and item.text or ""
				-- Pattern breakdown:
				-- '^' anchors at start
				-- '[cC]' accepts uppercase and lowercase C
				if choice:match("^[cC]") then
					do_close()
				-- Pattern breakdown:
				-- '^' anchors at start
				-- '[sS]' accepts uppercase and lowercase S
				elseif choice:match("^[sS]") then
					local ok = self.doc:save_current()
					if ok then
						do_close()
					end
				-- Pattern breakdown:
				-- '^' anchors at start
				-- '[kK]' accepts uppercase and lowercase K
				elseif choice:match("^[kK]") then
					return
				end
			end,
			--- Filter unsaved-changes menu items by typed prefix
			-- @param text string current command input
			-- @return table items filtered item labels
			suggest = function(text)
				local items = {}
				-- Pattern breakdown:
				-- '^' anchors at start
				-- '[^cC]' matches any non C/c first char
				-- find returns nil when first char is C/c, so option stays visible
				if not text:find("^[^cC]") then
					table.insert(items, "Close without Saving")
				end
				-- Pattern breakdown:
				-- '^' anchors at start
				-- '[^sS]' matches any non S/s first char
				if not text:find("^[^sS]") then
					table.insert(items, "Save and Close")
				end
				-- Pattern breakdown:
				-- '^' anchors at start
				-- '[^kK]' matches any non K/k first char
				if not text:find("^[^kK]") then
					table.insert(items, "Keep Editing")
				end

				return items
			end,
		})
	else
		do_close()
	end
end

return DocView
