-- C/C++ Major Mode for Lumacs

-- Define helper function for escaping Lua patterns
local function escape_pattern(text)
    return text:gsub("([%%^%%$%ስ%%(%%)%%%.%%[%]%*%+%-%?])", "%%%%%1")
end

-- C/C++ Keywords
local cpp_keywords = {
    "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit",
    "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case",
    "catch", "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept",
    "const", "consteval", "constexpr", "constinit", "const_cast", "continue",
    "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do",
    "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern",
    "false", "float", "for", "friend", "goto", "if", "inline", "int", "long",
    "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator",
    "or", "or_eq", "private", "protected", "public", "reflexpr", "register",
    "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static",
    "static_assert", "static_cast", "struct", "switch", "synchronized", "template",
    "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename",
    "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while",
    "xor", "xor_eq"
}

-- C/C++ Types (some overlap with keywords, but good to have separate for flexibility)
local cpp_types = {
    "void", "char", "short", "int", "long", "float", "double", "bool",
    "signed", "unsigned", "class", "struct", "union", "enum", "typename",
    -- Standard library types commonly used
    "size_t", "ptrdiff_t", "int8_t", "int16_t", "int32_t", "int64_t",
    "uint8_t", "uint16_t", "uint32_t", "uint64_t", "string", "vector",
    "map", "set", "unique_ptr", "shared_ptr", "cout", "cin", "cerr", "endl" -- Common std:: things
}

-- C/C++ Preprocessor directives
local preprocessor_directives = {
    "include", "define", "undef", "if", "ifdef", "ifndef", "else", "elif",
    "endif", "error", "pragma", "line"
}

-- C/C++ Operators (for potential future highlighting, regex is hard here)
-- This list is mostly for reference, direct highlighting of all operators is complex
local cpp_operators = {
    "+", "-", "*", "/", "%", "==", "!=", "<", ">", "<= ", ">=", "&&", "||", "!",
    "&", "|", "^", "~", "<<", ">>", "=", "+=", "-=", "*=", "/=", "%=", "&=",
    "|=", "^=", "<<=", ">>=", "?", ":", ".", "->", "::", "++", "--"
}

-- Build keyword patterns
local keyword_patterns = {}
for _, kw in ipairs(cpp_keywords) do
    table.insert(keyword_patterns, "%f[%w]" .. escape_pattern(kw) .. "%f[%W]")
end
local full_keyword_pattern = table.concat(keyword_patterns, "|")

-- Build type patterns
local type_patterns = {}
for _, ty in ipairs(cpp_types) do
    table.insert(type_patterns, "%f[%w]" .. escape_pattern(ty) .. "%f[%W]")
end
local full_type_pattern = table.concat(type_patterns, "|")

-- Function to perform syntax highlighting
local function highlight_cpp_buffer()
    local buf = editor.buffer
    buf:clear_styles()

    local in_multiline_comment = false

    for line_num = 0, buf:line_count() - 1 do
        local line_text = buf:line(line_num)
        local line_len = #line_text

        -- Handle multiline comments
        if in_multiline_comment then
            local comment_end_pos = string.find(line_text, "*/", 1, true)
            if comment_end_pos then
                local range = lumacs.Range(
                    lumacs.Position(line_num, 0),
                    lumacs.Position(line_num, comment_end_pos + 1)
                )
                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
                in_multiline_comment = false
            else
                local range = lumacs.Range(
                    lumacs.Position(line_num, 0),
                    lumacs.Position(line_num, line_len)
                )
                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
            end
        end

        -- Single-line comments (//)
        local single_comment_pos = string.find(line_text, "//", 1, true)
        if single_comment_pos and not in_multiline_comment then -- only if not already in multi-line
            local range = lumacs.Range(
                lumacs.Position(line_num, single_comment_pos - 1),
                lumacs.Position(line_num, line_len)
            )
            buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
        end

        -- Multiline comment start (/*)
        local multi_comment_start_pos = string.find(line_text, "/*", 1, true)
        if multi_comment_start_pos and not in_multiline_comment then -- only if not already in multi-line
            local multi_comment_end_pos = string.find(line_text, "*/", multi_comment_start_pos + 2, true)
            if multi_comment_end_pos then
                -- Single line multiline comment
                local range = lumacs.Range(
                    lumacs.Position(line_num, multi_comment_start_pos - 1),
                    lumacs.Position(line_num, multi_comment_end_pos + 1)
                )
                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
            else
                -- Multiline comment continues to next line
                local range = lumacs.Range(
                    lumacs.Position(line_num, multi_comment_start_pos - 1),
                    lumacs.Position(line_num, line_len)
                )
                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
                in_multiline_comment = true
            end
        end

        -- Strings (double and single quotes)
        local search_from = 1
        while search_from <= line_len do
            -- Pattern for double-quoted strings (handles escaped quotes)
            local dq_start_idx, dq_end_idx = string.find(line_text, '"[^"\\]*(?:\\.[^"\\]*)*"', search_from)
            -- Pattern for single-quoted characters/strings (handles escaped quotes)
            local sq_start_idx, sq_end_idx = string.find(line_text, "'[^'\\]*(?:\\.[^'\\]*)*'", search_from)
            
            local current_match_start, current_match_end
            if dq_start_idx and (not sq_start_idx or dq_start_idx < sq_start_idx) then
                current_match_start, current_match_end = dq_start_idx, dq_end_idx
            elseif sq_start_idx then
                current_match_start, current_match_end = sq_start_idx, sq_end_idx
            else
                break -- No more strings found on this line
            end

            local range = lumacs.Range(
                lumacs.Position(line_num, current_match_start - 1),
                lumacs.Position(line_num, current_match_end - 1)
            )
            buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.String, 0))
            search_from = current_match_end + 1
        end

        -- Numbers
        -- This regex matches integers, decimals, and scientific notation, ensuring it's not part of a word.
        for num_start_idx, num_end_idx in string.gmatch(line_text, "()%f[%D]%d+%.?%d*[eE]?[%+%-]?%d*%f[%D]()") do
            local range = lumacs.Range(
                lumacs.Position(line_num, num_start_idx - 1),
                lumacs.Position(line_num, num_end_idx - 1)
            )
            buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Number, 0))
        end

        -- Preprocessor directives
        local preprocessor_match_start, preprocessor_match_end = string.find(line_text, "^%s*#(%a+)", 1) -- find and capture directive name
        if preprocessor_match_start then
            local directive_name = string.match(line_text, "^%s*#(%a+)")
            for _, pp_dir in ipairs(preprocessor_directives) do
                if directive_name == pp_dir then
                    local hash_start_idx = string.find(line_text, "#", 1, true)
                    local range = lumacs.Range(
                        lumacs.Position(line_num, hash_start_idx - 1),
                        lumacs.Position(line_num, line_len)
                    )
                    buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Variable, 0)) -- Using Variable for directives
                    break -- Found and styled, move to next
                end
            end
        end

        -- Keywords
        for kw_start_idx, kw_end_idx in string.gmatch(line_text, "()" .. full_keyword_pattern .. "()") do
            local range = lumacs.Range(
                lumacs.Position(line_num, kw_start_idx - 1),
                lumacs.Position(line_num, kw_end_idx - 1)
            )
            buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Keyword, 0))
        end

        -- Types
        for type_start_idx, type_end_idx in string.gmatch(line_text, "()" .. full_type_pattern .. "()") do
             local range = lumacs.Range(
                lumacs.Position(line_num, type_start_idx - 1),
                lumacs.Position(line_num, type_end_idx - 1)
            )
            buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Type, 0))
        end

        -- Operators (simple approach: highlight some common multi-char operators)
        local ops_to_highlight = {"==", "!=", "<= ", ">=", "&&", "||", "++", "--", "->", "::", "<<", ">>", ".*", "->*"}
        for _, op in ipairs(ops_to_highlight) do
            local op_start_pos = 1
            while op_start_pos <= line_len do
                local pos = string.find(line_text, escape_pattern(op), op_start_pos, true)
                if not pos then break end
                local range = lumacs.Range(
                    lumacs.Position(line_num, pos - 1),
                    lumacs.Position(line_num, pos + #op - 1)
                )
                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Operator, 0))
                op_start_pos = pos + #op
            end
        end
    end
end

-- Function to format the current buffer using clang-format
local function format_buffer()
    local buf = editor.buffer
    local filepath = buf:filepath()

    if not filepath then
        editor:message("Cannot format unsaved buffer. Save the file first.")
        return
    end

    editor:message("Formatting buffer with clang-format...")

    -- Create a temporary file
    local temp_file = os.tmpname() .. ".cpp" -- Use .cpp extension for clang-format detection

    -- Write buffer content to temp file
    local file = io.open(temp_file, "w")
    if file then
        file:write(buf:get_all_text())
        file:close()
    else
        editor:message("Error: Could not create temporary file for formatting.")
        os.remove(temp_file) -- Try to clean up if file creation failed mid-way
        return
    end

    -- Execute clang-format
    local clang_format_cmd = string.format("clang-format -style=file -i %s", temp_file)
    local success, status_string, code = os.execute(clang_format_cmd) -- os.execute returns true/false, exit status string, and exit code

    if success then
        -- Read formatted content back
        local formatted_file = io.open(temp_file, "r")
        if formatted_file then
            local formatted_text = formatted_file:read("*all")
            formatted_file:close()

            -- Preserve cursor position
            local original_cursor = editor.cursor

            -- Replace buffer content
            local full_buffer_range = lumacs.Range(lumacs.Position(0,0), lumacs.Position(buf:line_count() - 1, #buf:line(buf:line_count() - 1)))
            buf:replace(full_buffer_range, formatted_text)
            
            -- Restore cursor position (clamp if necessary)
            local new_line_count = buf:line_count()
            local new_line = original_cursor.line
            local new_col = original_cursor.column

            if new_line >= new_line_count then
                new_line = new_line_count - 1
                if new_line < 0 then new_line = 0 end -- Handle empty buffer after format
            end
            if new_line >= 0 then
                local line_len = #buf:line(new_line)
                if new_col > line_len then
                    new_col = line_len
                end
            else
                new_col = 0 -- Should be (0,0) for empty buffer
            end
            editor.cursor = lumacs.Position(new_line, new_col)

            editor:message("Buffer formatted successfully.")
        else
            editor:message("Error: Could not read formatted content from temporary file.")
        end
    else
        editor:message(string.format("Error: clang-format failed with status '%s', code '%s'", tostring(status_string), tostring(code)))
        editor:message("Please ensure clang-format is installed and available in your PATH.")
    end

    -- Clean up temporary file
    os.remove(temp_file)
end

-- Register the C/C++ Major Mode
define_major_mode("c-cpp-mode", {
    file_patterns = {
        "%.c$", "%.cpp$", "%.h$", "%.hpp$", "%.cc$", "%.cxx$", "%.hxx$",
        "%.C$", "%.CPP$", "%.H$", "%.HPP$", "%.CC$", "%.CXX$", "%.HXX$" -- Case-insensitive extensions
    },
    comment_syntax = "//", -- Default for single-line comments

    setup = function()
        editor:message("[c-cpp-mode] C/C++ mode activated")
    end,

    cleanup = function()
        editor:message("[c-cpp-mode] C/C++ mode deactivated")
    end,

    highlight = highlight_cpp_buffer,

    keybindings = {
        [" "] = function() editor:execute_command("self-insert-command", {" "}) end, -- Explicitly bind space
        ["M-q"] = function() format_buffer() end, -- Bind M-q to format the buffer
        ["C-c C-f"] = function() format_buffer() end, -- Alternative binding
    }
})

-- Register format-buffer command so it can be called via M-x
editor:register_command(
    "format-buffer",
    "Formats the current C/C++ buffer using clang-format.",
    format_buffer,
    true -- Interactive, can be called via M-x
)