namespace PhpefRuntimeCore {
    public errordomain PhpError {
        EXCEPTION
    }

    private class StacktraceHandlerMetadata : Object {
        public string php_name { get; private set; }
        public string handler_symbol { get; private set; }
        public string call_type { get; private set; }

        public StacktraceHandlerMetadata(string php_name, string handler_symbol, string call_type = "") {
            this.php_name = php_name;
            this.handler_symbol = handler_symbol;
            this.call_type = call_type;
        }
    }

    private class PhpTraceFrame : Object {
        public string php_name { get; private set; }
        public string call_type { get; private set; }
        public string file_path { get; private set; }
        public int64 line_number { get; private set; }

        public PhpTraceFrame(string php_name, string call_type, string file_path, int64 line_number) {
            this.php_name = php_name;
            this.call_type = call_type;
            this.file_path = file_path;
            this.line_number = line_number;
        }
    }

    private class EmbeddedStacktraceEntry : Object {
        public uint64 start { get; private set; }
        public uint64 end { get; private set; }
        public string display_name { get; private set; }
        public string native_symbol { get; private set; }
        public EmbeddedStacktraceEntry(uint64 start, uint64 end, string display_name, string native_symbol) {
            this.start = start; this.end = end; this.display_name = display_name; this.native_symbol = native_symbol;
        }
    }

    private class EmbeddedStacktraceModule : Object {
        public string elf_path { get; private set; }
        public EmbeddedStacktraceEntry[] entries;
        public bool loaded { get; set; }
        public EmbeddedStacktraceModule(string elf_path) {
            this.elf_path = elf_path;
            this.entries = {};
            this.loaded = false;
        }
    }

    private HashTable<string, string>? stacktrace_frame_names_by_symbol = null;
    private HashTable<string, EmbeddedStacktraceModule>? embedded_stacktrace_modules = null;
    private string? current_executable_path = null;

    private static void ensure_stacktrace_frame_registry() {
        if (stacktrace_frame_names_by_symbol == null) {
            stacktrace_frame_names_by_symbol = new HashTable<string, string>(str_hash, str_equal);
        }
    }

    public static void register_stacktrace_frame(string frame_symbol, string display_name) {
        ensure_stacktrace_frame_registry();
        stacktrace_frame_names_by_symbol.insert(frame_symbol, display_name);
        foreach (var alias in handler_symbol_stacktrace_aliases(frame_symbol)) {
            stacktrace_frame_names_by_symbol.insert(alias, display_name);
        }
    }

    public static string? lookup_stacktrace_frame(string frame_symbol) {
        ensure_stacktrace_frame_registry();
        if (stacktrace_frame_names_by_symbol == null) {
            return null;
        }
        var display_name = stacktrace_frame_names_by_symbol.lookup(frame_symbol);
        if (display_name != null) {
            return display_name;
        }
        var normalized_symbol = normalize_stacktrace_symbol_for_lookup(frame_symbol);
        return normalized_symbol != frame_symbol ? stacktrace_frame_names_by_symbol.lookup(normalized_symbol) : null;
    }

    public static bool exception_is_a(Error e, string class_name) {
        var throwable = exception_throwable_object(e);
        if (throwable != null) {
            return throwable.is_a(class_name);
        }

        var actual = normalize_exception_class(throwable_class_name_from_error(e));
        var wanted = normalize_exception_class(class_name);
        if (actual == wanted) {
            return true;
        }
        return wanted == "Exception" && (actual == "UnexpectedValueException" || actual == "RuntimeException" || actual == "Exception");
    }

    public static Zval? exception_get_throwable(Error e) {
        return exception_throwable_zval(e);
    }

    public static Zval exception_get_or_create_throwable(Error e) {
        return exception_get_throwable(e) ?? create_throwable_zval("Exception", exception_get_message(e), exception_get_code(e));
    }

    public static Zval exception_get_message(Error e) {
        return throwable_message_from_error(e);
    }

    public static Zval exception_get_code(Error e) {
        return throwable_code_from_error(e);
    }

    public static void throw_exception(string class_name, Zval message, Zval? code = null, Zval? previous = null) throws Error {
        throw_exception_at(class_name, message, code, "", 0, previous);
    }

    public static void throw_exception_at(string class_name, Zval message, Zval? code, string file_name, int line, Zval? previous = null) throws Error {
        var php_class = normalize_exception_class(class_name);
        var exception_code = code ?? Zval.from_int(0);
        var fatal_message = create_throwable_transport_message(php_class, message, exception_code, file_name, line, previous);
        // StacktraceError already skips vala-stacktrace internals; skip this
        // runtime-core throw helper as well so the visible throw-time trace
        // starts at the user/generated PHP frame.
        var stacktrace_error = new StacktraceError(fatal_message, 2);
        set_throwable_trace_from_transport_message(fatal_message, stacktrace_to_php_trace(stacktrace_error.stacktrace));
        throw new PhpError.EXCEPTION("%s", normalize_phpef_stacktrace_symbols(stacktrace_error.to_error_message(), stacktrace_error.stacktrace));
    }

    public static void throw_throwable(Zval throwable) throws Error {
        if (!throwable_zval_is_a(throwable, "Throwable")) {
            throw_exception("Error", Zval.from_string("Can only throw objects implementing Throwable"));
        }
        var fatal_message = create_throwable_transport_message_from_zval(throwable);
        var stacktrace_error = new StacktraceError(fatal_message, 2);
        set_throwable_trace_from_transport_message(fatal_message, stacktrace_to_php_trace(stacktrace_error.stacktrace));
        throw new PhpError.EXCEPTION("%s", normalize_phpef_stacktrace_symbols(stacktrace_error.to_error_message(), stacktrace_error.stacktrace));
    }

    /**
     * Deliberately crash the current process with SIGSEGV.
     *
     * This PHP-visible test helper exists only for validating native crash,
     * stacktrace, debugger, ELF/DWARF, and strip/debug-file behavior. It never
     * returns in normal execution; the return type keeps it usable wherever a
     * phpfunc_* wrapper is expected by the compiler pipeline.
     */
    [CCode (cname = "raise", cheader_filename = "signal.h")]
    private static extern int c_raise(int signal_number);

    private const int SIGNAL_SEGV = 11;

    public static Zval phpfunc_phpef_test_segfault() {
        c_raise(SIGNAL_SEGV);
        return Zval.from_null();
    }

    public static int handle_uncaught(Error e) {
        var message = e.message ?? "";

        if (!message.contains("Stacktrace at throw-time:")) {
            var captured = StacktraceError.get_captured_stacktrace(e);
            if (captured != null && captured != "") {
                message = "%s\n\nStacktrace at throw-time:\n%s".printf(message, captured);
            } else {
                var stacktrace = new Stacktrace(ProcessSignal.TTOU, 1);
                var stacktrace_text = StacktraceError.format_stacktrace(stacktrace);
                if (stacktrace_text != "") {
                    message = "%s\n\nStacktrace at catch-time:\n%s".printf(message, stacktrace_text);
                }
            }
        }

        message = throwable_display_text(message);
        var throwable_object = exception_throwable_object(e);
        var fallback_file_name = throwable_object != null ? throwable_object.file_name : "";
        var fallback_line = throwable_object != null ? throwable_object.line : 0;
        message = normalize_phpef_stacktrace_symbols(message, null, fallback_file_name, fallback_line);
        stderr.printf("PHPEF Fatal error: Uncaught %s\n", message);

        return 255;
    }

    private static string normalize_phpef_stacktrace_symbols(string message, Stacktrace? stacktrace = null, string fallback_throw_file = "", int fallback_throw_line = 0) {
        var builder = new StringBuilder();
        var lines = message.split("\n");
        var truncate_generated_frames = false;
        var skipping_frame_location = false;
        var wrote_line = false;
        var emitted_frame_number = 0;
        var last_emitted_registered_handler_symbol = "";
        var last_emitted_embedded_display_name = "";
        var have_emitted_php_context = false;
        var have_meaningful_stacktrace_symbol = false;
        var unknown_stacktrace_frames = 0;
        string[] involved_libraries = {};
        var stacktrace_frame_index = 0;

        for (int i = 0; i < lines.length; i++) {
            var line = lines[i];
            if (skipping_frame_location) {
                if (line.has_prefix("    in ")) {
                    continue;
                }
                skipping_frame_location = false;
            }

            if (truncate_generated_frames) {
                if (line.has_prefix("#")) {
                    skipping_frame_location = true;
                    continue;
                }
            }

            var raw_name = extract_stacktrace_function_name(line);
            StacktraceHandlerMetadata? metadata = null;
            var is_delegate_handler_frame = false;
            EmbeddedStacktraceEntry? embedded_entry = null;
            string? embedded_frame_name = null;
            var frame_elf_path = "";
            if (raw_name != null && stacktrace != null && stacktrace_frame_index < stacktrace.frames.size) {
                var frame = stacktrace.frames.get(stacktrace_frame_index);
                frame_elf_path = frame.elf_path;
                if (raw_name == "??") {
                    embedded_entry = lookup_embedded_stacktrace_frame(frame.elf_path, frame.address);
                    if (embedded_entry != null) embedded_frame_name = embedded_entry.display_name;
                }
            }
            var explicit_frame_name = raw_name != null ? (lookup_stacktrace_frame(raw_name) ?? embedded_frame_name) : null;
            if (raw_name != null) {
                stacktrace_frame_index++;
                var library_name = extract_stacktrace_library_name(line);
                if (library_name != null) {
                    involved_libraries = append_collapsed_stacktrace_library(involved_libraries, library_name);
                }
                if (raw_name == "??") {
                    if (embedded_frame_name != null) have_meaningful_stacktrace_symbol = true;
                    else unknown_stacktrace_frames++;
                } else if (raw_name != "__libc_start_main" && raw_name != "_start") {
                    have_meaningful_stacktrace_symbol = true;
                }
                metadata = lookup_stacktrace_registered_handler(raw_name, out is_delegate_handler_frame);
            }

            if (raw_name != null && metadata != null && last_emitted_registered_handler_symbol == metadata.handler_symbol && is_native_stacktrace_handler_frame(line, is_delegate_handler_frame)) {
                skipping_frame_location = true;
                continue;
            }

            if (raw_name != null && raw_name == "??" && embedded_entry != null && last_emitted_embedded_display_name == embedded_entry.display_name && is_embedded_stacktrace_handler_frame(embedded_entry.native_symbol)) {
                skipping_frame_location = true;
                continue;
            }

            if (raw_name != null && is_runtime_core_stacktrace_frame(raw_name, frame_elf_path)) {
                skipping_frame_location = true;
                continue;
            }

            if (raw_name != null && line.contains(".vala:")) {
                skipping_frame_location = true;
                continue;
            }

            var normalized_line = normalize_phpef_stacktrace_line(line, raw_name, metadata, explicit_frame_name, emitted_frame_number == 0 ? fallback_throw_file : "", emitted_frame_number == 0 ? fallback_throw_line : 0);
            if (normalized_line.has_prefix("#")) {
                emitted_frame_number++;
                normalized_line = renumber_stacktrace_line(normalized_line, emitted_frame_number);
            }
            if (wrote_line) {
                builder.append("\n");
            }
            builder.append(normalized_line);
            wrote_line = true;

            if (raw_name != null) {
                if (metadata != null) {
                    last_emitted_registered_handler_symbol = metadata.handler_symbol;
                    last_emitted_embedded_display_name = metadata.php_name;
                    have_emitted_php_context = true;
                } else {
                    last_emitted_registered_handler_symbol = "";
                    if (explicit_frame_name != null) {
                        have_emitted_php_context = true;
                        last_emitted_embedded_display_name = explicit_frame_name;
                    }
                }
            }

            if (normalized_line.contains(" in '{main}'")) {
                truncate_generated_frames = true;
            }
        }

        if (!have_emitted_php_context && !have_meaningful_stacktrace_symbol && unknown_stacktrace_frames > 0 && involved_libraries.length > 0) {
            return replace_stacktrace_frames_with_unavailable_summary(builder.str, involved_libraries);
        }

        return builder.str;
    }


    private static Zval stacktrace_to_php_trace(Stacktrace stacktrace) {
        ZvalArrayEntry[] trace_entries = {};
        PhpTraceFrame[] php_frames = {};
        var last_emitted_registered_handler_symbol = "";
        var last_emitted_embedded_display_name = "";

        for (int i = 0; i < stacktrace.frames.size; i++) {
            var frame = stacktrace.frames.get(i);
            var raw_name = frame.function;
            if (raw_name == null || raw_name == "") continue;

            StacktraceHandlerMetadata? metadata = null;
            var is_delegate_handler_frame = false;
            EmbeddedStacktraceEntry? embedded_entry = null;
            string? embedded_frame_name = null;
            if (raw_name == "??") {
                embedded_entry = lookup_embedded_stacktrace_frame(frame.elf_path, frame.address);
                if (embedded_entry != null) embedded_frame_name = embedded_entry.display_name;
            }
            var explicit_frame_name = lookup_stacktrace_frame(raw_name) ?? embedded_frame_name;
            metadata = lookup_stacktrace_registered_handler(raw_name, out is_delegate_handler_frame);
            if (metadata == null && embedded_entry != null && embedded_entry.native_symbol != "") {
                metadata = lookup_stacktrace_registered_handler(embedded_entry.native_symbol, out is_delegate_handler_frame);
            }

            if (metadata != null && last_emitted_registered_handler_symbol == metadata.handler_symbol && (is_native_stacktrace_handler_frame(frame.to_string(), is_delegate_handler_frame) || (raw_name == "??" && embedded_entry != null && is_embedded_stacktrace_handler_frame(embedded_entry.native_symbol)))) continue;
            if (raw_name == "??" && embedded_entry != null && last_emitted_embedded_display_name == embedded_entry.display_name && is_embedded_stacktrace_handler_frame(embedded_entry.native_symbol)) continue;
            if (is_runtime_core_stacktrace_frame(raw_name, frame.elf_path)) continue;
            if (raw_name == "__libc_start_main" || raw_name == "_start") continue;

            var php_name = raw_name;
            var call_type = "";
            if (metadata != null) php_name = metadata.php_name;
            else if (explicit_frame_name != null) php_name = explicit_frame_name;
            if (metadata != null) call_type = metadata.call_type;
            if (php_name == "" || php_name == "??") continue;

            var file_path = frame.file_path;
            var php_file_path = "";
            if (file_path != null && file_path != "" && !file_path.contains(".vala")) {
                php_file_path = file_path;
            }
            int64 line_number = 0;
            var line_text = frame.line_number;
            if (line_text == null || !int64.try_parse(line_text, out line_number) || line_number <= 0) line_number = 0;

            php_frames += new PhpTraceFrame(php_name, call_type, php_file_path, line_number);
            if (php_name == "{main}") break;

            if (metadata != null) {
                last_emitted_registered_handler_symbol = metadata.handler_symbol;
                last_emitted_embedded_display_name = metadata.php_name;
            } else {
                last_emitted_registered_handler_symbol = "";
                last_emitted_embedded_display_name = explicit_frame_name ?? "";
            }
        }

        for (int i = 0; i < php_frames.length; i++) {
            var php_frame = php_frames[i];
            if (php_frame.php_name == "{main}") break;

            var callsite_frame = i + 1 < php_frames.length ? php_frames[i + 1] : php_frame;
            var file_path = callsite_frame.file_path != "" ? callsite_frame.file_path : php_frame.file_path;
            var line_number = callsite_frame.line_number > 0 ? callsite_frame.line_number : php_frame.line_number;

            ZvalArrayEntry[] frame_entries = {};
            if (file_path != "") {
                frame_entries += new ZvalArrayEntry(Zval.from_string("file"), Zval.from_string(file_path));
            }
            if (line_number > 0) {
                frame_entries += new ZvalArrayEntry(Zval.from_string("line"), Zval.from_int(line_number));
            }

            var class_name = "";
            var function_name = php_frame.php_name;
            var type_name = "";
            var method_pos = php_frame.php_name.index_of("::");
            if (method_pos >= 0) {
                class_name = php_frame.php_name.substring(0, method_pos);
                function_name = php_frame.php_name.substring(method_pos + 2);
                type_name = php_frame.call_type != "" ? php_frame.call_type : "::";
            }
            frame_entries += new ZvalArrayEntry(Zval.from_string("function"), Zval.from_string(function_name));
            if (class_name != "") {
                frame_entries += new ZvalArrayEntry(Zval.from_string("class"), Zval.from_string(class_name));
                frame_entries += new ZvalArrayEntry(Zval.from_string("type"), Zval.from_string(type_name));
            }

            trace_entries += new ZvalArrayEntry(null, Zval.from_array(frame_entries));
        }

        return Zval.from_array(trace_entries);
    }

    private static string replace_stacktrace_frames_with_unavailable_summary(string message, string[] involved_libraries) {
        var builder = new StringBuilder();
        var lines = message.split("\n");
        var wrote_line = false;
        var replacing_stacktrace_frames = false;
        var wrote_summary = false;

        for (int i = 0; i < lines.length; i++) {
            var line = lines[i];
            if (extract_stacktrace_function_name(line) != null) {
                if (!wrote_summary) {
                    append_normalized_message_line(builder, ref wrote_line, "no stacktrace available, involved libraries:");
                    for (int library_index = 0; library_index < involved_libraries.length; library_index++) {
                        append_normalized_message_line(builder, ref wrote_line, "#" + (library_index + 1).to_string() + " " + involved_libraries[library_index]);
                    }
                    wrote_summary = true;
                }
                replacing_stacktrace_frames = true;
                continue;
            }
            if (replacing_stacktrace_frames && line.has_prefix("    in ")) {
                continue;
            }
            replacing_stacktrace_frames = false;
            append_normalized_message_line(builder, ref wrote_line, line);
        }

        return builder.str;
    }

    private static void append_normalized_message_line(StringBuilder builder, ref bool wrote_line, string line) {
        if (wrote_line) {
            builder.append("\n");
        }
        builder.append(line);
        wrote_line = true;
    }

    private static string[] append_collapsed_stacktrace_library(string[] libraries, string library_name) {
        if (library_name == "") {
            return libraries;
        }
        if (libraries.length > 0 && libraries[libraries.length - 1] == library_name) {
            return libraries;
        }
        string[] out = {};
        foreach (var existing in libraries) {
            out += existing;
        }
        out += library_name;
        return out;
    }

    private static string? extract_stacktrace_library_name(string line) {
        var marker = " at ";
        var marker_pos = line.index_of(marker);
        if (marker_pos < 0) {
            return null;
        }
        var library_name = line.substring(marker_pos + marker.length).strip();
        return library_name != "" ? library_name : null;
    }

    private static string? extract_stacktrace_function_name(string line) {
        var marker = " in '";
        var marker_pos = line.index_of(marker);
        if (marker_pos < 0) {
            return null;
        }

        var name_start = marker_pos + marker.length;
        var rest = line.substring(name_start);
        var name_end_rel = rest.index_of("'");
        if (name_end_rel < 0) {
            return null;
        }

        return rest.substring(0, name_end_rel);
    }

    private static EmbeddedStacktraceEntry? lookup_embedded_stacktrace_frame(string elf_path, string address_text) {
        var address = parse_stacktrace_address(address_text);
        var module = load_embedded_stacktrace_module(elf_path);
        if (module == null) return null;
        var module_offset = stacktrace_module_offset(module.elf_path, address);
        if (module_offset == 0) return null;
        foreach (var entry in module.entries) {
            if (module_offset >= entry.start && module_offset < entry.end) return entry;
        }
        return null;
    }

    private static EmbeddedStacktraceModule? load_embedded_stacktrace_module(string elf_path) {
        var resolved_elf_path = elf_path;
        if (resolved_elf_path == "" && current_executable_path != null) resolved_elf_path = current_executable_path;
        if (resolved_elf_path == "") return null;
        if (embedded_stacktrace_modules == null) {
            embedded_stacktrace_modules = new HashTable<string, EmbeddedStacktraceModule>(str_hash, str_equal);
        }
        var module = embedded_stacktrace_modules.lookup(resolved_elf_path);
        if (module == null) {
            module = new EmbeddedStacktraceModule(resolved_elf_path);
            embedded_stacktrace_modules.insert(resolved_elf_path, module);
        }
        if (module.loaded) return module;
        module.loaded = true;
        uint8[] section_data;
        if (!read_elf_section(resolved_elf_path, ".phpef_stacktrace", out section_data)) return module;
        if (load_binary_embedded_stacktrace_section(section_data, ref module.entries)) return module;
        load_text_embedded_stacktrace_section(section_data, ref module.entries);
        return module;
    }

    private static bool load_binary_embedded_stacktrace_section(uint8[] section_data, ref EmbeddedStacktraceEntry[] entries) {
        if (!has_stacktrace_binary_magic(section_data)) return false;
        if (!range_available(section_data, 0, 24)) return false;
        var version = read_le32(section_data, 8);
        if (version == 1) return load_binary_embedded_stacktrace_section_v1(section_data, ref entries);
        if (version == 2) return load_binary_embedded_stacktrace_section_v2(section_data, ref entries);
        if (version == 3) return load_binary_embedded_stacktrace_section_v3(section_data, ref entries);
        return false;
    }

    private static bool load_binary_embedded_stacktrace_section_v1(uint8[] section_data, ref EmbeddedStacktraceEntry[] entries) {
        var count = read_le32(section_data, 16);
        var string_table_size = read_le32(section_data, 20);
        uint64 entries_offset = 24;
        uint64 entry_size = 24;
        uint64 entries_size = (uint64) count * entry_size;
        uint64 string_table_offset = entries_offset + entries_size;
        if (!range_available(section_data, entries_offset, entries_size)) return false;
        if (!range_available(section_data, string_table_offset, string_table_size)) return false;

        for (uint32 i = 0; i < count; i++) {
            uint64 entry_offset = entries_offset + ((uint64) i * entry_size);
            var start = read_le64(section_data, entry_offset);
            var end = read_le64(section_data, entry_offset + 8);
            var display_offset = read_le32(section_data, entry_offset + 16);
            var native_symbol_offset = read_le32(section_data, entry_offset + 20);
            var display = read_stacktrace_string_table_string(section_data, string_table_offset, string_table_size, display_offset);
            var native_symbol = read_stacktrace_string_table_string(section_data, string_table_offset, string_table_size, native_symbol_offset);
            if (start > 0 && end > start && display != "") append_embedded_stacktrace_entry(ref entries, new EmbeddedStacktraceEntry(start, end, display, native_symbol));
        }
        return true;
    }

    private static bool load_binary_embedded_stacktrace_section_v2(uint8[] section_data, ref EmbeddedStacktraceEntry[] entries) {
        if (!range_available(section_data, 0, 36)) return false;
        var entry_count = read_le32(section_data, 16);
        var phrase_count = read_le32(section_data, 20);
        var phrase_part_count = read_le32(section_data, 24);
        var glossary_count = read_le32(section_data, 28);
        var glossary_table_size = read_le32(section_data, 32);
        uint64 entries_offset = 36;
        uint64 entry_size = 24;
        uint64 entries_size = (uint64) entry_count * entry_size;
        uint64 phrase_table_offset = entries_offset + entries_size;
        uint64 phrase_table_size = (uint64) phrase_count * 8;
        uint64 phrase_parts_offset = phrase_table_offset + phrase_table_size;
        uint64 phrase_parts_size = (uint64) phrase_part_count * 4;
        uint64 glossary_offsets_offset = phrase_parts_offset + phrase_parts_size;
        uint64 glossary_offsets_size = (uint64) glossary_count * 4;
        uint64 glossary_table_offset = glossary_offsets_offset + glossary_offsets_size;
        if (!range_available(section_data, entries_offset, entries_size)) return false;
        if (!range_available(section_data, phrase_table_offset, phrase_table_size)) return false;
        if (!range_available(section_data, phrase_parts_offset, phrase_parts_size)) return false;
        if (!range_available(section_data, glossary_offsets_offset, glossary_offsets_size)) return false;
        if (!range_available(section_data, glossary_table_offset, glossary_table_size)) return false;

        for (uint32 i = 0; i < entry_count; i++) {
            uint64 entry_offset = entries_offset + ((uint64) i * entry_size);
            var start = read_le64(section_data, entry_offset);
            var end = read_le64(section_data, entry_offset + 8);
            var display_phrase_index = read_le32(section_data, entry_offset + 16);
            var native_symbol_phrase_index = read_le32(section_data, entry_offset + 20);
            var display = read_stacktrace_v2_phrase(section_data, phrase_table_offset, phrase_count, phrase_parts_offset, phrase_part_count, glossary_offsets_offset, glossary_count, glossary_table_offset, glossary_table_size, display_phrase_index);
            var native_symbol = read_stacktrace_v2_phrase(section_data, phrase_table_offset, phrase_count, phrase_parts_offset, phrase_part_count, glossary_offsets_offset, glossary_count, glossary_table_offset, glossary_table_size, native_symbol_phrase_index);
            if (start > 0 && end > start && display != "") append_embedded_stacktrace_entry(ref entries, new EmbeddedStacktraceEntry(start, end, display, native_symbol));
        }
        return true;
    }

    private static bool load_binary_embedded_stacktrace_section_v3(uint8[] section_data, ref EmbeddedStacktraceEntry[] entries) {
        if (!range_available(section_data, 0, 36)) return false;
        var entry_count = read_le32(section_data, 16);
        var phrase_count = read_le32(section_data, 20);
        var phrase_part_count = read_le32(section_data, 24);
        var string_pool_size = read_le32(section_data, 32);
        uint64 entries_offset = 36;
        uint64 entry_size = 24;
        uint64 entries_size = (uint64) entry_count * entry_size;
        uint64 phrase_table_offset = entries_offset + entries_size;
        uint64 phrase_table_size = (uint64) phrase_count * 8;
        uint64 phrase_parts_offset = phrase_table_offset + phrase_table_size;
        uint64 phrase_parts_size = (uint64) phrase_part_count * 4;
        uint64 string_pool_offset = phrase_parts_offset + phrase_parts_size;
        if (!range_available(section_data, entries_offset, entries_size)) return false;
        if (!range_available(section_data, phrase_table_offset, phrase_table_size)) return false;
        if (!range_available(section_data, phrase_parts_offset, phrase_parts_size)) return false;
        if (!range_available(section_data, string_pool_offset, string_pool_size)) return false;

        for (uint32 i = 0; i < entry_count; i++) {
            uint64 entry_offset = entries_offset + ((uint64) i * entry_size);
            var start = read_le64(section_data, entry_offset);
            var end = read_le64(section_data, entry_offset + 8);
            var display_phrase_index = read_le32(section_data, entry_offset + 16);
            var native_symbol_phrase_index = read_le32(section_data, entry_offset + 20);
            var display = read_stacktrace_v3_phrase(section_data, phrase_table_offset, phrase_count, phrase_parts_offset, phrase_part_count, string_pool_offset, string_pool_size, display_phrase_index);
            var native_symbol = read_stacktrace_v3_phrase(section_data, phrase_table_offset, phrase_count, phrase_parts_offset, phrase_part_count, string_pool_offset, string_pool_size, native_symbol_phrase_index);
            if (start > 0 && end > start && display != "") append_embedded_stacktrace_entry(ref entries, new EmbeddedStacktraceEntry(start, end, display, native_symbol));
        }
        return true;
    }

    private static void append_embedded_stacktrace_entry(ref EmbeddedStacktraceEntry[] entries, EmbeddedStacktraceEntry entry) {
        EmbeddedStacktraceEntry[] updated_entries = entries;
        updated_entries += entry;
        entries = updated_entries;
    }

    private static bool has_stacktrace_binary_magic(uint8[] data) {
        if (data.length < 8) return false;
        var magic = "PHPEFSTK";
        for (int i = 0; i < 8; i++) {
            if (data[i] != (uint8) magic[i]) return false;
        }
        return true;
    }

    private static string read_stacktrace_string_table_string(uint8[] data, uint64 table_offset, uint64 table_size, uint32 string_offset) {
        if ((uint64) string_offset >= table_size) return "";
        return read_null_string(data, table_offset + (uint64) string_offset, table_offset + table_size);
    }


    private static string read_stacktrace_v2_phrase(uint8[] data, uint64 phrase_table_offset, uint32 phrase_count, uint64 phrase_parts_offset, uint32 phrase_part_count, uint64 glossary_offsets_offset, uint32 glossary_count, uint64 glossary_table_offset, uint64 glossary_table_size, uint32 phrase_index) {
        if (phrase_index >= phrase_count) return "";
        uint64 phrase_record_offset = phrase_table_offset + ((uint64) phrase_index * 8);
        var part_offset = read_le32(data, phrase_record_offset);
        var part_count = read_le32(data, phrase_record_offset + 4);
        if (part_offset > phrase_part_count || part_count > phrase_part_count - part_offset) return "";
        var builder = new StringBuilder();
        for (uint32 i = 0; i < part_count; i++) {
            var glossary_index = read_le32(data, phrase_parts_offset + ((uint64) (part_offset + i) * 4));
            if (glossary_index >= glossary_count) return "";
            var string_offset = read_le32(data, glossary_offsets_offset + ((uint64) glossary_index * 4));
            builder.append(read_stacktrace_string_table_string(data, glossary_table_offset, glossary_table_size, string_offset));
        }
        return builder.str;
    }


    private static string read_stacktrace_v3_phrase(uint8[] data, uint64 phrase_table_offset, uint32 phrase_count, uint64 phrase_parts_offset, uint32 phrase_part_count, uint64 string_pool_offset, uint64 string_pool_size, uint32 phrase_index) {
        if (phrase_index >= phrase_count) return "";
        uint64 phrase_record_offset = phrase_table_offset + ((uint64) phrase_index * 8);
        var part_offset = read_le32(data, phrase_record_offset);
        var part_count = read_le32(data, phrase_record_offset + 4);
        if (part_offset > phrase_part_count || part_count > phrase_part_count - part_offset) return "";
        var builder = new StringBuilder();
        for (uint32 i = 0; i < part_count; i++) {
            uint64 part_record_offset = phrase_parts_offset + ((uint64) (part_offset + i) * 4);
            var string_offset = read_le16(data, part_record_offset);
            var string_length = read_le16(data, part_record_offset + 2);
            if ((uint64) string_offset > string_pool_size || (uint64) string_length > string_pool_size - (uint64) string_offset) return "";
            for (uint32 j = 0; j < string_length; j++) {
                builder.append_c((char) data[string_pool_offset + (uint64) string_offset + (uint64) j]);
            }
        }
        return builder.str;
    }

    private static void load_text_embedded_stacktrace_section(uint8[] section_data, ref EmbeddedStacktraceEntry[] entries) {
        var out_text = bytes_to_text(section_data);
        foreach (var line in out_text.split("\n")) {
            var trimmed = line.strip();
            if (trimmed == "" || trimmed == "phpef-stacktrace-map-v0") continue;
            var parts = trimmed.split("\t");
            if (parts.length < 3) continue;
            var start = parse_stacktrace_address(parts[0]);
            var end = parse_stacktrace_address(parts[1]);
            var display = decode_stacktrace_map_field(parts[2]);
            var native_symbol = parts.length >= 4 ? decode_stacktrace_map_field(parts[3]) : "";
            if (start > 0 && end > start && display != "") append_embedded_stacktrace_entry(ref entries, new EmbeddedStacktraceEntry(start, end, display, native_symbol));
        }
    }

    private static string bytes_to_text(uint8[] data) {
        var builder = new StringBuilder();
        foreach (var b in data) {
            if (b == 0) break;
            builder.append_c((char) b);
        }
        return builder.str;
    }

    private static bool read_elf_section(string path, string section_name, out uint8[] section_data) {
        section_data = {};
        uint8[] data;
        try { FileUtils.get_data(path, out data); } catch (Error e) { return false; }
        if (data.length < 64 || data[0] != 0x7f || data[1] != 'E' || data[2] != 'L' || data[3] != 'F') return false;
        if (data[4] != 2 || data[5] != 1) return false;
        var shoff = read_le64(data, 40);
        var shentsize = read_le16(data, 58);
        var shnum = read_le16(data, 60);
        var shstrndx = read_le16(data, 62);
        if (shoff == 0 || shentsize < 64 || shnum == 0 || shstrndx >= shnum) return false;
        var shstr_header = shoff + ((uint64) shstrndx * (uint64) shentsize);
        if (!range_available(data, shstr_header, 64)) return false;
        var shstr_offset = read_le64(data, shstr_header + 24);
        var shstr_size = read_le64(data, shstr_header + 32);
        for (uint16 i = 0; i < shnum; i++) {
            var header = shoff + ((uint64) i * (uint64) shentsize);
            if (!range_available(data, header, 64)) return false;
            var name_offset = read_le32(data, header);
            var name = read_null_string(data, shstr_offset + (uint64) name_offset, shstr_offset + shstr_size);
            if (name != section_name) continue;
            var section_offset = read_le64(data, header + 24);
            var section_size = read_le64(data, header + 32);
            if (!range_available(data, section_offset, section_size)) return false;
            uint8[] out_data = {};
            for (uint64 n = 0; n < section_size; n++) {
                out_data += data[(int) (section_offset + n)];
            }
            section_data = out_data;
            return true;
        }
        return false;
    }

    private static bool range_available(uint8[] data, uint64 offset, uint64 size) {
        return offset <= (uint64) data.length && size <= ((uint64) data.length - offset);
    }

    private static uint16 read_le16(uint8[] data, uint64 offset) {
        if (!range_available(data, offset, 2)) return 0;
        return (uint16) data[(int) offset] | ((uint16) data[(int) offset + 1] << 8);
    }

    private static uint32 read_le32(uint8[] data, uint64 offset) {
        if (!range_available(data, offset, 4)) return 0;
        return (uint32) data[(int) offset]
            | ((uint32) data[(int) offset + 1] << 8)
            | ((uint32) data[(int) offset + 2] << 16)
            | ((uint32) data[(int) offset + 3] << 24);
    }

    private static uint64 read_le64(uint8[] data, uint64 offset) {
        if (!range_available(data, offset, 8)) return 0;
        uint64 value = 0;
        for (int i = 7; i >= 0; i--) value = (value << 8) | data[(int) offset + i];
        return value;
    }

    private static string read_null_string(uint8[] data, uint64 offset, uint64 limit) {
        var builder = new StringBuilder();
        if (offset >= (uint64) data.length) return "";
        if (limit > (uint64) data.length) limit = (uint64) data.length;
        for (uint64 i = offset; i < limit; i++) {
            var b = data[(int) i];
            if (b == 0) break;
            builder.append_c((char) b);
        }
        return builder.str;
    }

    private static string decode_stacktrace_map_field(string value) {
        return value.replace("\\t", "\t").replace("\\n", "\n").replace("\\r", "\r").replace("\\\\", "\\");
    }

    private static uint64 parse_stacktrace_address(string text) {
        var value = text.strip();
        if (value.has_prefix("0x") || value.has_prefix("0X")) value = value.substring(2);
        uint64 result = 0;
        for (int i = 0; i < value.length; i++) {
            var c = value[i]; uint64 digit;
            if (c >= '0' && c <= '9') digit = (uint64)(c - '0');
            else if (c >= 'a' && c <= 'f') digit = (uint64)(c - 'a' + 10);
            else if (c >= 'A' && c <= 'F') digit = (uint64)(c - 'A' + 10);
            else return 0;
            result = (result << 4) | digit;
        }
        return result;
    }

    private static uint64 stacktrace_module_offset(string elf_path, uint64 address) {
        if (address == 0) return 0;
        var target_elf_path = elf_path;
        if (target_elf_path == "" && current_executable_path != null) target_elf_path = current_executable_path;
        if (target_elf_path == "") return 0;
        string maps_text;
        try { FileUtils.get_contents("/proc/self/maps", out maps_text); } catch (Error e) { return 0; }
        foreach (var line in maps_text.split("\n")) {
            if (line.index_of(target_elf_path) < 0) continue;
            var fields = split_whitespace(line);
            if (fields.length < 3) continue;
            var dash = fields[0].index_of("-");
            if (dash <= 0) continue;
            var start = parse_stacktrace_address(fields[0].substring(0, dash));
            var end = parse_stacktrace_address(fields[0].substring(dash + 1));
            var file_offset = parse_stacktrace_address(fields[2]);
            if (start > 0 && end > start && address >= start && address < end) return (address - start) + file_offset;
        }
        return 0;
    }

    private static string[] split_whitespace(string text) {
        string[] fields = {}; var in_field = false; var start = 0;
        for (int i = 0; i < text.length; i++) {
            var ws = text[i] == ' ' || text[i] == '\t';
            if (ws && in_field) { fields += text.substring(start, i - start); in_field = false; }
            else if (!ws && !in_field) { start = i; in_field = true; }
        }
        if (in_field) fields += text.substring(start);
        return fields;
    }

    private static StacktraceHandlerMetadata? lookup_stacktrace_registered_handler(string raw_name, out bool is_delegate_handler_frame) {
        is_delegate_handler_frame = false;
        var lookup_name = normalize_stacktrace_symbol_for_lookup(raw_name);

        var function_metadata = lookup_registered_function_by_handler(lookup_name);
        if (function_metadata != null) {
            return new StacktraceHandlerMetadata(function_metadata.php_name, function_metadata.handler_symbol);
        }

        var method_metadata = lookup_registered_static_method_by_handler(lookup_name);
        if (method_metadata != null) {
            return new StacktraceHandlerMetadata(method_metadata.php_name, method_metadata.handler_symbol, "::");
        }

        var instance_method_metadata = lookup_registered_instance_method_by_handler(lookup_name);
        if (instance_method_metadata != null) {
            return new StacktraceHandlerMetadata(instance_method_metadata.php_name, instance_method_metadata.handler_symbol, "->");
        }

        var delegate_suffix = lookup_name.has_suffix("_phpef_runtime_core_instance_method_handler")
            ? "_phpef_runtime_core_instance_method_handler"
            : "_phpef_runtime_core_user_function_handler";
        if (lookup_name.has_suffix(delegate_suffix)) {
            var handler_name = lookup_name.substring(0, lookup_name.length - delegate_suffix.length);
            while (handler_name.has_prefix("_")) {
                handler_name = handler_name.substring(1);
            }
            function_metadata = lookup_registered_function_by_handler(handler_name);
            if (function_metadata != null) {
                is_delegate_handler_frame = true;
                return new StacktraceHandlerMetadata(function_metadata.php_name, function_metadata.handler_symbol);
            }
            method_metadata = lookup_registered_static_method_by_handler(handler_name);
            if (method_metadata != null) {
                is_delegate_handler_frame = true;
                return new StacktraceHandlerMetadata(method_metadata.php_name, method_metadata.handler_symbol, "::");
            }
            instance_method_metadata = lookup_registered_instance_method_by_handler(handler_name);
            if (instance_method_metadata != null) {
                is_delegate_handler_frame = true;
                return new StacktraceHandlerMetadata(instance_method_metadata.php_name, instance_method_metadata.handler_symbol, "->");
            }
        }

        return null;
    }

    private static bool is_native_stacktrace_handler_frame(string line, bool is_delegate_handler_frame) {
        return is_delegate_handler_frame || line.contains(".c:");
    }

    private static bool is_embedded_stacktrace_handler_frame(string native_symbol) {
        return native_symbol.has_suffix("_phpef_runtime_core_instance_method_handler") || native_symbol.has_suffix("_phpef_runtime_core_user_function_handler");
    }

    private static bool is_runtime_core_stacktrace_frame(string raw_name, string elf_path = "") {
        if (is_runtime_core_stacktrace_elf(elf_path)) return true;
        return raw_name == "phpef_runtime_core_call_function" || raw_name == "phpef_runtime_core_call_static_method" || raw_name == "phpef_runtime_core_call_instance_method";
    }

    private static bool is_runtime_core_stacktrace_elf(string elf_path) {
        if (elf_path == "") return false;
        return Path.get_basename(elf_path) == "libphpef-runtime-core.so";
    }

    private static string renumber_stacktrace_line(string line, int frame_number) {
        var marker_pos = line.index_of(" in '");
        if (marker_pos < 0) {
            return line;
        }

        return "#" + frame_number.to_string() + line.substring(marker_pos);
    }

    private static string normalize_phpef_stacktrace_line(string line, string? raw_name, StacktraceHandlerMetadata? metadata, string? explicit_frame_name, string fallback_throw_file = "", int fallback_throw_line = 0) {
        if (raw_name == null) {
            return line;
        }

        var php_name = raw_name;
        if (metadata != null) {
            php_name = metadata.php_name;
        } else if (explicit_frame_name != null) {
            php_name = explicit_frame_name;
        }

        var marker = " in '";
        var marker_pos = line.index_of(marker);
        if (marker_pos < 0) {
            return line;
        }
        var name_start = marker_pos + marker.length;
        var rest = line.substring(name_start);
        var name_end_rel = rest.index_of("'");
        if (name_end_rel < 0) {
            return line;
        }

        var normalized = line;
        if (php_name != raw_name) {
            normalized = line.substring(0, name_start) + php_name + rest.substring(name_end_rel);
        }
        if (metadata != null && line.contains(".c:")) {
            var location_pos = normalized.index_of(" at ", name_start);
            if (location_pos >= 0) {
                normalized = normalized.substring(0, location_pos);
            }
        }

        if (fallback_throw_file != "" && fallback_throw_line > 0 && normalized.has_prefix("#")) {
            var fallback_location_path = fallback_throw_file + ":" + fallback_throw_line.to_string();
            var fallback_location = " at " + fallback_location_path;
            var location_pos = normalized.index_of(" at ", name_start);
            var native_location = "";
            if (location_pos >= 0) {
                native_location = normalized.substring(location_pos + " at ".length).strip();
                normalized = normalized.substring(0, location_pos) + fallback_location;
            } else {
                normalized += fallback_location;
            }
            if (native_location != "" && native_location != fallback_location_path && !native_location.has_prefix(fallback_throw_file + ":")) {
                normalized += "\n    in " + native_location;
            }
        }

        return normalized;
    }

    private static string normalize_exception_class(string class_name) {
        var normalized = class_name;
        while (normalized.has_prefix("\\")) {
            normalized = normalized.substring(1);
        }

        return normalized == "" ? "Exception" : normalized;
    }
}
