//! C++23 code generation backend

use crate::frame_c::compiler::codegen::ast::*;
use crate::frame_c::compiler::codegen::backend::*;
use crate::frame_c::visitors::TargetLanguage;

/// Coroutine promise-type emitted at file scope before any async Frame
/// class. Header-guarded so a file with multiple `@@system`s (each
/// potentially async) only declares the template once.
const FRAME_TASK_PRELUDE: &str = r#"#ifndef FRAME_TASK_H
#define FRAME_TASK_H
#include <coroutine>
#include <exception>
#include <utility>

template <typename T>
struct FrameTask {
    struct promise_type {
        T value_{};
        std::exception_ptr err_;
        FrameTask get_return_object() noexcept { return FrameTask{std::coroutine_handle<promise_type>::from_promise(*this)}; }
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        template <typename U> void return_value(U&& v) { value_ = std::forward<U>(v); }
        void unhandled_exception() noexcept { err_ = std::current_exception(); }
    };
    std::coroutine_handle<promise_type> h_;
    explicit FrameTask(std::coroutine_handle<promise_type> h) noexcept : h_(h) {}
    FrameTask(const FrameTask&) = delete;
    FrameTask& operator=(const FrameTask&) = delete;
    FrameTask(FrameTask&& o) noexcept : h_(std::exchange(o.h_, {})) {}
    FrameTask& operator=(FrameTask&& o) noexcept { if (this != &o) { if (h_) h_.destroy(); h_ = std::exchange(o.h_, {}); } return *this; }
    ~FrameTask() { if (h_) h_.destroy(); }
    bool await_ready() const noexcept { return h_.done(); }
    void await_suspend(std::coroutine_handle<>) const noexcept {}
    T await_resume() { if (h_.promise().err_) std::rethrow_exception(h_.promise().err_); return std::move(h_.promise().value_); }
    T get() { if (h_.promise().err_) std::rethrow_exception(h_.promise().err_); return std::move(h_.promise().value_); }
};

template <>
struct FrameTask<void> {
    struct promise_type {
        std::exception_ptr err_;
        FrameTask get_return_object() noexcept { return FrameTask{std::coroutine_handle<promise_type>::from_promise(*this)}; }
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() noexcept {}
        void unhandled_exception() noexcept { err_ = std::current_exception(); }
    };
    std::coroutine_handle<promise_type> h_;
    explicit FrameTask(std::coroutine_handle<promise_type> h) noexcept : h_(h) {}
    FrameTask(const FrameTask&) = delete;
    FrameTask& operator=(const FrameTask&) = delete;
    FrameTask(FrameTask&& o) noexcept : h_(std::exchange(o.h_, {})) {}
    FrameTask& operator=(FrameTask&& o) noexcept { if (this != &o) { if (h_) h_.destroy(); h_ = std::exchange(o.h_, {}); } return *this; }
    ~FrameTask() { if (h_) h_.destroy(); }
    bool await_ready() const noexcept { return h_.done(); }
    void await_suspend(std::coroutine_handle<>) const noexcept {}
    void await_resume() { if (h_.promise().err_) std::rethrow_exception(h_.promise().err_); }
    void get() { if (h_.promise().err_) std::rethrow_exception(h_.promise().err_); }
};
#endif // FRAME_TASK_H

"#;

/// Rewrite every `return;` / `return expr;` in an async-method body to
/// `co_return;` / `co_return expr;`. The existing state-dispatch and
/// frame-expansion emitters sprinkle plain `return`s in transition/forward
/// code paths (~20 sites across the codebase), and each is synchronous
/// when emitted. Inside a C++ coroutine, plain `return` is ill-formed —
/// the compiler refuses to mix the two. Post-processing the body string
/// keeps the upstream emitters language-agnostic.
///
/// Word-boundary match on `return` (leading whitespace OK, not inside an
/// identifier) — we don't want to clobber `returned`, string literals, or
/// a `// return` comment. This walks lines, checks the trimmed prefix,
/// and preserves indentation and trailing content (e.g. `return __result;`).
/// Rewrite member references in absorbed frame-init lines so they
/// target a local `c` instead of an implicit `this`.
///
/// The bare-ctor body lines use two member-access styles:
///
/// - **`this->X = Y;`** — emitted by `constructor.rs` for domain
///   assignments. These become `c.X = Y;`.
/// - **Bare references** — `__compartment`, `_context_stack`,
///   `__kernel`, `__prepareEnter`, etc. The C++ codegen omits the
///   `this->` prefix on these because they're called from member
///   functions. After absorption into a static factory they must be
///   qualified with `c.`.
///
/// Word-boundary aware: only rewrites when the symbol is preceded by
/// a non-word character (or start of string) AND followed by a
/// non-word character (or end of string). Avoids double-prefixing
/// (`c.c.__compartment`) by skipping symbols whose immediate prefix
/// is already `c.`.
fn rewrite_cpp_member_refs_for_factory(line: &str) -> String {
    let after_this = line.replace("this->", "c.");
    const MEMBERS: &[&str] = &[
        "__compartment",
        "_context_stack",
        "__next_compartment",
        "__kernel",
        "__router",
        "__prepareEnter",
        "__prepareExit",
        "__transition",
        "__hsm_chain",
        "hsm_chain",
    ];
    let mut s = after_this;
    for m in MEMBERS {
        s = word_boundary_prefix_replace(&s, m, "c.");
    }
    s
}

/// Replace standalone occurrences of `needle` with `prefix + needle`,
/// honoring word boundaries and skipping cases where `needle` is
/// already preceded by `prefix` (avoids `c.c.X`).
fn word_boundary_prefix_replace(haystack: &str, needle: &str, prefix: &str) -> String {
    let bytes = haystack.as_bytes();
    let nb = needle.as_bytes();
    let pb = prefix.as_bytes();
    let mut result = String::with_capacity(haystack.len() + 32);
    let mut i = 0;
    while i < bytes.len() {
        let starts_here = bytes[i..].starts_with(nb);
        if starts_here {
            let prev_is_word =
                i > 0 && (bytes[i - 1].is_ascii_alphanumeric() || bytes[i - 1] == b'_');
            let end = i + nb.len();
            let next_is_word =
                end < bytes.len() && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_');
            let prev_is_prefix = i >= pb.len() && &bytes[i - pb.len()..i] == pb;
            if !prev_is_word && !next_is_word && !prev_is_prefix {
                result.push_str(prefix);
                result.push_str(needle);
                i = end;
                continue;
            }
        }
        result.push(bytes[i] as char);
        i += 1;
    }
    result
}

fn rewrite_return_to_co_return(body: &str) -> String {
    let mut out = String::with_capacity(body.len() + 64);
    for line in body.split_inclusive('\n') {
        let trimmed_start = line.trim_start();
        let indent_len = line.len() - trimmed_start.len();
        // Bail if this isn't a `return` statement. Comment lines
        // (`// ...`) already leave `return` inside a non-statement
        // position, so a strict prefix check is safe.
        let bare_return = trimmed_start == "return;" || trimmed_start == "return;\n";
        let return_with_val = trimmed_start.starts_with("return ")
            && !trimmed_start.starts_with("return_")  // defensive: no identifier collision
            && !trimmed_start.starts_with("returns");
        if bare_return || return_with_val {
            out.push_str(&line[..indent_len]);
            out.push_str("co_");
            out.push_str(trimmed_start);
        } else {
            out.push_str(line);
        }
    }
    out
}

/// C++23 backend for code generation
pub struct CppBackend;

impl LanguageBackend for CppBackend {
    fn emit(&self, node: &CodegenNode, ctx: &mut EmitContext) -> String {
        match node {
            CodegenNode::Module { imports, items } => {
                let mut result = String::new();
                for import in imports {
                    result.push_str(&self.emit(import, ctx));
                    result.push('\n');
                }
                if !imports.is_empty() && !items.is_empty() {
                    result.push('\n');
                }
                for (i, item) in items.iter().enumerate() {
                    if i > 0 {
                        result.push_str("\n\n");
                    }
                    result.push_str(&self.emit(item, ctx));
                }
                result
            }

            CodegenNode::Import { module, .. } => format!("#include <{}>", module),

            CodegenNode::Class {
                name,
                fields,
                methods,
                base_classes,
                is_abstract: _,
                ..
            } => {
                let mut result = String::new();
                let extends = if base_classes.is_empty() {
                    String::new()
                } else {
                    format!(" : public {}", base_classes.join(", public "))
                };

                // C++ async: if any method is async, emit a self-contained
                // `FrameTask<T>` coroutine promise at file scope before the
                // class. `#ifndef FRAME_TASK_H` guards multi-@@system
                // files (e.g. 33_ai_agent) from re-declaring the template.
                //
                // Model: `initial_suspend` is `suspend_never` (bodies run
                // synchronously from creation to the first real await or
                // `co_return`), and `final_suspend` is `suspend_always` so
                // the caller can extract the return value before the
                // handle is destroyed. This matches Frame's semantics:
                // the dispatch chain has no true async I/O — `co_await`
                // just threads return values.
                let has_async = methods
                    .iter()
                    .any(|m| matches!(m, CodegenNode::Method { is_async: true, .. }));
                if has_async {
                    result.push_str(FRAME_TASK_PRELUDE);
                }

                result.push_str(&format!(
                    "{}class {}{} {{\n",
                    ctx.get_indent(),
                    name,
                    extends
                ));

                // Group fields and methods by visibility using sections
                // Private section first (fields + methods)
                let private_fields: Vec<_> = fields
                    .iter()
                    .filter(|f| !matches!(f.visibility, Visibility::Public))
                    .collect();
                let private_methods: Vec<_> = methods
                    .iter()
                    .filter(|m| {
                        if let CodegenNode::Method { visibility, .. } = m {
                            !matches!(visibility, Visibility::Public)
                        } else {
                            false
                        }
                    })
                    .collect();

                if !private_fields.is_empty() || !private_methods.is_empty() {
                    result.push_str("private:\n");
                    ctx.push_indent();
                    for field in &private_fields {
                        result.push_str(&self.emit_field(field, ctx));
                    }
                    if !private_fields.is_empty() && !private_methods.is_empty() {
                        result.push('\n');
                    }
                    for (i, method) in private_methods.iter().enumerate() {
                        if i > 0 {
                            result.push('\n');
                        }
                        result.push_str(&self.emit(method, ctx));
                    }
                    ctx.pop_indent();
                    result.push('\n');
                }

                // Public section (fields + constructor + methods)
                let public_fields: Vec<_> = fields
                    .iter()
                    .filter(|f| matches!(f.visibility, Visibility::Public))
                    .collect();
                let public_methods: Vec<_> = methods
                    .iter()
                    .filter(|m| {
                        matches!(m, CodegenNode::Constructor { .. })
                            || matches!(
                                m,
                                CodegenNode::Method {
                                    visibility: Visibility::Public,
                                    ..
                                }
                            )
                            // NativeBlock at class scope is used for class-
                            // static declarations (e.g. the `@@persist`
                            // __skipInitialEnter flag) that don't fit the
                            // Method/Field/Constructor node shapes. Emit
                            // them alongside public members.
                            || matches!(m, CodegenNode::NativeBlock { .. })
                    })
                    .collect();

                if !public_fields.is_empty() || !public_methods.is_empty() {
                    result.push_str("public:\n");
                    ctx.push_indent();
                    for field in &public_fields {
                        result.push_str(&self.emit_field(field, ctx));
                    }
                    if !public_fields.is_empty() && !public_methods.is_empty() {
                        result.push('\n');
                    }
                    for (i, method) in public_methods.iter().enumerate() {
                        if i > 0 {
                            result.push('\n');
                        }
                        result.push_str(&self.emit(method, ctx));
                    }
                    ctx.pop_indent();
                }

                result.push_str(&format!("{}}};\n", ctx.get_indent()));
                result
            }

            CodegenNode::Enum { name, variants } => {
                let mut result = format!("{}enum class {} {{\n", ctx.get_indent(), name);
                ctx.push_indent();
                for (i, variant) in variants.iter().enumerate() {
                    let comma = if i < variants.len() - 1 { "," } else { "" };
                    result.push_str(&format!("{}{}{}\n", ctx.get_indent(), variant.name, comma));
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}};\n", ctx.get_indent()));
                result
            }

            CodegenNode::Method {
                name,
                params,
                return_type,
                body,
                is_async,
                is_static,
                visibility: _,
                ..
            } => {
                let static_kw = if *is_static { "static " } else { "" };
                let raw_return = self.map_type(return_type.as_ref().unwrap_or(&"void".to_string()));
                // C++23 async: methods marked async become coroutines
                // returning `FrameTask<T>`. Void methods return `FrameTask<void>`.
                // Bodies must `co_return` (or `co_return value`) — the
                // interface_gen Cpp arm emits `co_return` directly, and
                // any transition-level `return;` / `return expr;` left in
                // the rest of the body is rewritten after emission below
                // (mixing `return` with `co_return` / `co_await` is a
                // hard compile error in a coroutine).
                let return_str = if *is_async {
                    format!("FrameTask<{}>", raw_return)
                } else {
                    raw_return
                };
                let params_str = self.emit_params(params);

                let mut body_str = String::new();
                ctx.push_indent();
                for stmt in body {
                    body_str.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        body_str.push_str(";\n");
                    } else {
                        body_str.push('\n');
                    }
                }
                ctx.pop_indent();

                if *is_async {
                    body_str = rewrite_return_to_co_return(&body_str);
                }

                let mut result = format!(
                    "{}{}{} {}({}) {{\n",
                    ctx.get_indent(),
                    static_kw,
                    return_str,
                    name,
                    params_str
                );
                result.push_str(&body_str);
                result.push_str(&format!("{}}}\n", ctx.get_indent()));
                result
            }

            CodegenNode::Constructor {
                params,
                body,
                super_call,
            } => {
                // RFC-0020: two artifacts — bare ctor + static __create
                // factory. The intermediate `__frame_init` member that
                // RFC-0017 emitted is gone — its body is absorbed inline
                // into `__create`. Framework helper classes
                // (FrameEvent/FrameContext/Compartment) keep the
                // original single-ctor emission below.
                let class_name = ctx.system_name.clone().unwrap_or("Class".to_string());
                let is_frame_helper = class_name.ends_with("FrameEvent")
                    || class_name.ends_with("FrameContext")
                    || class_name.ends_with("Compartment");

                if is_frame_helper {
                    let params_str = self.emit_params(params);
                    let init_list = super_call
                        .as_ref()
                        .map(|sc| format!(" : {}", self.emit(sc, ctx)))
                        .unwrap_or_default();
                    let mut result = format!(
                        "{}{}({}){} {{\n",
                        ctx.get_indent(),
                        class_name,
                        params_str,
                        init_list
                    );
                    ctx.push_indent();
                    for stmt in body {
                        result.push_str(&self.emit(stmt, ctx));
                        if self.needs_semicolon(stmt) {
                            result.push_str(";\n");
                        } else {
                            result.push('\n');
                        }
                    }
                    ctx.pop_indent();
                    result.push_str(&format!("{}}}\n", ctx.get_indent()));
                    return result;
                }

                // System class: classify body.
                //
                // Lines fall into two buckets:
                // - `framework_lines` — run in the bare ctor (`Counter()`)
                //   so `@@!Counter()` produces a usable-but-not-entered
                //   instance.
                // - `frame_init_lines` — run only in the factory
                //   (`Counter::__create(...)`), AFTER constructing a
                //   local `c` of type Counter. These include compartment
                //   setup (`__prepareEnter`), domain-param assignments,
                //   and the kernel call that fires the start $>.
                //
                // Lines that touch the kernel (`__kernel(...)`), set
                // `__compartment`, or mention any system parameter go
                // to `frame_init_lines`; everything else stays in the
                // bare ctor.
                let param_names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
                ctx.push_indent();
                let mut framework_lines: Vec<String> = Vec::new();
                let mut frame_init_lines: Vec<String> = Vec::new();
                for stmt in body {
                    let mut rendered = self.emit(stmt, ctx);
                    if self.needs_semicolon(stmt) && !rendered.ends_with(";\n") {
                        if !rendered.ends_with('\n') {
                            rendered.push_str(";\n");
                        } else {
                            let trimmed = rendered.trim_end_matches('\n').to_string();
                            rendered = format!("{};\n", trimmed);
                        }
                    } else if !rendered.ends_with('\n') {
                        rendered.push('\n');
                    }
                    // Scope to kernel call + context-stack push/pop/peek.
                    // The compartment-init line (`__compartment = __prepareEnter(...)`)
                    // must run in the bare ctor too so `@@!Foo()` shells are
                    // usable (with empty-args compartment); when it mentions
                    // params, the mentions_param branch below handles the split.
                    let frame_init_only = rendered.contains("__kernel(")
                        || rendered.contains("_context_stack.push_back(")
                        || rendered.contains("_context_stack.pop_back(")
                        || rendered.contains("_context_stack.back(");
                    if frame_init_only {
                        frame_init_lines.push(rendered);
                        continue;
                    }
                    let mentions_param = param_names.iter().any(|p| {
                        rendered
                            .split(|c: char| !c.is_alphanumeric() && c != '_')
                            .any(|w| w == *p)
                    });
                    if mentions_param {
                        frame_init_lines.push(rendered);
                    } else {
                        framework_lines.push(rendered);
                    }
                }
                ctx.pop_indent();

                let init_list = super_call
                    .as_ref()
                    .map(|sc| format!(" : {}", self.emit(sc, ctx)))
                    .unwrap_or_default();

                // A member-initializer list (const domain fields seeded
                // from system parameters: `const x: int = x` -> `: x(x)`)
                // can only run in a constructor, so when one is present
                // the bare ctor must take the system parameters too —
                // `__create` then threads them through the ctor. With
                // no member-init list the bare ctor stays parameterless
                // (a parameterless ctor is what the restore path's
                // `make_shared<T>()` and `@@!T()` rely on).
                let needs_ctor_params = super_call.is_some() && !params.is_empty();
                let bare_ctor_params = if needs_ctor_params {
                    self.emit_params(params)
                } else {
                    String::new()
                };

                // Emit `Counter()` — bare framework
                let mut result = format!(
                    "{}{}({}){} {{\n",
                    ctx.get_indent(),
                    class_name,
                    bare_ctor_params,
                    init_list
                );
                for line in &framework_lines {
                    result.push_str(line);
                }
                result.push_str(&format!("{}}}\n", ctx.get_indent()));

                // Emit `static Counter __create(...)` — factory,
                // returning by value. Driver code does `auto x =
                // Counter::__create(7); x.method()`, so `__create`
                // must hand back a `Counter` value, not a
                // `shared_ptr<Counter>` (that would force `x->method()`)
                // and not a `Counter*` (raw, same problem + leak).
                // System-typed *domain fields* are `shared_ptr<T>`;
                // their initializer wraps the factory result —
                // `std::make_shared<Counter>(Counter::__create(7))`,
                // move-constructed from the returned temporary — see
                // `expand_system_instantiation_in_domain`.
                result.push('\n');
                let create_params = self.emit_params(params);
                result.push_str(&format!(
                    "{}static {} __create({}) {{\n",
                    ctx.get_indent(),
                    class_name,
                    create_params
                ));
                ctx.push_indent();
                let arg_pass: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
                if needs_ctor_params {
                    result.push_str(&format!(
                        "{}{} c({});\n",
                        ctx.get_indent(),
                        class_name,
                        arg_pass.join(", ")
                    ));
                } else {
                    result.push_str(&format!("{}{} c;\n", ctx.get_indent(), class_name));
                }
                // Absorb the frame_init body inline, rewriting
                // member references so they target the local `c` instead
                // of an implicit `this`. The bare-ctor body uses
                // `this->X` for member writes and bare references
                // (`__compartment`, `_context_stack`, `__kernel`, ...)
                // for framework members. In a static factory `this` is
                // not valid; each such reference must be qualified.
                for line in &frame_init_lines {
                    let rewritten = rewrite_cpp_member_refs_for_factory(line);
                    result.push_str(&rewritten);
                }
                result.push_str(&format!("{}return c;\n", ctx.get_indent()));
                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));

                result
            }

            CodegenNode::VarDecl {
                name,
                type_annotation,
                init,
                is_const,
            } => {
                let const_kw = if *is_const { "const " } else { "" };
                let type_str = type_annotation
                    .as_ref()
                    .unwrap_or(&"auto".to_string())
                    .clone();
                if let Some(init_expr) = init {
                    format!(
                        "{}{}{} {} = {}",
                        ctx.get_indent(),
                        const_kw,
                        type_str,
                        name,
                        self.emit(init_expr, ctx)
                    )
                } else {
                    format!("{}{}{} {}", ctx.get_indent(), const_kw, type_str, name)
                }
            }

            CodegenNode::Assignment { target, value } => {
                format!(
                    "{}{} = {}",
                    ctx.get_indent(),
                    self.emit(target, ctx),
                    self.emit(value, ctx)
                )
            }

            CodegenNode::Return { value } => {
                if let Some(val) = value {
                    format!("{}return {}", ctx.get_indent(), self.emit(val, ctx))
                } else {
                    format!("{}return", ctx.get_indent())
                }
            }

            CodegenNode::If {
                condition,
                then_block,
                else_block,
            } => {
                let mut result = format!(
                    "{}if ({}) {{\n",
                    ctx.get_indent(),
                    self.emit(condition, ctx)
                );
                ctx.push_indent();
                for stmt in then_block {
                    result.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        result.push_str(";\n");
                    } else {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();

                if let Some(else_stmts) = else_block {
                    result.push_str(&format!("{}}} else {{\n", ctx.get_indent()));
                    ctx.push_indent();
                    for stmt in else_stmts {
                        result.push_str(&self.emit(stmt, ctx));
                        if self.needs_semicolon(stmt) {
                            result.push_str(";\n");
                        } else {
                            result.push('\n');
                        }
                    }
                    ctx.pop_indent();
                }
                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::Match { scrutinee, arms } => {
                let mut result = format!(
                    "{}switch ({}) {{\n",
                    ctx.get_indent(),
                    self.emit(scrutinee, ctx)
                );
                ctx.push_indent();
                for arm in arms {
                    result.push_str(&format!(
                        "{}case {}:\n",
                        ctx.get_indent(),
                        self.emit(&arm.pattern, ctx)
                    ));
                    ctx.push_indent();
                    for stmt in &arm.body {
                        result.push_str(&self.emit(stmt, ctx));
                        if self.needs_semicolon(stmt) {
                            result.push_str(";\n");
                        } else {
                            result.push('\n');
                        }
                    }
                    result.push_str(&format!("{}break;\n", ctx.get_indent()));
                    ctx.pop_indent();
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::While { condition, body } => {
                let mut result = format!(
                    "{}while ({}) {{\n",
                    ctx.get_indent(),
                    self.emit(condition, ctx)
                );
                ctx.push_indent();
                for stmt in body {
                    result.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        result.push_str(";\n");
                    } else {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::For {
                var,
                iterable,
                body,
            } => {
                let mut result = format!(
                    "{}for (auto {} : {}) {{\n",
                    ctx.get_indent(),
                    var,
                    self.emit(iterable, ctx)
                );
                ctx.push_indent();
                for stmt in body {
                    result.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        result.push_str(";\n");
                    } else {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::Break => format!("{}break", ctx.get_indent()),
            CodegenNode::Continue => format!("{}continue", ctx.get_indent()),
            CodegenNode::ExprStmt(expr) => format!("{}{}", ctx.get_indent(), self.emit(expr, ctx)),
            CodegenNode::Await(expr) => {
                format!("co_await {}", self.emit(expr, ctx))
            }
            CodegenNode::Comment { text, .. } => format!("{}// {}", ctx.get_indent(), text),
            CodegenNode::Empty => String::new(),

            CodegenNode::Ident(name) => name.clone(),
            CodegenNode::Literal(lit) => self.emit_literal(lit, ctx),
            CodegenNode::BinaryOp { op, left, right } => self.emit_binary_op(op, left, right, ctx),
            CodegenNode::UnaryOp { op, operand } => self.emit_unary_op(op, operand, ctx),

            CodegenNode::Call { target, args } => {
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                format!("{}({})", self.emit(target, ctx), args_str.join(", "))
            }

            CodegenNode::MethodCall {
                object,
                method,
                args,
            } => {
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                format!(
                    "{}->{}({})",
                    self.emit(object, ctx),
                    method,
                    args_str.join(", ")
                )
            }

            CodegenNode::FieldAccess { object, field } => {
                format!("{}->{}", self.emit(object, ctx), field)
            }
            CodegenNode::IndexAccess { object, index } => {
                format!("{}[{}]", self.emit(object, ctx), self.emit(index, ctx))
            }
            CodegenNode::SelfRef => "this".to_string(),

            CodegenNode::Array(elements) => {
                let elems: Vec<String> = elements.iter().map(|e| self.emit(e, ctx)).collect();
                format!("{{ {} }}", elems.join(", "))
            }

            CodegenNode::Dict(pairs) => {
                let pairs_str: Vec<String> = pairs
                    .iter()
                    .map(|(k, v)| format!("{{{}, {}}}", self.emit(k, ctx), self.emit(v, ctx)))
                    .collect();
                format!(
                    "std::unordered_map<std::string, std::any>{{ {} }}",
                    pairs_str.join(", ")
                )
            }

            CodegenNode::Ternary {
                condition,
                then_expr,
                else_expr,
            } => {
                format!(
                    "{} ? {} : {}",
                    self.emit(condition, ctx),
                    self.emit(then_expr, ctx),
                    self.emit(else_expr, ctx)
                )
            }

            CodegenNode::Lambda { params, body } => {
                let params_str = params
                    .iter()
                    .map(|p| {
                        let t = p
                            .type_annotation
                            .as_ref()
                            .unwrap_or(&"auto".to_string())
                            .clone();
                        format!("{} {}", t, p.name)
                    })
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("[&]({}) {{ return {}; }}", params_str, self.emit(body, ctx))
            }

            CodegenNode::Cast { expr, target_type } => {
                format!("static_cast<{}>({})", target_type, self.emit(expr, ctx))
            }

            CodegenNode::New { class, args } => {
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                format!("new {}({})", class, args_str.join(", "))
            }

            // Frame-specific (expanded upstream as NativeBlock in normal pipeline)
            CodegenNode::Transition {
                target_state,
                indent,
                ..
            } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}this->_transition({})", ind, target_state)
            }
            CodegenNode::ChangeState {
                target_state,
                indent,
                ..
            } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}this->_changeState({})", ind, target_state)
            }
            CodegenNode::Forward { indent, .. } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}return", ind)
            }
            CodegenNode::StackPush { indent } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}_state_stack.push_back(__compartment->clone())", ind)
            }
            CodegenNode::StackPop { indent } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}auto __saved = std::move(_state_stack.back()); _state_stack.pop_back(); __transition(std::move(__saved))", ind)
            }
            CodegenNode::StateContext { state_name } => {
                format!("this->_stateContext[\"{}\"]", state_name)
            }

            CodegenNode::SendEvent { event, args } => {
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                if args_str.is_empty() {
                    format!("{}this->{}()", ctx.get_indent(), event)
                } else {
                    format!(
                        "{}this->{}({})",
                        ctx.get_indent(),
                        event,
                        args_str.join(", ")
                    )
                }
            }

            CodegenNode::NativeBlock { code, .. } => {
                let indent = ctx.get_indent();
                let mut out = code
                    .lines()
                    .map(|line| {
                        if line.trim().is_empty() {
                            String::new()
                        } else {
                            format!("{}{}", indent, line)
                        }
                    })
                    .collect::<Vec<_>>()
                    .join("\n");
                // Ensure trailing newline. `code.lines()` strips it; the
                // class emitter relies on each item ending its own line
                // so the closing `};` doesn't glue onto the last
                // emitted token. Idempotent — already-newline-ended
                // content stays unchanged.
                if !out.ends_with('\n') {
                    out.push('\n');
                }
                out
            }
            CodegenNode::SplicePoint { id } => format!("// SPLICE_POINT: {}", id),
        }
    }

    fn emit_module_imports(
        &self,
        imports: &[crate::frame_c::compiler::frame_ast::Import],
    ) -> Vec<String> {
        // RFC-0022 Phase 1 lax — `#include "x.h"`. C++ headers expose
        // class declarations file-globally after include. User-provided
        // header is required (framec currently emits `.cpp` only).
        imports
            .iter()
            .filter_map(|imp| {
                let path = imp.module.as_str();
                if path.is_empty() {
                    return None;
                }
                let stem = match path.rfind('.') {
                    Some(idx) => &path[..idx],
                    None => path,
                };
                Some(format!("#include \"{}.h\"", stem))
            })
            .collect()
    }

    fn runtime_imports(&self) -> Vec<String> {
        vec![
            "#include <string>".to_string(),
            "#include <unordered_map>".to_string(),
            "#include <vector>".to_string(),
            "#include <any>".to_string(),
            "#include <memory>".to_string(),
            "#include <functional>".to_string(),
        ]
    }

    fn class_syntax(&self) -> ClassSyntax {
        ClassSyntax::cpp()
    }
    fn target_language(&self) -> TargetLanguage {
        TargetLanguage::Cpp
    }
    fn null_keyword(&self) -> &'static str {
        "nullptr"
    }
}

impl CppBackend {
    fn emit_params(&self, params: &[Param]) -> String {
        params
            .iter()
            .map(|p| {
                let raw_type = p.type_annotation.as_deref().unwrap_or("auto");
                // Route through the shared cpp_map_type so emit_params,
                // map_type, and the std::any_cast prefetch agree on
                // Frame → C++ mappings (esp. `float` → `double`,
                // otherwise interface signature emits `float` while
                // handler bodies cast to `double` and any_cast fails).
                let type_ann = super::super::codegen_utils::cpp_map_type(raw_type);
                if let Some(ref default_val) = p.default_value {
                    let default_str =
                        self.emit(default_val, &mut super::super::backend::EmitContext::new());
                    format!("{} {} = {}", type_ann, p.name, default_str)
                } else {
                    format!("{} {}", type_ann, p.name)
                }
            })
            .collect::<Vec<_>>()
            .join(", ")
    }

    fn map_type(&self, t: &str) -> String {
        super::super::codegen_utils::cpp_map_type(t)
    }

    fn needs_semicolon(&self, node: &CodegenNode) -> bool {
        !matches!(
            node,
            CodegenNode::If { .. }
                | CodegenNode::While { .. }
                | CodegenNode::For { .. }
                | CodegenNode::Match { .. }
                | CodegenNode::Comment { .. }
                | CodegenNode::NativeBlock { .. }
                | CodegenNode::Empty
        )
    }

    /// Emit a single C++ field declaration line:
    ///   `<indent>[const ]<type> <name>[ = <init>];\n`
    fn emit_field(&self, field: &Field, ctx: &mut EmitContext) -> String {
        let const_kw = if field.is_const { "const " } else { "" };
        let raw_type = field
            .type_annotation
            .as_ref()
            .map(|s| s.as_str())
            .unwrap_or("void*");
        // Cross-system domain reference (`inner: Counter = @@Counter()`):
        // the codegen emits `new Counter()` for the initializer
        // (returns `Counter*`); a bare `Counter` field by value can't
        // accept the pointer. The convention used by existing matrix
        // demos (`tests/common/positive/demos/20_multi_system_composition.fcpp`)
        // is `std::shared_ptr<T>` — wrap the type and rewrite the
        // initializer's `new T(...)` to `std::make_shared<T>(...)`.
        let is_system_ref = ctx.defined_systems.contains(raw_type);
        let type_str = if is_system_ref {
            format!("std::shared_ptr<{}>", raw_type)
        } else {
            raw_type.to_string()
        };
        let init_suffix = match &field.initializer {
            Some(init) => {
                let mut init_src = self.emit(init, ctx);
                if is_system_ref {
                    let needle = format!("new {}(", raw_type);
                    let replacement = format!("std::make_shared<{}>(", raw_type);
                    init_src = init_src.replace(&needle, &replacement);
                }
                format!(" = {}", init_src)
            }
            None => String::new(),
        };
        let comments = field.format_leading_comments(&ctx.get_indent());
        format!(
            "{}{}{}{} {}{};\n",
            comments,
            ctx.get_indent(),
            const_kw,
            type_str,
            field.name,
            init_suffix
        )
    }
}
