use crate::codegen::builder::RustFileBuilder;

pub(super) fn add_generated_module_attributes(builder: &mut RustFileBuilder) {
    // Suppress documentation and cast lints in generated code. Python stubs carry
    // docs, and numeric casts are intentional FFI conversions.
    builder.add_inner_attribute("allow(missing_docs)");
    builder.add_inner_attribute("allow(deprecated, dead_code, unused_imports, unused_variables)");
    builder.add_inner_attribute(
        "allow(clippy::default_trait_access, clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::just_underscores_and_digits, clippy::unused_unit, clippy::let_unit_value, clippy::needless_borrow, clippy::too_many_arguments, clippy::map_identity, clippy::unnecessary_cast, clippy::unwrap_or_default, clippy::derivable_impls, clippy::needless_borrows_for_generic_args, clippy::unnecessary_fallible_conversions, clippy::useless_conversion, clippy::arc_with_non_send_sync, clippy::collapsible_if, clippy::clone_on_copy, clippy::should_implement_trait)",
    );
    // Pedantic/nursery lints that do not apply to autogenerated FFI bindings.
    builder.add_inner_attribute(
        "allow(clippy::unsafe_derive_deserialize, clippy::must_use_candidate, clippy::return_self_not_must_use, clippy::use_self, clippy::missing_const_for_fn, clippy::missing_errors_doc, clippy::needless_pass_by_value, clippy::doc_markdown, clippy::derive_partial_eq_without_eq, clippy::uninlined_format_args, clippy::redundant_clone, clippy::implicit_clone, clippy::redundant_closure_for_method_calls, clippy::wildcard_imports, clippy::option_if_let_else, clippy::too_many_lines)",
    );
    builder.add_inner_attribute("allow(clippy::multiple_unsafe_ops_per_block)");
    builder.add_inner_attribute("allow(unsafe_code)");
}

pub(super) fn add_py_visitor_ref(builder: &mut RustFileBuilder) {
    builder.add_item(
        r#"
/// Wrapper for trait visitor types (`Py<PyAny>`) that implements Clone.
///
/// `Py<PyAny>` is not Clone. This wrapper uses `Arc<Py<PyAny>>` internally for cheap cloning.
/// The .inner field is public for compatibility with generated code that needs to access
/// the underlying `Py<PyAny>` for trait dispatch.
#[derive(Debug)]
pub struct PyVisitorRef {
    pub inner: std::sync::Arc<pyo3::Py<pyo3::PyAny>>,
}

impl Clone for PyVisitorRef {
    fn clone(&self) -> Self {
        PyVisitorRef {
            inner: std::sync::Arc::clone(&self.inner),
        }
    }
}

impl From<pyo3::Py<pyo3::PyAny>> for PyVisitorRef {
    fn from(visitor: pyo3::Py<pyo3::PyAny>) -> Self {
        PyVisitorRef {
            inner: std::sync::Arc::new(visitor),
        }
    }
}

impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for PyVisitorRef {
    type Error = pyo3::PyErr;

    fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
        Ok(PyVisitorRef {
            inner: std::sync::Arc::new(ob.to_owned().unbind()),
        })
    }
}

impl<'py> pyo3::conversion::IntoPyObject<'py> for PyVisitorRef {
    type Target = pyo3::PyAny;
    type Output = pyo3::Bound<'py, pyo3::PyAny>;
    type Error = std::convert::Infallible;

    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
        Ok((*self.inner).bind(py).clone())
    }
}
"#,
    );
}

pub(super) fn add_json_helpers(builder: &mut RustFileBuilder) {
    builder.add_item(
        r#"
mod alef_json_str {
    use serde::{Deserialize, Deserializer};
    use serde_json::Value;
    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
    where
        D: Deserializer<'de>,
    {
        let v = Value::deserialize(deserializer)?;
        Ok(match v {
            Value::String(s) => s,
            other => other.to_string(),
        })
    }
}

mod alef_json_str_opt {
    use serde::{Deserialize, Deserializer};
    use serde_json::Value;
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let v: Option<Value> = Option::deserialize(deserializer)?;
        Ok(v.and_then(|val| match val {
            Value::Null => None,
            Value::String(s) => Some(s),
            other => Some(other.to_string()),
        }))
    }
}
"#,
    );
}
