// Generated by alef. Do not edit by hand — this file is the codegen for codegen.
use crate::codegen::generators::type_paths::build_type_path_lookup;
use crate::codegen::shared::binding_fields;
use crate::core::backend::GeneratedFile;
use crate::core::config::{AdapterConfig, AdapterPattern, ResolvedCrateConfig, resolve_output_dir};
use crate::core::ir::{ApiSurface, CoreWrapper, EnumDef, FieldDef, MethodDef, ReceiverKind, TypeDef, TypeRef};
use std::collections::HashSet;

mod bridge_fn;
mod cargo;
mod conversions;
mod helpers;
mod mirror;
mod trait_bridge;
mod trait_types;

use bridge_fn::emit_bridge_fn;
use cargo::{emit_build_rs, emit_cargo_toml, emit_frb_yaml};
use mirror::{emit_mirror_enum, emit_mirror_error, emit_mirror_struct};
use trait_bridge::emit_trait_bridge;

/// Emit the Rust-side flutter_rust_bridge bridge crate for the given API surface.
///
/// Returns four files that together form the `packages/dart/rust/` crate:
/// - `packages/dart/rust/Cargo.toml`
/// - `packages/dart/rust/src/lib.rs`
/// - `packages/dart/rust/build.rs`
/// - `packages/dart/rust/flutter_rust_bridge.yaml`
pub fn emit(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
    let rust_dir = resolve_output_dir(None, &config.name, "packages/dart/rust");
    let module_name = dart_module_name(&config.name);
    let source_crate_name = config.name.replace('-', "_");

    let exclude_functions: std::collections::HashSet<String> = config
        .dart
        .as_ref()
        .map(|c| c.exclude_functions.iter().cloned().collect())
        .unwrap_or_default();
    let exclude_types: std::collections::HashSet<String> = config
        .dart
        .as_ref()
        .map(|c| c.exclude_types.iter().cloned().collect())
        .unwrap_or_default();
    let stub_methods: Vec<String> = config.dart.as_ref().map(|c| c.stub_methods.clone()).unwrap_or_default();

    Ok(vec![
        emit_cargo_toml(&rust_dir, api, config, &source_crate_name),
        emit_lib_rs(
            &rust_dir,
            api,
            config,
            &source_crate_name,
            &exclude_functions,
            &exclude_types,
            &stub_methods,
        ),
        emit_build_rs(&rust_dir, &config.dart_pubspec_name(), &module_name, &source_crate_name),
        emit_frb_yaml(&rust_dir, &module_name),
    ])
}

fn build_type_path_lookup_for_source(
    api: &ApiSurface,
    source_crate_name: &str,
) -> std::collections::HashMap<String, String> {
    let _ = source_crate_name;
    build_type_path_lookup(api)
}

fn emit_lib_rs(
    rust_dir: &str,
    api: &ApiSurface,
    config: &ResolvedCrateConfig,
    source_crate_name: &str,
    exclude_functions: &std::collections::HashSet<String>,
    exclude_types: &std::collections::HashSet<String>,
    stub_methods: &[String],
) -> GeneratedFile {
    let mut content = String::new();
    // batch_extract_bytes* functions use unimplemented!() for the items argument,
    // making the `config` parameter appear unreachable to rustc. Suppress the
    // resulting unused-variable and unreachable-code noise crate-wide; all other
    // functions use every parameter they declare and have reachable code paths.
    // Inner crate-level attributes must appear before any items (including `mod frb_generated`).
    // We also declare `mod frb_generated` explicitly so FRB codegen doesn't prepend it
    // BEFORE the #![allow] attrs (which would make those attrs invalid per E0753).
    content.push_str("// Generated by alef. Do not edit by hand.\n");
    content.push_str("#![allow(unused_variables, unreachable_code)]\n");
    content.push_str("#![allow(\n");
    content.push_str("    clippy::map_identity,\n");
    content.push_str("    clippy::let_and_return,\n");
    content.push_str("    clippy::collapsible_match,\n");
    content.push_str("    clippy::manual_flatten,\n");
    content.push_str("    clippy::too_many_arguments,\n");
    content.push_str("    clippy::unit_arg,\n");
    content.push_str("    clippy::type_complexity,\n");
    // `From<Box<str>> for String` and `From<String> for Box<str>` are needed when
    // core uses `HashMap<Box<str>, _>` and the mirror uses `HashMap<String, _>` (or
    // vice versa). When both sides are plain `String`, the emitted `(k.into(),
    // v.into())` collapses to identity — silenced here so the same codegen path
    // works for both wrapped and unwrapped string keys/values.
    content.push_str("    clippy::useless_conversion,\n");
    content.push_str(")]\n");
    // Declare frb_generated after the crate-level attrs so FRB doesn't inject it at line 1.
    content.push_str("mod frb_generated;\n");
    content.push_str("use flutter_rust_bridge::frb;\n");
    // DartFnFuture is re-exported so frb_generated.rs (which does `use crate::*`)
    // can reference it by bare name in the generated closure types.
    content.push_str("pub use flutter_rust_bridge::DartFnFuture;\n");

    // FRB strips module paths from `frb_generated.rs` when it serializes closure
    // signatures, leaving bare type names that resolve against `use crate::*`.
    // Re-export every excluded type referenced by trait-bridge method signatures so
    // those bare names resolve. Without this, types like `InternalDocument` show up
    // as E0425 in the generated bridge file.
    {
        use crate::core::ir::TypeRef;
        fn collect_named(ty: &TypeRef, out: &mut std::collections::BTreeSet<String>) {
            match ty {
                TypeRef::Named(n) => {
                    out.insert(n.clone());
                }
                TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named(inner, out),
                TypeRef::Map(k, v) => {
                    collect_named(k, out);
                    collect_named(v, out);
                }
                _ => {}
            }
        }
        let mut referenced: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
        for ty in &api.types {
            if !ty.is_trait {
                continue;
            }
            // Match the filter applied by `emit_trait_bridge`: skip methods inherited
            // from super-traits, AND skip methods whose return type references another
            // trait (these are emitted as `Option<&dyn Trait>` in the source IR and the
            // bridge cannot dispatch them — see `return_type_references_trait`).
            // Default-impl methods that DO participate in the bridge still need their
            // referenced types re-exported.
            for method in &ty.methods {
                if method.trait_source.is_some() || trait_bridge::return_type_references_trait(&method.return_type, api)
                {
                    continue;
                }
                for p in &method.params {
                    collect_named(&p.ty, &mut referenced);
                }
                collect_named(&method.return_type, &mut referenced);
            }
        }
        // D2: pre-compute the set of opaque type names that will be emitted as
        // `#[frb(opaque)] pub struct {Name}` by `emit_mirror_struct`. A `pub use` re-export
        // of the same short name would cause E0255 "defined multiple times".
        let opaque_struct_names_in_scope: std::collections::HashSet<&str> = api
            .types
            .iter()
            .filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name))
            .map(|t| t.name.as_str())
            .collect();

        let mut emitted: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
        for name in &referenced {
            // D2: skip names that are already emitted as an opaque wrapper struct; a `pub use`
            // for the same short name would redefine the identifier and cause E0255.
            if opaque_struct_names_in_scope.contains(name.as_str()) {
                continue;
            }
            if let Some(path) = api.excluded_type_paths.get(name) {
                if path.is_empty() || emitted.contains(path) {
                    continue;
                }
                content.push_str("#[allow(unused_imports)]\n");
                content.push_str(&format!("pub use {};\n", path.replace('-', "_")));
                emitted.insert(path.clone());
            }
        }
    }

    // Compute the set of types that have DIRECT sanitized fields (or Duration/Path fields that
    // cause layout mismatches) BEFORE emitting impl blocks so opaque method bodies can use
    // `From` conversion instead of `transmute` for these types.
    // Also include types with Duration or Path fields: the FRB mirror maps Duration→i64 (8B vs 16B)
    // and Path→String, creating layout mismatches that make transmute unsound for these types.
    // Both structs and enums are checked — sanitized enum variant fields (e.g.
    // `Scroll { selector: Option<String> }` sanitized to `Scroll { selector: String }`)
    // change the enum's discriminant payload size and make Vec/single transmute UB.
    let types_with_direct_sanitized_fields: HashSet<String> = api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
        .filter(|t| {
            t.fields
                .iter()
                .any(|f| f.sanitized || has_duration_or_path_field(&f.ty))
        })
        .map(|t| t.name.clone())
        .chain(
            api.enums
                .iter()
                .filter(|e| !exclude_types.contains(&e.name))
                .filter(|e| {
                    e.variants.iter().any(|v| {
                        v.fields.iter().any(|f| {
                            // FRB mirror enum variants emit `frb_rust_type_inner(field.ty)`
                            // which strips Option wrappers — `Option<String>` becomes bare
                            // `String` in the mirror. That makes the mirror enum's variant
                            // payload smaller than core's, so transmute is UB. Treat any
                            // variant field that's optional in core (or otherwise layout-
                            // shifting like Duration/Path) as evidence the enum needs
                            // `From<Mirror> for Core` instead of a transmute.
                            f.sanitized || f.optional || has_duration_or_path_field(&f.ty)
                        })
                    })
                })
                .map(|e| e.name.clone()),
        )
        .collect();

    // Compute the transitive closure: types that DIRECTLY or INDIRECTLY contain a type
    // with sanitized fields. These cannot be safely transmuted because the inner types
    // have different memory layouts (e.g. BatchBytesItem contains FileExtractionConfig
    // which has Option<String> where core has Option<HtmlOutputConfig>).
    // Bridge functions use `From<MirrorT> for SourceT` for all of these.
    let types_needing_from_conversion: HashSet<String> =
        compute_types_containing_sanitized(api, &types_with_direct_sanitized_fields, exclude_types);

    for ty in api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait)
    {
        content.push('\n');
        emit_mirror_struct(&mut content, ty, source_crate_name);
    }

    // Build a lookup from "OwnerType.method_name" → AdapterConfig for streaming adapters.
    // This lets emit_opaque_impl_block emit StreamSink<T> methods instead of skipping them.
    let streaming_adapters: std::collections::HashMap<String, &AdapterConfig> = config
        .adapters
        .iter()
        .filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
        .filter_map(|a| {
            a.owner_type.as_deref().map(|owner| {
                let key = format!("{}.{}", owner, a.name);
                (key, a)
            })
        })
        .collect();

    // Pre-build the type-path lookup so impl-block emission can resolve Named types
    // referenced in method signatures and emit the corresponding `use` statements.
    let type_paths_for_impls = build_type_path_lookup_for_source(api, source_crate_name);

    // Build the set of mirror type names: non-opaque, non-trait types that receive a
    // `#[frb(mirror(TypeName))]` struct declaration above. These types are already in
    // scope under their short name; emitting a `use source_crate::TypeName;` for them
    // inside impl blocks would cause E0255 "defined multiple times" errors.
    let mirror_type_names: HashSet<String> = api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
        .map(|t| t.name.clone())
        .collect();

    // Collect opaque type names (is_opaque = true, not traits) — these use a wrapper struct
    // in the generated bridge crate and must be accessed via .inner, not transmuted.
    // Computed here (before emit_opaque_impl_block) so the impl bodies can use it for D3.
    let opaque_type_names: HashSet<String> = api
        .types
        .iter()
        .filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name))
        .map(|t| t.name.clone())
        .collect();

    // Emit impl blocks for opaque types that expose methods.
    // FRB generates Dart-side methods on opaque handles only when the bridge crate
    // contains `impl TypeName { #[frb] pub fn method(...) }` blocks. Without these
    // blocks FRB emits an empty `abstract class TypeName implements RustOpaqueInterface {}`
    // with no methods, causing method-not-found errors in Dart callers.
    for ty in api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && t.is_opaque && !t.methods.is_empty())
    {
        content.push('\n');
        emit_opaque_impl_block(
            &mut content,
            ty,
            source_crate_name,
            stub_methods,
            &types_needing_from_conversion,
            &opaque_type_names,
            &streaming_adapters,
            config,
            &type_paths_for_impls,
            &mirror_type_names,
        );
    }

    // Client constructors for opaque handles (emit a separate impl block with #[frb])
    for ty in api
        .types
        .iter()
        .filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name))
    {
        if let Some(ctor) = config.client_constructors.get(&ty.name) {
            let ctor_body =
                crate::codegen::generators::gen_opaque_constructor(ctor, &ty.name, source_crate_name, "#[frb]");
            content.push('\n');
            content.push_str(&format!("impl {} {{\n{}}}", ty.name, ctor_body));
            content.push('\n');
        }
    }

    for en in api.enums.iter().filter(|e| !exclude_types.contains(&e.name)) {
        content.push('\n');
        emit_mirror_enum(&mut content, en);
    }

    // Emit mirror enums for error types so flutter_rust_bridge generates a Dart sealed
    // class for each error. The `impl` block with `#[frb]` methods surfaces introspection
    // methods (e.g. `status_code`, `is_transient`, `error_type`) as Dart instance methods.
    for error in api.errors.iter().filter(|e| !e.binding_excluded) {
        content.push('\n');
        emit_mirror_error(&mut content, error, source_crate_name);
    }

    // Emit From<SourceT> for T conversions for all struct and enum types.
    // These are required because the bridge functions use local mirror types but the core
    // functions return source-crate types that may differ in layout (e.g. Cow vs String).
    // transmute cannot be used when sizes differ, so explicit field-by-field From impls
    // are generated instead.
    content.push_str("\n// From<SourceT> conversions for bridge return types.\n");
    for ty in api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
    {
        content.push('\n');
        emit_from_impl_for_struct(&mut content, ty, source_crate_name);
    }
    for en in api.enums.iter().filter(|e| !exclude_types.contains(&e.name)) {
        content.push('\n');
        emit_from_impl_for_enum(&mut content, en, source_crate_name);
    }

    // Collect only the types transitively containing sanitized fields that appear as
    // function input parameters. Output-only types (result structs) are excluded —
    // they never flow Dart→Rust and must not get From<Mirror> for Core impls.
    // Also include method parameters from opaque types (e.g. DefaultClient::chat takes
    // ChatCompletionRequest) — these methods also pass mirror types to core and need
    // From<Mirror> for Core impls to convert safely without transmute.
    let param_types_needing_from: HashSet<String> = api
        .functions
        .iter()
        .filter(|f| !exclude_functions.contains(&f.name) && !has_unbridgeable_param(f))
        .flat_map(|f| f.params.iter())
        .flat_map(|p| collect_named_types_from_type_ref(&p.ty))
        .chain(
            // Method parameters from opaque types that are not stub methods.
            api.types
                .iter()
                .filter(|t| t.is_opaque && !t.is_trait && !exclude_types.contains(&t.name))
                .flat_map(|t| t.methods.iter())
                .filter(|m| !m.sanitized)
                .flat_map(|m| m.params.iter())
                .filter(|p| !p.sanitized)
                .flat_map(|p| collect_named_types_from_type_ref(&p.ty)),
        )
        .filter(|name| types_needing_from_conversion.contains(name))
        // Trait-bridge method return types: when a trait is bridged (e.g. HtmlVisitor),
        // each Dart callback returns a mirror value, and the bridge impl converts it back
        // to the core type via `.into()`. Include those return types so a
        // `From<Mirror> for Core` impl is emitted. Chained AFTER the
        // `types_needing_from_conversion` filter because these return types are not
        // necessarily reachable via the sanitized-field transitive closure (e.g.
        // `VisitResult` is a simple enum with no sanitized fields, but the trait bridge
        // still needs a `From<Mirror> for Core` impl for it).
        .chain(
            config
                .trait_bridges
                .iter()
                .filter(|cfg| !cfg.exclude_languages.iter().any(|l| l == "dart"))
                .filter_map(|cfg| api.types.iter().find(|t| t.name == cfg.trait_name && t.is_trait))
                .flat_map(|trait_def| trait_def.methods.iter())
                .filter(|m| m.trait_source.is_none())
                .flat_map(|m| collect_named_types_from_type_ref(&m.return_type)),
        )
        .collect();

    // Compute the transitive closure of all struct/enum types reachable from function-
    // parameter types that need From conversion, via non-sanitized field references.
    let types_needing_from_impl = compute_types_needing_from_impl(api, &param_types_needing_from, exclude_types);

    // Emit From<T> for SourceT impls (mirror-to-core direction) for types in the
    // transitive closure. Only those types (not all types) get this impl, avoiding
    // Default::default() issues for output-only types that don't implement Default.
    content.push_str("\n// From<T> for SourceT conversions (mirror-to-core direction).\n");
    content.push_str("// Used in bridge functions for types with sanitized fields, and by\n");
    content.push_str("// nested conversions within those types.\n");
    // Filter out trait and opaque types: trait types are not constructible with `{}` literals,
    // and opaque types are handled separately by their own bridge impl block. Both can land in
    // `types_needing_from_impl` via trait-bridge return types (e.g. `Option<&dyn SyncExtractor>`
    // contributes `SyncExtractor` to the seed via `collect_named_types_from_type_ref`).
    for ty in api
        .types
        .iter()
        .filter(|t| types_needing_from_impl.contains(&t.name) && !t.is_trait && !t.is_opaque)
    {
        content.push('\n');
        emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
    }
    // Emit From<MirrorEnum> for SourceEnum so that enum-typed struct fields
    // can use `.into()` in the mirror-to-core From impls above.
    for en in api.enums.iter().filter(|e| types_needing_from_impl.contains(&e.name)) {
        content.push('\n');
        emit_from_mirror_to_core_enum(&mut content, en, source_crate_name);
    }

    // Streaming adapter method bodies use `let core_req: crate::T = req.into()` which
    // requires `From<LocalMirrorT> for crate::T`. Simple request types (e.g.
    // `CrawlStreamRequest { url: String }`) have no sanitized fields, so they never enter
    // `types_needing_from_impl` via the sanitized-field transitive closure above. Emit the
    // mirror-to-core `From` impl for any streaming adapter param type that is a declared
    // mirror type and has not already been covered above.
    {
        let streaming_param_mirror_types: Vec<&TypeDef> = config
            .adapters
            .iter()
            .filter(|a| matches!(a.pattern, AdapterPattern::Streaming))
            .flat_map(|a| a.params.iter())
            .map(|p| p.ty.as_str())
            .filter(|ty_name| mirror_type_names.contains(*ty_name))
            .filter(|ty_name| !types_needing_from_impl.contains(*ty_name))
            .collect::<std::collections::BTreeSet<_>>()
            .into_iter()
            .filter_map(|ty_name| api.types.iter().find(|t| t.name == ty_name))
            .collect();
        if !streaming_param_mirror_types.is_empty() {
            content.push_str("\n// From<T> for SourceT conversions for streaming-adapter mirror request types.\n");
            for ty in streaming_param_mirror_types {
                content.push('\n');
                emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
            }
        }
    }

    let type_paths = build_type_path_lookup_for_source(api, source_crate_name);
    // opaque_type_names already computed above (before emit_opaque_impl_block).

    for f in api
        .functions
        .iter()
        .filter(|f| !exclude_functions.contains(&f.name))
        // Skip functions with unbridgeable parameters UNLESS they are in stub_methods.
        // Stub methods emit `unimplemented!()` bodies and do not attempt argument conversion,
        // so their unbridgeable parameters are irrelevant.
        .filter(|f| !has_unbridgeable_param(f) || stub_methods.contains(&f.name))
        // Skip functions whose name matches a trait_bridge.clear_fn — the trait-bridge
        // emission path emits its own forwarder; a duplicate `pub fn clear_*` here
        // would either fail to compile or be silently de-duped by frb_codegen
        // (which logs noisy warnings on every regen).
        .filter(|f| {
            !crate::codegen::generators::trait_bridge::is_trait_bridge_managed_fn(&f.name, &config.trait_bridges)
        })
    {
        content.push('\n');
        emit_bridge_fn(
            &mut content,
            f,
            source_crate_name,
            &type_paths,
            &types_needing_from_conversion,
            &opaque_type_names,
            stub_methods,
        );
    }

    // Emit `create_<snake_name>_from_json` free functions for all non-opaque, non-trait
    // mirror struct types. These allow e2e tests (and other callers) to construct typed
    // request objects from a JSON string without manually filling every field — important
    // for FRB's named-parameter calling convention where passing a plain JSON map is not
    // possible. Each function deserializes via `serde_json::from_str` into the core type,
    // then converts to the local mirror type using the already-emitted `From<CoreT> for T`
    // impl. FRB generates the corresponding `BridgeClass.createTypeNameFromJson(json)` Dart
    // helper automatically from the `#[frb]`-annotated Rust function.
    content.push_str("\n// `create_<Type>_from_json` helpers — deserialize a JSON string into a mirror type.\n");
    for ty in api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
        // Only types that derive serde::Deserialize on the core side can be deserialized
        // from a JSON string. Internal types like `MergedChunk`, `ResolvedStyle`,
        // `CharShape`, etc. exist in the binding surface as From-converted mirrors but
        // aren't serde-Deserialize on the core side — emitting `serde_json::from_str::<T>`
        // for them produces an E0277 trait-bound error at compile time.
        .filter(|t| t.has_serde)
    {
        content.push('\n');
        emit_from_json_fn(&mut content, ty, source_crate_name);
    }

    // Emit FRB trait bridge wrappers for each configured [[trait_bridges]] entry.
    // The Dart-side wiring (implementing the abstract class in Dart) is post-FRB-codegen-runtime
    // work — only the Rust side is generated here. The generated Rust code uses
    // DartFnFuture<T> callbacks from flutter_rust_bridge for async-to-sync bridging.
    let dart_backend_name = "dart";
    for bridge_cfg in &config.trait_bridges {
        if bridge_cfg.exclude_languages.iter().any(|l| l == dart_backend_name) {
            continue;
        }
        if let Some(trait_def) = api.types.iter().find(|t| t.name == bridge_cfg.trait_name && t.is_trait) {
            content.push('\n');
            emit_trait_bridge(&mut content, trait_def, bridge_cfg, api, source_crate_name, &type_paths);
        }
    }

    GeneratedFile {
        path: std::path::PathBuf::from(format!("{rust_dir}/src/lib.rs")),
        content,
        generated_header: false,
    }
}

fn dart_module_name(crate_name: &str) -> String {
    crate_name.replace('-', "_")
}

/// Compute the transitive closure of all struct types that DIRECTLY OR INDIRECTLY
/// contain a type from `direct_sanitized` via their fields. Used to find all types
/// that cannot be safely transmuted (because an inner type has a layout mismatch).
fn compute_types_containing_sanitized(
    api: &ApiSurface,
    direct_sanitized: &HashSet<String>,
    exclude_types: &HashSet<String>,
) -> HashSet<String> {
    let struct_by_name: std::collections::HashMap<&str, &TypeDef> = api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
        .map(|t| (t.name.as_str(), t))
        .collect();
    let enum_by_name: std::collections::HashMap<&str, &EnumDef> = api
        .enums
        .iter()
        .filter(|e| !exclude_types.contains(&e.name))
        .map(|e| (e.name.as_str(), e))
        .collect();

    // Start with all directly-sanitized types and expand to any type that contains them.
    let mut result: HashSet<String> = direct_sanitized.clone();
    let mut changed = true;
    while changed {
        changed = false;
        for ty in struct_by_name.values() {
            if result.contains(&ty.name) {
                continue;
            }
            let references_sanitized = ty
                .fields
                .iter()
                .any(|f| collect_named_types(&f.ty).iter().any(|n| result.contains(n)));
            if references_sanitized {
                result.insert(ty.name.clone());
                changed = true;
            }
        }
        // Enums also "contain" their variant field types — a Vec<EnumE> bridge argument
        // is layout-incompatible if any variant references a type whose mirror layout
        // differs from core.
        for en in enum_by_name.values() {
            if result.contains(&en.name) {
                continue;
            }
            let references_sanitized = en.variants.iter().any(|v| {
                v.fields
                    .iter()
                    .any(|f| collect_named_types(&f.ty).iter().any(|n| result.contains(n)))
            });
            if references_sanitized {
                result.insert(en.name.clone());
                changed = true;
            }
        }
    }

    result
}

/// Compute the transitive closure of all struct/enum types reachable from
/// `seed_types` (types with sanitized fields) via non-sanitized field references.
///
/// These are the types that need `From<MirrorT> for SourceT` impls so that
/// `.into()` calls in the generated From impls for sanitized-field types work.
/// Output-only types (e.g. result structs with sanitized fields) are excluded
/// from the seed set — they're never passed as function inputs.
fn compute_types_needing_from_impl(
    api: &ApiSurface,
    seed_types: &HashSet<String>,
    exclude_types: &HashSet<String>,
) -> HashSet<String> {
    // Build lookup maps for quick access by name.
    let struct_by_name: std::collections::HashMap<&str, &TypeDef> = api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque)
        .map(|t| (t.name.as_str(), t))
        .collect();
    let enum_by_name: std::collections::HashMap<&str, &EnumDef> = api
        .enums
        .iter()
        .filter(|e| !exclude_types.contains(&e.name))
        .map(|e| (e.name.as_str(), e))
        .collect();

    let mut result: HashSet<String> = seed_types.clone();
    let mut worklist: Vec<String> = seed_types.iter().cloned().collect();

    while let Some(type_name) = worklist.pop() {
        if let Some(ty) = struct_by_name.get(type_name.as_str()) {
            for field in binding_fields(&ty.fields) {
                if field.sanitized {
                    continue; // sanitized fields are not converted via From
                }
                // Collect all Named type references from this field.
                for named in collect_named_types(&field.ty) {
                    if !result.contains(&named)
                        && (struct_by_name.contains_key(named.as_str()) || enum_by_name.contains_key(named.as_str()))
                    {
                        result.insert(named.clone());
                        worklist.push(named);
                    }
                }
            }
        } else if let Some(en) = enum_by_name.get(type_name.as_str()) {
            // Process enum variant field types: the generated From<MirrorEnum> for CoreEnum
            // uses `.into()` for each variant field, so all variant field types also need
            // From<MirrorT> for CoreT impls.
            for variant in &en.variants {
                for field in &variant.fields {
                    if field.sanitized {
                        continue;
                    }
                    for named in collect_named_types(&field.ty) {
                        if !result.contains(&named)
                            && (struct_by_name.contains_key(named.as_str())
                                || enum_by_name.contains_key(named.as_str()))
                        {
                            result.insert(named.clone());
                            worklist.push(named);
                        }
                    }
                }
            }
        }
    }

    result
}

/// Collect all Named type names referenced (possibly nested) in a TypeRef.
fn collect_named_types(ty: &TypeRef) -> Vec<String> {
    collect_named_types_from_type_ref(ty)
}

/// Collect all Named type names referenced (possibly nested) in a TypeRef.
fn collect_named_types_from_type_ref(ty: &TypeRef) -> Vec<String> {
    match ty {
        TypeRef::Named(name) => vec![name.clone()],
        TypeRef::Vec(inner) | TypeRef::Optional(inner) => collect_named_types_from_type_ref(inner),
        TypeRef::Map(k, v) => {
            let mut names = collect_named_types_from_type_ref(k);
            names.extend(collect_named_types_from_type_ref(v));
            names
        }
        _ => vec![],
    }
}

fn emit_rust_struct_field(out: &mut String, cfg: Option<&str>, field_name: &str, expr: &str) {
    out.push_str(&crate::backends::dart::template_env::render(
        "rust_struct_field_assignment.jinja",
        minijinja::context! {
            cfg => cfg,
            field_name => field_name,
            expr => expr,
        },
    ));
}

/// Emit a `From<SourceT> for T` implementation for a mirror struct.
///
/// Each field is converted using the appropriate strategy:
/// - `CoreWrapper::Cow` fields: `.into()` (Cow<'_, str> → String)
/// - `TypeRef::Json` fields: `serde_json::to_string(&v).unwrap_or_default()`
/// - `TypeRef::Named(n)` fields: `n::from(v.field)` (recursive)
/// - Other fields: `.into()` or direct copy
fn emit_from_impl_for_struct(out: &mut String, ty: &TypeDef, source_crate_name: &str) {
    let name = &ty.name;
    let core_ty = if ty.rust_path.is_empty() {
        format!("{source_crate_name}::{name}")
    } else {
        ty.rust_path.replace('-', "_")
    };

    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_core_struct_open.jinja",
        minijinja::context! {
            core_ty => core_ty.as_str(),
            name => name.as_str(),
        },
    ));

    for field in binding_fields(&ty.fields) {
        if field.sanitized {
            // Sanitized fields (unknown types mapped to String/i64) can't be auto-converted.
            // Use a best-effort fallback.
            let fallback = sanitized_field_from_expr(field);
            // `cfg = None`: the dart bridge crate enables `features = ["full"]` on
            // the source dependency, so every core-side cfg-gated field is present
            // at compile time. Emitting `#[cfg(...)]` here would gate on the dart
            // crate's own (undefined) features, evaluating to false and leaving the
            // struct literal missing fields.
            emit_rust_struct_field(out, None, &field.name, &fallback);
        } else {
            let expr = field_from_expr(field, source_crate_name);
            // `cfg = None`: the dart bridge crate enables `features = ["full"]` on
            // the source dependency, so every core-side cfg-gated field is present
            // at compile time. Emitting `#[cfg(...)]` here would gate on the dart
            // crate's own (undefined) features, evaluating to false and leaving the
            // struct literal missing fields.
            emit_rust_struct_field(out, None, &field.name, &expr);
        }
    }

    // Note: no ..Default::default() here — the mirror struct has exactly the fields
    // known to the IR. has_stripped_cfg_fields only affects the CORE struct, not the mirror.
    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_impl_close.jinja",
        minijinja::context! {},
    ));
}

/// Build the conversion expression for one struct field (core → mirror direction).
fn field_from_expr(field: &FieldDef, source_crate_name: &str) -> String {
    let name = &field.name;
    let _ = source_crate_name;
    match &field.ty {
        TypeRef::Json => {
            // Core has serde_json::Value or similar; mirror has String.
            if field.optional {
                format!("v.{name}.map(|j| serde_json::to_string(&j).unwrap_or_default())")
            } else {
                format!("serde_json::to_string(&v.{name}).unwrap_or_default()")
            }
        }
        TypeRef::String => {
            // The IR collapses `Cow<'_, str>` / `Box<str>` / `Arc<str>` into
            // `TypeRef::String` and only `Cow` is tracked on `core_wrapper`. Emit
            // `.into()` unconditionally so wrapped-string core fields convert
            // correctly (e.g. `Box<str> → String`); the crate-level
            // `#[allow(clippy::useless_conversion)]` absorbs the `String → String`
            // no-op case.
            if field.optional {
                format!("v.{name}.map(|s| s.into())")
            } else {
                format!("v.{name}.into()")
            }
        }
        TypeRef::Char => {
            // Core has char; mirror has String. Convert via to_string().
            if field.optional {
                format!("v.{name}.map(|c| c.to_string())")
            } else {
                format!("v.{name}.to_string()")
            }
        }
        TypeRef::Path => {
            // Core has PathBuf; mirror has String.
            // PathBuf does not implement Into<String>; use to_string_lossy().
            if field.optional {
                format!("v.{name}.map(|p| p.to_string_lossy().into_owned())")
            } else {
                format!("v.{name}.to_string_lossy().into_owned()")
            }
        }
        TypeRef::Bytes => {
            // bytes::Bytes or Vec<u8>; mirror uses Vec<u8>.
            match field.core_wrapper {
                CoreWrapper::Arc | CoreWrapper::ArcMutex => {
                    if field.optional {
                        format!("v.{name}.map(|a| (*a).clone().into())")
                    } else {
                        format!("(*v.{name}).clone().into()")
                    }
                }
                _ => {
                    if field.optional {
                        format!("v.{name}.map(|b| b.into())")
                    } else {
                        format!("v.{name}.into()")
                    }
                }
            }
        }
        TypeRef::Named(inner_name) => {
            // Handle Arc wrapper on named types.
            match field.core_wrapper {
                CoreWrapper::Arc | CoreWrapper::ArcMutex => {
                    // Core has Arc<T>; clone out of the Arc.
                    if field.optional {
                        format!("v.{name}.map(|a| {inner_name}::from((*a).clone()))")
                    } else {
                        format!("{inner_name}::from((*v.{name}).clone())")
                    }
                }
                _ => {
                    if field.optional && field.is_boxed {
                        format!("v.{name}.map(|b| {inner_name}::from(*b))")
                    } else if field.optional {
                        format!("v.{name}.map({inner_name}::from)")
                    } else if field.is_boxed {
                        format!("{inner_name}::from(*v.{name})")
                    } else {
                        format!("{inner_name}::from(v.{name})")
                    }
                }
            }
        }
        TypeRef::Vec(inner) => vec_inner_from_expr(
            inner,
            &field.vec_inner_core_wrapper,
            field.newtype_wrapper.as_deref(),
            name,
            field.optional,
        ),
        TypeRef::Optional(inner) => {
            // Nested-optional field. Core is `Option<Option<T>>` (the outer Option was
            // stripped into `field.optional`, leaving `field.ty = Optional(T)`). The
            // mirror flattens to a single `Option<T>` per `frb_rust_type`, so we must
            // flatten the core value before converting elements. When `!field.optional`
            // the existing direct map shape applies (no outer Option around v).
            let flatten = if field.optional { ".flatten()" } else { "" };
            match inner.as_ref() {
                TypeRef::Named(inner_name) => {
                    format!("v.{name}{flatten}.map({inner_name}::from)")
                }
                TypeRef::String => {
                    // The IR loses `Box<str>` / `Cow<'_, str>` / `Arc<str>` wrappers,
                    // so emit `.into()` to bridge them; absorbed by the crate-level
                    // `#[allow(clippy::useless_conversion)]` for plain `String`.
                    format!("v.{name}{flatten}.map(|s| s.into())")
                }
                TypeRef::Char => {
                    format!("v.{name}{flatten}.map(|s| s.into())")
                }
                TypeRef::Path => {
                    format!("v.{name}{flatten}.map(|p| p.to_string_lossy().into_owned())")
                }
                TypeRef::Primitive(_) => {
                    format!("v.{name}{flatten}.map(|x| x as _)")
                }
                _ => format!("v.{name}{flatten}"),
            }
        }
        TypeRef::Map(k, v_ty) => {
            // Maps: may need iter-collect to convert BTreeMap/AHashMap → HashMap,
            // and Value → String for value types.
            map_from_expr(name, k, v_ty, field.optional)
        }
        TypeRef::Duration => {
            // Duration: convert to i64 millis (FRB ABI). Duration is not a primitive
            // so `as _` casts do not compile; use `.as_millis() as i64` instead.
            if field.optional {
                format!("v.{name}.map(|d| d.as_millis() as i64)")
            } else {
                format!("v.{name}.as_millis() as i64")
            }
        }
        TypeRef::Primitive(_) | TypeRef::Unit => {
            // Primitives: alef widens to i64/f64/bool; core may use narrower types.
            // When newtype_wrapper is set, the core field is NewType(inner); unwrap with .0.
            if let Some(_nw) = &field.newtype_wrapper {
                // Newtype wrapper: unwrap .0 then cast.
                if field.optional {
                    format!("v.{name}.map(|x| x.0 as _)")
                } else {
                    format!("v.{name}.0 as _")
                }
            } else if field.optional {
                format!("v.{name}.map(|x| x as _)")
            } else {
                format!("v.{name} as _")
            }
        }
    }
}

/// Build the Vec field conversion expression (core → mirror).
fn vec_inner_from_expr(
    inner: &TypeRef,
    vec_inner_core_wrapper: &CoreWrapper,
    field_newtype_wrapper: Option<&str>,
    name: &str,
    optional: bool,
) -> String {
    let item_conv = match (inner, vec_inner_core_wrapper) {
        (TypeRef::Named(inner_name), CoreWrapper::Arc | CoreWrapper::ArcMutex) => {
            // Vec<Arc<T>> — clone out of Arc then convert.
            format!("|a| {inner_name}::from((*a).clone())")
        }
        (TypeRef::Named(inner_name), _) => {
            format!("{inner_name}::from")
        }
        (TypeRef::String, _) => {
            // The IR collapses wrapped string types (`Box<str>`, `Cow<'_, str>`,
            // `Arc<str>`) into `TypeRef::String`, and `CoreWrapper` only tracks `Cow`.
            // Emit `.into()` unconditionally so `Vec<Box<str>>` → `Vec<String>` (and
            // friends) compile — the crate-level `#[allow(clippy::useless_conversion)]`
            // absorbs the `Vec<String> → Vec<String>` no-op case.
            "|s| s.into()".to_string()
        }
        (TypeRef::Char, _) => "|s| s.into()".to_string(),
        (TypeRef::Json, _) => "|j| serde_json::to_string(&j).unwrap_or_default()".to_string(),
        (TypeRef::Path, _) => "|p: std::path::PathBuf| p.to_string_lossy().into_owned()".to_string(),
        (TypeRef::Bytes, CoreWrapper::Arc | CoreWrapper::ArcMutex) => "|a| (*a).clone().into()".to_string(),
        (TypeRef::Bytes, _) => "|b| b.into()".to_string(),
        (TypeRef::Primitive(_), _) => {
            // When a newtype_wrapper is set on the field, the Vec elements are
            // newtypes (e.g. NodeIndex(usize)), not raw primitives. Unwrap with .0.
            if field_newtype_wrapper.is_some() {
                "|x| x.0 as _".to_string()
            } else {
                "|x| x as _".to_string()
            }
        }
        (TypeRef::Vec(inner2), _) => {
            // Vec<Vec<T>>
            match inner2.as_ref() {
                TypeRef::Primitive(_) => {
                    // Vec<Vec<primitive>>: inner cast needed.
                    return if optional {
                        format!(
                            "v.{name}.map(|vec| vec.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect()).collect())"
                        )
                    } else {
                        format!(
                            "v.{name}.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect()).collect()"
                        )
                    };
                }
                _ => {
                    return format!("v.{name}");
                }
            }
        }
        _ => {
            return format!("v.{name}");
        }
    };

    if optional {
        format!("v.{name}.map(|vec| vec.into_iter().map({item_conv}).collect())")
    } else {
        format!("v.{name}.into_iter().map({item_conv}).collect()")
    }
}

/// Emit `From<MirrorT> for SourceT` for types with sanitized fields.
///
/// This is the mirror-to-core direction, required by bridge functions that accept a
/// `MirrorT` parameter and need to call the core function with SourceT.
/// Transmute is unsound for these types because sanitized fields (e.g. `Option<String>`
/// substituted for `Option<CancellationToken>`) have different memory sizes than the
/// corresponding core field, making the transmute layout assumption false.
///
/// Non-sanitized fields use field_from_expr_to_core (the inverse of field_from_expr).
/// Sanitized fields use `Default::default()` since they represent types that cannot
/// be meaningfully passed from Dart (e.g. CancellationToken, ConcurrencyConfig).
fn emit_from_mirror_to_core_struct(out: &mut String, ty: &TypeDef, source_crate_name: &str) {
    let name = &ty.name;
    let core_ty = if ty.rust_path.is_empty() {
        format!("{source_crate_name}::{name}")
    } else {
        ty.rust_path.replace('-', "_")
    };

    // When the core struct has cfg-gated fields stripped from the IR, the generated
    // body ends with `..Default::default()` to fill them in. clippy flags this as
    // `needless_update` even though the field list is otherwise complete from the
    // mirror's perspective — silence it with an allow.
    if ty.has_stripped_cfg_fields {
        out.push_str("#[allow(clippy::needless_update)]\n");
    }
    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_mirror_struct_open.jinja",
        minijinja::context! {
            core_ty => core_ty.as_str(),
            name => name.as_str(),
        },
    ));

    for field in &ty.fields {
        if field.binding_excluded {
            emit_rust_struct_field(out, None, &field.name, "Default::default()");
            continue;
        }
        // Sanitized String fields with a non-Cow core_wrapper indicate the core type
        // is something completely unrelated to a string (e.g. `Option<BoundingBox>`
        // sanitized down to `Option<String>` because BoundingBox isn't in the API
        // surface). Treat those like other sanitized fields and fall back to
        // Default::default(). Only the Cow case (core `Cow<'static, str>` extracted
        // as Named("str") and sanitized to String) safely roundtrips via .into().
        let safe_sanitized_string = matches!(field.ty, TypeRef::String) && field.core_wrapper == CoreWrapper::Cow;
        if field.sanitized && !safe_sanitized_string {
            // Sanitized fields have an unknown core type simplified in the IR.
            // Only types in the transitive closure from input-parameter types get this
            // impl generated, and those core types implement Default (e.g. ExtractionConfig
            // has cancel_token: Option<CancellationToken> which implements Default).
            //
            // `cfg = None`: the dart bridge crate enables `features = ["full"]` on
            // the source dependency, so every core-side cfg-gated field is present
            // at compile time. Emitting `#[cfg(...)]` here would gate on the dart
            // crate's own (undefined) features, evaluating to false and leaving the
            // struct literal missing fields.
            emit_rust_struct_field(out, None, &field.name, "Default::default()");
        } else {
            let expr = field_from_expr_to_core(field, source_crate_name);
            // `cfg = None`: the dart bridge crate enables `features = ["full"]` on
            // the source dependency, so every core-side cfg-gated field is present
            // at compile time. Emitting `#[cfg(...)]` here would gate on the dart
            // crate's own (undefined) features, evaluating to false and leaving the
            // struct literal missing fields.
            emit_rust_struct_field(out, None, &field.name, &expr);
        }
    }

    // Use ..Default::default() only when the core struct has cfg-gated fields that
    // are absent from the IR (and therefore absent from the mirror struct). Without
    // this guard, types that don't implement Default would fail to compile.
    if ty.has_stripped_cfg_fields {
        out.push_str("            ..Default::default()\n");
    }
    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_impl_close.jinja",
        minijinja::context! {},
    ));
}

/// Emit a `From<MirrorEnum> for SourceEnum` implementation.
///
/// Unit-only enums: simple variant match. Data enums: reconstruct each variant.
fn emit_from_mirror_to_core_enum(out: &mut String, en: &EnumDef, source_crate_name: &str) {
    let name = &en.name;
    let core_ty = if en.rust_path.is_empty() {
        format!("{source_crate_name}::{name}")
    } else {
        en.rust_path.replace('-', "_")
    };

    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_mirror_enum_open.jinja",
        minijinja::context! {
            core_ty => core_ty.as_str(),
            name => name.as_str(),
        },
    ));

    for variant in &en.variants {
        let vname = &variant.name;
        if variant.fields.is_empty() {
            // Unit variant: straightforward.
            out.push_str(&crate::backends::dart::template_env::render(
                "rust_enum_unit_to_core_arm.jinja",
                minijinja::context! {
                    name => name.as_str(),
                    vname => vname.as_str(),
                    core_ty => core_ty.as_str(),
                },
            ));
        } else if variant.is_tuple {
            // Mirror uses struct syntax (FRB converts tuple variants to named struct variants).
            // Core uses tuple syntax.
            let mirror_bindings: Vec<String> = (0..variant.fields.len()).map(|i| format!("field{i}")).collect();
            let core_args: Vec<String> = variant
                .fields
                .iter()
                .enumerate()
                .map(|(i, field)| enum_variant_field_conv_to_core(&format!("field{i}"), field))
                .collect();
            out.push_str(&crate::backends::dart::template_env::render(
                "rust_enum_tuple_to_core_arm.jinja",
                minijinja::context! {
                    name => name.as_str(),
                    vname => vname.as_str(),
                    core_ty => core_ty.as_str(),
                    mirror_bindings => mirror_bindings.join(", "),
                    core_args => core_args.join(", "),
                },
            ));
        } else {
            // Struct variant: named fields on both sides.
            let field_names: Vec<&str> = variant.fields.iter().map(|f| f.name.as_str()).collect();
            let core_args: Vec<String> = variant
                .fields
                .iter()
                .map(|field| {
                    let fname = &field.name;
                    let conv = enum_variant_field_conv_to_core(fname, field);
                    format!("{fname}: {conv}")
                })
                .collect();
            out.push_str(&crate::backends::dart::template_env::render(
                "rust_enum_struct_to_core_arm.jinja",
                minijinja::context! {
                    name => name.as_str(),
                    vname => vname.as_str(),
                    core_ty => core_ty.as_str(),
                    field_names => field_names.join(", "),
                    core_args => core_args.join(", "),
                },
            ));
        }
    }

    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_impl_close.jinja",
        minijinja::context! {},
    ));
}

/// Build conversion expression for one enum variant field in the mirror-to-core direction.
fn enum_variant_field_conv_to_core(binding: &str, field: &FieldDef) -> String {
    if field.sanitized {
        // Sanitized enum variant fields can't be mapped — use a safe default.
        return "Default::default()".to_string();
    }
    match &field.ty {
        TypeRef::Named(_) => {
            // Handle core wrappers: mirror has bare T but core may have Arc<T>, Box<T>.
            // `From<T> for Arc<T>` / `From<T> for Box<T>` are not provided by std, so we
            // must wrap explicitly. Mirrors the struct-field logic in
            // `field_from_expr_to_core`.
            match field.core_wrapper {
                CoreWrapper::Arc | CoreWrapper::ArcMutex => {
                    if field.optional {
                        format!("{binding}.map(|x| std::sync::Arc::new(x.into()))")
                    } else {
                        format!("std::sync::Arc::new({binding}.into())")
                    }
                }
                _ if field.is_boxed => {
                    if field.optional {
                        format!("{binding}.map(|x| Box::new(x.into()))")
                    } else {
                        format!("Box::new({binding}.into())")
                    }
                }
                _ => {
                    if field.optional {
                        format!("{binding}.map(Into::into)")
                    } else {
                        format!("{binding}.into()")
                    }
                }
            }
        }
        TypeRef::String => {
            // Mirror flattens enum-variant `Option<String>` to bare `String` (via
            // `unwrap_or_default()` on the forward direction). Reverse: empty → None,
            // non-empty → Some(_), matching the `TypeRef::Path` pattern below.
            if field.optional {
                if matches!(field.core_wrapper, CoreWrapper::Cow) {
                    format!("if {binding}.is_empty() {{ None }} else {{ Some({binding}.into()) }}")
                } else {
                    format!("if {binding}.is_empty() {{ None }} else {{ Some({binding}) }}")
                }
            } else if matches!(field.core_wrapper, CoreWrapper::Cow) {
                format!("{binding}.into()")
            } else {
                binding.to_string()
            }
        }
        TypeRef::Char => {
            // Mirror has String; core has char.
            if field.optional {
                format!("{binding}.as_deref().and_then(|s| s.chars().next())")
            } else {
                format!("{binding}.chars().next().unwrap_or_default()")
            }
        }
        TypeRef::Path => {
            // Mirror collapses Option<PathBuf> → String (with unwrap_or_default()).
            // Reverse: produce Option<PathBuf> from the String (None if empty).
            if field.optional {
                format!("if {binding}.is_empty() {{ None }} else {{ Some(std::path::PathBuf::from({binding})) }}")
            } else {
                format!("std::path::PathBuf::from({binding})")
            }
        }
        TypeRef::Vec(inner) => match inner.as_ref() {
            TypeRef::Named(_) => format!("{binding}.into_iter().map(Into::into).collect()"),
            TypeRef::String => binding.to_string(),
            _ => format!("{binding}.into_iter().map(|x| x as _).collect()"),
        },
        TypeRef::Primitive(prim) => {
            use crate::core::ir::PrimitiveType;
            // Mirror the struct-field handling: newtype_wrapper means the core type
            // is a tuple newtype around a primitive (e.g. NodeIndex(usize)). Enum
            // variants flatten `Option<T>` to bare `T` in the mirror with
            // `unwrap_or_default()`, so reverse: 0 → None, non-zero → Some(_),
            // matching the `TypeRef::String` and `TypeRef::Path` conventions.
            //
            // Bool needs its own arm because `bool == 0` does not compile — false is
            // the unwrap_or_default() of `Option<bool>`, so reverse: false → None.
            if matches!(prim, PrimitiveType::Bool) {
                return match field.optional {
                    true => format!("if {binding} {{ Some({binding}) }} else {{ None }}"),
                    false => binding.to_string(),
                };
            }
            match (&field.newtype_wrapper, field.optional) {
                (Some(nw), true) => format!("if {binding} == 0 {{ None }} else {{ Some({nw}({binding} as _)) }}"),
                (Some(nw), false) => format!("{nw}({binding} as _)"),
                (None, true) => format!("if {binding} == 0 {{ None }} else {{ Some({binding} as _) }}"),
                (None, false) => format!("{binding} as _"),
            }
        }
        _ => {
            if field.optional {
                format!("{binding}.map(Into::into)")
            } else {
                format!("{binding}.into()")
            }
        }
    }
}

/// Build the conversion expression for one struct field in the mirror-to-core direction.
/// This is the inverse of `field_from_expr` (which handles core-to-mirror).
fn field_from_expr_to_core(field: &FieldDef, _source_crate_name: &str) -> String {
    let name = &field.name;
    match &field.ty {
        TypeRef::String => {
            // The IR collapses `Cow<'_, str>` / `Box<str>` / `Arc<str>` into
            // `TypeRef::String` and only `Cow` is tracked on `core_wrapper`. Emit
            // `.into()` unconditionally so wrapped-string core fields receive the
            // right type (e.g. `String → Box<str>`); the crate-level
            // `#[allow(clippy::useless_conversion)]` absorbs the `String → String`
            // no-op case.
            if field.optional {
                format!("v.{name}.map(Into::into)")
            } else {
                format!("v.{name}.into()")
            }
        }
        TypeRef::Char => {
            // D5: `char: From<String>` does not exist in std. Use explicit extraction.
            // Mirror holds String; core holds char. Take the first character or the
            // default char ('\0') when the string is empty.
            if field.optional {
                format!("v.{name}.as_deref().and_then(|s| s.chars().next())")
            } else {
                format!("v.{name}.chars().next().unwrap_or_default()")
            }
        }
        TypeRef::Path => {
            if field.optional {
                format!("v.{name}.map(std::path::PathBuf::from)")
            } else {
                format!("std::path::PathBuf::from(v.{name})")
            }
        }
        TypeRef::Bytes => {
            if field.optional {
                format!("v.{name}.map(Into::into)")
            } else {
                format!("v.{name}.into()")
            }
        }
        TypeRef::Json => {
            // Mirror has String; core has serde_json::Value.
            if field.optional {
                format!("v.{name}.as_deref().and_then(|s| serde_json::from_str(s).ok())")
            } else {
                format!("serde_json::from_str(&v.{name}).unwrap_or_default()")
            }
        }
        TypeRef::Named(_) => {
            // Handle Arc core wrapper: mirror has bare T but core has Arc<T>.
            // Handle is_boxed: mirror has bare T but core has Box<T>.
            match field.core_wrapper {
                CoreWrapper::Arc | CoreWrapper::ArcMutex => {
                    if field.optional {
                        format!("v.{name}.map(|x| std::sync::Arc::new(x.into()))")
                    } else {
                        format!("std::sync::Arc::new(v.{name}.into())")
                    }
                }
                _ if field.is_boxed => {
                    if field.optional {
                        format!("v.{name}.map(|x| Box::new(x.into()))")
                    } else {
                        format!("Box::new(v.{name}.into())")
                    }
                }
                _ => {
                    if field.optional {
                        format!("v.{name}.map(Into::into)")
                    } else {
                        format!("v.{name}.into()")
                    }
                }
            }
        }
        TypeRef::Vec(inner) => {
            match inner.as_ref() {
                TypeRef::Named(_) => {
                    // Handle Arc core wrapper on Vec element types.
                    match field.vec_inner_core_wrapper {
                        CoreWrapper::Arc | CoreWrapper::ArcMutex => {
                            if field.optional {
                                format!(
                                    "v.{name}.map(|vec| vec.into_iter().map(|x| std::sync::Arc::new(x.into())).collect())"
                                )
                            } else {
                                format!("v.{name}.into_iter().map(|x| std::sync::Arc::new(x.into())).collect()")
                            }
                        }
                        _ => {
                            if field.optional {
                                format!("v.{name}.map(|vec| vec.into_iter().map(Into::into).collect())")
                            } else {
                                format!("v.{name}.into_iter().map(Into::into).collect()")
                            }
                        }
                    }
                }
                TypeRef::Vec(inner_inner) => {
                    // Vec<Vec<T>>: FRB uses f64 for f32 primitives — need explicit cast.
                    match inner_inner.as_ref() {
                        TypeRef::Primitive(_) => {
                            if field.optional {
                                format!(
                                    "v.{name}.map(|vv| vv.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect()).collect())"
                                )
                            } else {
                                format!(
                                    "v.{name}.into_iter().map(|inner| inner.into_iter().map(|x| x as _).collect()).collect()"
                                )
                            }
                        }
                        _ => {
                            if field.optional {
                                format!(
                                    "v.{name}.map(|vv| vv.into_iter().map(|inner| inner.into_iter().map(Into::into).collect()).collect())"
                                )
                            } else {
                                format!(
                                    "v.{name}.into_iter().map(|inner| inner.into_iter().map(Into::into).collect()).collect()"
                                )
                            }
                        }
                    }
                }
                TypeRef::Primitive(_) => {
                    // When the field has a newtype_wrapper, the Vec elements are newtypes
                    // (e.g. NodeIndex(usize)) on the core side. The mirror flattens to
                    // the raw primitive, so reverse wraps with the tuple constructor.
                    let elem_conv = if let Some(nw) = &field.newtype_wrapper {
                        format!("|x| {nw}(x as _)")
                    } else {
                        "|x| x as _".to_string()
                    };
                    if field.optional {
                        format!("v.{name}.map(|vec| vec.into_iter().map({elem_conv}).collect())")
                    } else {
                        format!("v.{name}.into_iter().map({elem_conv}).collect()")
                    }
                }
                _ => {
                    // Vec<String> too: the IR collapses `Box<str>` / `Cow<'_, str>` /
                    // `Arc<str>` to `TypeRef::String` and only the `Cow` shape is
                    // tracked on `vec_inner_core_wrapper`. Emit `Into::into`
                    // unconditionally so `Vec<String>` → `Vec<Box<str>>` (etc.)
                    // compiles; the crate-level `#[allow(clippy::useless_conversion)]`
                    // absorbs the `Vec<String> → Vec<String>` no-op case.
                    if field.optional {
                        format!("v.{name}.map(|vec| vec.into_iter().map(Into::into).collect())")
                    } else {
                        format!("v.{name}.into_iter().map(Into::into).collect()")
                    }
                }
            }
        }
        TypeRef::Optional(inner) => {
            // Inverse of `field_from_expr` Optional arm: mirror's flattened `Option<T>`
            // → core's nested `Option<Option<T>>`. Wrap the per-element conversion in
            // `Some(...)` so `Some(x_mirror) → Some(Some(x_core))` and `None → None`
            // (collapsing the "no change" vs "explicit clear" distinction; see the
            // `frb_rust_type` comment for the trade-off).
            let wrap_some = if field.optional { ".map(Some)" } else { "" };
            match inner.as_ref() {
                TypeRef::Named(_) => format!("v.{name}.map(Into::into){wrap_some}"),
                TypeRef::String | TypeRef::Char => format!("v.{name}.map(Into::into){wrap_some}"),
                TypeRef::Path => format!("v.{name}.map(std::path::PathBuf::from){wrap_some}"),
                TypeRef::Primitive(_) => format!("v.{name}.map(|x| x as _){wrap_some}"),
                _ => format!("v.{name}{wrap_some}"),
            }
        }
        TypeRef::Primitive(_) => {
            if let Some(nw) = &field.newtype_wrapper {
                if field.optional {
                    format!("v.{name}.map(|x| {nw}(x as _))")
                } else {
                    format!("{nw}(v.{name} as _)")
                }
            } else if field.optional {
                format!("v.{name}.map(|x| x as _)")
            } else {
                format!("v.{name} as _")
            }
        }
        TypeRef::Duration => {
            // Mirror i64 → core Duration (stored as millis).
            if field.optional {
                format!("v.{name}.map(|ms| std::time::Duration::from_millis(ms as u64))")
            } else {
                format!("std::time::Duration::from_millis(v.{name} as u64)")
            }
        }
        TypeRef::Map(_, v_ty) => {
            // HashMap: convert via iterator. The IR collapses wrapped string types
            // (`Box<str>`, `Cow<'_, str>`, `Arc<str>`) into `TypeRef::String`, so emit
            // `.into()` on both keys and values — bridges `String → Box<str>` etc. at
            // the type level. Crate-level `#[allow(clippy::useless_conversion)]` absorbs
            // the `String → String` identity case.
            let val_conv = match v_ty.as_ref() {
                TypeRef::Primitive(_) => "v as _",
                TypeRef::Named(_) => "v.into()",
                // String / Path / etc.: `.into()` covers `String → Box<str>` and identity.
                _ => "v.into()",
            };
            if field.optional {
                format!("v.{name}.map(|m| m.into_iter().map(|(k, v)| (k.into(), {val_conv})).collect())")
            } else {
                format!("v.{name}.into_iter().map(|(k, v)| (k.into(), {val_conv})).collect()")
            }
        }
        TypeRef::Unit => "()".to_string(),
    }
}

/// Build conversion expression for a Map field (core → mirror).
/// Mirror always uses HashMap<String, String> or HashMap<String, T>.
/// Core may use BTreeMap, AHashMap, HashMap with Value values, etc.
fn map_from_expr(name: &str, _k: &TypeRef, v_ty: &TypeRef, optional: bool) -> String {
    // Determine value conversion strategy. The IR collapses wrapped string types
    // (`Box<str>`, `Cow<'_, str>`, `Arc<str>`) to `TypeRef::String`, so emit `.into()`
    // for the String case as well — it bridges `Box<str> → String` (which `(k, v)`
    // identity does NOT, and which trips `FromIterator` resolution at compile time)
    // while remaining a no-op for plain `String → String` under the crate-level
    // `#[allow(clippy::useless_conversion)]`.
    let value_conv = match v_ty {
        TypeRef::Json => {
            // serde_json serialize to String.
            "serde_json::to_string(&v).unwrap_or_default()"
        }
        TypeRef::Named(mirror_name) => return map_named_from_expr(name, mirror_name, optional),
        TypeRef::Primitive(_) => {
            // Cast to target primitive (i64 for integers, f64 for floats).
            "v as _"
        }
        // String / Path / Bytes / etc.: rely on the appropriate `From` impl. For
        // String this is `From<Box<str>> for String` (or identity). For other types
        // `.into()` covers `Bytes → Vec<u8>`, `PathBuf → String` is NOT auto so the
        // explicit branches above must be kept exhaustive when new shapes appear.
        _ => "v.into()",
    };

    // Keys: same reasoning as values — the IR loses `Box<str>` / `Cow<'_, str>`
    // wrappers, so always emit `.into()` rather than the identity `k`. This bridges
    // `HashMap<Box<str>, _>` → `HashMap<String, _>` at the type level; the
    // crate-level `clippy::useless_conversion` allow absorbs the no-op String case.
    let iter_expr = format!("into_iter().map(|(k, v)| (k.into(), {value_conv})).collect()");

    if optional {
        format!("v.{name}.map(|m| m.{iter_expr})")
    } else {
        format!("v.{name}.{iter_expr}")
    }
}

fn map_named_from_expr(field_name: &str, mirror_name: &str, optional: bool) -> String {
    let iter_expr = format!("into_iter().map(|(k, v)| (k.into(), {mirror_name}::from(v))).collect()");
    if optional {
        format!("v.{field_name}.map(|m| m.{iter_expr})")
    } else {
        format!("v.{field_name}.{iter_expr}")
    }
}

/// Fallback expression for sanitized fields (unknown core types mapped to String/i64).
///
/// Sanitized fields have an unknown or complex core type that was simplified in the IR.
/// We use Default::default() as a safe fallback — attempting serde_json::to_string
/// would require the type to implement Serialize, which is not guaranteed for all
/// sanitized types (e.g. StyleType, ParagraphProperties, InternalDocument).
fn sanitized_field_from_expr(field: &FieldDef) -> String {
    let name = &field.name;
    match &field.ty {
        TypeRef::Primitive(_) => {
            // Sanitized primitive: try direct cast.
            if field.optional {
                format!("v.{name}.map(|x| x as _)")
            } else {
                format!("v.{name} as _")
            }
        }
        // Cow<'_, str> fields are erroneously marked sanitized by the IR extractor
        // even though the underlying type is plainly `String`. Convert via `.into()` /
        // `.into_owned()` so the actual value reaches the mirror struct rather than an
        // empty `String::default()` placeholder (which silently broke `mime_type`,
        // `format`, and similar Cow-wrapped string fields).
        TypeRef::String | TypeRef::Char if field.core_wrapper == CoreWrapper::Cow => {
            if field.optional {
                format!("v.{name}.map(|s| s.into_owned())")
            } else {
                format!("v.{name}.into_owned()")
            }
        }
        _ => {
            // All other sanitized types: use Default.
            // We cannot safely serde-serialize unknown types.
            let _ = name;
            String::from("Default::default()")
        }
    }
}

/// Emit a `From<SourceE> for E` implementation for a mirror enum.
///
/// Unit-only enums use a simple match. Data enums recursively convert variant fields.
fn emit_from_impl_for_enum(out: &mut String, en: &EnumDef, source_crate_name: &str) {
    let name = &en.name;
    let core_ty = if en.rust_path.is_empty() {
        format!("{source_crate_name}::{name}")
    } else {
        en.rust_path.replace('-', "_")
    };

    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_core_enum_open.jinja",
        minijinja::context! {
            core_ty => core_ty.as_str(),
            name => name.as_str(),
        },
    ));

    for variant in &en.variants {
        let vname = &variant.name;
        if variant.fields.is_empty() {
            out.push_str(&crate::backends::dart::template_env::render(
                "rust_enum_unit_from_core_arm.jinja",
                minijinja::context! {
                    core_ty => core_ty.as_str(),
                    vname => vname.as_str(),
                    name => name.as_str(),
                },
            ));
        } else if variant.is_tuple {
            // Core side: tuple pattern `Variant(f0, f1, ...)`.
            // Mirror side: ALWAYS struct syntax `Variant { field0: expr, field1: expr, ... }`
            // because flutter_rust_bridge converts tuple variants to named struct variants
            // in mirror enums (with fieldN naming).
            let field_patterns: Vec<String> = (0..variant.fields.len()).map(|i| format!("f{i}")).collect();
            let mirror_fields: Vec<String> = variant
                .fields
                .iter()
                .enumerate()
                .map(|(i, field)| {
                    let conv = enum_variant_field_conv(&format!("f{i}"), field, source_crate_name);
                    format!("field{i}: {conv}")
                })
                .collect();
            out.push_str(&crate::backends::dart::template_env::render(
                "rust_enum_tuple_from_core_arm.jinja",
                minijinja::context! {
                    core_ty => core_ty.as_str(),
                    vname => vname.as_str(),
                    name => name.as_str(),
                    field_patterns => field_patterns.join(", "),
                    mirror_fields => mirror_fields.join(", "),
                },
            ));
        } else {
            // Struct variant: named fields on both sides.
            let field_names: Vec<&str> = variant.fields.iter().map(|f| f.name.as_str()).collect();
            let field_convs: Vec<String> = variant
                .fields
                .iter()
                .map(|field| {
                    let fname = &field.name;
                    let conv = enum_variant_field_conv(fname, field, source_crate_name);
                    format!("{fname}: {conv}")
                })
                .collect();
            out.push_str(&crate::backends::dart::template_env::render(
                "rust_enum_struct_from_core_arm.jinja",
                minijinja::context! {
                    core_ty => core_ty.as_str(),
                    vname => vname.as_str(),
                    name => name.as_str(),
                    field_names => field_names.join(", "),
                    field_convs => field_convs.join(", "),
                },
            ));
        }
    }

    out.push_str(&crate::backends::dart::template_env::render(
        "rust_from_impl_close.jinja",
        minijinja::context! {},
    ));
}

/// Build the conversion expression for one enum variant field.
///
/// For enum struct variant fields extracted from core, the binding is the actual
/// core type (which may be optional, a newtype, etc.). The mirror variant always
/// uses concrete types (String not Option<String>, i64 not usize).
fn enum_variant_field_conv(binding: &str, field: &FieldDef, source_crate_name: &str) -> String {
    let _ = source_crate_name;
    // Sanitized fields: the core type was unknown, the IR simplified it.
    // The mirror may have String, Vec<String>, i64, etc.
    if field.sanitized {
        match &field.ty {
            TypeRef::Primitive(_) => {
                if field.optional {
                    return format!("{binding}.map(|x| x as _).unwrap_or_default()");
                }
                return format!("{binding} as _");
            }
            TypeRef::Vec(inner) => {
                // Vec<Vec<String>>: sanitized from Vec<(String, String)> (homogeneous tuple pairs).
                // The Java backend uses the same pattern — mirror each pair as vec![a, b].
                if matches!(inner.as_ref(), TypeRef::Vec(inner_inner) if matches!(inner_inner.as_ref(), TypeRef::String))
                {
                    if field.optional {
                        return format!(
                            "{binding}.map(|v| v.into_iter().map(|(a, b)| vec![a.to_string(), b.to_string()]).collect()).unwrap_or_default()"
                        );
                    }
                    return format!("{binding}.into_iter().map(|(a, b)| vec![a.to_string(), b.to_string()]).collect()");
                }
                // Fallback: Core has Vec<ComplexType>; mirror has Vec<String>.
                // Serialize each element to JSON string.
                if field.optional {
                    return format!(
                        "{binding}.map(|v| v.into_iter().map(|e| serde_json::to_string(&e).unwrap_or_default()).collect()).unwrap_or_default()"
                    );
                }
                return format!(
                    "{binding}.into_iter().map(|e| serde_json::to_string(&e).unwrap_or_default()).collect()"
                );
            }
            _ => {
                // Try serde serialization; mirror field is String.
                if field.optional {
                    return format!(
                        "{binding}.map(|v| serde_json::to_string(&v).unwrap_or_default()).unwrap_or_default()"
                    );
                }
                return format!("serde_json::to_string(&{binding}).unwrap_or_default()");
            }
        }
    }

    match &field.ty {
        TypeRef::Named(inner_name) => {
            if field.is_boxed && field.optional {
                format!("{binding}.map(|b| {inner_name}::from(*b)).unwrap_or_default()")
            } else if field.is_boxed {
                format!("{inner_name}::from(*{binding})")
            } else if field.optional {
                // Core has Option<T>, mirror has T — unwrap with default.
                format!("{binding}.map({inner_name}::from).unwrap_or_default()")
            } else {
                format!("{inner_name}::from({binding})")
            }
        }
        TypeRef::Vec(inner) => {
            let item_conv = match inner.as_ref() {
                TypeRef::Named(inner_name) => Some(format!("{inner_name}::from")),
                TypeRef::Primitive(_) => Some("|x| x as _".to_string()),
                // Vec<String> → Vec<String> is identity — emit no per-item map.
                TypeRef::String => None,
                _ => Some("|s| s.into()".to_string()),
            };
            match (item_conv, field.optional) {
                (None, true) => format!("{binding}.unwrap_or_default()"),
                (None, false) => binding.to_string(),
                (Some(conv), true) => {
                    format!("{binding}.map(|v| v.into_iter().map({conv}).collect()).unwrap_or_default()")
                }
                (Some(conv), false) => format!("{binding}.into_iter().map({conv}).collect()"),
            }
        }
        TypeRef::String => {
            if field.optional {
                // Core has Option<String>, mirror has String.
                format!("{binding}.unwrap_or_default()")
            } else if matches!(field.core_wrapper, CoreWrapper::Cow) {
                // Core has Cow<str> → mirror String (real conversion).
                format!("{binding}.into()")
            } else {
                // Core has String → mirror String: identity move
                // (clippy::useless_conversion flags `.into()` here).
                binding.to_string()
            }
        }
        TypeRef::Char => {
            if field.optional {
                format!("{binding}.map(|c| c.to_string()).unwrap_or_default()")
            } else {
                format!("{binding}.to_string()")
            }
        }
        TypeRef::Path => {
            if field.optional {
                // Core has Option<PathBuf>, mirror has String.
                format!("{binding}.map(|p| p.to_string_lossy().into_owned()).unwrap_or_default()")
            } else {
                format!("{binding}.to_string_lossy().into_owned()")
            }
        }
        TypeRef::Json => {
            if field.optional {
                format!("{binding}.map(|j| serde_json::to_string(&j).unwrap_or_default()).unwrap_or_default()")
            } else {
                format!("serde_json::to_string(&{binding}).unwrap_or_default()")
            }
        }
        TypeRef::Primitive(_) => {
            if let Some(_nw) = &field.newtype_wrapper {
                if field.optional {
                    format!("{binding}.map(|x| x.0 as _).unwrap_or_default()")
                } else {
                    format!("{binding}.0 as _")
                }
            } else if field.optional {
                format!("{binding}.map(|x| x as _).unwrap_or_default()")
            } else {
                format!("{binding} as _")
            }
        }
        TypeRef::Map(_, v_ty) => {
            let needs_value_conv = matches!(v_ty.as_ref(), TypeRef::Json | TypeRef::Named(_));
            if needs_value_conv {
                format!(
                    "{binding}.into_iter().map(|(k, v)| (k.into(), serde_json::to_string(&v).unwrap_or_default())).collect()"
                )
            } else {
                format!("{binding}.into_iter().map(|(k, v)| (k.into(), v.into())).collect()")
            }
        }
        _ => binding.to_string(),
    }
}

/// Returns true if `ty` (or any inner type) has a layout-incompatible mapping in the mirror.
///
/// The FRB bridge maps several core types to different Rust types in the mirror:
/// - Duration → i64 (8 bytes) vs core Duration (16 bytes)
/// - Path → String (24 bytes) vs core PathBuf
/// - Json (serde_json::Value) → String (24 bytes) vs core Value (32 bytes)
/// - Non-i64/f64/bool primitives: u32/i32/u8/etc. → i64 (8 bytes vs 4 bytes for u32)
///
/// All of these cause layout mismatches that make transmute unsound for structs containing them.
fn has_duration_or_path_field(ty: &TypeRef) -> bool {
    use crate::core::ir::PrimitiveType;
    match ty {
        TypeRef::Duration | TypeRef::Path | TypeRef::Json => true,
        // Non-identity primitive widening: u8/i8/u16/i16/u32/i32/u64/usize/isize/f32 all
        // get widened to i64 (or f64 for floats) in the FRB bridge, causing size mismatches.
        TypeRef::Primitive(p) => !matches!(p, PrimitiveType::I64 | PrimitiveType::F64 | PrimitiveType::Bool),
        TypeRef::Optional(inner) | TypeRef::Vec(inner) => has_duration_or_path_field(inner),
        _ => false,
    }
}

/// Returns true if `f` has any param the Dart bridge cannot reconstruct.
///
/// Currently the only such case is `Vec<(Vec<u8>, …)>` — a tuple-of-bytes
/// container whose IR-flattened form (`Vec<String>`) cannot be losslessly
/// converted back into `Vec<(Vec<u8>, T)>`. Skipping the function entirely
/// at the bridge surface is preferred over emitting a panicking shim.
fn has_unbridgeable_param(f: &crate::core::ir::FunctionDef) -> bool {
    for p in &f.params {
        let Some(orig) = p.original_type.as_deref() else {
            continue;
        };
        let stripped_orig = orig.replace(' ', "");
        // IR format for a tuple-vec param after sanitization:
        //   Vec(Named("(Vec<u8>, T, …)"))
        // (the `original_type` records the tuple shape; the real `ty` is `Vec<String>`).
        // Round-tripping `Vec<u8>` through a JSON string is lossy, so skip emission
        // entirely rather than emit a panicking shim.
        if stripped_orig.starts_with("Vec(Named(\"(Vec<u8>,") {
            return true;
        }
    }
    false
}

/// Emit an `impl TypeName { }` block for an opaque type that exposes methods.
///
/// FRB v2 generates Dart-side instance methods on opaque handles only when the
/// bridge crate contains `impl TypeName { #[frb] pub fn method(...) }` blocks.
/// Without these blocks FRB emits an empty abstract class with no methods.
///
/// Each method body delegates to `self.inner.method_name(...)` after converting
/// mirror-type parameters to the core type via `unsafe { transmute }` (for types
/// whose mirror layout is identical to core) or `From` conversion (for sanitized
/// types). Async methods use `.await` and return `Result<MirrorType, String>`.
///
/// Methods listed in `stub_methods` get `unimplemented!()` bodies, matching the
/// treatment of top-level free functions (e.g. `chat_stream` which returns a
/// `BoxStream` that cannot be bridged through FRB).
#[allow(clippy::too_many_arguments)]
fn emit_opaque_impl_block(
    out: &mut String,
    ty: &TypeDef,
    source_crate_name: &str,
    stub_methods: &[String],
    types_needing_from_conversion: &HashSet<String>,
    opaque_type_names: &HashSet<String>,
    streaming_adapters: &std::collections::HashMap<String, &AdapterConfig>,
    config: &ResolvedCrateConfig,
    type_paths: &std::collections::HashMap<String, String>,
    mirror_type_names: &HashSet<String>,
) {
    let type_name = &ty.name;

    // Collect unique paths that need to be brought into scope:
    //   1. Trait sources, so trait-provided methods on `self.inner` resolve.
    //   2. Named types referenced in method param/return signatures — FRB strips full
    //      module paths from generated code, so types like `InternalDocument` and
    //      `SyncExtractor` referenced via fully-qualified paths in the IR appear bare
    //      in the emitted bridge and need a `use` to resolve.
    let mut trait_uses: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
    fn collect_named(ty: &crate::core::ir::TypeRef, out: &mut std::collections::BTreeSet<String>) {
        use crate::core::ir::TypeRef;
        match ty {
            TypeRef::Named(n) => {
                out.insert(n.clone());
            }
            TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named(inner, out),
            TypeRef::Map(k, v) => {
                collect_named(k, out);
                collect_named(v, out);
            }
            _ => {}
        }
    }
    let mut named_refs: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
    for method in &ty.methods {
        if method.sanitized && !stub_methods.contains(&method.name) {
            let adapter_key = format!("{type_name}.{}", method.name);
            if !streaming_adapters.contains_key(&adapter_key) {
                continue;
            }
        }
        if let Some(path) = method.trait_source.as_deref() {
            trait_uses.insert(path.to_string());
        }
        for p in &method.params {
            collect_named(&p.ty, &mut named_refs);
        }
        collect_named(&method.return_type, &mut named_refs);
    }
    // Map each Named name → its qualified Rust path, and skip names that:
    //   - resolve to the current type (already in scope), OR
    //   - already have a `#[frb(mirror(TypeName))]` struct in scope (would cause E0255
    //     if we also emit a `use source_crate::TypeName;` for the same short name), OR
    //   - lack a path entry (primitives, sanitized types, etc.).
    for name in &named_refs {
        if name == type_name {
            continue;
        }
        // Skip types that already have a local struct in scope — the frb(mirror) macro
        // (for mirror types) and the opaque wrapper struct (for opaque types) both
        // bring the type's short name into scope, so a separate `use` would conflict
        // (E0255: "the name X is defined multiple times").
        if mirror_type_names.contains(name) || opaque_type_names.contains(name) {
            continue;
        }
        if let Some(path) = type_paths.get(name)
            && path.contains("::")
        {
            trait_uses.insert(path.clone());
        }
    }
    for path in &trait_uses {
        out.push_str(&format!("#[allow(unused_imports)]\nuse {path};\n"));
    }

    out.push_str(&format!("impl {type_name} {{\n"));

    for method in &ty.methods {
        let method_name = &method.name;

        // Sanitized methods that are NOT listed as stub_methods: check for a streaming adapter.
        // If one exists, emit a StreamSink<T> variant; otherwise skip entirely.
        if method.sanitized && !stub_methods.contains(method_name) {
            let adapter_key = format!("{type_name}.{method_name}");
            if let Some(adapter) = streaming_adapters.get(&adapter_key) {
                emit_streaming_sink_method(out, method_name, adapter, types_needing_from_conversion, config);
            } else {
                out.push_str(&format!(
                    "    // Method `{method_name}` has a sanitized return type that cannot be bridged through FRB — skipped.\n"
                ));
            }
            continue;
        }

        // Skip static / associated functions (no `&self`). The current emitter only
        // generates `&self` instance bodies, so `T::new()` or `T::default()` end up as
        // `self.inner.{name}()` — which trips E0599 (associated fn, not a method).
        if method.is_static {
            out.push_str(&format!(
                "    // Method `{method_name}` is a static/associated function and is not yet bridged through FRB — skipped.\n"
            ));
            continue;
        }

        emit_opaque_method(
            out,
            ty,
            method,
            source_crate_name,
            stub_methods,
            types_needing_from_conversion,
            opaque_type_names,
        );
    }

    out.push_str("}\n");
}

/// Emit a `pub fn method_name(&self, params..., sink: StreamSink<ItemType>)` method
/// for a streaming adapter. FRB v2 recognises the `StreamSink<T>` parameter and generates
/// a `Stream<T>` accessor on the Dart side.
fn emit_streaming_sink_method(
    out: &mut String,
    method_name: &str,
    adapter: &AdapterConfig,
    _types_needing_from_conversion: &HashSet<String>,
    config: &ResolvedCrateConfig,
) {
    let item_type = adapter.item_type.as_deref().unwrap_or("()");
    // Build the Rust parameter list (excluding self and sink).
    let params: Vec<String> = adapter
        .params
        .iter()
        .map(|p| {
            let ty = if p.optional {
                format!("Option<{}>", p.ty)
            } else {
                p.ty.clone()
            };
            format!("{}: {ty}", p.name)
        })
        .collect();
    let params_str = if params.is_empty() {
        String::new()
    } else {
        format!(", {}", params.join(", "))
    };

    // Delegate to the streaming body generator for the Dart language.
    let (body, _struct_def) =
        crate::adapters::streaming::generate_body(adapter, crate::core::config::Language::Dart, config)
            .unwrap_or_else(|_| (String::from("unimplemented!()"), None));

    out.push_str(&format!(
        "    #[frb]\n    pub fn {method_name}(&self{params_str}, sink: crate::frb_generated::StreamSink<{item_type}>) {{\n        {body}\n    }}\n"
    ));
}

/// Emit one method inside an `impl TypeName { }` block for an FRB opaque type.
fn emit_opaque_method(
    out: &mut String,
    _ty: &TypeDef,
    method: &MethodDef,
    source_crate_name: &str,
    stub_methods: &[String],
    types_needing_from_conversion: &HashSet<String>,
    opaque_type_names: &HashSet<String>,
) {
    use bridge_fn::frb_rust_type_mirror;

    let method_name = &method.name;

    // Receiver: FRB opaque types support `&self`, `&mut self`, and owned `self`.
    // D7: when the inner method takes owned `self` (e.g. builder `build(self)`),
    // emit owned `self` here so Rust can move out of `self.inner` rather than trying
    // to move out of a shared reference (`error[E0507]`).
    let self_param = match &method.receiver {
        Some(ReceiverKind::RefMut) => "&mut self",
        Some(ReceiverKind::Owned) => "self",
        _ => "&self",
    };

    // Build parameter list (excluding the receiver).
    let params: Vec<String> = method
        .params
        .iter()
        .map(|p| {
            let rust_ty = frb_rust_type_mirror(&p.ty, p.optional);
            format!("{}: {rust_ty}", p.name)
        })
        .collect();

    let async_kw = if method.is_async { "async " } else { "" };

    // Return type: always `Result<MirrorType, String>` when an error type is present.
    let has_error = method.error_type.is_some();
    let ret_ty = if has_error {
        let ok_ty = frb_rust_type_mirror(&method.return_type, false);
        format!("Result<{ok_ty}, String>")
    } else {
        frb_rust_type_mirror(&method.return_type, false)
    };

    // Emit `#[frb]` so FRB generates the Dart-side method wrapper.
    out.push_str("    #[frb]\n");

    // Signature.
    let params_str = params.join(", ");
    out.push_str(&format!(
        "    pub {async_kw}fn {method_name}({self_param}, {params_str}) -> {ret_ty} {{\n"
    ));

    // Body: stub methods or real delegation.
    if stub_methods.contains(method_name) {
        out.push_str(&format!(
            "        ::std::unimplemented!(\"method `{method_name}` is listed in dart.stub_methods\")\n"
        ));
    } else {
        emit_opaque_method_body(
            out,
            method,
            source_crate_name,
            types_needing_from_conversion,
            opaque_type_names,
        );
    }

    out.push_str("    }\n");
}

/// Emit the body of a method inside an opaque-type `impl` block.
///
/// Converts each parameter from the local mirror type to the core type, calls
/// `self.inner.method_name(...)`, and wraps the return value in the mirror type.
fn emit_opaque_method_body(
    out: &mut String,
    method: &MethodDef,
    source_crate_name: &str,
    types_needing_from_conversion: &HashSet<String>,
    opaque_type_names: &HashSet<String>,
) {
    use conversions::frb_rust_type_inner;

    let method_name = &method.name;
    let has_error = method.error_type.is_some();

    // Build per-argument conversion: mirror type → core type.
    // For Named mirror types (i.e. FRB mirror structs), transmute is sound ONLY when
    // the mirror layout is identical to the core layout. Types in
    // `types_needing_from_conversion` have sanitized fields (e.g. Option<String>
    // substituted for Option<CancellationToken>) causing size mismatches — use From
    // conversion for those. Transmute is zero-cost unlike From, so we prefer it for
    // types with identical layouts.
    // D3: opaque wrapper types use `.inner` access — transmute across crate boundaries
    // for opaque types is unsound (the inner type may not be pub in the source crate).
    let call_args: Vec<String> = method
        .params
        .iter()
        .map(|p| {
            let param_name = &p.name;
            match &p.ty {
                TypeRef::Named(mirror_name) => {
                    // D3: opaque wrapper types (e.g. VisitorHandle) expose .inner directly.
                    if opaque_type_names.contains(mirror_name.as_str()) {
                        if p.optional {
                            return format!("{param_name}.map(|h| h.inner)");
                        }
                        if p.is_ref {
                            return format!("&{param_name}.inner");
                        }
                        return format!("{param_name}.inner");
                    }
                    let core_ty = format!("{source_crate_name}::{mirror_name}");
                    if types_needing_from_conversion.contains(mirror_name.as_str()) {
                        // Layout differs — use the generated From<MirrorT> for CoreT impl.
                        if p.optional {
                            format!("{param_name}.map({core_ty}::from)")
                        } else if p.is_ref {
                            // Cannot take a reference to a temporary — convert to owned then borrow.
                            format!("&{core_ty}::from({param_name})")
                        } else {
                            format!("{core_ty}::from({param_name})")
                        }
                    } else {
                        // Named mirror type with identical layout: transmute to the source-crate type.
                        if p.optional {
                            format!("{param_name}.map(|v| unsafe {{ ::std::mem::transmute::<{mirror_name}, {core_ty}>(v) }})")
                        } else if p.is_ref {
                            format!("unsafe {{ ::std::mem::transmute::<&{mirror_name}, &{core_ty}>(&{param_name}) }}")
                        } else {
                            format!("unsafe {{ ::std::mem::transmute::<{mirror_name}, {core_ty}>({param_name}) }}")
                        }
                    }
                }
                TypeRef::Vec(inner) => {
                    if let TypeRef::Named(mirror_name) = inner.as_ref() {
                        let core_ty = format!("{source_crate_name}::{mirror_name}");
                        if types_needing_from_conversion.contains(mirror_name.as_str()) {
                            // Elements have differing layouts — convert each via From.
                            if p.optional {
                                format!("{param_name}.map(|v| v.into_iter().map({core_ty}::from).collect())")
                            } else {
                                format!("{param_name}.into_iter().map({core_ty}::from).collect()")
                            }
                        } else {
                            if p.optional {
                                format!("{param_name}.map(|v| unsafe {{ ::std::mem::transmute::<Vec<{mirror_name}>, Vec<{core_ty}>>(v) }})")
                            } else {
                                format!("unsafe {{ ::std::mem::transmute::<Vec<{mirror_name}>, Vec<{core_ty}>>({param_name}) }}")
                            }
                        }
                    } else if matches!(inner.as_ref(), TypeRef::String) && p.is_ref {
                        // Core takes `&[&str]`; FRB delivers `Vec<String>`.
                        // Borrow the temporary Vec<&str> into &[&str] — the temporary lives
                        // long enough for the enclosing statement.
                        format!("&{param_name}.iter().map(|s| s.as_str()).collect::<Vec<_>>()")
                    } else if p.is_ref {
                        // Core takes a slice reference (e.g. `&[u8]`, `&[u32]`).
                        // Borrowing Vec<T> produces &Vec<T> which coerces to &[T].
                        format!("&{param_name}")
                    } else {
                        param_name.clone()
                    }
                }
                TypeRef::Bytes => {
                    // FRB bridges `bytes::Bytes` as `Vec<u8>`. When the core takes `&[u8]`
                    // (is_ref=true), borrow the Vec so Rust coerces Vec<u8> → &[u8].
                    if p.is_ref {
                        format!("&{param_name}")
                    } else {
                        // Owned case: core takes `Bytes` — convert via From.
                        format!("::bytes::Bytes::from({param_name})")
                    }
                }
                TypeRef::Primitive(prim) => {
                    // FRB widens all integers to i64 and floats to f64. Use the actual
                    // core primitive name to decide whether a narrowing cast is needed.
                    let native = conversions::primitive_name(prim);
                    let frb_ty = frb_rust_type_inner(&TypeRef::Primitive(prim.clone()));
                    if native == frb_ty {
                        // Types match (e.g. both i64, f64, bool) — pass through unchanged.
                        param_name.clone()
                    } else if p.optional {
                        format!("{param_name}.map(|v| v as {native})")
                    } else {
                        format!("{param_name} as {native}")
                    }
                }
                TypeRef::String => {
                    // Core may take `&str` — pass by reference when is_ref is set.
                    if p.is_ref && p.optional {
                        format!("{param_name}.as_deref()")
                    } else if p.is_ref {
                        format!("&{param_name}")
                    } else {
                        param_name.clone()
                    }
                }
                TypeRef::Json => {
                    if p.optional {
                        format!("{param_name}.as_deref().and_then(|s| serde_json::from_str(s).ok())")
                    } else {
                        format!("serde_json::from_str(&{param_name}).unwrap_or(serde_json::Value::Null)")
                    }
                }
                TypeRef::Path => {
                    // FRB bridges PathBuf as String. Convert to PathBuf for the core call.
                    if p.optional {
                        format!("{param_name}.map(::std::path::PathBuf::from)")
                    } else {
                        format!("::std::path::PathBuf::from({param_name})")
                    }
                }
                _ => param_name.clone(),
            }
        })
        .collect();

    let call = format!("self.inner.{method_name}({})", call_args.join(", "));

    // Wrap the return value: Named return types need to be converted FROM the core
    // type into the local mirror type using the generated `From<source::T> for T` impl.
    // `TypeRef::Bytes` returns `bytes::Bytes` from core but the bridge declares `Vec<u8>` —
    // convert via `.to_vec()`. Primitive widening (i32→i64, usize→i64, f32→f64) and
    // by-ref returns (`&str`, `&Path`, `&[&str]`) need explicit conversion too.
    let wrap_return = build_opaque_return_wrap(&method.return_type, method.returns_ref);

    if method.is_async {
        if has_error {
            if wrap_return.is_empty() {
                out.push_str(&format!("        {call}.await.map_err(|e| e.to_string())\n"));
            } else {
                out.push_str(&format!(
                    "        {call}.await.map({wrap_return}).map_err(|e| e.to_string())\n"
                ));
            }
        } else if wrap_return.is_empty() {
            out.push_str(&format!("        {call}.await\n"));
        } else {
            out.push_str(&format!("        ({wrap_return})({call}.await)\n"));
        }
    } else if has_error {
        if wrap_return.is_empty() {
            out.push_str(&format!("        {call}.map_err(|e| e.to_string())\n"));
        } else {
            out.push_str(&format!(
                "        {call}.map({wrap_return}).map_err(|e| e.to_string())\n"
            ));
        }
    } else if wrap_return.is_empty() {
        out.push_str(&format!("        {call}\n"));
    } else {
        out.push_str(&format!("        ({wrap_return})({call})\n"));
    }
}

/// Build the return-value wrapping closure for an opaque method return type.
///
/// Returns an empty string when no wrapping is needed (primitive, String, etc.).
/// Returns a closure expression like `|v| ReturnType::from(v)` for Named types.
/// Returns `|v| v.to_vec()` for `TypeRef::Bytes` (core returns `bytes::Bytes`,
/// mirror declares `Vec<u8>`).
///
/// `returns_ref` indicates the core method returns by reference — `&str`, `&Path`,
/// or `&[&str]` need conversion to the owned mirror types (`String`, `Vec<String>`).
fn build_opaque_return_wrap(ty: &TypeRef, returns_ref: bool) -> String {
    use crate::core::ir::PrimitiveType;
    match ty {
        TypeRef::Named(mirror_name) => {
            format!("|v| {mirror_name}::from(v)")
        }
        TypeRef::Bytes => {
            // Core returns `bytes::Bytes`, bridge declares `Vec<u8>`.
            "|v| v.to_vec()".to_string()
        }
        TypeRef::String if returns_ref => {
            // Core returns `&str`, mirror declares `String`.
            "|v: &str| v.to_string()".to_string()
        }
        TypeRef::Path => {
            // Core returns `&Path` or `PathBuf`, mirror declares `String`.
            if returns_ref {
                "|v: &::std::path::Path| v.to_string_lossy().to_string()".to_string()
            } else {
                "|v: ::std::path::PathBuf| v.to_string_lossy().to_string()".to_string()
            }
        }
        TypeRef::Vec(inner) => match inner.as_ref() {
            TypeRef::Named(mirror_name) => {
                format!("|v| v.into_iter().map({mirror_name}::from).collect()")
            }
            TypeRef::String if returns_ref => {
                // Core returns `&[&str]`, mirror declares `Vec<String>`.
                "|v: &[&str]| v.iter().map(|s| s.to_string()).collect()".to_string()
            }
            _ => String::new(),
        },
        TypeRef::Optional(inner) => match inner.as_ref() {
            TypeRef::Named(mirror_name) => {
                format!("|v: Option<_>| v.map({mirror_name}::from)")
            }
            TypeRef::String if returns_ref => "|v: Option<&str>| v.map(|s| s.to_string())".to_string(),
            _ => String::new(),
        },
        TypeRef::Primitive(prim) => {
            // FRB widens integers to i64 and floats to f64. Cast the core value.
            match prim {
                PrimitiveType::I64 | PrimitiveType::F64 | PrimitiveType::Bool => String::new(),
                PrimitiveType::F32 => "|v| v as f64".to_string(),
                _ => "|v| v as i64".to_string(),
            }
        }
        _ => String::new(),
    }
}

/// Emit a `#[frb] pub fn create_<snake_name>_from_json(json: String) -> Result<TypeName, String>`
/// free function for a non-opaque mirror struct type.
///
/// FRB generates `static Future<TypeName> createTypeNameFromJson(String json)` on the Dart
/// bridge class from this function. Dart e2e tests call this helper to construct typed
/// request objects from the raw JSON fixtures without manually filling every field — this
/// is the `options_via = "from_json"` path for the Dart e2e codegen.
///
/// The body deserializes via `serde_json::from_str` into the core type and converts to the
/// local mirror type using the `From<source_crate::TypeName> for TypeName` impl that is
/// already emitted by `emit_from_impl_for_struct`.
fn emit_from_json_fn(out: &mut String, ty: &TypeDef, source_crate_name: &str) {
    let type_name = &ty.name;
    // snake_case function name: e.g. ChatCompletionRequest → create_chat_completion_request_from_json
    let snake = to_snake_case(type_name);
    let fn_name = format!("create_{snake}_from_json");
    let core_ty = if ty.rust_path.is_empty() {
        format!("{source_crate_name}::{type_name}")
    } else {
        ty.rust_path.replace('-', "_")
    };

    out.push_str("#[frb]\n");
    out.push_str(&format!(
        "pub fn {fn_name}(json: String) -> Result<{type_name}, String> {{\n"
    ));
    out.push_str(&format!("    serde_json::from_str::<{core_ty}>(&json)\n"));
    out.push_str(&format!("        .map({type_name}::from)\n"));
    out.push_str("        .map_err(|e| e.to_string())\n");
    out.push_str("}\n");
}

/// Convert a PascalCase type name to snake_case for use in function names.
/// E.g. `ChatCompletionRequest` → `chat_completion_request`.
fn to_snake_case(s: &str) -> String {
    let mut result = String::with_capacity(s.len() + 8);
    for (i, ch) in s.char_indices() {
        if ch.is_uppercase() {
            if i > 0 {
                result.push('_');
            }
            result.extend(ch.to_lowercase());
        } else {
            result.push(ch);
        }
    }
    result
}
