//! Service-API codegen for the Dart backend (FRB-based).
//!
//! Generates Rust source that declares FRB-opaque service owners and handler bridges.
//! The Dart side is auto-generated by flutter_rust_bridge from these declarations.
//!
//! **Output: Rust source** appended to `packages/dart/rust/src/lib.rs`:
//! - An `#[frb(opaque)]` owner struct per service
//! - Constructor and configurator methods (`#[frb(sync)]`)
//! - Registration methods accepting `DartFnFuture<String>` callbacks
//! - Entrypoint methods (async `run` or sync/async `finalize`)
//! - `DartHandler` adapter structs implementing handler contracts via manual `Pin<Box<dyn Future>>`

use crate::core::backend::GeneratedFile;
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{ApiSurface, EntrypointKind, HandlerContractDef, ServiceDef, TypeRef};
use std::collections::BTreeSet;
use std::path::PathBuf;

// ───────────────────────────────────────────────────────────────── helpers ──

/// Format a multi-line Rust doc as a Rust `///` block at the given column
/// indent. Every non-blank line is prefixed with `/// `; blank lines stay as
/// bare `///` so paragraph breaks survive. Includes the trailing newline.
fn format_dart_comment(text: &str, indent: usize) -> String {
    let trimmed = text.trim();
    if trimmed.is_empty() {
        return String::new();
    }
    let pad = " ".repeat(indent);
    let mut out = String::new();
    for line in trimmed.lines() {
        if line.trim().is_empty() {
            out.push_str(&pad);
            out.push_str("///\n");
        } else {
            out.push_str(&pad);
            out.push_str("/// ");
            out.push_str(line);
            out.push('\n');
        }
    }
    out
}

/// Whether an entrypoint's return type can be represented as a Rust type suitable for FRB.
///
/// Unit/primitive/string/bytes map to simple Rust types; a `Named` type is representable only
/// when this surface wraps it. Anything else (e.g. a foreign framework type a `finalize` converts into)
/// is not representable and the entrypoint is skipped from FRB emission.
///
/// Additionally, if the entrypoint's source method is marked as `sanitized` (e.g., method returns
/// an unknown type that was mapped to String), the entrypoint is skipped — the return type cannot
/// be safely used without the original type information.
fn entrypoint_return_representable(
    ep: &crate::core::ir::EntrypointDef,
    service: &ServiceDef,
    api: &ApiSurface,
) -> bool {
    // Check if the source method is sanitized; if so, the entrypoint is not representable.
    if let Some(svc_type) = api.types.iter().find(|t| t.name == service.name) {
        if let Some(method) = svc_type.methods.iter().find(|m| m.name == ep.method) {
            if method.sanitized {
                return false;
            }
        }
    }

    // Check if the return type itself is representable.
    match &ep.return_type {
        TypeRef::Unit | TypeRef::String | TypeRef::Char | TypeRef::Primitive(_) | TypeRef::Bytes => true,
        TypeRef::Named(n) => api.types.iter().any(|t| t.name == *n),
        _ => false,
    }
}

/// Find the `HandlerContractDef` by trait name in the surface.
fn find_contract<'a>(api: &'a ApiSurface, trait_name: &str) -> Option<&'a HandlerContractDef> {
    api.handler_contracts.iter().find(|c| c.trait_name == trait_name)
}

// ──────────────────────────────────────────────────────── Rust service codegen ──

/// Generate the Rust source for FRB-bridged service owners and handlers.
///
/// For each service this emits:
/// - An `#[frb(opaque)]` owner struct holding the inner service + registrations
/// - Constructor, configurators, registration methods, and entrypoints
/// - Handler adapter structs (`DartHandler*`) that bridge `DartFnFuture<String>` callbacks to trait impls
pub fn gen_service_rust(api: &ApiSurface, config: &ResolvedCrateConfig) -> String {
    let core_import = config.core_import_name();
    let mut out = String::new();

    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/file_header.rs.jinja",
        minijinja::context! {},
    ));
    out.push_str("\n\n");

    // Emit handler bridges for all unique contracts referenced by registrations
    let referenced_contracts: Vec<&HandlerContractDef> = {
        let names: BTreeSet<&str> = api
            .services
            .iter()
            .flat_map(|s| s.registrations.iter())
            .map(|r| r.callback_contract.as_str())
            .collect();
        names.iter().filter_map(|n| find_contract(api, n)).collect()
    };

    for contract in &referenced_contracts {
        gen_handler_bridge(&mut out, contract, &core_import);
        out.push('\n');
    }

    // Emit one service owner per service definition
    for service in &api.services {
        gen_service_owner(&mut out, service, api, &core_import);
        out.push('\n');
    }

    out
}

/// Emit a `DartHandler{ContractName}` struct + trait impl for an async handler contract.
///
/// The struct wraps a `DartFnFuture<String>` callback and implements the trait's dispatch method
/// using manual `Pin<Box<dyn Future>>` (not `#[async_trait]`). The dispatch:
/// 1. Serializes the wire request to JSON
/// 2. Calls the Dart handler closure with the JSON string
/// 3. Deserializes the JSON response back to the wire response type
/// 4. If a response_adapter is configured, applies it before returning
fn gen_handler_bridge(out: &mut String, contract: &HandlerContractDef, core_import: &str) {
    let trait_name = &contract.trait_name;
    let bridge_name = format!("DartHandler{}", trait_name);

    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/handler_bridge_doc.rs.jinja",
        minijinja::context! {
            trait_name => trait_name.as_str(),
        },
    ));
    out.push('\n');

    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/handler_bridge_struct.rs.jinja",
        minijinja::context! {
            bridge_name => bridge_name.as_str(),
        },
    ));
    out.push_str("\n\n");

    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/handler_bridge_constructor.rs.jinja",
        minijinja::context! {
            bridge_name => bridge_name.as_str(),
        },
    ));
    out.push_str("\n\n");

    // Determine wire types — derive from the contract definition
    let req_type = contract.wire_request_type.as_deref().unwrap_or("serde_json::Value");
    let resp_type = contract.wire_response_type.as_deref().unwrap_or("serde_json::Value");

    // Build the request and response type paths
    let req_path = if req_type.contains("::") {
        req_type.to_string()
    } else {
        format!("{core_import}::{req_type}")
    };
    let resp_path = if resp_type.contains("::") {
        resp_type.to_string()
    } else {
        format!("{core_import}::{resp_type}")
    };

    // Leading dispatch parameters (e.g. from dispatch_extra_params)
    let extra_params: String = contract
        .dispatch_extra_params
        .iter()
        .map(|p| format!(", {p}"))
        .collect();

    let wire_param_name = contract.wire_param_name.as_deref().unwrap_or("request");
    let dispatch_name = &contract.dispatch.name;

    // The future's Output type
    let box_err = "Box<dyn std::error::Error + Send + Sync>";
    let wire_output = format!("Result<{resp_path}, {box_err}>");
    let output_type = contract
        .dispatch_return_type
        .clone()
        .unwrap_or_else(|| wire_output.clone());

    // Tail: apply response_adapter if configured
    let tail = match &contract.response_adapter {
        Some(adapter) => format!("{adapter}(outcome)"),
        None => "outcome".to_string(),
    };

    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/handler_bridge_impl_open.rs.jinja",
        minijinja::context! {
            core_import => core_import,
            trait_name => trait_name.as_str(),
            bridge_name => bridge_name.as_str(),
            dispatch_name => dispatch_name.as_str(),
            extra_params => &extra_params,
            wire_param_name => wire_param_name,
            req_path => req_path.as_str(),
            resp_path => resp_path.as_str(),
            wire_output => wire_output.as_str(),
            box_err => box_err,
            output_type => output_type.as_str(),
            tail => tail.as_str(),
        },
    ));
}

/// Emit an `#[frb(opaque)]` service owner struct and its impl block.
///
/// The struct holds:
/// - `inner`: `tokio::sync::Mutex<Option<service_type>>` — the actual service
/// - `registrations`: `tokio::sync::Mutex<Vec<DartRegistration>>` — pending registrations
///
/// Methods include constructor, configurators, registration, and entrypoints.
fn gen_service_owner(out: &mut String, service: &ServiceDef, api: &ApiSurface, _core_import: &str) {
    let service_name = &service.name;
    let owner_path = &service.rust_path;

    // Emit doc comment
    if !service.doc.is_empty() {
        let doc_lines = format_dart_comment(&service.doc, 0);
        out.push_str(&crate::backends::dart::template_env::render(
            "service_api/service_owner_doc.rs.jinja",
            minijinja::context! {
                service_name => service_name.as_str(),
                doc => service.doc.as_str(),
                doc_lines => doc_lines.as_str(),
            },
        ));
    } else {
        out.push_str(&crate::backends::dart::template_env::render(
            "service_api/default_service_owner_doc.rs.jinja",
            minijinja::context! {
                service_name => service_name.as_str(),
            },
        ));
    }

    // Emit service owner struct
    out.push('\n');
    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/service_owner_struct.rs.jinja",
        minijinja::context! {
            service_name => service_name.as_str(),
            owner_path => owner_path.as_str(),
        },
    ));
    out.push_str("\n\n");

    // Emit constructor and impl open
    let ctor_params = format_ctor_params(service);
    let ctor_call = format_ctor_call(service);

    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/service_owner_impl_open.rs.jinja",
        minijinja::context! {
            service_name => service_name.as_str(),
            owner_path => owner_path.as_str(),
            ctor_params => ctor_params.as_str(),
            ctor_call => ctor_call.as_str(),
        },
    ));
    out.push('\n');

    // Emit configurator methods
    for config in &service.configurators {
        let method_params = format_method_params(config);
        let configurator_params: Vec<_> = config
            .params
            .iter()
            .map(|p| {
                // Named params resolving to opaque mirrors use `.inner`; other Named
                // params (non-opaque mirrors with derived `From<MirrorT> for SourceT`)
                // use `.into()`; primitives / strings pass through.
                let (is_opaque, is_named) = match &p.ty {
                    TypeRef::Named(n) => {
                        let opaque = api.types.iter().any(|t| t.name == *n && t.is_opaque);
                        (opaque, true)
                    }
                    _ => (false, false),
                };
                minijinja::context! {
                    name => p.name.as_str(),
                    rust_type => typeref_to_rust_type(&p.ty).as_str(),
                    is_opaque => is_opaque,
                    is_named => is_named,
                }
            })
            .collect();

        let is_owned = matches!(config.receiver, Some(crate::core::ir::ReceiverKind::Owned));

        out.push_str(&crate::backends::dart::template_env::render(
            "service_api/configurator_method.rs.jinja",
            minijinja::context! {
                config_name => config.name.as_str(),
                method_params => method_params.as_str(),
                configurator_params => configurator_params,
                is_owned => is_owned,
            },
        ));
        out.push('\n');
    }

    // Emit registration methods
    for reg in &service.registrations {
        let metadata_params: Vec<_> = reg
            .metadata_params
            .iter()
            .map(|p| {
                // Named params resolving to an opaque local mirror need `.inner`
                // to extract the wrapped core value before being passed to the
                // inner service method — FRB-opaque wrappers carry the core value
                // in a field named `inner`. Primitives / strings / non-opaque
                // mirrors pass through unchanged via `From<MirrorT> for SourceT`.
                let is_opaque = matches!(&p.ty, TypeRef::Named(n)
                    if api.types.iter().any(|t| t.name == *n && t.is_opaque));
                minijinja::context! {
                    name => p.name.as_str(),
                    rust_type => typeref_to_rust_type(&p.ty).as_str(),
                    is_opaque => is_opaque,
                }
            })
            .collect();

        let bridge_name = format!("DartHandler{}", reg.callback_contract);
        // Resolve the trait's full Rust path for the `Arc<dyn Trait>` cast in the
        // registration body. Falls back to the bare trait name when the contract
        // is missing a rust_path (defensive — the extractor populates it).
        let trait_path = find_contract(api, &reg.callback_contract)
            .map(|c| {
                if c.rust_path.is_empty() {
                    c.trait_name.clone()
                } else {
                    c.rust_path.clone()
                }
            })
            .unwrap_or_else(|| reg.callback_contract.clone());

        out.push_str(&crate::backends::dart::template_env::render(
            "service_api/registration_method.rs.jinja",
            minijinja::context! {
                method_name => reg.method.as_str(),
                callback_param => reg.callback_param.as_str(),
                metadata_params => metadata_params,
                bridge_name => bridge_name.as_str(),
                trait_path => trait_path.as_str(),
            },
        ));
        out.push('\n');

        // Emit registration variants
        for variant in &reg.variants {
            let signature_params: Vec<_> = variant
                .signature_params
                .iter()
                .map(|p| {
                    minijinja::context! {
                        name => p.name.as_str(),
                        rust_type => typeref_to_rust_type(&p.ty).as_str(),
                    }
                })
                .collect();

            let (wrapper_type_path, wrapper_local_type, constructor_method, wrapper_args) =
                if let Some(wc) = &variant.wrapper_call {
                    let args: Vec<_> = wc
                        .args
                        .iter()
                        .map(|arg| match arg {
                            crate::core::ir::WrapperConstructorArg::Fixed {
                                param_name: _,
                                value_expr,
                            } => minijinja::context! {
                                kind => "fixed",
                                value_expr => value_expr.as_str(),
                            },
                            crate::core::ir::WrapperConstructorArg::Free { param } => minijinja::context! {
                                kind => "free",
                                param => minijinja::context! {
                                    name => param.name.as_str(),
                                    rust_type => typeref_to_rust_type(&param.ty).as_str(),
                                },
                            },
                        })
                        .collect();
                    (
                        wc.wrapper_type_path.clone(),
                        wc.wrapper_type_name.clone(),
                        wc.constructor_method.clone(),
                        args,
                    )
                } else {
                    (String::new(), String::new(), String::new(), vec![])
                };

            out.push_str(&crate::backends::dart::template_env::render(
                "service_api/registration_variant.rs.jinja",
                minijinja::context! {
                    variant_name => variant.name.as_str(),
                    signature_params => signature_params,
                    base_method_name => reg.method.as_str(),
                    wrapper_call => variant.wrapper_call.is_some(),
                    wrapper_type_path => wrapper_type_path.as_str(),
                    wrapper_local_type => wrapper_local_type.as_str(),
                    constructor_method => constructor_method.as_str(),
                    wrapper_args => wrapper_args,
                },
            ));
            out.push('\n');
        }
    }

    // Emit entrypoint methods
    for ep in &service.entrypoints {
        gen_entrypoint_method(out, service, ep, api);
        out.push('\n');
    }

    // Close the impl block
    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/service_owner_impl_close.rs.jinja",
        minijinja::context! {},
    ));
}

/// Emit an entrypoint method (run or finalize).
///
/// For `Run` entrypoints: async method that drains registrations, wraps handlers,
/// registers them on the inner service, and calls the run method.
///
/// For representable `Finalize` entrypoints: sync/async method that does similar but returns the result.
fn gen_entrypoint_method(
    out: &mut String,
    service: &ServiceDef,
    ep: &crate::core::ir::EntrypointDef,
    api: &ApiSurface,
) {
    // Skip non-representable finalizers
    if matches!(ep.kind, EntrypointKind::Finalize) && !entrypoint_return_representable(ep, service, api) {
        return;
    }

    let method_name = &ep.method;
    let is_async = ep.is_async;
    let return_type = typeref_to_rust_type(&ep.return_type);

    let params: Vec<_> = ep
        .params
        .iter()
        .map(|p| {
            minijinja::context! {
                name => p.name.as_str(),
                rust_type => typeref_to_rust_type(&p.ty).as_str(),
            }
        })
        .collect();

    let registrations: Vec<_> = service
        .registrations
        .iter()
        .map(|r| {
            minijinja::context! {
                method => r.method.as_str(),
            }
        })
        .collect();

    out.push_str(&crate::backends::dart::template_env::render(
        "service_api/entrypoint_method.rs.jinja",
        minijinja::context! {
            method_name => method_name.as_str(),
            is_async => is_async,
            return_type => return_type.as_str(),
            params => params,
            registrations => registrations,
        },
    ));
}

// ──────────────────────────────────────────────────────────── helpers ──

/// Format constructor parameters into a Rust param list.
fn format_ctor_params(service: &ServiceDef) -> String {
    service
        .constructor
        .params
        .iter()
        .map(|p| {
            let ty = typeref_to_rust_type(&p.ty);
            format!("{}: {}", p.name, ty)
        })
        .collect::<Vec<_>>()
        .join(", ")
}

/// Format the constructor call expression.
fn format_ctor_call(service: &ServiceDef) -> String {
    if service.constructor.params.is_empty() {
        "::new()".to_string()
    } else {
        let args = service
            .constructor
            .params
            .iter()
            .map(|p| p.name.clone())
            .collect::<Vec<_>>()
            .join(", ");
        format!("::new({})", args)
    }
}

/// Format configurator method parameters.
fn format_method_params(method: &crate::core::ir::MethodDef) -> String {
    let mut out = String::new();
    for param in &method.params {
        let ty = typeref_to_rust_type(&param.ty);
        out.push_str(&crate::backends::dart::template_env::render(
            "service_api/method_param.rs.jinja",
            minijinja::context! {
                name => param.name.as_str(),
                ty => ty.as_str(),
            },
        ));
    }
    out
}

/// Convert a TypeRef to a Rust type annotation.
fn typeref_to_rust_type(ty: &TypeRef) -> String {
    match ty {
        TypeRef::String => "String".to_owned(),
        TypeRef::Char => "char".to_owned(),
        TypeRef::Primitive(p) => {
            use crate::core::ir::PrimitiveType;
            match p {
                PrimitiveType::Bool => "bool".to_owned(),
                PrimitiveType::U8 => "u8".to_owned(),
                PrimitiveType::U16 => "u16".to_owned(),
                PrimitiveType::U32 => "u32".to_owned(),
                PrimitiveType::U64 => "u64".to_owned(),
                PrimitiveType::I8 => "i8".to_owned(),
                PrimitiveType::I16 => "i16".to_owned(),
                PrimitiveType::I32 => "i32".to_owned(),
                PrimitiveType::I64 => "i64".to_owned(),
                PrimitiveType::F32 => "f32".to_owned(),
                PrimitiveType::F64 => "f64".to_owned(),
                PrimitiveType::Usize => "usize".to_owned(),
                PrimitiveType::Isize => "isize".to_owned(),
            }
        }
        TypeRef::Bytes => "Vec<u8>".to_owned(),
        TypeRef::Unit => "()".to_owned(),
        TypeRef::Optional(inner) => format!("Option<{}>", typeref_to_rust_type(inner)),
        TypeRef::Vec(inner) => format!("Vec<{}>", typeref_to_rust_type(inner)),
        TypeRef::Map(k, v) => format!(
            "std::collections::HashMap<{}, {}>",
            typeref_to_rust_type(k),
            typeref_to_rust_type(v)
        ),
        TypeRef::Named(n) => n.clone(),
        TypeRef::Json => "serde_json::Value".to_owned(),
        TypeRef::Path => "std::path::PathBuf".to_owned(),
        TypeRef::Duration => "std::time::Duration".to_owned(),
    }
}

// ──────────────────────────────────────────────────────── public entry point ──

/// Generate all service-API files for the Dart backend.
///
/// Returns one `GeneratedFile` when services are present:
/// - Rust source destined for `packages/dart/rust/src/lib.rs`
pub fn generate(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
    if api.services.is_empty() {
        return Ok(vec![]);
    }

    let rust_source = gen_service_rust(api, config);

    Ok(vec![GeneratedFile {
        path: PathBuf::from("packages/dart/rust/src/service_api.rs"),
        content: rust_source,
        generated_header: true,
    }])
}

// ─────────────────────────────────────────────────────────────────────── tests ──

#[cfg(test)]
mod tests;
