-- 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_type = grid.get_indent_type,
	get_automatic_indent_size = grid.get_automatic_indent_size,
	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,
	detect_indentation = file.detect_indentation,
	open = file.open,
	get_reopen_filename = file.get_reopen_filename,
	reopen = file.reopen,
	read_location_source = file.read_location_source,
	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,
	clear_redo = edit.clear_redo,
	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,
	convert_indentation = edit.convert_indentation,
	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)
	-- A newly loaded document starts with automatic format detection
	self.format_override = nil
	-- Clear file evidence before new content is loaded into a reused document
	self.detected_indent_type = nil
	self.detected_indent_size = nil
	self.highlighter = Highlighter.create(self)
	self:reset_format()
end

--- Keep an explicit format or re-detect one from the filename and header text
function TextDocument:reset_format()
	local format_definition = self.format_override
	local detect_meta = nil

	if not format_definition then
		-- Use the initial file-open sample size so later automatic resets cannot change dialects
		local sample_size = file.get_format_detection_sample_size()
		local header = self:get_text(1, 1, self:position_offset(1, 1, sample_size))
		format_definition, detect_meta = format_registry.get(
			self.filename or "",
			header,
			self.is_binary == true
		)
	end

	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)
		if self:raw_replace(line1, col1, line2, col2, text, redo_stack, cmd.time) == false then
			return false
		end
	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
		if edit.replay_selection_text_replace(self, cmd[1], redo_stack, cmd.time) == false then
			return false
		end
	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
