use crate::backends::swift::gen_bindings::boxes::{swift_adapter_conversions, swift_box_ffi_type, swift_box_params};
use crate::backends::swift::naming::{swift_rust_shim_ident as swift_ident, swift_source_ident as swift_case_ident};
use crate::core::backend::GeneratedFile;
use crate::core::config::{BridgeBinding, ResolvedCrateConfig};
use crate::core::ir::{ApiSurface, FunctionDef, MethodDef, TypeRef};
use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
use std::collections::HashSet;
use std::path::PathBuf;

pub(crate) fn find_swift_bridge_out_dir(binding_crate_name: &str) -> Option<PathBuf> {
    let cwd = std::env::current_dir().ok()?;
    let workspace_root = std::iter::once(cwd.clone())
        .chain(cwd.ancestors().skip(1).map(|p| p.to_path_buf()))
        .take(8)
        .find(|p| p.join("Cargo.lock").exists())?;
    let target = workspace_root.join("target");

    let crate_prefix = format!("{binding_crate_name}-");
    let mut best: Option<(std::time::SystemTime, PathBuf)> = None;

    for profile in ["release", "debug"] {
        let build_dir = target.join(profile).join("build");
        let entries = match std::fs::read_dir(&build_dir) {
            Ok(e) => e,
            Err(_) => continue,
        };
        for entry in entries.flatten() {
            let name = entry.file_name();
            let name_str = name.to_string_lossy();
            if !name_str.starts_with(&crate_prefix) {
                continue;
            }
            let out = entry.path().join("out");
            let marker = out.join("SwiftBridgeCore.swift");
            if !marker.exists() {
                continue;
            }
            let mtime = std::fs::metadata(&marker)
                .and_then(|m| m.modified())
                .unwrap_or(std::time::UNIX_EPOCH);
            if best.as_ref().map(|(t, _)| mtime > *t).unwrap_or(true) {
                best = Some((mtime, out));
            }
        }
    }
    best.map(|(_, p)| p)
}

pub(crate) fn emit_swift_bridge_files(
    crate_name: &str,
    binding_crate_name: &str,
    package_root: &std::path::Path,
) -> anyhow::Result<Option<Vec<GeneratedFile>>> {
    let out_dir = match find_swift_bridge_out_dir(binding_crate_name) {
        Some(d) => d,
        None => {
            let sources_rust_bridge_c = package_root.join("Sources").join("RustBridgeC");
            let header_path = sources_rust_bridge_c.join("RustBridgeC.h");

            // Preserve an already-populated header. `alef all --clean` regenerates
            // without compiling the binding crate, so reverting to the placeholder
            // here would drop the real `__swift_bridge__$*` declarations and break
            // every SwiftPM consumer of the published source package. The presence
            // of a `__swift_bridge__$` symbol reliably distinguishes a populated
            // header (alef umbrella or a consumer concat script) from the
            // typedef-only placeholder. Mirrors the guard in
            // `scaffold::languages::swift::render_rust_bridge_c_header`.
            if let Ok(existing) = std::fs::read_to_string(&header_path) {
                if existing.contains("__swift_bridge__$") {
                    return Ok(None);
                }
            }
            let minimal_header = format!(
                "#ifndef RUST_BRIDGE_C_H\n\
                 #define RUST_BRIDGE_C_H\n\
                 \n\
                 // Placeholder header for the RustBridgeC SwiftPM target.\n\
                 // Run `cargo build -p {binding_crate_name}` and re-run `alef generate` to populate.\n\
                 // The typedefs below are the minimum required for SwiftBridgeCore.swift\n\
                 // to compile before the full cargo build has been run.\n\
                 \n\
                 #include <stdint.h>\n\
                 #include <stdbool.h>\n\
                 \n\
                 typedef struct RustStr {{ uint8_t* const start; uintptr_t len; }} RustStr;\n\
                 typedef struct __private__FfiSlice {{ void* const start; uintptr_t len; }} __private__FfiSlice;\n\
                 typedef struct __private__OptionU8 {{ uint8_t val; bool is_some; }} __private__OptionU8;\n\
                 typedef struct __private__OptionI8 {{ int8_t val; bool is_some; }} __private__OptionI8;\n\
                 typedef struct __private__OptionU16 {{ uint16_t val; bool is_some; }} __private__OptionU16;\n\
                 typedef struct __private__OptionI16 {{ int16_t val; bool is_some; }} __private__OptionI16;\n\
                 typedef struct __private__OptionU32 {{ uint32_t val; bool is_some; }} __private__OptionU32;\n\
                 typedef struct __private__OptionI32 {{ int32_t val; bool is_some; }} __private__OptionI32;\n\
                 typedef struct __private__OptionU64 {{ uint64_t val; bool is_some; }} __private__OptionU64;\n\
                 typedef struct __private__OptionI64 {{ int64_t val; bool is_some; }} __private__OptionI64;\n\
                 typedef struct __private__OptionUsize {{ uintptr_t val; bool is_some; }} __private__OptionUsize;\n\
                 typedef struct __private__OptionIsize {{ intptr_t val; bool is_some; }} __private__OptionIsize;\n\
                 typedef struct __private__OptionF32 {{ float val; bool is_some; }} __private__OptionF32;\n\
                 typedef struct __private__OptionF64 {{ double val; bool is_some; }} __private__OptionF64;\n\
                 typedef struct __private__OptionBool {{ bool val; bool is_some; }} __private__OptionBool;\n\
                 \n\
                 #endif /* RUST_BRIDGE_C_H */\n"
            );
            return Ok(Some(vec![GeneratedFile {
                path: header_path,
                content: minimal_header,
                generated_header: false,
            }]));
        }
    };

    let core_swift_src = out_dir.join("SwiftBridgeCore.swift");
    let crate_swift_src = out_dir
        .join(binding_crate_name)
        .join(format!("{binding_crate_name}.swift"));
    let core_h_src = out_dir.join("SwiftBridgeCore.h");
    let crate_h_src = out_dir.join(binding_crate_name).join(format!("{binding_crate_name}.h"));

    for p in [&core_swift_src, &crate_swift_src, &core_h_src, &crate_h_src] {
        if !p.exists() {
            return Ok(None);
        }
    }

    let core_swift = std::fs::read_to_string(&core_swift_src)
        .map_err(|e| anyhow::anyhow!("failed to read {}: {e}", core_swift_src.display()))?;
    let crate_swift = std::fs::read_to_string(&crate_swift_src)
        .map_err(|e| anyhow::anyhow!("failed to read {}: {e}", crate_swift_src.display()))?;
    let core_h = std::fs::read_to_string(&core_h_src)
        .map_err(|e| anyhow::anyhow!("failed to read {}: {e}", core_h_src.display()))?;
    let crate_h = std::fs::read_to_string(&crate_h_src)
        .map_err(|e| anyhow::anyhow!("failed to read {}: {e}", crate_h_src.display()))?;

    let core_swift_content = make_swift_bridge_ref_ptr_public(&append_rust_string_ref_to_string_extension(
        &add_retroactive_to_imported_protocol_conformances(&prepend_rust_bridge_c_import(&core_swift)),
    ));
    let crate_swift_content = make_swift_bridge_ref_ptr_public(&prepend_rust_bridge_c_import(&crate_swift));

    let rust_bridge_c_h = format!(
        "#ifndef RUST_BRIDGE_C_H\n\
         #define RUST_BRIDGE_C_H\n\
         \n\
         // Auto-generated by alef — do not edit by hand.\n\
         // Concatenates SwiftBridgeCore.h and {binding_crate_name}.h produced by\n\
         // `cargo build -p {binding_crate_name}` via swift_bridge_build.\n\
         \n\
         {core_h}\n\
         {crate_h}\n\
         #endif /* RUST_BRIDGE_C_H */\n"
    );

    let sources_rust_bridge = package_root.join("Sources").join("RustBridge");
    let sources_rust_bridge_c = package_root.join("Sources").join("RustBridgeC");
    let _ = crate_name;
    let files = vec![
        GeneratedFile {
            path: sources_rust_bridge.join("SwiftBridgeCore.swift"),
            content: core_swift_content,
            generated_header: false,
        },
        GeneratedFile {
            path: sources_rust_bridge.join(format!("{binding_crate_name}.swift")),
            content: crate_swift_content,
            generated_header: false,
        },
        GeneratedFile {
            path: sources_rust_bridge_c.join("RustBridgeC.h"),
            content: rust_bridge_c_h,
            generated_header: false,
        },
    ];
    Ok(Some(files))
}

pub(super) fn emit_inbound_protocols(
    api: &ApiSurface,
    config: &ResolvedCrateConfig,
    exclude_types: &HashSet<String>,
    out: &mut String,
) {
    for bridge_cfg in &config.trait_bridges {
        if bridge_cfg.bind_via != BridgeBinding::OptionsField {
            continue;
        }
        if bridge_cfg.exclude_languages.iter().any(|l| l == "swift") {
            continue;
        }
        let trait_name = &bridge_cfg.trait_name;
        let type_alias = match bridge_cfg.type_alias.as_deref() {
            Some(a) => a,
            None => continue,
        };
        let Some(options_type) = bridge_cfg.options_type.as_deref() else {
            continue;
        };
        let Some(field) = bridge_cfg.resolved_options_field() else {
            continue;
        };
        let result_type_name = bridge_cfg.result_type.as_deref();
        let protocol_return_type = result_type_name.unwrap_or("Void");

        let Some(trait_def) = api.types.iter().find(|t| t.is_trait && t.name == *trait_name) else {
            continue;
        };

        let result_enum = result_type_name.and_then(|name| api.enums.iter().find(|e| e.name == name));
        let box_name = format!("Swift{trait_name}Box");
        let adapter_name = format!("_{trait_name}ProtocolAdapter");
        let protocol_name = format!("{trait_name}Protocol");
        let delegate_protocol_name = format!("_Swift{trait_name}BoxDelegate");
        // Factory function name: `make{TraitCamel}Handle`, matching the spec
        // in `mod.rs` ("A factory func `make{TraitCamel}Handle(...)`") and the
        // identical convention used by the e2e test generator
        // (`swift_visitors::build_swift_visitor` → `make{Trait}Handle`) and the
        // user-facing snippets under `docs/snippets/swift/visitor/`. Naming this
        // `make{Trait}{TypeAlias}` (e.g. `makeHtmlVisitorVisitorHandle` because
        // type_alias is `VisitorHandle`) silently doubles the trait stem and
        // means every e2e + snippet reference fails at compile time with
        // `cannot find 'make{Trait}Handle' in scope`. `type_alias` is still used
        // downstream as the return type.
        let factory_fn = format!("make{}Handle", trait_name.to_upper_camel_case());

        out.push_str(&crate::backends::swift::template_env::render(
            "swift_bridge_protocol_open.swift.jinja",
            minijinja::context! { protocol_name => &protocol_name, },
        ));
        for method in &trait_def.methods {
            let method_snake = method.name.to_snake_case();
            let method_camel = method_snake.to_lower_camel_case();
            let params = swift_protocol_params(method, exclude_types);
            out.push_str(&crate::backends::swift::template_env::render(
                "swift_bridge_protocol_method.swift.jinja",
                minijinja::context! {
                    method_name => &method_camel,
                    params => &params,
                    return_type => protocol_return_type,
                },
            ));
        }
        out.push_str("}\n\n");

        let default_case = result_enum
            .and_then(|en| en.variants.iter().find(|v| v.fields.is_empty()))
            .map(|v| swift_case_ident(&v.name.to_lower_camel_case()));
        let default_case_doc = default_case.as_ref().map(|case| format!(".{case}"));
        out.push_str(&crate::backends::swift::template_env::render(
            "swift_bridge_protocol_default_open.swift.jinja",
            minijinja::context! {
                protocol_name => &protocol_name,
                default_case => default_case_doc.as_deref(),
            },
        ));
        for method in &trait_def.methods {
            let method_snake = method.name.to_snake_case();
            let method_camel = method_snake.to_lower_camel_case();
            let underscore_params = swift_protocol_underscore_params(method, exclude_types);
            let (return_type, body) = if let Some(default_case) = &default_case {
                (Some(protocol_return_type), format!("return .{default_case}"))
            } else {
                (None, String::new())
            };
            out.push_str(&crate::backends::swift::template_env::render(
                "swift_bridge_protocol_default_method.swift.jinja",
                minijinja::context! {
                    method_name => &method_camel,
                    params => &underscore_params,
                    return_type => return_type,
                    body => &body,
                },
            ));
        }
        out.push_str("}\n\n");

        out.push_str(&crate::backends::swift::template_env::render(
            "swift_bridge_adapter_open.swift.jinja",
            minijinja::context! {
                adapter_name => &adapter_name,
                delegate_protocol_name => &delegate_protocol_name,
                protocol_name => &protocol_name,
            },
        ));
        for method in &trait_def.methods {
            let method_snake = method.name.to_snake_case();
            let method_camel = method_snake.to_lower_camel_case();
            let delegate_method = swift_ident(&method_camel);
            let delegate_params = swift_box_params(method);
            let (conversion_lines, call_args) = swift_adapter_conversions(method, exclude_types);
            out.push_str(&crate::backends::swift::template_env::render(
                "swift_bridge_adapter_method_open.swift.jinja",
                minijinja::context! {
                    method_name => &delegate_method,
                    params => &delegate_params,
                },
            ));
            for line in &conversion_lines {
                out.push_str(&crate::backends::swift::template_env::render(
                    "swift_forwarder_conversion_line.swift.jinja",
                    minijinja::context! { line => line, },
                ));
            }
            let result_json = if let Some(result_type_name) = result_type_name.filter(|_| result_enum.is_some()) {
                format!(
                    "        return {}_toJson(inner.{method_camel}({call_args}))\n",
                    result_type_name.to_snake_case()
                )
            } else {
                let call = if call_args.is_empty() {
                    format!("inner.{method_camel}()")
                } else {
                    format!("inner.{method_camel}({call_args})")
                };
                crate::backends::swift::template_env::render(
                    "swift_bridge_adapter_void_return.swift.jinja",
                    minijinja::context! { call => &call, },
                )
            };
            out.push_str(&result_json);
            out.push_str(&crate::backends::swift::template_env::render(
                "swift_bridge_adapter_method_close.swift.jinja",
                minijinja::context! {},
            ));
        }
        out.push_str("}\n\n");

        if let Some(en) = result_enum {
            let result_type_name = en.name.as_str();
            let fn_name = format!("{}_toJson", result_type_name.to_snake_case());
            out.push_str(&crate::backends::swift::template_env::render(
                "swift_bridge_result_helper_open.swift.jinja",
                minijinja::context! {
                    result_type_name => result_type_name,
                    function_name => &fn_name,
                },
            ));
            for variant in &en.variants {
                let variant_name = &variant.name;
                let swift_case = swift_case_ident(&variant_name.to_lower_camel_case());
                if variant.fields.is_empty() {
                    out.push_str(&crate::backends::swift::template_env::render(
                        "swift_bridge_result_unit_case.swift.jinja",
                        minijinja::context! {
                            swift_case => &swift_case,
                            variant_name => variant_name,
                        },
                    ));
                } else if variant.is_tuple && variant.fields.len() == 1 {
                    out.push_str(&crate::backends::swift::template_env::render(
                        "swift_bridge_result_newtype_case.swift.jinja",
                        minijinja::context! {
                            swift_case => &swift_case,
                            variant_name => variant_name,
                        },
                    ));
                }
            }
            out.push_str("    }\n}\n\n");
            out.push_str(&crate::backends::swift::template_env::render(
                "swift_bridge_json_escape_helper.swift.jinja",
                minijinja::context! {},
            ));
            out.push('\n');
        }

        let opts_snake = options_type.to_snake_case();
        let options_fn = format!("{opts_snake}FromJsonWith{}", field.to_upper_camel_case()).to_lower_camel_case();
        out.push_str(&crate::backends::swift::template_env::render(
            "swift_bridge_factory.swift.jinja",
            minijinja::context! {
                protocol_name => &protocol_name,
                type_alias => type_alias,
                options_fn => &options_fn,
                factory_fn => &factory_fn,
                box_name => &box_name,
                adapter_name => &adapter_name,
            },
        ));
        out.push('\n');

        out.push_str(&crate::backends::swift::template_env::render(
            "swift_bridge_options_forwarder.swift.jinja",
            minijinja::context! {
                options_type => options_type,
                type_alias => type_alias,
                options_fn => &options_fn,
                field => &field,
            },
        ));
        out.push('\n');
    }
}

pub(super) fn already_emitted_top_level_names(api: &ApiSurface) -> HashSet<String> {
    let mut names: HashSet<String> = HashSet::new();
    for func in &api.functions {
        if func.is_async {
            continue;
        }
        let first = func.params.first().map(|p| &p.ty);
        let is_bytes_or_path = matches!(first, Some(TypeRef::Bytes) | Some(TypeRef::Path));
        if !is_bytes_or_path {
            continue;
        }
        if convenience_name_shadows_bridge(func) {
            continue;
        }
        let swift_inner = swift_ident(&func.name.to_lower_camel_case());
        let wrapper_name = if swift_inner.ends_with("Sync") {
            swift_inner[..swift_inner.len() - 4].to_string()
        } else {
            swift_inner
        };
        names.insert(wrapper_name);
    }
    names
}

pub(super) fn emit_ref_property_extensions(api: &ApiSurface) -> Option<(String, String)> {
    let eligible_types: Vec<_> = api
        .types
        .iter()
        .filter(|t| !t.is_trait && !t.is_opaque && !t.methods.is_empty())
        .collect();

    if eligible_types.is_empty() {
        return None;
    }

    let mut content = String::new();
    content.push_str("import RustBridge\n\n");
    content.push_str("// MARK: - Property-access ergonomics for e2e tests\n");
    content.push_str("//\n");
    content.push_str("// This file provides computed-property aliases for methods on swift-bridge-generated types,\n");
    content.push_str("// allowing callers to write `result.mimeType` rather than `result.mimeType()`.\n");
    content.push_str("// These extensions are especially useful in e2e test assertions where the alef\n");
    content.push_str("// fixture generator emits property-access syntax.\n");
    content.push_str("//\n");
    content.push_str("// Although these are primarily for test convenience, they are part of the public API\n");
    content.push_str("// and can be used in production code for more ergonomic access to generated ref types.\n");

    let mut has_any_extensions = false;

    for ty in eligible_types {
        let mut type_has_extensions = false;
        let mut type_content = String::new();
        for method in &ty.methods {
            if method.is_async || method.is_static || method.binding_excluded {
                continue;
            }
            if !matches!(&method.return_type, TypeRef::String) || method.params.is_empty() {
                continue;
            }
            if !method.params.iter().all(|p| is_extension_param_bridgeable(&p.ty, api)) {
                continue;
            }

            if !type_has_extensions {
                type_content.push('\n');
                type_content.push_str(&crate::backends::swift::template_env::render(
                    "swift_ref_extension_open.swift.jinja",
                    minijinja::context! { type_name => &ty.name, },
                ));
                type_has_extensions = true;
            } else {
                type_content.push('\n');
            }

            let camel = method.name.to_lower_camel_case();
            type_content.push_str(&crate::backends::swift::template_env::render(
                "swift_ref_string_alias_property.swift.jinja",
                minijinja::context! {
                    method_name => &camel,
                    property_name => &camel,
                },
            ));
        }
        if type_has_extensions {
            type_content.push_str("}\n");
            type_content.push_str(&crate::backends::swift::template_env::render(
                "swift_ref_extension_inheritance_comment.swift.jinja",
                minijinja::context! {
                    type_name => &ty.name,
                },
            ));
            content.push_str(&type_content);
            has_any_extensions = true;
        }
    }

    if has_any_extensions {
        Some(("RustBridgeRefExtensions.swift".to_string(), content))
    } else {
        None
    }
}

fn convenience_name_shadows_bridge(func: &FunctionDef) -> bool {
    let swift_name = swift_ident(&func.name.to_lower_camel_case());
    let wrapper_name = if swift_name.ends_with("Sync") {
        swift_name[..swift_name.len() - 4].to_string()
    } else {
        swift_name.clone()
    };
    wrapper_name == swift_name
}

fn swift_protocol_params(method: &MethodDef, exclude_types: &HashSet<String>) -> String {
    let params: Vec<String> = method
        .params
        .iter()
        .map(|p| {
            let name = p.name.to_lower_camel_case();
            let ty = swift_inbound_type(&p.ty, p.optional, exclude_types);
            format!("_ {name}: {ty}")
        })
        .collect();
    params.join(", ")
}

fn swift_protocol_underscore_params(method: &MethodDef, exclude_types: &HashSet<String>) -> String {
    let params: Vec<String> = method
        .params
        .iter()
        .map(|p| {
            let name = p.name.to_lower_camel_case();
            let ty = swift_inbound_type(&p.ty, p.optional, exclude_types);
            format!("_ _{name}: {ty}")
        })
        .collect();
    params.join(", ")
}

fn swift_inbound_type(ty: &TypeRef, optional: bool, exclude_types: &HashSet<String>) -> String {
    use crate::core::ir::PrimitiveType;
    let inner = match ty {
        TypeRef::Named(name) if exclude_types.contains(name) => "String".to_string(),
        TypeRef::Named(name) => name.clone(),
        TypeRef::String => "String".to_string(),
        TypeRef::Primitive(PrimitiveType::Bool) => "Bool".to_string(),
        TypeRef::Primitive(PrimitiveType::U32) => "UInt32".to_string(),
        TypeRef::Primitive(PrimitiveType::U64) => "UInt64".to_string(),
        TypeRef::Primitive(PrimitiveType::I32) => "Int32".to_string(),
        TypeRef::Primitive(PrimitiveType::I64) => "Int64".to_string(),
        TypeRef::Primitive(PrimitiveType::Usize) => "UInt".to_string(),
        TypeRef::Primitive(PrimitiveType::Isize) => "Int".to_string(),
        TypeRef::Primitive(PrimitiveType::F32) => "Float".to_string(),
        TypeRef::Primitive(PrimitiveType::F64) => "Double".to_string(),
        TypeRef::Primitive(PrimitiveType::U8) => "UInt8".to_string(),
        TypeRef::Primitive(PrimitiveType::I8) => "Int8".to_string(),
        TypeRef::Primitive(PrimitiveType::U16) => "UInt16".to_string(),
        TypeRef::Primitive(PrimitiveType::I16) => "Int16".to_string(),
        TypeRef::Vec(inner) => format!("RustVec<{}>", swift_box_ffi_type(inner, false)),
        TypeRef::Optional(inner) => return format!("{}?", swift_inbound_type(inner, false, exclude_types)),
        TypeRef::Unit => "Void".to_string(),
        TypeRef::Bytes => "RustVec<UInt8>".to_string(),
        TypeRef::Char => "Character".to_string(),
        TypeRef::Path => "String".to_string(),
        TypeRef::Json => "String".to_string(),
        TypeRef::Duration => "Double".to_string(),
        TypeRef::Map(_, _) => "String".to_string(),
    };
    if optional { format!("{inner}?") } else { inner }
}

fn append_rust_string_ref_to_string_extension(content: &str) -> String {
    const MARKER: &str = "// alef: RustStringRef.toString() shim";
    if let Some(idx) = content.find(MARKER) {
        let mut head = content[..idx].to_string();
        while head.ends_with('\n') {
            head.pop();
        }
        head.push('\n');
        head
    } else {
        content.to_string()
    }
}

fn make_swift_bridge_ref_ptr_public(content: &str) -> String {
    content
        .replace(
            "    var ptr: UnsafeMutableRawPointer",
            "    public var ptr: UnsafeMutableRawPointer",
        )
        .replace("    var isOwned: Bool = true", "    public var isOwned: Bool = true")
}

fn add_retroactive_to_imported_protocol_conformances(content: &str) -> String {
    const TARGETS: &[(&str, &str)] = &[
        (
            "extension RustStr: Identifiable",
            "extension RustStr: @retroactive Identifiable",
        ),
        (
            "extension RustStr: Equatable",
            "extension RustStr: @retroactive Equatable",
        ),
    ];
    let mut out = content.to_string();
    for (from, to) in TARGETS {
        out = out.replace(from, to);
    }
    out
}

fn prepend_rust_bridge_c_import(content: &str) -> String {
    const IMPORT: &str = "import RustBridgeC";
    const IGNORE: &str = "// swift-format-ignore-file";
    let head: Vec<&str> = content.lines().take(5).collect();
    let has_import = head.iter().any(|l| l.trim() == IMPORT);
    let has_ignore = head.iter().any(|l| l.trim() == IGNORE);
    match (has_import, has_ignore) {
        (true, true) => content.to_string(),
        (true, false) => format!("{IGNORE}\n{content}"),
        (false, true) => format!("{IMPORT}\n\n{content}"),
        (false, false) => format!("{IGNORE}\n{IMPORT}\n\n{content}"),
    }
}

fn is_extension_param_bridgeable(ty: &TypeRef, api: &ApiSurface) -> bool {
    match ty {
        TypeRef::Named(n) if n.starts_with("Result") || n == "Result" => false,
        TypeRef::Primitive(_)
        | TypeRef::String
        | TypeRef::Path
        | TypeRef::Bytes
        | TypeRef::Duration
        | TypeRef::Unit => true,
        TypeRef::Named(n) => {
            if let Some(enum_def) = api.enums.iter().find(|e| &e.name == n) {
                enum_def.has_serde
            } else {
                true
            }
        }
        TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_extension_param_bridgeable(inner, api),
        TypeRef::Map(..) | TypeRef::Char | TypeRef::Json => false,
    }
}
