// 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::core::backend::GeneratedFile;
use crate::core::config::{AdapterConfig, AdapterPattern, ResolvedCrateConfig, resolve_output_dir};
use crate::core::ir::{ApiSurface, EnumDef, TypeDef};
use std::collections::HashSet;

mod bridge_fn;
mod cargo;
mod conversions;
mod enum_conversions;
mod helpers;
mod mirror;
mod mirror_conversions;
#[cfg(test)]
mod mirror_conversions_tests;
mod opaque;
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_excluded_bridge_types, emit_trait_bridge, needs_excluded_bridge_type};

/// 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>> {
    // Collapse same-named cfg-variant functions into one canonical entry. The FRB Rust mirror
    // emits a plain `pub fn` per function that delegates to the core crate (which resolves the
    // cfg itself) with no `#[cfg]` gate, so two same-named entries under disjoint cfg gates would
    // otherwise produce duplicate `pub fn` definitions (E0428) — e.g. `ensure_crypto_provider`.
    // The dart host-side `gen_bindings` already dedups; the mirror crate needs the same. See
    // codegen::fn_dedup.
    let deduped_api = api.with_deduped_functions();
    let api = &deduped_api;

    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();
    // 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, unreachable_patterns)]\n");
    // FRB-generated functions carry no human-authored docstrings; the names
    // and the bridge contract are the spec. Suppress `missing_docs` so
    // consumers running `cargo doc -- -D missing-docs` (shared `rustdoc-lint`
    // hook) can commit changes that regenerate the dart bridge.
    content.push_str("#![allow(missing_docs)]\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");

    let has_excluded_type_trait_bridge = 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())
        .any(|m| {
            needs_excluded_bridge_type(&m.return_type, &api.excluded_type_paths)
                || m.params
                    .iter()
                    .any(|p| needs_excluded_bridge_type(&p.ty, &api.excluded_type_paths))
        });
    if has_excluded_type_trait_bridge {
        emit_excluded_bridge_types(&mut content, api);
    }

    // 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, excluded carrier types can 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;
                }
                let rendered_path = path.replace('-', "_");
                content.push_str(&crate::backends::dart::template_env::render(
                    "rust_pub_use.rs.jinja",
                    minijinja::context! {
                        path => rendered_path.as_str(),
                    },
                ));
                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 || opaque::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 || opaque::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. a batch item contains a config DTO
    // 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> =
        mirror_conversions::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 && !t.binding_excluded)
    {
        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 AND enums that
    // receive a `#[frb(mirror(TypeName))]` 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. The
    // enum branch also avoids the orphan-rule trap where `use source_crate::Method;` at
    // module scope shadows the local mirror enum, causing `impl From<source_crate::Method>
    // for Method` to be interpreted as an impl for the source crate's `Method` (both
    // foreign → E0117).
    let mirror_type_names: HashSet<String> = api
        .types
        .iter()
        .filter(|t| !exclude_types.contains(&t.name) && !t.is_trait && !t.is_opaque && !t.binding_excluded)
        .map(|t| t.name.clone())
        .chain(
            api.enums
                .iter()
                .filter(|e| !exclude_types.contains(&e.name) && !e.binding_excluded)
                .map(|e| e.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.binding_excluded && !t.methods.is_empty()
    }) {
        content.push('\n');
        opaque::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) && !t.binding_excluded)
    {
        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(&crate::backends::dart::template_env::render(
                "rust_client_constructor_impl.rs.jinja",
                minijinja::context! {
                    type_name => ty.name.as_str(),
                    ctor_body => ctor_body.as_str(),
                },
            ));
            content.push('\n');
        }
    }

    for en in api
        .enums
        .iter()
        .filter(|e| !exclude_types.contains(&e.name) && !e.binding_excluded)
    {
        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 && !t.binding_excluded)
    {
        content.push('\n');
        mirror_conversions::emit_from_impl_for_struct(&mut content, ty, source_crate_name);
    }
    for en in api
        .enums
        .iter()
        .filter(|e| !exclude_types.contains(&e.name) && !e.binding_excluded)
    {
        content.push('\n');
        enum_conversions::emit_from_impl_for_enum(&mut content, en, source_crate_name);
    }

    // Collect service configurator-param types that need From<Mirror> for Source impls.
    // These types don't necessarily have sanitized fields but are passed as mirror types
    // from Dart to the core service and need explicit From conversions.
    let configurator_param_types: HashSet<String> = api
        .services
        .iter()
        .flat_map(|s| s.configurators.iter())
        .flat_map(|cfg| cfg.params.iter())
        .flat_map(|p| mirror_conversions::collect_named_types_from_type_ref(&p.ty))
        .filter(|name| !exclude_types.contains(name))
        .collect();

    // 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) && !opaque::has_unbridgeable_param(f))
        .flat_map(|f| f.params.iter())
        .flat_map(|p| mirror_conversions::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| mirror_conversions::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| mirror_conversions::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 =
        mirror_conversions::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 && !t.binding_excluded)
    {
        content.push('\n');
        mirror_conversions::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) && !e.binding_excluded)
    {
        content.push('\n');
        enum_conversions::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');
                mirror_conversions::emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
            }
        }
    }

    // Service configurator parameters are passed as mirror types from Dart to the core
    // service and need From<Mirror> for Source impls, even if they don't have sanitized
    // fields. Compute the transitive closure of configurator-param types and emit From
    // impls for those that haven't already been covered above.
    let configurator_param_closure =
        mirror_conversions::compute_types_needing_from_impl(api, &configurator_param_types, exclude_types);
    {
        let configurator_param_mirror_types: Vec<&TypeDef> = api
            .types
            .iter()
            .filter(|t| {
                configurator_param_closure.contains(&t.name)
                    && !types_needing_from_impl.contains(&t.name)
                    && !t.is_trait
                    && !t.is_opaque
                    && !t.binding_excluded
            })
            .collect();
        if !configurator_param_mirror_types.is_empty() {
            content.push_str("\n// From<T> for SourceT conversions for service-configurator mirror parameter types.\n");
            for ty in configurator_param_mirror_types {
                content.push('\n');
                mirror_conversions::emit_from_mirror_to_core_struct(&mut content, ty, source_crate_name);
            }
        }
    }
    // Also emit From<MirrorEnum> for enums in the configurator-param closure
    {
        let configurator_param_mirror_enums: Vec<&EnumDef> = api
            .enums
            .iter()
            .filter(|e| {
                configurator_param_closure.contains(&e.name)
                    && !types_needing_from_impl.contains(&e.name)
                    && !e.binding_excluded
            })
            .collect();
        if !configurator_param_mirror_enums.is_empty() {
            for en in configurator_param_mirror_enums {
                content.push('\n');
                enum_conversions::emit_from_mirror_to_core_enum(&mut content, en, 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))
        .filter(|f| !opaque::has_unbridgeable_param(f))
        // Skip functions whose name matches a trait-bridge lifecycle function. The
        // trait-bridge emission path emits its own binding-safe forwarder; a duplicate
        // ordinary wrapper here can expose raw Rust trait parameters that FRB cannot
        // compile.
        .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 && !t.binding_excluded)
        // 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');
        opaque::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');
            let lifetime_type_names: std::collections::HashSet<String> = api
                .types
                .iter()
                .filter(|t| t.has_lifetime_params)
                .map(|t| t.name.clone())
                .collect();
            emit_trait_bridge(
                &mut content,
                trait_def,
                bridge_cfg,
                api,
                source_crate_name,
                &type_paths,
                &lifetime_type_names,
            );
        }
    }

    // Include service-API module if services are present.
    // The service_api.rs file is generated separately and contains FRB opaque owners and handler bridges.
    if !api.services.is_empty() {
        content.push_str("\nmod service_api;\npub use service_api::*;\n");
    }

    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('-', "_")
}
