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

local core = require("core")
local style = require("core.style")
local command = require("core.command")
local keymap = require("core.keymap")
local DocView = require("core.text.editor")

local bracket_maps = {
	-- [     ]    (     )    {      }
	{ [91] = 93, [40] = 41, [123] = 125, step = 1 },
	-- ]     [    )     (    }      {
	{ [93] = 91, [41] = 40, [125] = 123, step = -1 },
}

-- Scans forward or backward from a bracket until matching depth returns to zero
local function get_matching_bracket(doc, line, col, line_limit, open_byte, close_byte, step)
	local end_line = line + line_limit * step
	local depth = 0

	while line ~= end_line do
		local byte = doc.lines[line]:byte(col)
		if byte == open_byte then
			depth = depth + 1
		elseif byte == close_byte then
			depth = depth - 1
			if depth == 0 then
				return line, col
			end
		end

		local prev_line, prev_col = line, col
		line, col = doc:position_offset(line, col, step)
		if line == prev_line and col == prev_col then
			break
		end
	end
end

-- Cached bracket match state for the current cursor and document revision
local state = {}

-- Recomputes bracket match state around the active cursor with optional line scan limit
local function update_state(line_limit)
	line_limit = line_limit or math.huge

	-- Reset when no text document with cursor helpers is active
	local active_view = core.active_view
	local doc = active_view and active_view.doc

	if
		not doc
		or type(doc.get_selection) ~= "function"
		or type(doc.position_offset) ~= "function"
		or type(doc.get_change_id) ~= "function"
	then
		state = {}
		return
	end

	-- Early exit when cursor and document revision are unchanged
	local line, col = doc:get_selection()
	local change_id = doc:get_change_id()
	if
		state.doc == doc
		and state.line == line
		and state.col == col
		and state.change_id == change_id
		and state.limit == line_limit
	then
		return
	end

	-- Check current byte and previous byte so cursor on either side of bracket works
	local line2, col2
	for _, map in ipairs(bracket_maps) do
		for i = 0, -1, -1 do
			local line, col = doc:position_offset(line, col, i)
			local open = doc.lines[line]:byte(col)
			local close = map[open]
			if close then
				line2, col2 = get_matching_bracket(doc, line, col, line_limit, open, close, map.step)
				goto found
			end
		end
	end
	::found::

	-- Store latest lookup snapshot
	state = {
		change_id = change_id,
		doc = doc,
		line = line,
		col = col,
		line2 = line2,
		col2 = col2,
		limit = line_limit,
	}
end

local update = DocView.update

-- Updates inherited doc view behavior and then refreshes bracket match state
function DocView:update(...)
	update(self, ...)
	update_state(100)
end

local draw_line_text = DocView.draw_line_text

-- Draws normal text and overlays match underline on the paired bracket location
function DocView:draw_line_text(idx, x, y)
	draw_line_text(self, idx, x, y)

	if self.doc == state.doc and idx == state.line2 then
		local color = style.bracketmatch_color or style.syntax["function"]
		local x1 = x + self:get_col_x_offset(idx, state.col2)
		local x2 = x + self:get_col_x_offset(idx, state.col2 + 1)
		local h = math.ceil(1 * SCALE)
		renderer.draw_rect(x1, y + self:get_line_height() - h, x2 - x1, h, color)
	end
end

command.add({
	-- Moves cursor directly to the matching bracket when available
	["bracket-match:move-to-matching"] = {
		requires = { "doc", "select" },
		perform = function()
		update_state()
		if state.line2 then
			core.active_view.doc:set_selection(state.line2, state.col2)
		end
		end,
	},
})

keymap.add({ ["ctrl+m"] = "bracket-match:move-to-matching" })
