-- lx-bundle:true

--- Text document model with format detection and syntax-highlighter ownership
-- @module core.text.document

local Doc = require("core.doc")
local Highlighter = require("core.text.highlighter")
local file = require("core.text.file")
local edit = require("core.text.edit")
local grid = require("core.text.grid")
local sel = require("core.text.select")
local format_registry = require("core.format")

local TextDocument = Doc:extend()

-- Keep text-model method ownership explicit as text-only behavior moves out of core.doc
for name, fn in pairs({
	set_selection = sel.set_selection,
	get_selection = sel.get_selection,
	set_selections = sel.set_selections,
	get_selections = sel.get_selections,
	add_selection = sel.add_selection,
	remove_selection = sel.remove_selection,
	get_selection_count = sel.get_selection_count,
	has_selection = sel.has_selection,
	has_any_selection = sel.has_any_selection,
	get_selected_texts = sel.get_selected_texts,
	get_selected_line_range = sel.get_selected_line_range,
	sanitize_selection = sel.sanitize_selection,
	sanitize_position = sel.sanitize_position,
	position_offset = sel.position_offset,
	get_text = sel.get_text,
	get_logical_line = sel.get_logical_line,
	line_has_lf = sel.line_has_lf,
	get_char = sel.get_char,
	get_indent_size = grid.get_indent_size,
	get_indent_end_col = grid.get_indent_end_col,
	is_in_indentation = grid.is_in_indentation,
	get_indent_visual_column = grid.get_indent_visual_column,
	get_indent_visual_width = grid.get_indent_visual_width,
	get_indent_col_for_visual_column = grid.get_indent_col_for_visual_column,
	get_previous_indent_caret_col = grid.get_previous_indent_caret_col,
	get_next_indent_caret_col = grid.get_next_indent_caret_col,
	get_indent_delete_range = grid.get_indent_delete_range,
	open = file.open,
	get_reopen_filename = file.get_reopen_filename,
	reopen = file.reopen,
	save = file.save,
	save_current = file.save_current,
	rename = file.rename,
	raw_insert = edit.raw_insert,
	raw_remove = edit.raw_remove,
	raw_replace = edit.raw_replace,
	insert = edit.insert,
	remove = edit.remove,
	text_input = edit.text_input,
	paste_text = edit.paste_text,
	insert_newline = edit.insert_newline,
	replace = edit.replace,
	replace_range = edit.replace_range,
	replace_ranges = edit.replace_ranges,
	delete_to = edit.delete_to,
	move_to = edit.move_to,
	collapse_selections = edit.collapse_selections,
	flash_line = edit.flash_line,
	select_to = edit.select_to,
}) do
	TextDocument[name] = fn
end

--- Reset text document state and rebuild text-specific services
function TextDocument:reset()
	TextDocument.super.reset(self)
	self.highlighter = Highlighter.create(self)
	self:reset_format()
end

--- Re-detect the document format from its filename and header text
function TextDocument:reset_format()
	local header = self:get_text(1, 1, self:position_offset(1, 1, 128))
	local format_definition, detect_meta = format_registry.get(
		self.filename or "",
		header,
		self.is_binary == true
	)
	self.format_detect_meta = detect_meta
	self.kind = format_registry.get_kind(format_definition)

	if self.format ~= format_definition then
		self.format = format_definition
		self.highlighter:reset()
	end
end

--- Replay one text undo entry using raw edit primitives
-- @param command table undo entry to replay
-- @param redo_stack table target stack for inverse entries
-- @return boolean true when applied, false when unsupported
function TextDocument:apply_undo_entry(cmd, redo_stack)
	if cmd.type == "insert" then
		local line, col, text = table.unpack(cmd)
		self:raw_insert(line, col, text, redo_stack, cmd.time)
	elseif cmd.type == "remove" then
		local line1, col1, line2, col2 = table.unpack(cmd)
		self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
	elseif cmd.type == "replace" then
		local line1, col1, line2, col2, text = table.unpack(cmd)
		self:raw_replace(line1, col1, line2, col2, text, redo_stack, cmd.time)
	elseif cmd.type == "caret_text_input" then
		edit.replay_caret_text_input(self, cmd[1], redo_stack, cmd.time)
	elseif cmd.type == "caret_text_delete" then
		edit.replay_caret_text_delete(self, cmd[1], redo_stack, cmd.time)
	elseif cmd.type == "selection_text_replace" then
		edit.replay_selection_text_replace(self, cmd[1], redo_stack, cmd.time)
	elseif cmd.type == "selection" then
		self:set_selection(cmd[1], cmd[2], cmd[3], cmd[4])
	else
		return false
	end

	return true
end

return TextDocument
