use super::*;

#[test]
fn test_gen_lossy_binding_to_core_fields_string_field() {
    let typ = simple_type_def();

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("name: self.name.clone(),"),
        "String field should be cloned"
    );
    assert!(
        result.contains("count: self.count,"),
        "Primitive field should be copied directly"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_cow_string_field() {
    let mut typ = simple_type_def();
    typ.fields[0].core_wrapper = CoreWrapper::Cow;

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("name: self.name.clone().into(),"),
        "Cow-backed String field should convert back into Cow"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_named_field() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "inner".to_string(),
        ty: TypeRef::Named("Config".to_string()),
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("inner: self.inner.clone().into(),"),
        "Named field should be cloned and converted"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_path_field() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "file_path".to_string(),
        ty: TypeRef::Path,
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("file_path: self.file_path.clone().into(),"),
        "Path field should be cloned and converted to PathBuf"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_path_optional() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "output_path".to_string(),
        ty: TypeRef::Path,
        optional: true,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("output_path: self.output_path.clone().map(Into::into),"),
        "Optional Path field should be cloned and mapped into PathBuf"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_json_field() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "metadata".to_string(),
        ty: TypeRef::Json,
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("serde_json::from_str(&self.metadata).unwrap_or_default()"),
        "Json field should be parsed from string"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_json_optional() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "extra".to_string(),
        ty: TypeRef::Json,
        optional: true,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("self.extra.as_ref().and_then(|s| serde_json::from_str(s).ok())"),
        "Optional Json field should be conditionally parsed"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_vec_named() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "items".to_string(),
        ty: TypeRef::Vec(Box::new(TypeRef::Named("Item".to_string()))),
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("items: self.items.clone().into_iter().map(Into::into).collect(),"),
        "Vec<Named> field should clone and convert elements"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_vec_named_optional() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "entries".to_string(),
        ty: TypeRef::Vec(Box::new(TypeRef::Named("Entry".to_string()))),
        optional: true,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("entries: self.entries.clone().map(|v| v.into_iter().map(Into::into).collect()),"),
        "Optional Vec<Named> field should map and convert"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_mut_declares_mutable() {
    let typ = simple_type_def();

    let result = binding_helpers::gen_lossy_binding_to_core_fields_mut(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("let mut core_self"),
        "gen_lossy_binding_to_core_fields_mut should declare core_self as mutable"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_has_stripped_cfg_fields() {
    let mut typ = simple_type_def();
    typ.has_stripped_cfg_fields = true;

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("..Default::default()"),
        "should include ..Default::default() for stripped cfg fields"
    );
    assert!(
        result.contains("needless_update"),
        "should suppress needless_update lint for stripped cfg fields"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_char_field() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "separator".to_string(),
        ty: TypeRef::Char,
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("separator: self.separator.chars().next().unwrap_or('*'),"),
        "Char field should extract first char with fallback"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_char_optional() {
    let mut typ = simple_type_def();
    typ.fields.push(FieldDef {
        name: "delimiter".to_string(),
        ty: TypeRef::Char,
        optional: true,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("delimiter: self.delimiter.as_ref().and_then(|s| s.chars().next()),"),
        "Optional Char field should conditionally extract first char"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_duration_option_on_defaults() {
    // When option_duration_on_defaults=true and has_default=true, non-optional Duration fields
    // are stored as Option<u64> and should use .map(...).unwrap_or_default()
    let mut typ = simple_type_def();
    typ.has_default = true;
    typ.fields.push(FieldDef {
        name: "timeout".to_string(),
        ty: TypeRef::Duration,
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: false,
        binding_exclusion_reason: None,
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        true,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("self.timeout.map(std::time::Duration::from_millis).unwrap_or_default()"),
        "option_duration_on_defaults should use map+unwrap_or_default pattern"
    );
}

#[test]
fn test_has_named_params_vec_string_with_is_ref() {
    // Vec<String> with is_ref=true should count as having named params (needs &[&str] conversion)
    let opaque_types = AHashSet::new();
    let params = vec![ParamDef {
        name: "labels".to_string(),
        ty: TypeRef::Vec(Box::new(TypeRef::String)),
        optional: false,
        default: None,
        sanitized: false,
        typed_default: None,
        is_ref: true,
        is_mut: false,
        newtype_wrapper: None,
        original_type: None,
        map_is_ahash: false,
        map_key_is_cow: false,
        vec_inner_is_ref: false,
        map_is_btree: false,
        core_wrapper: alef::core::ir::CoreWrapper::None,
    }];

    assert!(
        binding_helpers::has_named_params(&params, &opaque_types),
        "Vec<String> with is_ref=true should require let bindings"
    );
}

#[test]
fn test_has_named_params_vec_string_without_is_ref() {
    // Vec<String> without is_ref should NOT require let bindings
    let opaque_types = AHashSet::new();
    let params = vec![ParamDef {
        name: "labels".to_string(),
        ty: TypeRef::Vec(Box::new(TypeRef::String)),
        optional: false,
        default: None,
        sanitized: false,
        typed_default: None,
        is_ref: false,
        is_mut: false,
        newtype_wrapper: None,
        original_type: None,
        map_is_ahash: false,
        map_key_is_cow: false,
        vec_inner_is_ref: false,
        map_is_btree: false,
        core_wrapper: alef::core::ir::CoreWrapper::None,
    }];

    assert!(
        !binding_helpers::has_named_params(&params, &opaque_types),
        "Vec<String> without is_ref should not require let bindings"
    );
}

#[test]
fn test_has_named_params_vec_named_always_requires_binding() {
    // Vec<Named> (non-opaque) always requires a let binding
    let opaque_types = AHashSet::new();
    let params = vec![ParamDef {
        name: "items".to_string(),
        ty: TypeRef::Vec(Box::new(TypeRef::Named("Item".to_string()))),
        optional: false,
        default: None,
        sanitized: false,
        typed_default: None,
        is_ref: false,
        is_mut: false,
        newtype_wrapper: None,
        original_type: None,
        map_is_ahash: false,
        map_key_is_cow: false,
        vec_inner_is_ref: false,
        map_is_btree: false,
        core_wrapper: alef::core::ir::CoreWrapper::None,
    }];

    assert!(
        binding_helpers::has_named_params(&params, &opaque_types),
        "Vec<Named> non-opaque should require let bindings"
    );
}

#[test]
fn test_has_named_params_vec_opaque_named_no_binding_needed() {
    // Vec<OpaqueNamed> does NOT need a let binding (handled directly by gen_call_args)
    let mut opaque_types = AHashSet::new();
    opaque_types.insert("Item".to_string());
    let params = vec![ParamDef {
        name: "items".to_string(),
        ty: TypeRef::Vec(Box::new(TypeRef::Named("Item".to_string()))),
        optional: false,
        default: None,
        sanitized: false,
        typed_default: None,
        is_ref: false,
        is_mut: false,
        newtype_wrapper: None,
        original_type: None,
        map_is_ahash: false,
        map_key_is_cow: false,
        vec_inner_is_ref: false,
        map_is_btree: false,
        core_wrapper: alef::core::ir::CoreWrapper::None,
    }];

    assert!(
        !binding_helpers::has_named_params(&params, &opaque_types),
        "Vec<Opaque> should not require let bindings"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_binding_excluded_no_default_per_field_fallback() {
    // Regression: spikard rc.25 — `spikard::UploadFile` has an internal
    // `cursor: Cursor<Bytes>` field annotated `#[cfg_attr(alef, alef(skip))]` but
    // the struct derives only Clone/Debug/Serialize/Deserialize (no Default).
    // Previously this helper unconditionally appended `..Default::default()`
    // whenever any binding-excluded field was skipped, producing a struct literal
    // that failed to compile with E0277 against no-Default core types. Mirrors
    // the parallel fix in `binding_to_core/render.rs` (096eb298c).
    let mut typ = simple_type_def();
    typ.has_default = false;
    typ.fields.push(FieldDef {
        name: "cursor".to_string(),
        ty: TypeRef::Named("Cursor".to_string()),
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: true,
        binding_exclusion_reason: Some("internal read cursor".to_string()),
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("cursor: Default::default()"),
        "binding-excluded field on a no-Default core type must fall back to per-field \
         `Default::default()`; got:\n{result}"
    );
    assert!(
        !result.contains("..Default::default()"),
        "must not emit `..Default::default()` trailer when the core type lacks Default — \
         it fails to compile (E0277); got:\n{result}"
    );
}

#[test]
fn test_gen_lossy_binding_to_core_fields_binding_excluded_with_default_uses_spread() {
    // Companion to the no-Default test above: when the core type DOES impl Default,
    // skipping the binding-excluded field and emitting `..Default::default()` is the
    // correct behavior (preserves bespoke core Default invariants like env-derived
    // policy values that an explicit per-field `Default::default()` would bypass).
    let mut typ = simple_type_def();
    typ.has_default = true;
    typ.fields.push(FieldDef {
        name: "policy".to_string(),
        ty: TypeRef::Named("SsrfPolicy".to_string()),
        optional: false,
        default: None,
        doc: String::new(),
        sanitized: false,
        is_boxed: false,
        type_rust_path: None,
        cfg: None,
        typed_default: None,
        core_wrapper: CoreWrapper::None,
        vec_inner_core_wrapper: CoreWrapper::None,
        newtype_wrapper: None,
        serde_rename: None,
        serde_flatten: false,
        binding_excluded: true,
        binding_exclusion_reason: Some("derived from env".to_string()),
        original_type: None,
    });

    let result = binding_helpers::gen_lossy_binding_to_core_fields(
        &typ,
        "my_crate",
        false,
        &ahash::AHashSet::new(),
        false,
        false,
        &[],
    );

    assert!(
        result.contains("..Default::default()"),
        "binding-excluded field on a has_default core type must use the spread trailer to \
         preserve bespoke core Default semantics; got:\n{result}"
    );
    assert!(
        !result.contains("policy: Default::default()"),
        "must not emit per-field Default::default() for binding-excluded fields when the core \
         type has Default — would bypass bespoke Default semantics; got:\n{result}"
    );
}
