// Code generator: emits Rust source code from a Gust AST.
//
// Each `machine` becomes:
//   - A state enum with serde derives
//   - A machine struct holding the current state
//   - An impl block with transition methods (type-checked)
//   - Transition methods that enforce valid from-state at runtime
//     (compile-time via enum matching / exhaustiveness)
//   - Effect traits (if machine declares effects)

use crate::ast::*;
use crate::codegen_common::{
    collect_known_types, collect_referenced_idents, detect_ctx_param, escape_string_literal,
    handler_used_channels, handler_uses_perform, handler_uses_spawn, has_timeout_transition,
    to_snake_case,
};
use std::collections::HashSet;

/// Rust code generator. Consumes a validated [`Program`] and emits
/// idiomatic `.g.rs` source targeting the `gust_runtime` crate.
pub struct RustCodegen {
    output: String,
    indent: usize,
    ctx_param: Option<String>,
    from_state_fields: Vec<String>,
    known_types: HashSet<String>,
    current_effects: Vec<EffectDecl>,
    tracing: bool,
}

impl RustCodegen {
    /// Construct a new Rust code generator with tracing disabled.
    pub fn new() -> Self {
        Self {
            output: String::new(),
            indent: 0,
            ctx_param: None,
            from_state_fields: Vec::new(),
            known_types: HashSet::new(),
            current_effects: Vec::new(),
            tracing: false,
        }
    }

    /// Enable tracing instrumentation in generated code.
    ///
    /// When enabled, the generated Rust code will include `#[cfg(feature = "tracing")]`-guarded
    /// span creation and event emission for state transitions and effect invocations.
    /// The user's crate must add `tracing` as an optional dependency and declare a `"tracing"`
    /// feature flag for these hooks to compile in.
    pub fn with_tracing(mut self, enabled: bool) -> Self {
        self.tracing = enabled;
        self
    }

    /// Generate the full `.g.rs` source for `program`.
    pub fn generate(mut self, program: &Program) -> String {
        self.emit_prelude(program);

        self.known_types = collect_known_types(program);

        for channel in &program.channels {
            self.emit_channel_decl(channel);
            self.newline();
        }

        // Emit type declarations as Rust structs
        for type_decl in &program.types {
            self.emit_type_decl(type_decl);
            self.newline();
        }

        // Emit each machine
        for machine in &program.machines {
            self.emit_machine(machine, &program.channels);
            self.newline();
        }

        self.output
    }

    fn emit_prelude(&mut self, program: &Program) {
        self.line("// Generated by Gust compiler — do not edit manually");
        self.line("use serde::{Serialize, Deserialize};");
        self.line("use gust_runtime::prelude::*;");
        if !program.channels.is_empty() || has_timeout_transition(program) {
            self.line("use tokio;");
        }
        if self.tracing {
            self.line("#[cfg(feature = \"tracing\")]");
            self.line("use tracing;");
        }
        for use_path in &program.uses {
            if use_path.segments.is_empty() {
                continue;
            }
            // `std::*` is a Gust-virtual namespace for stdlib machines/types
            // (see `gust-stdlib`). The consumer's build pipeline is
            // responsible for compiling the stdlib source and placing it in
            // the same module as the dependent machine, so no Rust `use`
            // statement is emitted. See issue #66.
            if use_path.segments.first().map(String::as_str) == Some("std") {
                continue;
            }
            self.line(&format!("use {};", use_path.segments.join("::")));
        }
        self.newline();
    }

    fn emit_channel_decl(&mut self, channel: &ChannelDecl) {
        let name = &channel.name;
        let message_type = self.type_expr_to_rust(&channel.message_type);
        let channel_struct = format!("{name}Channel");
        let capacity = channel.capacity.unwrap_or(1024).max(1);

        match channel.mode {
            ChannelMode::Broadcast => {
                self.line("#[derive(Clone)]");
                self.line(&format!("pub struct {channel_struct} {{"));
                self.indent += 1;
                self.line(&format!(
                    "sender: tokio::sync::broadcast::Sender<{message_type}>,"
                ));
                self.indent -= 1;
                self.line("}");
                self.line(&format!("impl {channel_struct} {{"));
                self.indent += 1;
                self.line("pub fn new() -> Self {");
                self.indent += 1;
                self.line(&format!(
                    "let (sender, _) = tokio::sync::broadcast::channel({capacity}usize);"
                ));
                self.line("Self { sender }");
                self.indent -= 1;
                self.line("}");
                self.line(&format!(
                    "pub fn sender(&self) -> tokio::sync::broadcast::Sender<{message_type}> {{"
                ));
                self.indent += 1;
                self.line("self.sender.clone()");
                self.indent -= 1;
                self.line("}");
                self.line(&format!("pub fn send(&self, msg: {message_type}) {{"));
                self.indent += 1;
                self.line("let _ = self.sender.send(msg);");
                self.indent -= 1;
                self.line("}");
                self.line(&format!(
                    "pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<{message_type}> {{"
                ));
                self.indent += 1;
                self.line("self.sender.subscribe()");
                self.indent -= 1;
                self.line("}");
                self.indent -= 1;
                self.line("}");
            }
            ChannelMode::Mpsc => {
                self.line(&format!("pub struct {channel_struct} {{"));
                self.indent += 1;
                self.line(&format!(
                    "sender: tokio::sync::mpsc::Sender<{message_type}>,"
                ));
                self.line(&format!(
                    "receiver: tokio::sync::Mutex<tokio::sync::mpsc::Receiver<{message_type}>>,"
                ));
                self.indent -= 1;
                self.line("}");
                self.line(&format!("impl {channel_struct} {{"));
                self.indent += 1;
                self.line("pub fn new() -> Self {");
                self.indent += 1;
                self.line(&format!(
                    "let (sender, receiver) = tokio::sync::mpsc::channel({capacity}usize);"
                ));
                self.line("Self { sender, receiver: tokio::sync::Mutex::new(receiver) }");
                self.indent -= 1;
                self.line("}");
                self.line(&format!(
                    "pub fn sender(&self) -> tokio::sync::mpsc::Sender<{message_type}> {{"
                ));
                self.indent += 1;
                self.line("self.sender.clone()");
                self.indent -= 1;
                self.line("}");
                self.line(&format!("pub fn try_send(&self, msg: {message_type}) {{"));
                self.indent += 1;
                self.line("let _ = self.sender.try_send(msg);");
                self.indent -= 1;
                self.line("}");
                self.line(&format!(
                    "pub async fn receive(&self) -> Option<{message_type}> {{"
                ));
                self.indent += 1;
                self.line("self.receiver.lock().await.recv().await");
                self.indent -= 1;
                self.line("}");
                self.indent -= 1;
                self.line("}");
            }
        }
    }

    // === Type Declarations ===

    fn emit_type_decl(&mut self, decl: &TypeDecl) {
        match decl {
            TypeDecl::Struct { name, fields, .. } => {
                self.line("#[derive(Debug, Clone, Serialize, Deserialize)]");
                self.line(&format!("pub struct {name} {{"));
                self.indent += 1;
                for field in fields {
                    self.line(&format!(
                        "pub {}: {},",
                        field.name,
                        self.type_expr_to_rust(&field.ty)
                    ));
                }
                self.indent -= 1;
                self.line("}");
            }
            TypeDecl::Enum { name, variants, .. } => {
                self.line("#[derive(Debug, Clone, Serialize, Deserialize)]");
                self.line(&format!("pub enum {name} {{"));
                self.indent += 1;
                for variant in variants {
                    if variant.payload.is_empty() {
                        self.line(&format!("{},", variant.name));
                    } else {
                        let payload = variant
                            .payload
                            .iter()
                            .map(|t| self.type_expr_to_rust(t))
                            .collect::<Vec<_>>()
                            .join(", ");
                        self.line(&format!("{}({payload}),", variant.name));
                    }
                }
                self.indent -= 1;
                self.line("}");
            }
        }
    }

    // === Machine ===

    fn emit_machine(&mut self, machine: &MachineDecl, channels: &[ChannelDecl]) {
        let name = &machine.name;
        let state_enum = format!("{name}State");
        let generic_decl = rust_generic_decl(&machine.generic_params);
        let generic_use = rust_generic_use(&machine.generic_params);

        // State enum
        self.emit_state_enum(name, &machine.states, &generic_decl);
        self.newline();

        // Effect trait (if machine has effects)
        if !machine.effects.is_empty() {
            self.emit_effect_trait(machine, &generic_decl);
            self.newline();
        }

        // Machine struct
        self.line("#[derive(Debug, Clone, Serialize, Deserialize)]");
        self.line(&format!("pub struct {name}{generic_decl} {{"));
        self.indent += 1;
        self.line(&format!("pub state: {state_enum}{generic_use},"));
        self.indent -= 1;
        self.line("}");
        self.newline();

        self.emit_channel_helpers(machine, channels);
        if !machine.sends.is_empty() || !machine.receives.is_empty() {
            self.newline();
        }

        // Transition error type
        self.line(&format!(
            r#"#[derive(Debug, Clone, thiserror::Error)]
pub enum {name}Error {{
    #[error("invalid transition '{{transition}}' from state '{{from}}'")]
    InvalidTransition {{ transition: String, from: String }},
    #[error("transition failed: {{reason}}")]
    Failed {{ reason: String }},
}}"#
        ));
        self.newline();

        // Impl block with transitions
        self.line(&format!("impl{generic_decl} {name}{generic_use} {{"));
        self.indent += 1;

        // Constructor
        if let Some(first_state) = machine.states.first() {
            self.emit_constructor(name, &state_enum, first_state);
            self.newline();
        }

        // Current state helper
        self.line(&format!(
            "pub fn state(&self) -> &{state_enum}{generic_use} {{"
        ));
        self.indent += 1;
        self.line("&self.state");
        self.indent -= 1;
        self.line("}");
        self.newline();

        // Transition methods
        self.current_effects = machine.effects.clone();
        for transition in &machine.transitions {
            self.emit_transition_method(
                name,
                &state_enum,
                transition,
                &machine.handlers,
                &machine.states,
                &machine.effects,
                channels,
                &generic_use,
            );
            self.newline();
        }
        self.current_effects.clear();

        self.indent -= 1;
        self.line("}");
    }

    fn emit_state_enum(&mut self, machine_name: &str, states: &[StateDecl], generic_decl: &str) {
        let enum_name = format!("{machine_name}State");
        self.line("#[derive(Debug, Clone, Serialize, Deserialize)]");
        self.line(&format!("pub enum {enum_name}{generic_decl} {{"));
        self.indent += 1;
        for state in states {
            if state.fields.is_empty() {
                self.line(&format!("{},", state.name));
            } else {
                self.line(&format!("{} {{", state.name));
                self.indent += 1;
                for field in &state.fields {
                    self.line(&format!(
                        "{}: {},",
                        field.name,
                        self.type_expr_to_rust(&field.ty)
                    ));
                }
                self.indent -= 1;
                self.line("},");
            }
        }
        self.indent -= 1;
        self.line("}");
    }

    fn emit_effect_trait(&mut self, machine: &MachineDecl, generic_decl: &str) {
        let trait_name = format!("{}Effects", machine.name);
        self.line(&format!("pub trait {trait_name}{generic_decl} {{"));
        self.indent += 1;

        for effect in &machine.effects {
            let params: Vec<String> = effect
                .params
                .iter()
                .map(|p| {
                    if is_copy_type(&p.ty) {
                        format!("{}: {}", p.name, self.type_expr_to_rust(&p.ty))
                    } else {
                        format!("{}: &{}", p.name, self.type_expr_to_rust_ref(&p.ty))
                    }
                })
                .collect();
            let return_type = self.type_expr_to_rust(&effect.return_type);
            let all_params = if params.is_empty() {
                "&self".to_string()
            } else {
                format!("&self, {}", params.join(", "))
            };
            let ret_annotation = if matches!(effect.return_type, TypeExpr::Unit) {
                String::new()
            } else {
                format!(" -> {return_type}")
            };
            // Mark actions in rustdoc so downstream tooling and humans can
            // tell them apart without re-parsing the .gu source. See #40.
            if matches!(effect.kind, EffectKind::Action) {
                self.line("/// action — not replay-safe / externally visible (#40).");
            }
            self.line(&format!(
                "{}fn {}({}){};",
                if effect.is_async { "async " } else { "" },
                effect.name,
                all_params,
                ret_annotation
            ));
        }

        self.indent -= 1;
        self.line("}");
    }

    fn emit_constructor(&mut self, _machine_name: &str, state_enum: &str, first_state: &StateDecl) {
        if first_state.fields.is_empty() {
            self.line("pub fn new() -> Self {");
            self.indent += 1;
            self.line(&format!(
                "Self {{ state: {state_enum}::{} }}",
                first_state.name
            ));
            self.indent -= 1;
            self.line("}");
        } else {
            // Constructor takes initial state fields as params
            let params: Vec<String> = first_state
                .fields
                .iter()
                .map(|f| format!("{}: {}", f.name, self.type_expr_to_rust(&f.ty)))
                .collect();
            self.line(&format!("pub fn new({}) -> Self {{", params.join(", ")));
            self.indent += 1;
            let field_names: Vec<&str> =
                first_state.fields.iter().map(|f| f.name.as_str()).collect();
            self.line(&format!(
                "Self {{ state: {state_enum}::{} {{ {} }} }}",
                first_state.name,
                field_names.join(", ")
            ));
            self.indent -= 1;
            self.line("}");
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn emit_transition_method(
        &mut self,
        machine_name: &str,
        state_enum: &str,
        transition: &TransitionDecl,
        handlers: &[OnHandler],
        states: &[StateDecl],
        effects: &[EffectDecl],
        channels: &[ChannelDecl],
        generic_use: &str,
    ) {
        let error_type = format!("{machine_name}Error");

        // Find matching handler for this transition
        let handler = handlers
            .iter()
            .find(|h| h.transition_name == transition.name);

        let ctx_param_name = handler.and_then(|h| detect_ctx_param(h, &self.known_types));

        // Collect handler params, filtering out the ctx param
        let extra_params = handler
            .map(|h| {
                h.params
                    .iter()
                    .filter(|p| ctx_param_name.as_ref() != Some(&p.name))
                    .map(|p| format!("{}: {}", p.name, self.type_expr_to_rust(&p.ty)))
                    .collect::<Vec<_>>()
                    .join(", ")
            })
            .unwrap_or_default();

        // Add effects parameter only if this handler uses perform
        let uses_effects = handler
            .map(|h| handler_uses_perform(&h.body))
            .unwrap_or(false);
        let uses_spawn = handler
            .map(|h| handler_uses_spawn(&h.body))
            .unwrap_or(false);
        let used_channels = handler
            .map(|h| handler_used_channels(&h.body))
            .unwrap_or_default();
        let mut params = vec!["&mut self".to_string()];
        if !extra_params.is_empty() {
            params.push(extra_params);
        }
        if uses_effects && !effects.is_empty() {
            params.push(format!(
                "effects: &impl {}Effects{}",
                machine_name, generic_use
            ));
        }
        if uses_spawn {
            params.push("supervisor: &gust_runtime::prelude::SupervisorRuntime".to_string());
        }
        for channel_name in used_channels {
            if let Some(channel) = channels.iter().find(|c| c.name == channel_name) {
                let ty = self.type_expr_to_rust(&channel.message_type);
                let param = match channel.mode {
                    ChannelMode::Broadcast => format!(
                        "{}_tx: &tokio::sync::broadcast::Sender<{ty}>",
                        to_snake_case(&channel.name)
                    ),
                    ChannelMode::Mpsc => format!(
                        "{}_tx: &tokio::sync::mpsc::Sender<{ty}>",
                        to_snake_case(&channel.name)
                    ),
                };
                params.push(param);
            }
        }
        let params_str = params.join(", ");

        self.line(&format!(
            "pub {}fn {}({params_str}) -> Result<(), {error_type}> {{",
            if handler.map(|h| h.is_async).unwrap_or(false) || transition.timeout.is_some() {
                "async "
            } else {
                ""
            },
            transition.name
        ));
        self.indent += 1;

        // Match on current state — only allow transition from the declared `from` state
        self.line("match self.state.clone() {");
        self.indent += 1;

        // Look up the from-state to see if it has fields
        let from_state = states.iter().find(|s| s.name == transition.from);

        // Collect idents referenced by the handler body to prefix unused fields with _
        let referenced_idents = handler
            .map(|h| collect_referenced_idents(&h.body, ctx_param_name.as_deref()))
            .unwrap_or_default();

        // Valid from-state arm with destructuring
        if let Some(state) = from_state {
            if state.fields.is_empty() {
                self.line(&format!("{state_enum}::{} => {{", transition.from));
            } else {
                let field_names: Vec<String> = state
                    .fields
                    .iter()
                    .map(|f| {
                        if referenced_idents.contains(&f.name) {
                            f.name.clone()
                        } else {
                            // Use `field: _field` so the pattern matches the actual field name
                            // while suppressing the unused-variable warning on the binding.
                            format!("{}: _{}", f.name, f.name)
                        }
                    })
                    .collect();
                self.line(&format!(
                    "{state_enum}::{} {{ {} }} => {{",
                    transition.from,
                    field_names.join(", ")
                ));
            }
        } else {
            // Fallback if state not found (shouldn't happen in valid AST)
            self.line(&format!("{state_enum}::{} => {{", transition.from));
        }

        self.indent += 1;

        // Emit tracing instrumentation for state transitions
        if self.tracing {
            let targets_str = transition.targets.join(" | ");
            self.line("#[cfg(feature = \"tracing\")]");
            self.line(&format!(
                "let __tracing_span = tracing::info_span!(\"{}\", machine = \"{}\", from = \"{}\", to = \"{}\");",
                transition.name, machine_name, transition.from, targets_str
            ));
            self.line("#[cfg(feature = \"tracing\")]");
            self.line("let __tracing_guard = __tracing_span.enter();");
            self.line("#[cfg(feature = \"tracing\")]");
            self.line(&format!(
                "tracing::info!(machine = \"{}\", transition = \"{}\", from = \"{}\", to = \"{}\", \"state transition\");",
                machine_name, transition.name, transition.from, targets_str
            ));
        }

        // Set ctx rewriting state for handler body emission
        if let Some(ref ctx_name) = ctx_param_name {
            self.ctx_param = Some(ctx_name.clone());
            if let Some(state) = from_state {
                self.from_state_fields = state.fields.iter().map(|f| f.name.clone()).collect();
            }
        }

        // Emit handler body if present
        if let Some(handler) = handler {
            if let Some(timeout) = transition.timeout {
                self.line("let __transition_result = tokio::time::timeout(");
                self.indent += 1;
                self.line(&format!("{},", self.duration_to_rust(timeout)));
                self.line("async {");
                self.indent += 1;
                self.emit_block(&handler.body, state_enum, states, effects, channels);
                self.line(&format!("Ok::<(), {error_type}>(())"));
                self.indent -= 1;
                self.line("},");
                self.indent -= 1;
                self.line(").await;");
                self.line("match __transition_result {");
                self.indent += 1;
                self.line("Ok(Ok(())) => {},");
                self.line("Ok(Err(err)) => return Err(err),");
                self.line("Err(_) => {");
                self.indent += 1;
                self.line(&format!("return Err({error_type}::Failed {{"));
                self.indent += 1;
                self.line(&format!(
                    "reason: format!(\"transition '{}' timed out after {{:?}}\", {}),",
                    transition.name,
                    self.duration_to_rust(timeout)
                ));
                self.indent -= 1;
                self.line("});");
                self.indent -= 1;
                self.line("},");
                self.indent -= 1;
                self.line("}");
            } else {
                self.emit_block(&handler.body, state_enum, states, effects, channels);
            }
        } else {
            // Default: transition to first target if no handler
            if let Some(first_target) = transition.targets.first() {
                // Look up target state to see if it needs field initialization
                let target_state = states.iter().find(|s| &s.name == first_target);
                if let Some(target) = target_state {
                    if target.fields.is_empty() {
                        self.line(&format!("self.state = {state_enum}::{first_target};"));
                    } else {
                        // Can't auto-initialize fields without arguments
                        self.line(&format!(
                            "// Cannot auto-transition to {first_target} - requires fields"
                        ));
                    }
                }
            }
        }

        // Clear ctx rewriting state
        self.ctx_param = None;
        self.from_state_fields.clear();

        self.line("Ok(())");
        self.indent -= 1;
        self.line("}");

        // All other states — invalid transition
        self.line(&format!("_ => Err({error_type}::InvalidTransition {{",));
        self.indent += 1;
        self.line(&format!("transition: \"{}\".to_string(),", transition.name));
        self.line("from: format!(\"{:?}\", self.state),");
        self.indent -= 1;
        self.line("}),");

        self.indent -= 1;
        self.line("}");

        self.indent -= 1;
        self.line("}");
    }

    // === Statement & Expression Codegen ===

    fn emit_block(
        &mut self,
        block: &Block,
        state_enum: &str,
        states: &[StateDecl],
        effects: &[EffectDecl],
        channels: &[ChannelDecl],
    ) {
        for stmt in &block.statements {
            self.emit_statement(stmt, state_enum, states, effects, channels);
        }
    }

    fn emit_statement(
        &mut self,
        stmt: &Statement,
        state_enum: &str,
        states: &[StateDecl],
        effects: &[EffectDecl],
        channels: &[ChannelDecl],
    ) {
        let async_effects: HashSet<&str> = effects
            .iter()
            .filter(|e| e.is_async)
            .map(|e| e.name.as_str())
            .collect();
        match stmt {
            Statement::Let { name, ty, value } => {
                // Emit tracing event for perform expressions used as let values
                if self.tracing {
                    if let Expr::Perform(effect_name, _, _) = value {
                        self.line("#[cfg(feature = \"tracing\")]");
                        self.line(&format!(
                            "tracing::info!(effect = \"{effect_name}\", \"effect invocation\");",
                        ));
                    }
                }
                if let Some(type_expr) = ty {
                    self.line(&format!(
                        "let {}: {} = {};",
                        name,
                        self.type_expr_to_rust(type_expr),
                        self.expr_to_rust(value, &async_effects)
                    ));
                } else {
                    self.line(&format!(
                        "let {} = {};",
                        name,
                        self.expr_to_rust(value, &async_effects)
                    ));
                }
            }
            Statement::Return(expr) => {
                self.line(&format!(
                    "return {};",
                    self.expr_to_rust(expr, &async_effects)
                ));
            }
            Statement::If {
                condition,
                then_block,
                else_block,
                span: _,
            } => {
                let cond = self.expr_to_rust(condition, &async_effects);
                let cond = if cond.starts_with('(') && cond.ends_with(')') {
                    &cond[1..cond.len() - 1]
                } else {
                    &cond
                };
                self.line(&format!("if {cond} {{"));
                self.indent += 1;
                self.emit_block(then_block, state_enum, states, effects, channels);
                self.indent -= 1;
                if let Some(else_blk) = else_block {
                    self.line("} else {");
                    self.indent += 1;
                    self.emit_block(else_blk, state_enum, states, effects, channels);
                    self.indent -= 1;
                }
                self.line("}");
            }
            Statement::Goto { state, args, .. } => {
                // Look up target state to determine if it has fields
                let target_state = states.iter().find(|s| &s.name == state);
                if let Some(target) = target_state {
                    if target.fields.is_empty() {
                        self.line(&format!("self.state = {state_enum}::{state};"));
                    } else {
                        // Map args to fields in order
                        let field_inits: Vec<String> = target
                            .fields
                            .iter()
                            .zip(args.iter())
                            .map(|(field, arg)| {
                                format!(
                                    "{}: {}",
                                    field.name,
                                    self.expr_to_rust(arg, &async_effects)
                                )
                            })
                            .collect();
                        self.line(&format!(
                            "self.state = {state_enum}::{state} {{ {} }};",
                            field_inits.join(", ")
                        ));
                    }
                } else {
                    // Fallback if state not found
                    self.line(&format!("self.state = {state_enum}::{state};"));
                }
            }
            Statement::Perform { effect, args, .. } => {
                if self.tracing {
                    self.line("#[cfg(feature = \"tracing\")]");
                    self.line(&format!(
                        "tracing::info!(effect = \"{effect}\", \"effect invocation\");",
                    ));
                }
                let effect_decl = effects.iter().find(|e| e.name == *effect);
                let arg_strs: Vec<String> = args
                    .iter()
                    .enumerate()
                    .map(|(i, a)| {
                        let is_copy = effect_decl
                            .and_then(|e| e.params.get(i))
                            .map(|p| is_copy_type(&p.ty))
                            .unwrap_or(false);
                        if is_copy {
                            self.expr_to_rust(a, &async_effects)
                        } else {
                            format!("&{}", self.expr_to_rust(a, &async_effects))
                        }
                    })
                    .collect();
                if async_effects.contains(effect.as_str()) {
                    self.line(&format!(
                        "effects.{}({}).await;",
                        effect,
                        arg_strs.join(", ")
                    ));
                } else {
                    self.line(&format!("effects.{}({});", effect, arg_strs.join(", ")));
                }
            }
            Statement::Send {
                channel, message, ..
            } => {
                let msg = self.expr_to_rust(message, &async_effects);
                let tx_name = format!("{}_tx", to_snake_case(channel));
                let mode = channels
                    .iter()
                    .find(|c| c.name == *channel)
                    .map(|c| c.mode)
                    .unwrap_or(ChannelMode::Broadcast);
                match mode {
                    ChannelMode::Broadcast => {
                        self.line(&format!("let _ = {tx_name}.send({msg});"));
                    }
                    ChannelMode::Mpsc => {
                        self.line(&format!("let _ = {tx_name}.try_send({msg});"));
                    }
                }
            }
            Statement::Spawn { machine, args, .. } => {
                let arg_strs: Vec<String> = args
                    .iter()
                    .map(|a| self.expr_to_rust(a, &async_effects))
                    .collect();
                self.line(&format!(
                    "let _child = supervisor.spawn_named(\"{}\", async move {{ let _ = ({}); Ok::<(), String>(()) }});",
                    machine,
                    arg_strs.join(", ")
                ));
            }
            Statement::Match { scrutinee, arms } => {
                self.line(&format!(
                    "match {} {{",
                    self.expr_to_rust(scrutinee, &async_effects)
                ));
                self.indent += 1;
                for arm in arms {
                    self.line(&format!("{} => {{", self.pattern_to_rust(&arm.pattern)));
                    self.indent += 1;
                    self.emit_block(&arm.body, state_enum, states, effects, channels);
                    self.indent -= 1;
                    self.line("}");
                }
                self.indent -= 1;
                self.line("}");
            }
            Statement::Expr(expr) => {
                self.line(&format!("{};", self.expr_to_rust(expr, &async_effects)));
            }
        }
    }

    fn expr_to_rust(&self, expr: &Expr, async_effects: &HashSet<&str>) -> String {
        match expr {
            Expr::IntLit(v) => format!("{v}"),
            Expr::FloatLit(v) => format!("{v}"),
            Expr::StringLit(s) => format!("\"{}\".to_string()", escape_string_literal(s)),
            Expr::BoolLit(b) => if *b { "true" } else { "false" }.to_string(),
            Expr::Ident(name) => name.clone(),
            Expr::FieldAccess(base, field) => {
                if let Expr::Ident(name) = base.as_ref() {
                    if self.ctx_param.as_deref() == Some(name.as_str()) {
                        // ctx.field → field (single-level rewrite)
                        return field.clone();
                    }
                }
                // Recurse into base — handles ctx.config.service_name
                // by first rewriting ctx.config → config, then emitting config.service_name
                let base_str = self.expr_to_rust(base, async_effects);
                format!("{base_str}.{field}")
            }
            Expr::FnCall(name, args) => {
                let arg_strs: Vec<String> = args
                    .iter()
                    .map(|a| self.expr_to_rust(a, async_effects))
                    .collect();
                format!("{}({})", name, arg_strs.join(", "))
            }
            Expr::BinOp(left, op, right, _) => {
                format!(
                    "({} {} {})",
                    self.expr_to_rust(left, async_effects),
                    self.binop_to_rust(op),
                    self.expr_to_rust(right, async_effects)
                )
            }
            Expr::UnaryOp(op, expr) => {
                format!(
                    "({}{})",
                    self.unaryop_to_rust(op),
                    self.expr_to_rust(expr, async_effects)
                )
            }
            Expr::Perform(effect, args, _) => {
                let effect_decl = self.current_effects.iter().find(|e| e.name == *effect);
                let arg_strs: Vec<String> = args
                    .iter()
                    .enumerate()
                    .map(|(i, a)| {
                        let is_copy = effect_decl
                            .and_then(|e| e.params.get(i))
                            .map(|p| is_copy_type(&p.ty))
                            .unwrap_or(false);
                        if is_copy {
                            self.expr_to_rust(a, async_effects)
                        } else {
                            format!("&{}", self.expr_to_rust(a, async_effects))
                        }
                    })
                    .collect();
                if async_effects.contains(effect.as_str()) {
                    format!("effects.{}({}).await", effect, arg_strs.join(", "))
                } else {
                    format!("effects.{}({})", effect, arg_strs.join(", "))
                }
            }
            Expr::Path(enum_name, variant) => format!("{enum_name}::{variant}"),
        }
    }

    fn binop_to_rust(&self, op: &BinOp) -> &'static str {
        match op {
            BinOp::Add => "+",
            BinOp::Sub => "-",
            BinOp::Mul => "*",
            BinOp::Div => "/",
            BinOp::Mod => "%",
            BinOp::Eq => "==",
            BinOp::Neq => "!=",
            BinOp::Lt => "<",
            BinOp::Lte => "<=",
            BinOp::Gt => ">",
            BinOp::Gte => ">=",
            BinOp::And => "&&",
            BinOp::Or => "||",
        }
    }

    fn unaryop_to_rust(&self, op: &UnaryOp) -> &'static str {
        match op {
            UnaryOp::Not => "!",
            UnaryOp::Neg => "-",
        }
    }

    // === Helpers ===

    fn type_expr_to_rust(&self, ty: &TypeExpr) -> String {
        match ty {
            TypeExpr::Unit => "()".to_string(),
            TypeExpr::Simple(name) => map_type_name(name),
            TypeExpr::Generic(name, args) => {
                let mapped = map_type_name(name);
                let arg_strs: Vec<String> =
                    args.iter().map(|a| self.type_expr_to_rust(a)).collect();
                format!("{mapped}<{}>", arg_strs.join(", "))
            }
            TypeExpr::Tuple(types) => {
                let members = types
                    .iter()
                    .map(|t| self.type_expr_to_rust(t))
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("({members})")
            }
        }
    }

    /// Like type_expr_to_rust but for use behind a reference (`&`).
    /// Maps `String` → `str` and `Vec<T>` → `[T]` for idiomatic Rust references.
    fn type_expr_to_rust_ref(&self, ty: &TypeExpr) -> String {
        match ty {
            TypeExpr::Unit => "()".to_string(),
            TypeExpr::Simple(name) if name == "String" => "str".to_string(),
            TypeExpr::Generic(name, args) if name == "Vec" => {
                let inner = args
                    .first()
                    .map(|a| self.type_expr_to_rust(a))
                    .unwrap_or_else(|| "_".to_string());
                format!("[{inner}]")
            }
            _ => self.type_expr_to_rust(ty),
        }
    }

    fn pattern_to_rust(&self, pattern: &Pattern) -> String {
        match pattern {
            Pattern::Wildcard => "_".to_string(),
            Pattern::Ident(name) => name.clone(),
            Pattern::Variant {
                enum_name,
                variant,
                bindings,
            } => {
                let full = if let Some(name) = enum_name {
                    format!("{name}::{variant}")
                } else {
                    variant.clone()
                };
                if bindings.is_empty() {
                    full
                } else {
                    format!("{full}({})", bindings.join(", "))
                }
            }
        }
    }

    fn line(&mut self, text: &str) {
        let indent = "    ".repeat(self.indent);
        self.output.push_str(&indent);
        self.output.push_str(text);
        self.output.push('\n');
    }

    fn newline(&mut self) {
        self.output.push('\n');
    }

    fn duration_to_rust(&self, duration: DurationSpec) -> String {
        match duration.unit {
            TimeUnit::Millis => format!("tokio::time::Duration::from_millis({})", duration.value),
            TimeUnit::Seconds => format!("tokio::time::Duration::from_secs({})", duration.value),
            TimeUnit::Minutes => {
                format!("tokio::time::Duration::from_secs({} * 60)", duration.value)
            }
            TimeUnit::Hours => format!(
                "tokio::time::Duration::from_secs({} * 60 * 60)",
                duration.value
            ),
        }
    }

    fn emit_channel_helpers(&mut self, machine: &MachineDecl, channels: &[ChannelDecl]) {
        for channel_name in &machine.sends {
            if let Some(channel) = channels.iter().find(|c| c.name == *channel_name) {
                let ty = self.type_expr_to_rust(&channel.message_type);
                let method = format!("send_{}", to_snake_case(channel_name));
                let tx_ty = match channel.mode {
                    ChannelMode::Broadcast => {
                        format!("tokio::sync::broadcast::Sender<{ty}>")
                    }
                    ChannelMode::Mpsc => {
                        format!("tokio::sync::mpsc::Sender<{ty}>")
                    }
                };
                self.line(&format!(
                    "pub fn {method}(&self, msg: {ty}, {}_tx: &{tx_ty}) {{",
                    to_snake_case(channel_name)
                ));
                self.indent += 1;
                match channel.mode {
                    ChannelMode::Broadcast => {
                        self.line(&format!(
                            "let _ = {}_tx.send(msg);",
                            to_snake_case(channel_name)
                        ));
                    }
                    ChannelMode::Mpsc => {
                        self.line(&format!(
                            "let _ = {}_tx.try_send(msg);",
                            to_snake_case(channel_name)
                        ));
                    }
                }
                self.indent -= 1;
                self.line("}");
            }
        }
    }
}

impl Default for RustCodegen {
    fn default() -> Self {
        Self::new()
    }
}

/// Whether a type is Copy in Rust and should be passed by value, not reference.
fn is_copy_type(ty: &TypeExpr) -> bool {
    matches!(ty, TypeExpr::Unit)
        || matches!(ty, TypeExpr::Simple(name) if matches!(name.as_str(),
            "i64" | "i32" | "u64" | "u32" | "f64" | "f32" | "bool"
        ))
}

/// Map Gust type names to Rust type names.
/// Gust type names are identical to Rust type names, so this is a passthrough.
fn map_type_name(name: &str) -> String {
    name.to_string()
}

fn rust_generic_decl(params: &[GenericParam]) -> String {
    if params.is_empty() {
        return String::new();
    }
    let joined = params
        .iter()
        .map(|p| {
            if p.bounds.is_empty() {
                p.name.clone()
            } else {
                format!("{}: {}", p.name, p.bounds.join(" + "))
            }
        })
        .collect::<Vec<_>>()
        .join(", ");
    format!("<{joined}>")
}

fn rust_generic_use(params: &[GenericParam]) -> String {
    if params.is_empty() {
        return String::new();
    }
    let joined = params
        .iter()
        .map(|p| p.name.as_str())
        .collect::<Vec<_>>()
        .join(", ");
    format!("<{joined}>")
}
