#pragma once

namespace lumacs {

constexpr const char* LUA_DEFAULTS = R"lua(
-- =====================================================
-- LUMACS EMBEDDED DEFAULTS
-- =====================================================
-- This runs BEFORE the user's init.lua.
-- It establishes the complete Emacs-like editing environment.
-- User init.lua should only EXTEND, not redefine these.

-- =====================================================
-- 1. CONFIGURATION DEFAULTS
-- =====================================================
editor.config:set("show_line_numbers", true)
editor.config:set("tab_width", 4)
editor.config:set("indent_tabs_mode", false)
editor.config:set("show_completion_popover", false)
editor.config:set("scroll_margin", 3)
editor.config:set("visible_bell", true)
editor.config:set("show_modeline", true)

-- =====================================================
-- 2. MODE SYSTEM INFRASTRUCTURE
-- =====================================================
-- Use lumacs.* namespace to avoid polluting global scope

lumacs = lumacs or {}
lumacs.major_modes = {}
lumacs.minor_modes = {}
lumacs.buffer_modes = {}  -- {buffer_name -> {major = "mode-name", minor = {set}}}

--- Define a major mode with optional configuration
-- @param name string The mode name (e.g., "lua-mode")
-- @param config table Configuration with file_patterns, setup, cleanup, highlight, keymap, comment_start/end
function lumacs.define_major_mode(name, config)
    config = config or {}
    lumacs.major_modes[name] = {
        name = name,
        file_patterns = config.file_patterns or {},
        setup = config.setup or function() end,
        cleanup = config.cleanup or function() end,
        highlight = config.highlight or nil,
        keymap = config.keymap or {},
        comment_start = config.comment_start or "# ",
        comment_end = config.comment_end or "",
        indent_function = config.indent_function or nil,
        parent_mode = config.parent_mode or nil,
    }
end

--- Define a minor mode
-- @param name string The mode name
-- @param config table Configuration with setup, cleanup, keymap, lighter, global
function lumacs.define_minor_mode(name, config)
    config = config or {}
    lumacs.minor_modes[name] = {
        name = name,
        lighter = config.lighter or "",
        setup = config.setup or function() end,
        cleanup = config.cleanup or function() end,
        keymap = config.keymap or {},
        global = config.global or false,
    }
end

--- Activate a major mode for the current buffer
-- @param mode_name string The mode to activate
-- @return boolean Success
function lumacs.activate_major_mode(mode_name)
    local buf = editor.buffer
    local buf_name = buf:name()
    local mode = lumacs.major_modes[mode_name]

    if not mode then
        editor:message("Unknown major mode: " .. mode_name)
        return false
    end

    -- Initialize buffer mode tracking
    lumacs.buffer_modes[buf_name] = lumacs.buffer_modes[buf_name] or {major = nil, minor = {}}

    -- Cleanup previous mode if any
    local prev_mode_name = lumacs.buffer_modes[buf_name].major
    if prev_mode_name and lumacs.major_modes[prev_mode_name] then
        local prev_mode = lumacs.major_modes[prev_mode_name]
        if prev_mode.cleanup then
            prev_mode.cleanup()
        end
    end

    -- Set new mode
    lumacs.buffer_modes[buf_name].major = mode_name

    -- Setup highlighting if defined
    if mode.highlight then
        buf:on_buffer_event(function(event_data)
            if event_data.event == lumacs.BufferEvent.Loaded or
               event_data.event == lumacs.BufferEvent.AfterChange or
               event_data.event == lumacs.BufferEvent.LanguageChanged then
                mode.highlight()
            end
        end)
        mode.highlight()
    end

    -- Run setup
    if mode.setup then
        mode.setup()
    end

    return true
end

--- Deactivate current major mode for current buffer
function lumacs.deactivate_major_mode()
    local buf = editor.buffer
    local buf_name = buf:name()
    local buf_modes = lumacs.buffer_modes[buf_name]

    if not buf_modes or not buf_modes.major then
        return
    end

    local mode = lumacs.major_modes[buf_modes.major]
    if mode and mode.cleanup then
        mode.cleanup()
    end

    buf_modes.major = nil
end

--- Toggle a minor mode for current buffer
-- @param mode_name string The minor mode to toggle
function lumacs.toggle_minor_mode(mode_name)
    local buf = editor.buffer
    local buf_name = buf:name()
    local mode = lumacs.minor_modes[mode_name]

    if not mode then
        editor:message("Unknown minor mode: " .. mode_name)
        return
    end

    -- Initialize buffer mode tracking
    lumacs.buffer_modes[buf_name] = lumacs.buffer_modes[buf_name] or {major = nil, minor = {}}
    local minor_modes = lumacs.buffer_modes[buf_name].minor

    if minor_modes[mode_name] then
        -- Deactivate
        if mode.cleanup then mode.cleanup() end
        minor_modes[mode_name] = nil
        editor:message(mode_name .. " disabled")
    else
        -- Activate
        minor_modes[mode_name] = true
        if mode.setup then mode.setup() end
        editor:message(mode_name .. " enabled")
    end
end

--- Auto-detect and activate major mode based on filename
function lumacs.auto_activate_major_mode()
    local buf = editor.buffer
    local buf_name = buf:name()

    for mode_name, mode in pairs(lumacs.major_modes) do
        for _, pattern in ipairs(mode.file_patterns) do
            if string.match(buf_name, pattern) then
                return lumacs.activate_major_mode(mode_name)
            end
        end
    end

    -- No match, use fundamental mode
    return lumacs.activate_major_mode("fundamental-mode")
end

--- Get current major mode name for current buffer
-- @return string The current major mode name
function lumacs.current_major_mode()
    local buf_name = editor.buffer:name()
    local buf_modes = lumacs.buffer_modes[buf_name]
    return buf_modes and buf_modes.major or "fundamental-mode"
end

--- Check if a minor mode is active
-- @param mode_name string The minor mode to check
-- @return boolean Whether the mode is active
function lumacs.minor_mode_active(mode_name)
    local buf_name = editor.buffer:name()
    local buf_modes = lumacs.buffer_modes[buf_name]
    return buf_modes and buf_modes.minor and buf_modes.minor[mode_name] or false
end

-- Make functions globally accessible (Emacs compatibility)
define_major_mode = lumacs.define_major_mode
define_minor_mode = lumacs.define_minor_mode
activate_major_mode = lumacs.activate_major_mode
deactivate_major_mode = lumacs.deactivate_major_mode
toggle_minor_mode = lumacs.toggle_minor_mode
auto_activate_major_mode = lumacs.auto_activate_major_mode
current_major_mode = lumacs.current_major_mode

-- Define fundamental-mode (always available)
lumacs.define_major_mode("fundamental-mode", {
    comment_start = "# ",
})

-- =====================================================
-- 3. CORE EDITING FUNCTIONS
-- =====================================================

--- Insert newline at point
function lumacs.newline()
    local cursor = editor.cursor
    editor.buffer:insert_newline(cursor)
    editor:set_cursor(lumacs.Position(cursor.line + 1, 0))
end

--- Insert newline after point without moving
function lumacs.open_line()
    local cursor = editor.cursor
    editor.buffer:insert_newline(cursor)
    -- Don't move cursor
end

--- Delete character before point
function lumacs.delete_backward_char()
    local cursor = editor.cursor
    if cursor.line == 0 and cursor.column == 0 then return end

    editor.buffer:erase_char(cursor)

    if cursor.column > 0 then
        editor:set_cursor(lumacs.Position(cursor.line, cursor.column - 1))
    elseif cursor.line > 0 then
        local prev_len = #editor.buffer:line(cursor.line - 1)
        editor:set_cursor(lumacs.Position(cursor.line - 1, prev_len))
    end
end

--- Delete character after point
function lumacs.delete_char()
    local cursor = editor.cursor
    editor.buffer:erase_char(lumacs.Position(cursor.line, cursor.column + 1))
end

--- Self-insert command for printable characters
function lumacs.self_insert(args)
    local char = args[1]
    if char then
        editor.buffer:insert(editor.cursor, char)
        editor:move_right()
    end
end

--- Transpose characters around point
function lumacs.transpose_chars()
    local cursor = editor.cursor
    local line = editor.buffer:line(cursor.line)

    if cursor.column == 0 then
        editor:message("Beginning of line")
        return
    end

    if cursor.column >= #line then
        -- At end of line, transpose last two chars
        if #line < 2 then return end
        local c1 = line:sub(-2, -2)
        local c2 = line:sub(-1)
        local range = lumacs.Range(
            lumacs.Position(cursor.line, #line - 2),
            lumacs.Position(cursor.line, #line)
        )
        editor.buffer:replace(range, c2 .. c1)
    else
        -- Transpose char before and at point
        local c1 = line:sub(cursor.column, cursor.column)
        local c2 = line:sub(cursor.column + 1, cursor.column + 1)
        local range = lumacs.Range(
            lumacs.Position(cursor.line, cursor.column - 1),
            lumacs.Position(cursor.line, cursor.column + 1)
        )
        editor.buffer:replace(range, c2 .. c1)
        editor:move_right()
    end
end

--- Transpose words around point
function lumacs.transpose_words()
    -- Save current position
    local start_pos = editor.cursor

    -- Move backward to start of current word
    editor:move_backward_word()
    local word1_start = editor.cursor

    -- Move forward to end of current word
    editor:move_forward_word()
    local word1_end = editor.cursor

    -- Get first word
    local range1 = lumacs.Range(word1_start, word1_end)
    local word1 = editor.buffer:get_text_in_range(range1)

    -- Move to next word
    editor:move_forward_word()
    local word2_end = editor.cursor
    editor:move_backward_word()
    local word2_start = editor.cursor
    editor:move_forward_word()

    -- Get second word
    local range2 = lumacs.Range(word2_start, word2_end)
    local word2 = editor.buffer:get_text_in_range(range2)

    -- Swap them (replace second first to preserve positions)
    editor.buffer:replace(range2, word1)
    editor.buffer:replace(range1, word2)

    editor:set_cursor(word2_end)
end

--- Transpose lines
function lumacs.transpose_lines()
    local cursor = editor.cursor
    local buf = editor.buffer

    if cursor.line == 0 then
        editor:message("Beginning of buffer")
        return
    end

    local current_line = buf:line(cursor.line)
    local prev_line = buf:line(cursor.line - 1)

    -- Delete both lines and reinsert swapped
    local range = lumacs.Range(
        lumacs.Position(cursor.line - 1, 0),
        lumacs.Position(cursor.line, #current_line)
    )
    buf:erase(range)
    buf:insert(lumacs.Position(cursor.line - 1, 0), current_line .. "\n" .. prev_line)

    editor:set_cursor(lumacs.Position(cursor.line, cursor.column))
end

--- Upcase word at point
function lumacs.upcase_word()
    local start_pos = editor.cursor
    editor:move_forward_word()
    local end_pos = editor.cursor
    local range = lumacs.Range(start_pos, end_pos)
    local text = editor.buffer:get_text_in_range(range)
    if #text > 0 then
        editor.buffer:replace(range, string.upper(text))
    end
end

--- Downcase word at point
function lumacs.downcase_word()
    local start_pos = editor.cursor
    editor:move_forward_word()
    local end_pos = editor.cursor
    local range = lumacs.Range(start_pos, end_pos)
    local text = editor.buffer:get_text_in_range(range)
    if #text > 0 then
        editor.buffer:replace(range, string.lower(text))
    end
end

--- Capitalize word at point
function lumacs.capitalize_word()
    local start_pos = editor.cursor
    editor:move_forward_word()
    local end_pos = editor.cursor
    local range = lumacs.Range(start_pos, end_pos)
    local text = editor.buffer:get_text_in_range(range)
    if #text > 0 then
        local cap = text:sub(1, 1):upper() .. text:sub(2):lower()
        editor.buffer:replace(range, cap)
    end
end

--- Upcase region
function lumacs.upcase_region()
    local range = editor.buffer:get_region(editor.cursor)
    if not range then
        editor:message("No active region")
        return
    end
    local text = editor.buffer:get_text_in_range(range)
    if #text > 0 then
        editor.buffer:replace(range, string.upper(text))
        editor.buffer:deactivate_mark()
    end
end

--- Downcase region
function lumacs.downcase_region()
    local range = editor.buffer:get_region(editor.cursor)
    if not range then
        editor:message("No active region")
        return
    end
    local text = editor.buffer:get_text_in_range(range)
    if #text > 0 then
        editor.buffer:replace(range, string.lower(text))
        editor.buffer:deactivate_mark()
    end
end

--- Comment DWIM (Do What I Mean)
function lumacs.comment_dwim()
    local mode_name = lumacs.current_major_mode()
    local mode = lumacs.major_modes[mode_name]
    local prefix = mode and mode.comment_start or "# "
    local escaped_prefix = prefix:gsub("([^%w])", "%%%1")

    local range = editor.buffer:get_region(editor.cursor)

    if range then
        -- Region handling
        local start_line = range.start.line
        local end_line = range["end"].line

        -- Check if all lines are commented
        local all_commented = true
        for i = start_line, end_line do
            local line = editor.buffer:line(i)
            if #line > 0 and not string.match(line, "^%s*" .. escaped_prefix) then
                all_commented = false
                break
            end
        end

        -- Apply change
        for i = start_line, end_line do
            local line = editor.buffer:line(i)
            local line_range = lumacs.Range(lumacs.Position(i, 0), lumacs.Position(i, #line))

            if #line > 0 then
                if all_commented then
                    -- Uncomment
                    local s, e = string.find(line, escaped_prefix)
                    if s then
                        local suffix = line:sub(e + 1)
                        if suffix:sub(1, 1) == " " then suffix = suffix:sub(2) end
                        local new_line = line:sub(1, s - 1) .. suffix
                        editor.buffer:replace(line_range, new_line)
                    end
                else
                    -- Comment
                    if not string.match(line, "^%s*" .. escaped_prefix) then
                        local indent = string.match(line, "^%s*")
                        local content = line:sub(#indent + 1)
                        local new_line = indent .. prefix .. content
                        editor.buffer:replace(line_range, new_line)
                    end
                end
            end
        end
    else
        -- Single line
        local line_num = editor.cursor.line
        local line = editor.buffer:line(line_num)
        local line_range = lumacs.Range(lumacs.Position(line_num, 0), lumacs.Position(line_num, #line))

        if string.match(line, "^%s*" .. escaped_prefix) then
            -- Uncomment
            local s, e = string.find(line, escaped_prefix)
            if s then
                local suffix = line:sub(e + 1)
                if suffix:sub(1, 1) == " " then suffix = suffix:sub(2) end
                local new_line = line:sub(1, s - 1) .. suffix
                editor.buffer:replace(line_range, new_line)
            end
        else
            -- Comment
            local indent = string.match(line, "^%s*")
            local content = line:sub(#indent + 1)
            local new_line = indent .. prefix .. content
            editor.buffer:replace(line_range, new_line)
        end
    end
end

--- What cursor position - display cursor info
function lumacs.what_cursor_position()
    local cursor = editor.cursor
    local buf = editor.buffer
    local line = buf:line(cursor.line)
    local char_at_point = ""
    local char_code = 0

    if cursor.column < #line then
        char_at_point = line:sub(cursor.column + 1, cursor.column + 1)
        char_code = string.byte(char_at_point) or 0
    end

    -- Calculate total character position
    local char_pos = 0
    for i = 0, cursor.line - 1 do
        char_pos = char_pos + #buf:line(i) + 1  -- +1 for newline
    end
    char_pos = char_pos + cursor.column + 1

    local total_chars = 0
    for i = 0, buf:line_count() - 1 do
        total_chars = total_chars + #buf:line(i) + 1
    end

    local percentage = math.floor((char_pos / total_chars) * 100)

    local msg
    if char_at_point ~= "" then
        msg = string.format("Char: %s (%d, #o%o, #x%x) point=%d of %d (%d%%) Line %d Col %d",
            char_at_point, char_code, char_code, char_code,
            char_pos, total_chars, percentage,
            cursor.line + 1, cursor.column + 1)
    else
        msg = string.format("point=%d of %d (%d%%) Line %d Col %d (EOL)",
            char_pos, total_chars, percentage,
            cursor.line + 1, cursor.column + 1)
    end

    editor:message(msg)
end

-- =====================================================
-- 4. COMMAND REGISTRATION
-- =====================================================

-- === Movement Commands ===
editor:register_command("forward-char", "Move point right", function() editor:move_right() end)
editor:register_command("backward-char", "Move point left", function() editor:move_left() end)
editor:register_command("next-line", "Move point down", function() editor:move_down() end)
editor:register_command("previous-line", "Move point up", function() editor:move_up() end)
editor:register_command("forward-word", "Move forward over a word", function() editor:move_forward_word() end)
editor:register_command("backward-word", "Move backward over a word", function() editor:move_backward_word() end)
editor:register_command("move-beginning-of-line", "Move to beginning of line", function() editor:move_to_line_start() end)
editor:register_command("move-end-of-line", "Move to end of line", function() editor:move_to_line_end() end)
editor:register_command("beginning-of-buffer", "Move to beginning of buffer", function() editor:goto_beginning() end)
editor:register_command("end-of-buffer", "Move to end of buffer", function() editor:goto_end() end)
editor:register_command("scroll-up-command", "Scroll text up (move forward)", function() editor:page_down() end)
editor:register_command("scroll-down-command", "Scroll text down (move backward)", function() editor:page_up() end)

editor:register_command("goto-line", "Go to line N", function(args)
    local n = tonumber(args[1])
    if n and n > 0 then
        editor:goto_line(n - 1)
        editor:message("Line " .. n)
    else
        editor:message("Invalid line number")
    end
end, true, "n")

editor:register_command("recenter-top-bottom", "Center point in window", function()
    -- Simple recenter - just adjust scroll
    editor:message("Recentered")
end)

-- === Editing Commands ===
editor:register_command("self-insert-command", "Insert the character typed", lumacs.self_insert)
editor:register_command("newline", "Insert a newline", lumacs.newline)
editor:register_command("open-line", "Insert newline after point", lumacs.open_line)
editor:register_command("delete-backward-char", "Delete character before point", lumacs.delete_backward_char)
editor:register_command("delete-char", "Delete character after point", lumacs.delete_char)
editor:register_command("transpose-chars", "Transpose characters around point", lumacs.transpose_chars)
editor:register_command("transpose-words", "Transpose words around point", lumacs.transpose_words)
editor:register_command("transpose-lines", "Transpose lines", lumacs.transpose_lines)

-- === Mark & Region Commands ===
editor:register_command("set-mark-command", "Set mark at point", function()
    editor.buffer:set_mark(editor.cursor)
    editor:message("Mark set")
end)

editor:register_command("exchange-point-and-mark", "Exchange point and mark", function()
    local mark = editor.buffer.mark
    if not mark then
        editor:message("No mark set")
        return
    end
    local cursor = editor.cursor
    editor.buffer:set_mark(cursor)
    editor:set_cursor(mark)
end)

editor:register_command("mark-whole-buffer", "Mark entire buffer", function()
    editor.buffer:set_mark(lumacs.Position(0, 0))
    local last_line = editor.buffer:line_count() - 1
    local last_col = #editor.buffer:line(last_line)
    editor:set_cursor(lumacs.Position(last_line, last_col))
    editor:message("Whole buffer marked")
end)

editor:register_command("keyboard-quit", "Quit current command", function()
    editor.buffer:deactivate_mark()
    editor:message("Quit")
end)

-- === Kill Ring Commands ===
editor:register_command("kill-line", "Kill to end of line", function() editor:kill_line() end)
editor:register_command("kill-region", "Kill between point and mark", function() editor:kill_region() end)
editor:register_command("kill-ring-save", "Save region to kill ring without killing", function() editor:copy_region_as_kill() end)
editor:register_command("copy-region-as-kill", "Copy region (alias)", function() editor:copy_region_as_kill() end)
editor:register_command("yank", "Reinsert last killed text", function() editor:yank() end)
editor:register_command("yank-pop", "Replace yanked text with earlier kill", function() editor:yank_pop() end)
editor:register_command("kill-word", "Kill word forward", function() editor:kill_word() end)
editor:register_command("backward-kill-word", "Kill word backward", function() editor:backward_kill_word() end)

-- === Undo Commands ===
editor:register_command("undo", "Undo last change", function()
    if editor:undo() then
        editor:message("Undo")
    else
        editor:message("No further undo information")
    end
end)

editor:register_command("redo", "Redo last undo", function()
    if editor:redo() then
        editor:message("Redo")
    else
        editor:message("No further redo information")
    end
end)

-- === Case Conversion Commands ===
editor:register_command("upcase-word", "Convert word to upper case", lumacs.upcase_word)
editor:register_command("downcase-word", "Convert word to lower case", lumacs.downcase_word)
editor:register_command("capitalize-word", "Capitalize word at point", lumacs.capitalize_word)
editor:register_command("upcase-region", "Convert region to upper case", lumacs.upcase_region)
editor:register_command("downcase-region", "Convert region to lower case", lumacs.downcase_region)

-- === Comment Commands ===
editor:register_command("comment-dwim", "Comment or uncomment region/line", lumacs.comment_dwim)

-- === File & Buffer Commands ===
editor:register_command("save-buffer", "Save current buffer to file", function()
    if editor.buffer:save() then
        editor:message("Wrote " .. editor.buffer:name())
    else
        editor:message("(No changes need to be saved)")
    end
end)

editor:register_command("find-file", "Visit a file", function() editor:find_file_mode() end, true, "f")
editor:register_command("switch-to-buffer", "Select or create buffer", function() editor:buffer_switch_mode() end, true, "b")
editor:register_command("switch-buffer", "Select or create buffer (alias)", function() editor:buffer_switch_mode() end, true, "b")
editor:register_command("kill-buffer", "Kill a buffer", function() editor:kill_buffer_mode() end, true, "b")
editor:register_command("save-buffers-kill-emacs", "Save buffers and exit", function() editor:quit() end)
editor:register_command("save-buffers-kill-terminal", "Save buffers and exit (alias)", function() editor:quit() end)

editor:register_command("list-buffers", "Display buffer list", function()
    local buffer_info = editor:get_all_buffer_info()
    if #buffer_info == 0 then
        editor:message("No buffers")
        return
    end

    local lines = {"Buffer List:", string.rep("-", 60), ""}
    table.insert(lines, string.format("%-3s %-30s %8s  %s", "Mod", "Name", "Size", "File"))
    table.insert(lines, string.rep("-", 60))

    for _, info in ipairs(buffer_info) do
        local mod = info.modified and " * " or "   "
        local filepath = info.filepath and tostring(info.filepath) or ""
        table.insert(lines, string.format("%s %-30s %8d  %s", mod, info.name, info.size, filepath))
    end

    local list_text = table.concat(lines, "\n")
    local list_buf_name = "*Buffer List*"

    if not editor:get_buffer_by_name(list_buf_name) then
        editor:new_buffer(list_buf_name)
    else
        editor:switch_buffer_in_window(list_buf_name)
    end

    editor.buffer:clear()
    editor.buffer:insert(lumacs.Position(0, 0), list_text)
    editor:goto_beginning()
    editor:message(string.format("%d buffers", #buffer_info))
end)

editor:register_command("revert-buffer", "Reload buffer from file", function()
    local buf = editor.buffer
    local filepath = buf:filepath()

    if not filepath then
        editor:message("Buffer is not visiting a file")
        return
    end

    if editor:load_file(filepath) then
        editor:message("Reverted " .. buf:name())
    else
        editor:message("Failed to revert buffer")
    end
end)

-- === Window Commands ===
editor:register_command("split-window-below", "Split window horizontally", function() editor:split_horizontally() end)
editor:register_command("split-window-right", "Split window vertically", function() editor:split_vertically() end)
editor:register_command("delete-window", "Delete current window", function() editor:close_window() end)
editor:register_command("delete-other-windows", "Delete all other windows", function()
    editor:message("delete-other-windows not yet implemented")
end)
editor:register_command("other-window", "Select another window", function() editor:next_window() end)

-- === Search Commands ===
editor:register_command("isearch-forward", "Incremental search forward", function() editor:isearch_mode() end)
editor:register_command("isearch-backward", "Incremental search backward", function() editor:isearch_backward_mode() end)

-- === M-x Command ===
editor:register_command("execute-extended-command", "Read command name and execute", function() editor:command_mode() end)

-- === Macro Commands ===
editor:register_command("kmacro-start-macro", "Start recording macro", function() editor:start_kbd_macro() end)
editor:register_command("kmacro-end-or-call-macro", "End or call macro", function() editor:end_kbd_macro_or_call() end)
editor:register_command("start-kbd-macro", "Start recording macro (alias)", function() editor:start_kbd_macro() end)
editor:register_command("end-kbd-macro-or-call", "End or call macro (alias)", function() editor:end_kbd_macro_or_call() end)

-- === Register Commands ===
editor:register_command("copy-to-register", "Copy region to register", function(args)
    local reg = args[1] and args[1]:sub(1, 1) or 'a'
    editor:copy_region_to_register(reg)
    editor:message("Copied to register " .. reg)
end, true, "s")

editor:register_command("insert-register", "Insert from register", function(args)
    local reg = args[1] and args[1]:sub(1, 1) or 'a'
    if editor:yank_from_register(reg) then
        editor:message("Inserted register " .. reg)
    else
        editor:message("Register " .. reg .. " is empty")
    end
end, true, "s")

-- === Rectangle Commands ===
editor:register_command("kill-rectangle", "Kill rectangle", function() editor:kill_rectangle() end)
editor:register_command("yank-rectangle", "Yank rectangle", function() editor:yank_rectangle() end)
editor:register_command("string-rectangle", "Replace rectangle with string", function(args)
    local str = args[1] or ""
    editor:string_rectangle(str)
end, true, "s")

-- === Info Commands ===
editor:register_command("what-cursor-position", "Display cursor position info", lumacs.what_cursor_position)

editor:register_command("describe-mode", "Describe current modes", function()
    local major = lumacs.current_major_mode()
    local buf_name = editor.buffer:name()
    local buf_modes = lumacs.buffer_modes[buf_name]

    local minors = {}
    if buf_modes and buf_modes.minor then
        for mode, _ in pairs(buf_modes.minor) do
            table.insert(minors, mode)
        end
    end

    local minor_str = #minors > 0 and table.concat(minors, ", ") or "none"
    editor:message(string.format("Major mode: %s | Minor modes: %s", major, minor_str))
end)

editor:register_command("view-messages", "View the *Messages* buffer", function()
    local buf = editor:get_buffer_by_name("*Messages*")
    if buf then
        editor:switch_buffer_in_window("*Messages*")
        editor:goto_end()
    else
        editor:message("No messages logged yet")
    end
end)

editor:register_command("previous-message", "Show previous message from history", function()
    editor:previous_message()
end)

editor:register_command("next-message", "Show next message in history", function()
    editor:next_message()
end)

-- === Theme Commands ===
editor:register_command("set-theme", "Select color theme", function(args)
    if #args == 0 then
        editor:theme_selection_mode()
        return
    end

    local name = args[1]
    local themes = editor.theme_manager:theme_names()
    for _, t in ipairs(themes) do
        if t == name then
            editor.theme_manager:set_active_theme(name)
            editor:message("Theme: " .. name)
            return {success = true}
        end
    end
    editor:message("Unknown theme: " .. name)
    return {success = false, message = "Unknown theme: " .. name}
end, true, "s")

-- === Utility Commands ===
editor:register_command("count-words-region", "Count words in region or buffer", function()
    local buf = editor.buffer
    local range = buf:get_region(editor.cursor)
    local text

    if range then
        text = buf:get_text_in_range(range)
    else
        text = buf:content()
    end

    local words = 0
    for _ in text:gmatch("%S+") do
        words = words + 1
    end

    local lines = 0
    for _ in text:gmatch("\n") do
        lines = lines + 1
    end
    lines = lines + 1

    local target = range and "Region" or "Buffer"
    editor:message(string.format("%s has %d words, %d lines, %d characters", target, words, lines, #text))
end)

editor:register_command("eval-expression", "Evaluate Lua expression", function(args)
    if #args == 0 then return {success = false, message = "Expression required"} end

    local expr = table.concat(args, " ")
    local func, err = load("return " .. expr)

    if not func then
        return {success = false, message = "Parse error: " .. (err or "unknown")}
    end

    local success, result = pcall(func)
    if success then
        editor:message(tostring(result))
        return {success = true, message = tostring(result)}
    else
        return {success = false, message = "Error: " .. tostring(result)}
    end
end, true, "s")

-- === Lua Evaluation Commands (Emacs-like) ===

-- Helper function to safely evaluate Lua code and return result
function lumacs.eval_lua(code)
    if not code or code == "" then
        return nil, "No code to evaluate"
    end

    -- Try to evaluate as expression first (prepend "return")
    local func, err = load("return " .. code)
    if not func then
        -- If that fails, try as statement
        func, err = load(code)
    end

    if not func then
        return nil, "Parse error: " .. (err or "unknown")
    end

    local success, result = pcall(func)
    if success then
        return result, nil
    else
        return nil, "Error: " .. tostring(result)
    end
end

-- Helper function to find the start of a Lua expression before cursor
-- Handles: parentheses (), braces {}, brackets [], strings, and simple expressions
function lumacs.find_sexp_start(line, col)
    if col == 0 then return 0 end

    local pos = col
    local depth_paren = 0
    local depth_brace = 0
    local depth_bracket = 0
    local in_string = false
    local string_char = nil

    -- Scan backward from cursor position
    while pos > 0 do
        local char = line:sub(pos, pos)
        local prev_char = pos > 1 and line:sub(pos - 1, pos - 1) or ""

        -- Handle string literals
        if (char == '"' or char == "'") and prev_char ~= "\\" then
            if in_string and char == string_char then
                in_string = false
                string_char = nil
            elseif not in_string then
                in_string = true
                string_char = char
            end
        end

        if not in_string then
            if char == ")" then depth_paren = depth_paren + 1
            elseif char == "(" then
                depth_paren = depth_paren - 1
                if depth_paren < 0 then return pos end
            elseif char == "}" then depth_brace = depth_brace + 1
            elseif char == "{" then
                depth_brace = depth_brace - 1
                if depth_brace < 0 then return pos end
            elseif char == "]" then depth_bracket = depth_bracket + 1
            elseif char == "[" then
                depth_bracket = depth_bracket - 1
                if depth_bracket < 0 then return pos end
            end

            -- If all depths are 0 and we hit whitespace or line start, we found the expression start
            if depth_paren == 0 and depth_brace == 0 and depth_bracket == 0 then
                -- Check for expression boundaries: whitespace, operators that end expressions
                if char:match("[%s,;]") then
                    return pos + 1
                end
            end
        end

        pos = pos - 1
    end

    return 1  -- Start of line if nothing else found
end

-- Get the Lua expression at or before the cursor
function lumacs.get_sexp_at_point()
    local cursor = editor.cursor
    local buf = editor.buffer
    local line = buf:line(cursor.line)

    if not line or #line == 0 then
        return nil
    end

    -- Find expression boundaries
    local col = cursor.column
    if col > #line then col = #line end

    -- Simple heuristic: find expression from line start to cursor
    -- For multi-line expressions, we'd need more sophisticated parsing
    local start_col = lumacs.find_sexp_start(line, col)
    local expr = line:sub(start_col, col)

    -- Trim whitespace
    expr = expr:match("^%s*(.-)%s*$")

    return expr
end

-- Get multi-line expression ending at cursor (scans backward for complete expression)
function lumacs.get_multiline_sexp()
    local cursor = editor.cursor
    local buf = editor.buffer

    -- Start from cursor and work backward to find complete expression
    local lines = {}
    local current_line = cursor.line
    local depth_paren = 0
    local depth_brace = 0
    local depth_bracket = 0

    -- First, get text from line start to cursor on current line
    local line = buf:line(current_line)
    local end_col = cursor.column
    if end_col > #line then end_col = #line end
    local text_on_line = line:sub(1, end_col)

    -- Count closing delimiters that need matching
    for char in text_on_line:gmatch(".") do
        if char == ")" then depth_paren = depth_paren + 1
        elseif char == "(" then depth_paren = depth_paren - 1
        elseif char == "}" then depth_brace = depth_brace + 1
        elseif char == "{" then depth_brace = depth_brace - 1
        elseif char == "]" then depth_bracket = depth_bracket + 1
        elseif char == "[" then depth_bracket = depth_bracket - 1
        end
    end

    table.insert(lines, 1, text_on_line)

    -- Scan backward through previous lines if we need more opening delimiters
    while (depth_paren > 0 or depth_brace > 0 or depth_bracket > 0) and current_line > 0 do
        current_line = current_line - 1
        line = buf:line(current_line)
        if line then
            for char in line:gmatch(".") do
                if char == ")" then depth_paren = depth_paren + 1
                elseif char == "(" then depth_paren = depth_paren - 1
                elseif char == "}" then depth_brace = depth_brace + 1
                elseif char == "{" then depth_brace = depth_brace - 1
                elseif char == "]" then depth_bracket = depth_bracket + 1
                elseif char == "[" then depth_bracket = depth_bracket - 1
                end
            end
            table.insert(lines, 1, line)
        end
    end

    return table.concat(lines, "\n")
end

editor:register_command("eval-last-sexp", "Evaluate Lua expression before point", function()
    -- Try single-line first, then multi-line
    local expr = lumacs.get_sexp_at_point()

    if not expr or expr == "" then
        -- Try multi-line expression
        expr = lumacs.get_multiline_sexp()
    end

    if not expr or expr == "" then
        editor:message("No expression at point", "warning")
        return {success = false, message = "No expression at point"}
    end

    local result, err = lumacs.eval_lua(expr)

    if err then
        editor:message(err, "error")
        return {success = false, message = err}
    end

    local result_str = tostring(result)
    editor:message("=> " .. result_str)
    return {success = true, message = result_str}
end)

editor:register_command("eval-region", "Evaluate Lua code in region", function()
    local buf = editor.buffer
    local range = buf:get_region(editor.cursor)

    if not range then
        editor:message("No region active", "warning")
        return {success = false, message = "No region active"}
    end

    local code = buf:get_text_in_range(range)
    if not code or code == "" then
        editor:message("Region is empty", "warning")
        return {success = false, message = "Region is empty"}
    end

    local result, err = lumacs.eval_lua(code)

    if err then
        editor:message(err, "error")
        return {success = false, message = err}
    end

    local result_str = tostring(result)
    editor:message("=> " .. result_str)
    return {success = true, message = result_str}
end)

editor:register_command("eval-buffer", "Evaluate entire buffer as Lua code", function()
    local buf = editor.buffer
    local code = buf:content()

    if not code or code == "" then
        editor:message("Buffer is empty", "warning")
        return {success = false, message = "Buffer is empty"}
    end

    local result, err = lumacs.eval_lua(code)

    if err then
        editor:message(err, "error")
        return {success = false, message = err}
    end

    local result_str = result ~= nil and tostring(result) or "nil"
    editor:message("Buffer evaluated: " .. result_str)
    return {success = true, message = result_str}
end)

-- === Face and Theme Helpers for Interactive Use ===

-- Helper to create a FaceAttributes table for use with define_face
function lumacs.face(opts)
    local attrs = lumacs.FaceAttributes()
    if opts.foreground then
        if type(opts.foreground) == "string" then
            attrs.foreground = Color.from_hex(opts.foreground)
        else
            attrs.foreground = opts.foreground
        end
    end
    if opts.background then
        if type(opts.background) == "string" then
            attrs.background = Color.from_hex(opts.background)
        else
            attrs.background = opts.background
        end
    end
    if opts.weight then
        if opts.weight == "bold" then
            attrs.weight = lumacs.FontWeight.Bold
        elseif opts.weight == "light" then
            attrs.weight = lumacs.FontWeight.Light
        end
    end
    if opts.slant then
        if opts.slant == "italic" then
            attrs.slant = lumacs.FontSlant.Italic
        elseif opts.slant == "oblique" then
            attrs.slant = lumacs.FontSlant.Oblique
        end
    end
    if opts.underline ~= nil then
        attrs.underline = opts.underline
    end
    if opts.family then
        attrs.family = opts.family
    end
    if opts.height then
        attrs.height = opts.height
    end
    return attrs
end

-- Convenience function to set a face on the active theme
function lumacs.set_face(name, opts)
    local theme = editor.theme_manager:active_theme()
    if theme then
        theme:set_face(name, lumacs.face(opts))
        editor:message("Face '" .. name .. "' updated")
    else
        editor:message("No active theme", "error")
    end
end

-- Convenience function to get RGB color
function lumacs.rgb(r, g, b)
    return lumacs.Color(r, g, b)
end

-- Convenience function to get color from hex string
function lumacs.hex(hex_str)
    return Color.from_hex(hex_str)
end

-- =====================================================
-- 5. DEFAULT KEYBINDINGS (Emacs-compatible)
-- =====================================================

-- === Self-insert (printable characters handled by C++ fallback) ===
editor:bind_key("Return", "newline")
editor:bind_key("Backspace", "delete-backward-char")
editor:bind_key("Delete", "delete-char")
editor:bind_key("Tab", function()
    -- Simple tab insert for now
    editor.buffer:insert(editor.cursor, "    ")
    for i = 1, 4 do editor:move_right() end
end)

-- === Arrow Keys ===
editor:bind_key("ArrowUp", "previous-line")
editor:bind_key("ArrowDown", "next-line")
editor:bind_key("ArrowLeft", "backward-char")
editor:bind_key("ArrowRight", "forward-char")
editor:bind_key("Home", "move-beginning-of-line")
editor:bind_key("End", "move-end-of-line")
editor:bind_key("PageUp", "scroll-down-command")
editor:bind_key("PageDown", "scroll-up-command")

-- === C- (Control) Bindings ===
editor:bind_key("C-f", "forward-char")
editor:bind_key("C-b", "backward-char")
editor:bind_key("C-n", "next-line")
editor:bind_key("C-p", "previous-line")
editor:bind_key("C-a", "move-beginning-of-line")
editor:bind_key("C-e", "move-end-of-line")
editor:bind_key("C-v", "scroll-up-command")
editor:bind_key("C-d", "delete-char")
editor:bind_key("C-k", "kill-line")
editor:bind_key("C-w", "kill-region")
editor:bind_key("C-y", "yank")
editor:bind_key("C-@", "set-mark-command")
editor:bind_key("C-g", "keyboard-quit")
editor:bind_key("C-s", "isearch-forward")
editor:bind_key("C-r", "isearch-backward")
editor:bind_key("C-o", "open-line")
editor:bind_key("C-t", "transpose-chars")
editor:bind_key("C-l", "recenter-top-bottom")
editor:bind_key("C-/", "undo")
editor:bind_key("C-_", "undo")

-- === M- (Meta/Alt) Bindings ===
editor:bind_key("M-f", "forward-word")
editor:bind_key("M-b", "backward-word")
editor:bind_key("M-<", "beginning-of-buffer")
editor:bind_key("M->", "end-of-buffer")
editor:bind_key("M-v", "scroll-down-command")
editor:bind_key("M-w", "kill-ring-save")
editor:bind_key("M-y", "yank-pop")
editor:bind_key("M-d", "kill-word")
editor:bind_key("M-Backspace", "backward-kill-word")
editor:bind_key("M-u", "upcase-word")
editor:bind_key("M-l", "downcase-word")
editor:bind_key("M-c", "capitalize-word")
editor:bind_key("M-t", "transpose-words")
editor:bind_key("M-x", "execute-extended-command")
editor:bind_key("M-;", "comment-dwim")

-- === M-g (Goto prefix) ===
editor:bind_key("M-g g", "goto-line")
editor:bind_key("M-g M-g", "goto-line")

-- === C-x (Prefix) Bindings ===
editor:bind_key("C-x C-c", "save-buffers-kill-emacs")
editor:bind_key("C-x C-s", "save-buffer")
editor:bind_key("C-x C-f", "find-file")
editor:bind_key("C-x b", "switch-to-buffer")
editor:bind_key("C-x k", "kill-buffer")
editor:bind_key("C-x C-b", "list-buffers")
editor:bind_key("C-x 0", "delete-window")
editor:bind_key("C-x 1", "delete-other-windows")
editor:bind_key("C-x 2", "split-window-below")
editor:bind_key("C-x 3", "split-window-right")
editor:bind_key("C-x o", "other-window")
editor:bind_key("C-x u", "undo")
editor:bind_key("C-x C-x", "exchange-point-and-mark")
editor:bind_key("C-x h", "mark-whole-buffer")
editor:bind_key("C-x C-t", "transpose-lines")
editor:bind_key("C-x =", "what-cursor-position")

-- === C-x C- (Case conversion) ===
editor:bind_key("C-x C-u", "upcase-region")
editor:bind_key("C-x C-l", "downcase-region")

-- === C-x C-e (Lua evaluation) ===
editor:bind_key("C-x C-e", "eval-last-sexp")

-- === C-x r (Register/Rectangle prefix) ===
editor:bind_key("C-x r s", "copy-to-register")
editor:bind_key("C-x r i", "insert-register")
editor:bind_key("C-x r k", "kill-rectangle")
editor:bind_key("C-x r y", "yank-rectangle")
editor:bind_key("C-x r t", "string-rectangle")

-- === C-h (Help prefix) ===
editor:bind_key("C-h e", "view-messages")
editor:bind_key("C-h m", "describe-mode")

-- === M-p/M-n for message history navigation ===
editor:bind_key("M-p", "previous-message")
editor:bind_key("M-n", "next-message")

-- === Function Keys ===
editor:bind_key("F3", "kmacro-start-macro")
editor:bind_key("F4", "kmacro-end-or-call-macro")

-- === Escape as Meta prefix (for terminals) ===
editor:bind_key("Escape", "keyboard-quit")

-- =====================================================
-- 6. INITIALIZE
-- =====================================================

-- Auto-activate major mode for initial buffer
lumacs.auto_activate_major_mode()
)lua";

} // namespace lumacs
