---@diagnostic disable: duplicate-set-field
-- lx-bundle:true

-- Insert/Overwrite mode toggle for lite

local style = require("core.style")
local common = require("core.common")
local status_items = require("core.status.items")
local TextDocument = require("core.text.document")
local utf8help = require("core.text.utf8help")

local state = {
	overwrite = false,
}

local base_cursor_width = style.cursor_width
local base_cursor = { style.cursor[1], style.cursor[2], style.cursor[3], style.cursor[4] }
local overwrite_status_bg = {
	style.warning[1],
	style.warning[2],
	style.warning[3],
	48,
}

--- Apply the cursor shape that matches the current insert mode
-- @return nil
local function apply_cursor_style()
	if state.overwrite then
		style.cursor_width = math.max(base_cursor_width * 4, common.round(8 * SCALE))
		local alpha = math.max(96, math.floor((base_cursor[4] or 255) * 0.7))
		style.cursor = { base_cursor[1], base_cursor[2], base_cursor[3], alpha }
	else
		style.cursor_width = base_cursor_width
		style.cursor = { base_cursor[1], base_cursor[2], base_cursor[3], base_cursor[4] }
	end
end

local raw_text_input = TextDocument.text_input

--- Count complete visual characters in input text
-- @param text string input text
-- @return number character_count
local function count_input_characters(text)
	local count = 0
	local offset = 1

	while offset <= #text do
		local next_offset = utf8help.next_visual_cluster_offset(text, offset)
		count = count + 1
		offset = next_offset and next_offset > offset and next_offset or (offset + 1)
	end

	return count
end

--- Advance over complete visual characters without crossing the line ending
-- @param line_text string stored line text
-- @param col number starting byte column
-- @param content_end number final content byte column
-- @param character_count number characters to advance
-- @return number end_col exclusive replacement column
local function get_overwrite_end_col(line_text, col, content_end, character_count)
	local end_col = col

	for _ = 1, character_count do
		if end_col > content_end then
			break
		end

		local next_col = utf8help.next_visual_cluster_offset(line_text, end_col)
		end_col = next_col and next_col > end_col and next_col or (end_col + 1)
	end

	return math.min(end_col, content_end + 1)
end

--- Replace text under the caret while overwrite mode is active
-- @param text string input text
-- @return nil
function TextDocument:text_input(text)
	if state.overwrite and not text:find("\n", 1, true) then
		local selections, primary_index = self:get_selections()
		local overwrite_selections = {}
		local character_count = count_input_characters(text)

		for index = 1, #selections do
			local selection = selections[index]

			if selection[1] ~= selection[3] or selection[2] ~= selection[4] then
				overwrite_selections[index] = selection
			else
				local line, col = selection[1], selection[2]
				local line_text = self.lines[line]
				local line_len = #line_text

				if line_text:sub(-1) == "\n" then
					line_len = line_len - 1
				end

				local end_col = get_overwrite_end_col(line_text, col, line_len, character_count)

				overwrite_selections[index] = {
					line,
					col,
					line,
					end_col,
					id = selection.id,
				}
			end
		end

		self:set_selections(overwrite_selections, primary_index)
	end

	raw_text_input(self, text)
end

--- Return the shared status-item spec for overwrite mode
-- @return table status item spec
local function get_overwrite_item_spec()
	return {
		side = "right",
		order = 10000,
		priority = 1500,
		tooltip = "Overwrite mode is active  Press Insert to return to normal insert mode",

		predicate = function(context)
			return context.doc ~= nil and context.can_edit
		end,

		get_tokens = function()
			if state.overwrite then
				return { "Overwrite" }
			else
				return {}
			end
		end,

		get_color = function()
			if state.overwrite then
				return nil, overwrite_status_bg
			else
				return nil, nil
			end
		end,
	}
end

-- Register the overwrite state as a real status item so it can stay rightmost
local overwrite_item = status_items.get_item("insertmode_overwrite")
if not overwrite_item then
	local overwrite_item_spec = get_overwrite_item_spec()
	overwrite_item_spec.id = "insertmode_overwrite"
	overwrite_item = status_items.register(overwrite_item_spec)
else
	overwrite_item:update(get_overwrite_item_spec())
end

--- Switch between normal insert and overwrite modes
-- @return nil
local function toggle()
	state.overwrite = not state.overwrite
	apply_cursor_style()
end

apply_cursor_style()
state.toggle = toggle

return state
