//! C# trait bridge support via P/Invoke and managed delegates.
//!
//! For C# backends that use C FFI (FFI dependency), this module generates:
//! 1. P/Invoke declarations for trait bridge registration/unregistration functions
//! 2. Managed `interface I{TraitName}` with Plugin lifecycle + trait methods
//! 3. Bridge class `{TraitName}Bridge` implementing marshal helpers, delegate rooting, and vtable construction
//! 4. Static registration helpers: `RegisterOcrBackend(IOcrBackend impl)`, `UnregisterOcrBackend(string name)`

use crate::type_map::csharp_type;
use alef_codegen::naming::to_csharp_name;
use alef_core::config::TraitBridgeConfig;
use alef_core::ir::{TypeDef, TypeRef};
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use std::fmt::Write;

/// Maps a TypeRef to its unmanaged C# type for use in [UnmanagedFunctionPointer] delegates.
/// Managed types (arrays, classes, strings) become IntPtr; primitives remain as-is.
fn csharp_unmanaged_type(ty: &TypeRef) -> String {
    match ty {
        TypeRef::Primitive(_) => csharp_type(ty).to_string(),
        TypeRef::Unit => csharp_type(ty).to_string(),
        // All managed types (String, Bytes, Vec, Optional containing managed, Named classes, etc.) become IntPtr
        _ => "IntPtr".to_string(),
    }
}

/// Generate P/Invoke trait bridge declarations for NativeMethods.cs.
///
/// For each trait bridge in the config, returns a C# P/Invoke declaration
/// for the register and unregister functions.
pub fn gen_native_methods_trait_bridges(
    _namespace: &str,
    prefix: &str,
    bridges: &[(String, &TraitBridgeConfig, &TypeDef)],
) -> String {
    let mut out = String::with_capacity(1024);

    if bridges.is_empty() {
        return out;
    }

    writeln!(out).ok();
    writeln!(out, "    // Trait Bridge FFI").ok();

    for (trait_name, _config, _trait_def) in bridges {
        let trait_snake = trait_name.to_snake_case();
        let register_fn = format!("{prefix}_register_{trait_snake}");
        let unregister_fn = format!("{prefix}_unregister_{trait_snake}");

        writeln!(out).ok();
        writeln!(
            out,
            "    [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = \"{register_fn}\")]"
        )
        .ok();
        writeln!(
            out,
            "    internal static extern int Register{}([MarshalAs(UnmanagedType.LPUTF8Str)] string name, IntPtr vtable, IntPtr userData, out IntPtr outError);",
            trait_name
        )
        .ok();
        writeln!(out).ok();
        writeln!(
            out,
            "    [DllImport(LibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = \"{unregister_fn}\")]"
        )
        .ok();
        writeln!(
            out,
            "    internal static extern int Unregister{}([MarshalAs(UnmanagedType.LPUTF8Str)] string name, out IntPtr outError);",
            trait_name
        )
        .ok();
    }

    out
}

/// Generate the complete TraitBridges.cs file for all configured trait bridges.
///
/// For each bridge in the config:
/// - Generates a managed `interface I{TraitName}` with Plugin lifecycle methods (when super_trait set)
/// - Generates a `{TraitName}Bridge` class with delegate rooting, GCHandle management, and vtable construction
/// - Generates static registration helpers for `Register{TraitName}` / `Unregister{TraitName}`
///
/// Returns a tuple of (filename, content) ready for GeneratedFile emission.
pub fn gen_trait_bridges_file(
    namespace: &str,
    prefix: &str,
    bridges: &[(String, &TraitBridgeConfig, &TypeDef)],
) -> (String, String) {
    let mut out = String::with_capacity(16_384);

    // Write file header
    writeln!(out, "// DO NOT EDIT — generated by alef").ok();
    writeln!(
        out,
        "// This file bridges managed C# trait implementations to the native FFI layer"
    )
    .ok();
    writeln!(out, "#nullable enable").ok();
    writeln!(out).ok();
    writeln!(out, "using System;").ok();
    writeln!(out, "using System.Collections.Concurrent;").ok();
    writeln!(out, "using System.Collections.Generic;").ok();
    writeln!(out, "using System.Runtime.InteropServices;").ok();
    writeln!(out, "using System.Text.Json;").ok();
    writeln!(out).ok();
    writeln!(out, "namespace {};", namespace).ok();
    writeln!(out).ok();

    // Generate each trait bridge
    for (trait_name, bridge_cfg, trait_def) in bridges {
        // Skip if csharp is in exclude_languages
        if bridge_cfg.exclude_languages.iter().any(|lang| lang == "csharp") {
            continue;
        }

        gen_single_trait_bridge(&mut out, trait_name, bridge_cfg, trait_def, prefix);
        writeln!(out).ok();
    }

    ("TraitBridges.cs".to_string(), out)
}

fn gen_single_trait_bridge(
    out: &mut String,
    trait_name: &str,
    bridge_cfg: &TraitBridgeConfig,
    trait_def: &TypeDef,
    _prefix: &str,
) {
    let trait_pascal = trait_name.to_pascal_case();
    let _trait_snake = trait_name.to_snake_case();
    let has_super_trait = bridge_cfg.super_trait.is_some();
    let has_bytes_param = trait_def
        .methods
        .iter()
        .flat_map(|m| m.params.iter())
        .any(|p| matches!(&p.ty, TypeRef::Bytes));

    // --- Public Interface ---
    writeln!(out, "/// <summary>").ok();
    writeln!(
        out,
        "/// Bridge interface for {} trait implementation via native FFI",
        trait_pascal
    )
    .ok();
    writeln!(out, "/// </summary>").ok();
    writeln!(out, "public interface I{} {{", trait_pascal).ok();
    writeln!(out).ok();

    // Plugin lifecycle methods (when super_trait is set)
    if has_super_trait {
        writeln!(out, "    /// <summary>Get the plugin name.</summary>").ok();
        writeln!(out, "    string Name {{ get; }}").ok();
        writeln!(out).ok();

        writeln!(out, "    /// <summary>Get the plugin version.</summary>").ok();
        writeln!(out, "    string Version {{ get; }}").ok();
        writeln!(out).ok();

        writeln!(out, "    /// <summary>Initialize the plugin.</summary>").ok();
        writeln!(out, "    void Initialize();").ok();
        writeln!(out).ok();

        writeln!(out, "    /// <summary>Shut down the plugin.</summary>").ok();
        writeln!(out, "    void Shutdown();").ok();
        writeln!(out).ok();
    }

    // Trait methods
    for method in &trait_def.methods {
        let return_type = csharp_type(&method.return_type);
        let params = method
            .params
            .iter()
            .map(|p| format!("{} {}", csharp_type(&p.ty), to_csharp_name(&p.name)))
            .collect::<Vec<_>>()
            .join(", ");

        writeln!(out, "    /// <summary>{}</summary>", method.name).ok();
        writeln!(out, "    {} {}({});", return_type, to_csharp_name(&method.name), params).ok();
        writeln!(out).ok();
    }

    writeln!(out, "}}").ok();
    writeln!(out).ok();

    // --- Bridge Class ---
    writeln!(out, "/// <summary>").ok();
    writeln!(
        out,
        "/// Manages the FFI vtable and delegates for a {} implementation",
        trait_pascal
    )
    .ok();
    writeln!(out, "/// </summary>").ok();
    writeln!(out, "public sealed class {}Bridge : IDisposable {{", trait_pascal).ok();
    writeln!(out).ok();

    // Count vtable slots: (4 super_trait slots) + trait methods + 1 free_user_data
    let num_methods = trait_def.methods.len();
    let num_super_slots = if has_super_trait { 4usize } else { 0usize };
    let num_vtable_fields = num_super_slots + num_methods + 1;

    writeln!(out, "    private readonly I{} _impl;", trait_pascal).ok();
    writeln!(out, "    private readonly GCHandle _implHandle;").ok();
    writeln!(out, "    internal IntPtr _vtable;").ok();
    writeln!(out, "    private bool _disposed;").ok();
    writeln!(out).ok();

    // Declare delegate types for each vtable slot
    writeln!(out, "    // Vtable slot delegates ({})", num_vtable_fields).ok();

    if has_super_trait {
        writeln!(out, "    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
        writeln!(
            out,
            "    private delegate int NameFn(IntPtr userData, out IntPtr outName);"
        )
        .ok();
        writeln!(out).ok();

        writeln!(out, "    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
        writeln!(
            out,
            "    private delegate int VersionFn(IntPtr userData, out IntPtr outVersion);"
        )
        .ok();
        writeln!(out).ok();

        writeln!(out, "    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
        writeln!(
            out,
            "    private delegate int InitializeFn(IntPtr userData, out IntPtr outError);"
        )
        .ok();
        writeln!(out).ok();

        writeln!(out, "    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
        writeln!(
            out,
            "    private delegate int ShutdownFn(IntPtr userData, out IntPtr outError);"
        )
        .ok();
        writeln!(out).ok();
    }

    // Trait method delegates (using unmanaged types)
    for method in &trait_def.methods {
        let params = method
            .params
            .iter()
            .map(|p| format!("{} {}", csharp_unmanaged_type(&p.ty), to_csharp_name(&p.name)))
            .collect::<Vec<_>>()
            .join(", ");

        writeln!(out, "    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
        let method_pascal = to_csharp_name(&method.name);
        if params.is_empty() {
            writeln!(
                out,
                "    private delegate int {}Fn(IntPtr userData, out IntPtr outResult, out IntPtr outError);",
                method_pascal
            )
            .ok();
        } else {
            writeln!(
                out,
                "    private delegate int {}Fn(IntPtr userData, {}, out IntPtr outResult, out IntPtr outError);",
                method_pascal, params
            )
            .ok();
        }
        writeln!(out).ok();
    }

    writeln!(out, "    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]").ok();
    writeln!(out, "    private delegate void FreeUserDataFn(IntPtr userData);").ok();
    writeln!(out).ok();

    // Constructor
    writeln!(out, "    public {}Bridge(I{} impl) {{", trait_pascal, trait_pascal).ok();
    writeln!(
        out,
        "        _impl = impl ?? throw new ArgumentNullException(nameof(impl));"
    )
    .ok();
    writeln!(out, "        _implHandle = GCHandle.Alloc(impl, GCHandleType.Normal);").ok();
    writeln!(out, "        _vtable = IntPtr.Zero;").ok();
    writeln!(out, "        _disposed = false;").ok();
    writeln!(out, "        BuildVtable();").ok();
    writeln!(out, "    }}").ok();
    writeln!(out).ok();

    // BuildVtable method
    writeln!(out, "    private void BuildVtable() {{").ok();
    writeln!(
        out,
        "        // Allocate unmanaged vtable struct (array of function pointers)"
    )
    .ok();
    writeln!(
        out,
        "        _vtable = Marshal.AllocHGlobal(IntPtr.Size * {});",
        num_vtable_fields
    )
    .ok();
    writeln!(out).ok();

    let mut offset = 0usize;
    let ptr_size = std::mem::size_of::<*const ()>();

    // Plugin lifecycle slots
    if has_super_trait {
        writeln!(out, "        // Slot {}: name_fn", offset).ok();
        writeln!(out, "        var nameFn = new NameFn(NameFnCallback);",).ok();
        writeln!(
            out,
            "        Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(nameFn));",
            offset * ptr_size
        )
        .ok();
        writeln!(out).ok();
        offset += 1;

        writeln!(out, "        // Slot {}: version_fn", offset).ok();
        writeln!(out, "        var versionFn = new VersionFn(VersionFnCallback);",).ok();
        writeln!(
            out,
            "        Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(versionFn));",
            offset * ptr_size
        )
        .ok();
        writeln!(out).ok();
        offset += 1;

        writeln!(out, "        // Slot {}: initialize_fn", offset).ok();
        writeln!(out, "        var initFn = new InitializeFn(InitializeFnCallback);",).ok();
        writeln!(
            out,
            "        Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(initFn));",
            offset * ptr_size
        )
        .ok();
        writeln!(out).ok();
        offset += 1;

        writeln!(out, "        // Slot {}: shutdown_fn", offset).ok();
        writeln!(out, "        var shutdownFn = new ShutdownFn(ShutdownFnCallback);",).ok();
        writeln!(
            out,
            "        Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(shutdownFn));",
            offset * ptr_size
        )
        .ok();
        writeln!(out).ok();
        offset += 1;
    }

    // Trait method slots
    for method in &trait_def.methods {
        let method_pascal = to_csharp_name(&method.name);
        writeln!(out, "        // Slot {}: {}_fn", offset, method.name).ok();
        writeln!(
            out,
            "        var {}Fn = new {}Fn({}FnCallback);",
            method.name.to_lower_camel_case(),
            method_pascal,
            method_pascal
        )
        .ok();
        writeln!(
            out,
            "        Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate({}Fn));",
            offset * ptr_size,
            method.name.to_lower_camel_case()
        )
        .ok();
        writeln!(out).ok();
        offset += 1;
    }

    // free_user_data slot
    writeln!(out, "        // Slot {}: free_user_data", offset).ok();
    writeln!(out, "        var freeFn = new FreeUserDataFn(FreeUserDataCallback);",).ok();
    writeln!(
        out,
        "        Marshal.WriteIntPtr(_vtable, {}, Marshal.GetFunctionPointerForDelegate(freeFn));",
        offset * ptr_size
    )
    .ok();
    writeln!(out, "    }}").ok();
    writeln!(out).ok();

    // JSON serialization helper
    writeln!(out, "    private static string ToJsonString<T>(T value) {{").ok();
    writeln!(out, "        return JsonSerializer.Serialize(value);").ok();
    writeln!(out, "    }}").ok();
    writeln!(out).ok();

    // Bytes marshaling helper — emitted only when at least one trait method takes a Bytes parameter.
    if has_bytes_param {
        writeln!(out, "    private static byte[] MarshalBytesFromIntPtr(IntPtr ptr) {{").ok();
        writeln!(out, "        if (ptr == IntPtr.Zero) return Array.Empty<byte>();").ok();
        writeln!(out, "        var json = Marshal.PtrToStringUTF8(ptr) ?? \"[]\";").ok();
        writeln!(
            out,
            "        return JsonSerializer.Deserialize<byte[]>(json) ?? Array.Empty<byte>();"
        )
        .ok();
        writeln!(out, "    }}").ok();
        writeln!(out).ok();
    }

    // Plugin lifecycle callbacks
    if has_super_trait {
        writeln!(
            out,
            "    private int NameFnCallback(IntPtr userData, out IntPtr outName) {{"
        )
        .ok();
        writeln!(out, "        try {{").ok();
        writeln!(out, "            var name = _impl.Name;").ok();
        writeln!(out, "            outName = Marshal.StringToCoTaskMemUTF8(name);").ok();
        writeln!(out, "            return 0;").ok();
        writeln!(out, "        }} catch {{").ok();
        writeln!(out, "            outName = IntPtr.Zero;").ok();
        writeln!(out, "            return 1;").ok();
        writeln!(out, "        }}").ok();
        writeln!(out, "    }}").ok();
        writeln!(out).ok();

        writeln!(
            out,
            "    private int VersionFnCallback(IntPtr userData, out IntPtr outVersion) {{"
        )
        .ok();
        writeln!(out, "        try {{").ok();
        writeln!(out, "            var version = _impl.Version;").ok();
        writeln!(out, "            outVersion = Marshal.StringToCoTaskMemUTF8(version);").ok();
        writeln!(out, "            return 0;").ok();
        writeln!(out, "        }} catch {{").ok();
        writeln!(out, "            outVersion = IntPtr.Zero;").ok();
        writeln!(out, "            return 1;").ok();
        writeln!(out, "        }}").ok();
        writeln!(out, "    }}").ok();
        writeln!(out).ok();

        writeln!(
            out,
            "    private int InitializeFnCallback(IntPtr userData, out IntPtr outError) {{"
        )
        .ok();
        writeln!(out, "        try {{").ok();
        writeln!(out, "            _impl.Initialize();").ok();
        writeln!(out, "            outError = IntPtr.Zero;").ok();
        writeln!(out, "            return 0;").ok();
        writeln!(out, "        }} catch (Exception ex) {{").ok();
        writeln!(out, "            outError = Marshal.StringToCoTaskMemUTF8(ex.Message);").ok();
        writeln!(out, "            return 1;").ok();
        writeln!(out, "        }}").ok();
        writeln!(out, "    }}").ok();
        writeln!(out).ok();

        writeln!(
            out,
            "    private int ShutdownFnCallback(IntPtr userData, out IntPtr outError) {{"
        )
        .ok();
        writeln!(out, "        try {{").ok();
        writeln!(out, "            _impl.Shutdown();").ok();
        writeln!(out, "            outError = IntPtr.Zero;").ok();
        writeln!(out, "            return 0;").ok();
        writeln!(out, "        }} catch (Exception ex) {{").ok();
        writeln!(out, "            outError = Marshal.StringToCoTaskMemUTF8(ex.Message);").ok();
        writeln!(out, "            return 1;").ok();
        writeln!(out, "        }}").ok();
        writeln!(out, "    }}").ok();
        writeln!(out).ok();
    }

    // Trait method callbacks
    for method in &trait_def.methods {
        let method_pascal = to_csharp_name(&method.name);

        // Build parameter signature for unmanaged delegate (what we receive)
        let unmanaged_param_sig = method
            .params
            .iter()
            .map(|p| format!("{} {}", csharp_unmanaged_type(&p.ty), to_csharp_name(&p.name)))
            .collect::<Vec<_>>()
            .join(", ");

        let params_decl = if unmanaged_param_sig.is_empty() {
            String::new()
        } else {
            format!("{}, ", unmanaged_param_sig)
        };

        writeln!(
            out,
            "    private int {}FnCallback(IntPtr userData, {}out IntPtr outResult, out IntPtr outError) {{",
            method_pascal, params_decl
        )
        .ok();
        writeln!(out, "        try {{").ok();

        // Marshal parameters from IntPtr to managed types
        let mut param_call_parts = Vec::new();
        for param in &method.params {
            let param_name = to_csharp_name(&param.name);
            let managed_type = csharp_type(&param.ty);

            match &param.ty {
                TypeRef::Primitive(_) | TypeRef::Unit => {
                    // Primitives don't need conversion
                    param_call_parts.push(param_name);
                }
                TypeRef::String | TypeRef::Char => {
                    writeln!(
                        out,
                        "            var managed_{} = Marshal.PtrToStringUTF8({}) ?? string.Empty;",
                        param_name, param_name
                    )
                    .ok();
                    param_call_parts.push(format!("managed_{}", param_name));
                }
                TypeRef::Bytes => {
                    writeln!(
                        out,
                        "            var managed_{} = MarshalBytesFromIntPtr({});",
                        param_name, param_name
                    )
                    .ok();
                    param_call_parts.push(format!("managed_{}", param_name));
                }
                _ => {
                    // For complex types, assume JSON deserialization
                    writeln!(
                        out,
                        "            var json_{} = Marshal.PtrToStringUTF8({}) ?? \"{{}}\";",
                        param_name, param_name
                    )
                    .ok();
                    writeln!(
                        out,
                        "            var managed_{} = JsonSerializer.Deserialize<{}>(json_{})!;",
                        param_name, managed_type, param_name
                    )
                    .ok();
                    param_call_parts.push(format!("managed_{}", param_name));
                }
            }
        }

        let param_call = param_call_parts.join(", ");

        if method.return_type == TypeRef::Unit {
            writeln!(out, "            _impl.{}({});", method_pascal, param_call).ok();
            writeln!(out, "            outResult = IntPtr.Zero;").ok();
        } else {
            writeln!(out, "            var result = _impl.{}({});", method_pascal, param_call).ok();
            writeln!(
                out,
                "            outResult = Marshal.StringToCoTaskMemUTF8(ToJsonString(result));"
            )
            .ok();
        }

        writeln!(out, "            outError = IntPtr.Zero;").ok();
        writeln!(out, "            return 0;").ok();
        writeln!(out, "        }} catch (Exception ex) {{").ok();
        writeln!(out, "            outResult = IntPtr.Zero;").ok();
        writeln!(out, "            outError = Marshal.StringToCoTaskMemUTF8(ex.Message);").ok();
        writeln!(out, "            return 1;").ok();
        writeln!(out, "        }}").ok();
        writeln!(out, "    }}").ok();
        writeln!(out).ok();
    }

    // free_user_data callback
    writeln!(out, "    private void FreeUserDataCallback(IntPtr userData) {{").ok();
    writeln!(out, "        if (userData != IntPtr.Zero) {{").ok();
    writeln!(out, "            try {{").ok();
    writeln!(out, "                var handle = GCHandle.FromIntPtr(userData);").ok();
    writeln!(out, "                handle.Free();").ok();
    writeln!(out, "            }} catch (ObjectDisposedException) {{").ok();
    writeln!(
        out,
        "                // Handle already freed; safe to ignore during finalization"
    )
    .ok();
    writeln!(out, "            }}").ok();
    writeln!(out, "        }}").ok();
    writeln!(out, "    }}").ok();
    writeln!(out).ok();

    // Dispose
    writeln!(out, "    public void Dispose() {{").ok();
    writeln!(out, "        if (_disposed) return;").ok();
    writeln!(out, "        _disposed = true;").ok();
    writeln!(out).ok();
    writeln!(out, "        if (_vtable != IntPtr.Zero) {{").ok();
    writeln!(out, "            Marshal.FreeHGlobal(_vtable);").ok();
    writeln!(out, "            _vtable = IntPtr.Zero;").ok();
    writeln!(out, "        }}").ok();
    writeln!(out).ok();
    writeln!(out, "        if (_implHandle.IsAllocated) {{").ok();
    writeln!(out, "            _implHandle.Free();").ok();
    writeln!(out, "        }}").ok();
    writeln!(out, "    }}").ok();
    writeln!(out, "}}").ok();
    writeln!(out).ok();

    // --- Registry Class ---
    writeln!(
        out,
        "/// <summary>Static helpers for registering trait implementations</summary>"
    )
    .ok();
    writeln!(out, "public static class {}Registry {{", trait_pascal).ok();
    writeln!(out).ok();

    writeln!(
        out,
        "    private static readonly ConcurrentDictionary<string, {}Bridge> _bridges =",
        trait_pascal
    )
    .ok();
    writeln!(
        out,
        "        new ConcurrentDictionary<string, {}Bridge>();",
        trait_pascal
    )
    .ok();
    writeln!(out).ok();

    writeln!(
        out,
        "    /// <summary>Register a {} implementation</summary>",
        trait_pascal
    )
    .ok();
    // Plugin traits (has_super_trait) expose Name on the interface; non-plugin traits
    // require the caller to supply a registry key explicitly.
    if has_super_trait {
        writeln!(out, "    public static void Register(I{} impl) {{", trait_pascal).ok();
    } else {
        writeln!(
            out,
            "    public static void Register(I{} impl, string name) {{",
            trait_pascal
        )
        .ok();
    }
    writeln!(out, "        if (impl == null)").ok();
    writeln!(out, "            throw new ArgumentNullException(nameof(impl));").ok();
    writeln!(out).ok();
    if has_super_trait {
        writeln!(out, "        var name = impl.Name;").ok();
    }
    writeln!(out, "        var bridge = new {}Bridge(impl);", trait_pascal).ok();
    writeln!(out).ok();
    writeln!(out, "        try {{").ok();
    writeln!(
        out,
        "            var userDataHandle = GCHandle.Alloc(bridge, GCHandleType.Normal);"
    )
    .ok();
    writeln!(out, "            var userData = GCHandle.ToIntPtr(userDataHandle);").ok();
    writeln!(out, "            var vtablePtr = bridge._vtable;").ok();
    writeln!(out).ok();
    writeln!(
        out,
        "            var result = NativeMethods.Register{}(name, vtablePtr, userData, out var outError);",
        trait_pascal
    )
    .ok();
    writeln!(out, "            if (result != 0) {{").ok();
    writeln!(out, "                userDataHandle.Free();").ok();
    writeln!(out, "                bridge.Dispose();").ok();
    writeln!(
        out,
        "                var errorMsg = Marshal.PtrToStringUTF8(outError) ?? \"Unknown error\";"
    )
    .ok();
    writeln!(out, "                Marshal.FreeCoTaskMem(outError);").ok();
    writeln!(
        out,
        "                throw new InvalidOperationException($\"Failed to register {{name}}: {{errorMsg}}\");"
    )
    .ok();
    writeln!(out, "            }}").ok();
    writeln!(out).ok();
    writeln!(out, "            _bridges.TryAdd(name, bridge);").ok();
    writeln!(out, "        }} catch {{").ok();
    writeln!(out, "            bridge.Dispose();").ok();
    writeln!(out, "            throw;").ok();
    writeln!(out, "        }}").ok();
    writeln!(out, "    }}").ok();
    writeln!(out).ok();

    writeln!(
        out,
        "    /// <summary>Unregister a {} implementation</summary>",
        trait_pascal
    )
    .ok();
    writeln!(out, "    public static void Unregister(string name) {{").ok();
    writeln!(out, "        if (string.IsNullOrEmpty(name))").ok();
    writeln!(
        out,
        "            throw new ArgumentException(\"Name cannot be empty\", nameof(name));"
    )
    .ok();
    writeln!(out).ok();
    writeln!(
        out,
        "        var result = NativeMethods.Unregister{}(name, out var outError);",
        trait_pascal
    )
    .ok();
    writeln!(out, "        if (result != 0) {{").ok();
    writeln!(
        out,
        "            var errorMsg = Marshal.PtrToStringUTF8(outError) ?? \"Unknown error\";"
    )
    .ok();
    writeln!(out, "            Marshal.FreeCoTaskMem(outError);").ok();
    writeln!(
        out,
        "            throw new InvalidOperationException($\"Failed to unregister {{name}}: {{errorMsg}}\");"
    )
    .ok();
    writeln!(out, "        }}").ok();
    writeln!(out).ok();
    writeln!(out, "        if (_bridges.TryRemove(name, out var bridge)) {{").ok();
    writeln!(out, "            bridge.Dispose();").ok();
    writeln!(out, "        }}").ok();
    writeln!(out, "    }}").ok();
    writeln!(out, "}}").ok();
}

// Placeholder for JSON serialization — in production, would use System.Text.Json
fn _to_json_string(_obj: &dyn std::any::Any) -> String {
    "null".to_string()
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_trait_def(name: &str) -> TypeDef {
        TypeDef {
            name: name.to_string(),
            rust_path: format!("kreuzberg::{}", name),
            original_rust_path: String::new(),
            fields: vec![],
            methods: vec![],
            is_opaque: false,
            is_clone: true,
            is_copy: false,
            is_trait: true,
            has_default: false,
            has_stripped_cfg_fields: false,
            is_return_type: false,
            serde_rename_all: None,
            has_serde: false,
            super_traits: vec![],
            doc: String::new(),
            cfg: None,
        }
    }

    fn make_bridge_cfg(trait_name: &str, super_trait: Option<&str>) -> TraitBridgeConfig {
        TraitBridgeConfig {
            trait_name: trait_name.to_string(),
            param_name: None,
            type_alias: None,
            exclude_languages: vec![],
            super_trait: super_trait.map(|s| s.to_string()),
            registry_getter: None,
            register_fn: None,
            register_extra_args: None,
        }
    }

    #[test]
    fn test_interface_contains_lifecycle_when_super_trait_set() {
        let trait_def = make_trait_def("OcrBackend");
        let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
        let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
        let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);

        assert!(content.contains("public interface IOcrBackend"));
        assert!(content.contains("string Name { get; }"));
        assert!(content.contains("string Version { get; }"));
        assert!(content.contains("void Initialize();"));
        assert!(content.contains("void Shutdown();"));
    }

    #[test]
    fn test_interface_omits_lifecycle_when_super_trait_empty() {
        let trait_def = make_trait_def("OcrBackend");
        let bridge_cfg = make_bridge_cfg("OcrBackend", None);
        let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
        let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);

        assert!(content.contains("public interface IOcrBackend"));
        assert!(!content.contains("string Name { get; }"));
    }

    #[test]
    fn test_bridge_class_exists() {
        let trait_def = make_trait_def("OcrBackend");
        let bridge_cfg = make_bridge_cfg("OcrBackend", None);
        let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
        let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);

        assert!(content.contains("public sealed class OcrBackendBridge : IDisposable"));
    }

    #[test]
    fn test_registry_no_super_trait_requires_explicit_name_param() {
        // Without super_trait, the interface has no Name property, so Register must
        // accept an explicit string name from the caller.
        let trait_def = make_trait_def("OcrBackend");
        let bridge_cfg = make_bridge_cfg("OcrBackend", None);
        let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
        let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);

        assert!(content.contains("public static class OcrBackendRegistry"));
        assert!(content.contains("public static void Register(IOcrBackend impl, string name)"));
        assert!(content.contains("public static void Unregister(string name)"));
        // No impl.Name reference when interface lacks it
        assert!(!content.contains("impl.Name"));
    }

    #[test]
    fn test_registry_with_super_trait_reads_name_from_impl() {
        // With super_trait, interface declares Name property; Register reads it from impl.
        let trait_def = make_trait_def("OcrBackend");
        let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
        let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
        let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);

        assert!(content.contains("public static class OcrBackendRegistry"));
        assert!(content.contains("public static void Register(IOcrBackend impl)"));
        assert!(!content.contains("Register(IOcrBackend impl, string name)"));
        assert!(content.contains("impl.Name"));
    }

    #[test]
    fn test_exclude_languages_skips_csharp() {
        let trait_def = make_trait_def("OcrBackend");
        let mut bridge_cfg = make_bridge_cfg("OcrBackend", None);
        bridge_cfg.exclude_languages = vec!["csharp".to_string()];
        let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
        let (_filename, content) = gen_trait_bridges_file("Kreuzberg", "kreuzberg", &bridges);

        assert!(!content.contains("interface IOcrBackend"));
        assert!(!content.contains("class OcrBackendBridge"));
    }

    #[test]
    fn test_native_methods_declarations() {
        let trait_def = make_trait_def("OcrBackend");
        let bridge_cfg = make_bridge_cfg("OcrBackend", None);
        let bridges = vec![("OcrBackend".to_string(), &bridge_cfg, &trait_def)];
        let content = gen_native_methods_trait_bridges("Kreuzberg", "kreuzberg", &bridges);

        assert!(content.contains("RegisterOcrBackend"));
        assert!(content.contains("UnregisterOcrBackend"));
        assert!(content.contains("[DllImport"));
        assert!(content.contains("kreuzberg_register_ocr_backend"));
        assert!(content.contains("kreuzberg_unregister_ocr_backend"));
    }
}
