-- lx-bundle:true

--- Key binding registration and context-aware dispatch
-- Keeps layer precedence and active-view contexts in one place
-- @module core.keymap

local command = require("core.command")
local keymap = {}

keymap.modkeys = {}
keymap.map = {}
keymap.reverse_map = {}
keymap.next_stroke_listener = nil
-- XL: Compatibility-oriented layered key dispatch to reduce global binding conflicts
keymap.layer_order = { "modal", "plugin", "user", "default" }
keymap.context_maps = {}
keymap.context_reverse_maps = {}
keymap.context_layer_maps = {}
keymap.layer_maps = {}

local modkey_map = {
	["left ctrl"] = "ctrl",
	["right ctrl"] = "ctrl",
	["left shift"] = "shift",
	["right shift"] = "shift",
	["left alt"] = "alt",
	["right alt"] = "altgr",
}

local modkeys = { "ctrl", "alt", "altgr", "shift" }
-- Recognize canonical modifier tokens passed from the SDL event bridge
local modifier_state_tokens = {
	["ctrl"] = true,
	["alt"] = true,
	["altgr"] = true,
	["shift"] = true,
}
local normalize_stroke

--- Normalize one context name and fall back to 'global'
-- Empty or invalid context names collapse to the global key binding scope
-- @param context string|nil raw context name
-- @return string normalized context name
local function normalize_context_name(context)
	if type(context) ~= "string" or context == "" then
		return "global"
	end

	return context
end

--- Return or initialize the per-layer binding tables for one context
-- The legacy 'keymap.layer_maps' table remains an alias to the 'global' context
-- @param context string|nil requested context
-- @return table context_maps layer -> stroke map
local function ensure_context_layer_maps(context)
	local normalized_context = normalize_context_name(context)
	local context_layer_maps = keymap.context_layer_maps[normalized_context]

	if context_layer_maps then
		return context_layer_maps
	end

	context_layer_maps = {}

	for _, layer in ipairs(keymap.layer_order) do
		context_layer_maps[layer] = {}
	end

	keymap.context_layer_maps[normalized_context] = context_layer_maps

	if normalized_context == "global" then
		keymap.layer_maps = context_layer_maps
	end

	return context_layer_maps
end

ensure_context_layer_maps("global")

--- Emit keymap dispatch diagnostics when enabled in config
-- @param fmt string format string
-- @param ... any format args
-- @return nil
local function debug_keymap_log(fmt, ...)
	local cfg = package.loaded["core.config"]
	if not cfg then
		return
	end
	local line = string.format(fmt, ...)
	local core = package.loaded["core"]
	core.log_quiet("[keymap-debug] %s", line)
end

--- Resolve whether one stroke should be traced for debug logging
-- @param stroke string normalized keystroke
-- @return boolean should log diagnostics for this stroke
local function should_trace_stroke(stroke)
	local cfg = package.loaded["core.config"]
	if not cfg then
		return false
	end
	-- Backward compatibility for the previous escape-only debug switch
	if cfg.debug_escape_keymap and stroke == "escape" then
		return true
	end
	if not cfg.debug_keymap then
		return false
	end
	local filter = cfg.debug_keymap_strokes
	if type(filter) ~= "table" or #filter == 0 then
		return true
	end
	for i = 1, #filter do
		if normalize_stroke(filter[i]) == stroke then
			return true
		end
	end
	return false
end

--- Return the active key binding contexts from the focused view
-- Views may provide a most-specific-first context chain through 'get_keymap_contexts'
-- Only real focused surfaces should add view contexts here
-- Transient popup state should stay on modal bindings instead of pretending to be its own view context
-- Missing or invalid view context hooks fall back to the 'global' context only
-- @return table contexts ordered active context chain
local function get_active_keymap_contexts()
	local contexts = {}
	local seen = {}
	local core = package.loaded["core"]
	local active_view = core and core.active_view or nil

	--- Append one context name once while preserving input order
	-- @param context string|nil requested context name
	local function add_context(context)
		local normalized_context = normalize_context_name(context)

		if seen[normalized_context] then
			return
		end

		seen[normalized_context] = true
		contexts[#contexts + 1] = normalized_context
	end

	if active_view and type(active_view.get_keymap_contexts) == "function" then
		local ok, active_contexts = pcall(active_view.get_keymap_contexts, active_view)

		if ok and type(active_contexts) == "table" then
			for index = 1, #active_contexts do
				add_context(active_contexts[index])
			end
		end
	end

	add_context("global")

	return contexts
end

--- Normalize one keystroke token into canonical modifier order
-- Reorders only leading modifier prefixes and preserves the key token exactly
-- @param stroke string|any input keystroke token
-- @return string|any normalized token or original value when non-string
function normalize_stroke(stroke)
	if type(stroke) ~= "string" then
		return stroke
	end

	local rest = stroke
	local seen = {}
	local progressed = true

	-- Consume only modifier prefixes so keys like ctrl+shift+x normalize consistently
	while progressed do
		progressed = false
		for _, modifier_key in ipairs(modkeys) do
			local prefix = modifier_key .. "+"
			if rest:sub(1, #prefix) == prefix then
				seen[modifier_key] = true
				rest = rest:sub(#prefix + 1)
				progressed = true
				break
			end
		end
	end

	local out = ""
	for _, modifier_key in ipairs(modkeys) do
		if seen[modifier_key] then
			out = out .. modifier_key .. "+"
		end
	end
	-- Canonicalize Enter naming so both aliases resolve to one binding key
	if rest == "return" then
		rest = "enter"
	elseif rest == "keypad return" then
		rest = "keypad enter"
	end
	return out .. rest
end

--- Build one stroke token from active modifier state and key token
-- @param k string key token received from input event
-- @return string composed stroke token
local function key_to_stroke(key)
	local stroke = ""
	for _, modifier_key in ipairs(modkeys) do
		if keymap.modkeys[modifier_key] then
			stroke = stroke .. modifier_key .. "+"
		end
	end
	return stroke .. key
end

--- Build one flat command context table for input-driven dispatch
-- Keeps command payload plumbing generic without changing legacy handlers
-- @param stroke string normalized stroke token
-- @param context table|nil optional source payload
-- @return table|nil payload enriched with stroke
local function build_stroke_command_context(stroke, context)
	if type(context) ~= "table" then
		return nil
	end

	local payload = {}

	for key, value in pairs(context) do
		payload[key] = value
	end

	payload.stroke = stroke

	return payload
end

--- Sync tracked modifier keys from one canonical modifier state string
-- Accepts strings like 'ctrl+shift' or ''
-- @param modifier_state string|nil canonical modifier state
-- @return nil
function keymap.sync_modkeys(modifier_state)
	if modifier_state == nil then
		return
	end

	local next_state = {}

	if type(modifier_state) == "string" and modifier_state ~= "" then
		for token in modifier_state:gmatch("[^+]+") do
			if modifier_state_tokens[token] then
				next_state[token] = true
			end
		end
	end

	for _, mk in ipairs(modkeys) do
		keymap.modkeys[mk] = next_state[mk] or false
	end

	-- Keep AltGr behavior aligned with the explicit keydown path
	if keymap.modkeys["altgr"] then
		keymap.modkeys["ctrl"] = false
	end
end

--- Execute one normalized stroke against mapped command candidates
-- @param stroke string stroke to dispatch
-- @param context table|nil optional source payload for matched commands
-- @return boolean true when one command handled the stroke
local function perform_stroke(stroke, context)
	stroke = normalize_stroke(stroke)
	context = build_stroke_command_context(stroke, context)
	local contexts = get_active_keymap_contexts()
	local trace = should_trace_stroke(stroke)

	for context_index = 1, #contexts do
		local keymap_context = contexts[context_index]
		local context_map = keymap.context_maps[keymap_context]
		local commands = context_map and context_map[stroke] or nil

		if commands then
			-- Context specificity wins first, while each context map has already been merged by layer order
			if trace then
				debug_keymap_log(
					"stroke=%s context=%s candidates=%s",
					stroke,
					keymap_context,
					table.concat(commands, ", ")
				)
			end

			local handled = false

			for _, cmd in ipairs(commands) do
				local performed = command.perform(cmd, context)

				if trace then
					debug_keymap_log("try %s => %s", cmd, tostring(performed == true))
				end

				if performed then
					handled = true
					break
				end
			end

			if trace then
				debug_keymap_log("result handled=%s", tostring(handled))
			end

			return handled
		end
	end

	if trace then
		debug_keymap_log("stroke=%s candidates=(none)", stroke)
	end
	return false
end

--- Read and normalize source path for one stack frame level
-- @param level number stack level for debug.getinfo
-- @return string normalized lowercase source path
local function get_caller_source(level)
	local info = debug.getinfo(level, "S")
	local src = info and (info.source or info.short_src) or ""
	src = tostring(src):gsub("^@", ""):gsub("\\", "/"):lower()
	return src
end

--- Detect default keymap layer from caller source path
-- @return string selected layer name
local function detect_layer_from_caller()
	local core_keymap_src = CORE_DIR and (CORE_DIR .. "keymap.lua"):gsub("\\", "/"):lower() or nil
	local plugins_src = PLUGINS_DIR and PLUGINS_DIR:gsub("\\", "/"):lower() or nil
	local src = ""

	-- Skip this module frame and find the external caller that requested keymap.add
	for level = 4, 10 do
		local candidate = get_caller_source(level)

		if candidate ~= "" and not (core_keymap_src and candidate:find(core_keymap_src, 1, true)) then
			src = candidate
			break
		end
	end

	if src == "" then
		src = get_caller_source(4)
	end

	local user_init_src = USER_DIR and (USER_DIR .. "init.lua"):gsub("\\", "/"):lower() or nil
	local user_settings_src = USER_SETTINGS_FILE and USER_SETTINGS_FILE:gsub("\\", "/"):lower() or nil

	-- Prioritize explicit user files so user bindings are never downgraded to plugin
	if (user_init_src and src:find(user_init_src, 1, true))
		or (user_settings_src and src:find(user_settings_src, 1, true)) then
		return "user"
	end

	if plugins_src and src:find(plugins_src, 1, true) then
		return "plugin"
	end

	if core_keymap_src and src:find(core_keymap_src, 1, true) then
		return "default"
	end

	return "plugin"
end

--- Rebuild flattened stroke and reverse maps from layered maps
-- @return nil
local function rebuild_flat_maps()
	keymap.context_maps = {}
	keymap.context_reverse_maps = {}

	for context, context_layer_maps in pairs(keymap.context_layer_maps) do
		local context_map = {}
		local context_reverse_map = {}
		local seen_by_stroke = {}

		for _, layer in ipairs(keymap.layer_order) do
			local layer_map = context_layer_maps[layer] or {}

			for stroke, commands in pairs(layer_map) do
				local merged = context_map[stroke]

				if not merged then
					merged = {}
					context_map[stroke] = merged
					seen_by_stroke[stroke] = {}
				end

				local seen = seen_by_stroke[stroke]

				for _, cmd in ipairs(commands) do
					-- Keep first occurrence order per stroke while removing duplicates across layers
					if not seen[cmd] then
						seen[cmd] = true
						table.insert(merged, cmd)

						if not context_reverse_map[cmd] then
							context_reverse_map[cmd] = stroke
						end
					end
				end
			end
		end

		keymap.context_maps[context] = context_map
		keymap.context_reverse_maps[context] = context_reverse_map
	end

	-- Keep the legacy flat globals pointed at the 'global' context for compatibility
	keymap.map = keymap.context_maps.global or {}
	keymap.reverse_map = keymap.context_reverse_maps.global or {}
	keymap.layer_maps = ensure_context_layer_maps("global")
end

--- Return sorted context names for keymap exports
-- Keeps broader 'global' output at the end for easier scanning
-- @return table names ordered context names
local function get_export_context_names()
	local names = {}

	for context in pairs(keymap.context_layer_maps) do
		names[#names + 1] = context
	end

	table.sort(names, function(left, right)
		if left == "global" then
			return false
		end

		if right == "global" then
			return true
		end

		return left < right
	end)

	return names
end

--- Copy one command list into a stable plain array
-- @param commands table|nil source commands
-- @return table copied commands
local function copy_command_list(commands)
	local out = {}

	if type(commands) ~= "table" then
		return out
	end

	for index = 1, #commands do
		out[index] = commands[index]
	end

	return out
end

--- Build one export row for one stroke and its commands
-- @param context string export context name
-- @param layer string effective layer or concrete layer name
-- @param stroke string normalized stroke
-- @param commands table source commands
-- @return table row export row
local function build_export_row(context, layer, stroke, commands)
	return {
		context = context,
		layer = layer,
		stroke = stroke,
		commands = copy_command_list(commands),
	}
end

--- Build one complete keymap export snapshot
-- Precomputes context and layer arrays so output formatters stay simple
-- @return table export_data structured export snapshot
function keymap.build_export_data()
	local export_data = {
		generated_at = os.date("%Y-%m-%d %H:%M:%S"),
		contexts = {},
	}
	local context_names = get_export_context_names()

	for context_index = 1, #context_names do
		local context = context_names[context_index]
		local effective_map = keymap.context_maps[context] or {}
		local effective_strokes = {}
		local layer_sections = {}

		for stroke in pairs(effective_map) do
			effective_strokes[#effective_strokes + 1] = stroke
		end

		table.sort(effective_strokes)

		local effective_rows = {}

		for stroke_index = 1, #effective_strokes do
			local stroke = effective_strokes[stroke_index]
			effective_rows[#effective_rows + 1] = build_export_row(context, "effective", stroke, effective_map[stroke])
		end

		for layer_index = 1, #keymap.layer_order do
			local layer = keymap.layer_order[layer_index]
			local context_layer_maps = keymap.context_layer_maps[context] or {}
			local layer_map = context_layer_maps[layer] or {}
			local layer_strokes = {}

			for stroke in pairs(layer_map) do
				layer_strokes[#layer_strokes + 1] = stroke
			end

			table.sort(layer_strokes)

			local layer_rows = {}

			for stroke_index = 1, #layer_strokes do
				local stroke = layer_strokes[stroke_index]
				layer_rows[#layer_rows + 1] = build_export_row(context, layer, stroke, layer_map[stroke])
			end

			layer_sections[#layer_sections + 1] = {
				context = context,
				layer = layer,
				rows = layer_rows,
			}
		end

		export_data.contexts[#export_data.contexts + 1] = {
			name = context,
			effective_rows = effective_rows,
			layer_sections = layer_sections,
		}
	end

	return export_data
end

--- Register key bindings into one layer and rebuild flattened dispatch maps
-- @param map table stroke -> command or list of commands
-- @param overwrite boolean|table optional overwrite flag or options table
-- @param options table|nil explicit options table
-- @return nil
function keymap.add(map, overwrite, options)
	-- XL: Backward-compatible extension supports per-layer behavior and optional global overwrite
	-- Backward compatibility:
	-- keymap.add(map, true)
	-- keymap.add(map, { layer = "user", overwrite = true })
	-- keymap.add(map, true, { layer = "user" })
	if type(overwrite) == "table" and options == nil then
		options = overwrite
		overwrite = options.overwrite
	end

	options = options or {}
	overwrite = overwrite == true
	local context = normalize_context_name(options.context)
	local layer = options.layer or detect_layer_from_caller()
	local context_layer_maps = ensure_context_layer_maps(context)
	local layer_map = context_layer_maps[layer]

	-- Overwrite scope:
	-- - user layer: replace user+default for legacy user override behavior
	-- - default layer: replace default only
	-- - plugin/modal/custom layers: replace same layer only
	-- Overwrite stays local to one context so specific contexts can coexist with broader ones
	local overwrite_scope

	if overwrite then
		if options.global_overwrite == true then
			overwrite_scope = "all"
		elseif options.overwrite_scope then
			overwrite_scope = options.overwrite_scope
		elseif layer == "user" then
			overwrite_scope = "user-default"
		elseif layer == "default" then
			overwrite_scope = "default"
		else
			overwrite_scope = "layer"
		end
	end

	for stroke, commands in pairs(map) do
		-- Accept any modifier order in bindings by normalizing to canonical order
		local normalized_stroke = normalize_stroke(stroke)
		if type(commands) == "string" then
			commands = { commands }
		end

		if overwrite_scope == "all" then
			for _, layer_name in ipairs(keymap.layer_order) do
				context_layer_maps[layer_name][normalized_stroke] = nil
			end
		elseif overwrite_scope == "user-default" then
			if context_layer_maps.user then
				context_layer_maps.user[normalized_stroke] = nil
			end
			if context_layer_maps.default then
				context_layer_maps.default[normalized_stroke] = nil
			end
		elseif overwrite_scope == "default" then
			if context_layer_maps.default then
				context_layer_maps.default[normalized_stroke] = nil
			end
		elseif overwrite_scope == "layer" then
			layer_map[normalized_stroke] = nil
		end

		if overwrite then
			layer_map[normalized_stroke] = commands
		else
			layer_map[normalized_stroke] = layer_map[normalized_stroke] or {}
			-- Insert in reverse so input order remains left-to-right after table.insert at index 1
			for i = #commands, 1, -1 do
				table.insert(layer_map[normalized_stroke], 1, commands[i])
			end
		end
	end

	rebuild_flat_maps()
end

--- Return first configured stroke for one command id
-- @param cmd string command id
-- @return string|nil stroke token
function keymap.get_binding(cmd)
	local contexts = get_active_keymap_contexts()

	for index = 1, #contexts do
		local reverse_map = keymap.context_reverse_maps[contexts[index]]
		local stroke = reverse_map and reverse_map[cmd] or nil

		if stroke then
			return stroke
		end
	end

	return nil
end

--- Returns all commands currently bound to one stroke
-- @param stroke string keystroke token with optional modifiers
-- @return string normalized_stroke
-- @return table commands bound to normalized stroke
function keymap.get_commands_for_stroke(stroke)
	local normalized_stroke = normalize_stroke(stroke)

	local contexts = get_active_keymap_contexts()

	for index = 1, #contexts do
		local context_map = keymap.context_maps[contexts[index]]
		local commands = context_map and context_map[normalized_stroke] or nil

		if type(commands) == "table" then
			local out = {}

			for command_index = 1, #commands do
				out[command_index] = commands[command_index]
			end

			return normalized_stroke, out
		end
	end

	return normalized_stroke, {}
end

--- Arms a one-shot callback that receives the next non-modifier keystroke
-- @param listener function callback with stroke and commands
-- @return boolean true when listener was accepted
function keymap.listen_next_stroke(listener)
	if type(listener) ~= "function" then
		return false
	end
	keymap.next_stroke_listener = listener
	return true
end

--- Cancel any pending one-shot keystroke listener
-- @return nil
function keymap.cancel_next_stroke_listener()
	keymap.next_stroke_listener = nil
end

--- Handle keydown event by tracking modifiers and dispatching stroke
-- @param k string key token from input event
-- @param modifier_state string|nil canonical modifier state
-- @return boolean true when key was handled
function keymap.on_key_down(k, modifier_state)
	keymap.sync_modkeys(modifier_state)

	local mk = modkey_map[k]

	if mk then
		keymap.modkeys[mk] = true
		-- Work around Windows altgr behavior that also toggles ctrl+alt
		if mk == "altgr" then
			keymap.modkeys["ctrl"] = false
		end
	else
		local stroke = key_to_stroke(k)
		local listener = keymap.next_stroke_listener
		local command_context = {
			origin = "keyboard",
			key = k,
		}

		if listener then
			keymap.next_stroke_listener = nil
			-- Consume this keystroke so inspection commands do not also execute mapped actions
			local normalized_stroke, commands = keymap.get_commands_for_stroke(stroke)
			listener(normalized_stroke, commands)
			return true
		end

		local handled = perform_stroke(stroke, command_context)
		return handled
	end
	return false
end

--- Handle mouse wheel event and route it through keymap dispatch
-- Accepts call shapes on_mouse_wheel(y), (x, y), or (x, y, dx, dy)
-- @param a number first wheel arg
-- @param b number|nil second wheel arg
-- @param c number|nil third wheel arg
-- @param d number|nil fourth wheel arg
-- @return boolean true when one wheel binding handled the event
function keymap.on_mouse_wheel(a, b, c, d)
	-- Prefer vertical delta when available in mixed call signatures
	local y = (d ~= nil and d) or (c ~= nil and c) or (b ~= nil and b) or a

	if not y or y == 0 then
		return false
	end

	local core = package.loaded["core"]
	local mouse = core and core.workspace_view and core.workspace_view.mouse or nil
	local wheel = (y > 0) and "wheelup" or "wheeldown"
	local stroke = key_to_stroke(wheel)

	return perform_stroke(stroke, {
		origin = "mouse",
		mouse_x = mouse and mouse.x or nil,
		mouse_y = mouse and mouse.y or nil,
		wheel_y = y,
	})
end

--- Handle keyup event and clear tracked modifier state
-- @param k string key token from input event
-- @param modifier_state string|nil canonical modifier state
-- @return nil
function keymap.on_key_up(k, modifier_state)
	keymap.sync_modkeys(modifier_state)

	local mk = modkey_map[k]

	if mk then
		keymap.modkeys[mk] = false
	end
end

keymap.add({
	["ctrl+shift+p"] = "core:find-command",
	["ctrl+p"] = "core:find-file",
	["ctrl+o"] = "core:open-file",
	["ctrl+n"] = "core:new-doc",
	-- Quit
	["ctrl+q"] = "core:quit",

	-- Toggle fullscreen
	["alt+enter"] = "core:toggle-fullscreen",
	["f11"] = "core:toggle-fullscreen",

	-- Toggle log
	["f12"] = "log:toggle",

	["alt+shift+j"] = "workspace:split-left",
	["alt+shift+l"] = "workspace:split-right",
	["alt+shift+i"] = "workspace:split-up",
	["alt+shift+k"] = "workspace:split-down",
	["alt+j"] = "workspace:switch-to-left",
	["alt+l"] = "workspace:switch-to-right",
	["alt+i"] = "workspace:switch-to-up",
	["alt+k"] = "workspace:switch-to-down",

	["ctrl+w"] = "workspace:close",
	["ctrl+tab"] = "workspace:switch-to-next-tab",
	["ctrl+shift+tab"] = "workspace:switch-to-previous-tab",
	["ctrl+pageup"] = "workspace:move-tab-left",
	["ctrl+pagedown"] = "workspace:move-tab-right",

	-- Switch active pane by index
	["alt+1"] = "workspace:switch-to-pane-1",
	["alt+2"] = "workspace:switch-to-pane-2",
	["alt+3"] = "workspace:switch-to-pane-3",
	["alt+4"] = "workspace:switch-to-pane-4",
	["alt+5"] = "workspace:switch-to-pane-5",
	["alt+6"] = "workspace:switch-to-pane-6",
	["alt+7"] = "workspace:switch-to-pane-7",
	["alt+8"] = "workspace:switch-to-pane-8",
	["alt+9"] = "workspace:switch-to-pane-9",
--[[
	-- Switch active tab by index
	["alt+1"] = "workspace:switch-to-tab-1",
	["alt+2"] = "workspace:switch-to-tab-2",
	["alt+3"] = "workspace:switch-to-tab-3",
	["alt+4"] = "workspace:switch-to-tab-4",
	["alt+5"] = "workspace:switch-to-tab-5",
	["alt+6"] = "workspace:switch-to-tab-6",
	["alt+7"] = "workspace:switch-to-tab-7",
	["alt+8"] = "workspace:switch-to-tab-8",
	["alt+9"] = "workspace:switch-to-tab-9",
]]
	-- Search
	["ctrl+f"] = "find-replace:find",
	["ctrl+h"] = "find-replace:replace",
	["f3"] = "find-replace:repeat-find",
	["shift+f3"] = "find-replace:previous-find",
	["ctrl+g"] = "doc:go-to-line",
	["ctrl+s"] = "doc:save",
	["ctrl+shift+s"] = "doc:save-as",
	-- Built-in content zoom bindings
	["ctrl+0"] = "zoom:reset",
	["ctrl+-"] = "zoom:out",
	["ctrl+="] = "zoom:in",
	["ctrl+keypad +"] = "zoom:in",
	["ctrl+keypad -"] = "zoom:out",
	["ctrl+keypad *"] = "zoom:reset",
	-- Undo
	["ctrl+z"] = "doc:undo",
	-- Toggle word wrap (line wrapping)
	["alt+z"] = "doc:toggle-word-wrap",
	-- Delete lines
	["ctrl+y"] = "doc:delete-lines",
	["ctrl+x"] = "doc:cut",
	["ctrl+c"] = "doc:copy",
	["ctrl+v"] = "doc:paste",
	["escape"] = "doc:select-none",
	["tab"] = "doc:indent",
	["shift+tab"] = "doc:unindent",
	["backspace"] = "doc:backspace",
	["shift+backspace"] = "doc:backspace",
	["ctrl+backspace"] = "doc:delete-to-previous-word-start",
	["ctrl+shift+backspace"] = "doc:delete-to-previous-word-start",
	["delete"] = "doc:delete",
	["shift+delete"] = "doc:delete",
	["ctrl+delete"] = "doc:delete-to-next-word-end",
	["ctrl+shift+delete"] = "doc:delete-to-next-word-end",
	["enter"] = "doc:newline",
	["keypad enter"] = "doc:newline",
	["shift+enter"] = { "doc:newline-below" },
	["shift+keypad enter"] = { "doc:newline-below" },
	["ctrl+shift+enter"] = "doc:newline-above",
	["ctrl+j"] = "doc:join-lines",
	["ctrl+a"] = "doc:select-all",
	["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
	["ctrl+l"] = "doc:select-lines",
	["ctrl+/"] = "doc:toggle-line-comments",
	-- Duplicate lines
	["ctrl+alt+d"] = "doc:duplicate-lines",

	["ctrl+["] = "doc:move-to-previous-block-start",
	["ctrl+]"] = "doc:move-to-next-block-end",
	["ctrl+shift+["] = "doc:select-to-previous-block-start",
	["ctrl+shift+]"] = "doc:select-to-next-block-end",
}, {
	layer = "default"
})
return keymap
