// SPDX-License-Identifier: MPL-2.0
use std::{
    fmt,
    sync::atomic::AtomicPtr,
};

/// A declaration of an evolvable function, generated by the `evolvable!` macro.
///
/// Contains the metadata needed to create the temporary dylib crate and validate
/// LLM-generated code against the expected function signatures.
pub struct EvolvableDecl {
    /// Function name (e.g., `"step"`).
    pub name: &'static str,
    /// Formatted signature string (e.g., `"fn step(counter: &mut usize)"`).
    pub signature: &'static str,
    /// Full source code for the dylib, including `#[unsafe(no_mangle)]` and `pub`.
    pub full_source: &'static str,
    /// Pointer to the per-function `AtomicPtr<()>` that caches the loaded symbol.
    /// Updated by the runtime on init and each reload.
    pub fn_ptr: &'static AtomicPtr<()>,
}

impl fmt::Debug for EvolvableDecl {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("EvolvableDecl")
            .field("name", &self.name)
            .field("signature", &self.signature)
            .field("full_source", &FullSource(self.full_source))
            .field("fn_ptr", &self.fn_ptr)
            .finish()
    }
}

/// Wrapper around a multi-line Rust source string that renders with real line
/// breaks under pretty-print (`{:#?}`), instead of the default escaped
/// single-line form.
///
/// Behaves like a `&str` for all practical purposes: dereferences to `str`,
/// implements `AsRef<str>` and `Display` (which writes the source verbatim).
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct FullSource<'a>(pub &'a str);

impl AsRef<str> for FullSource<'static> {
    #[inline(always)]
    fn as_ref(&self) -> &str {
        self.0
    }
}

impl fmt::Debug for FullSource<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            // Render each source line indented one level deeper than the
            // surrounding context. Avoid emitting a trailing newline so any
            // following punctuation (e.g. the `,` from `debug_struct` /
            // `debug_list`) sits directly after the last source line.
            let trimmed = self.0.trim_end_matches('\n');
            for line in trimmed.lines() {
                f.write_str("\n    ")?;
                f.write_str(line)?;
            }
            Ok(())
        } else {
            fmt::Debug::fmt(self.0, f)
        }
    }
}

impl fmt::Display for FullSource<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.0)
    }
}

impl<'a> From<&'a str> for FullSource<'a> {
    #[inline]
    fn from(s: &'a str) -> Self {
        Self(s)
    }
}

// SAFETY: AtomicPtr is Send+Sync, and the other fields are &'static str.
unsafe impl Send for EvolvableDecl {}
unsafe impl Sync for EvolvableDecl {}

#[cfg(test)]
mod tests {
    use std::ptr;

    use super::*;

    #[test]
    fn debug_renders_full_source_with_real_line_breaks() {
        static PTR: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
        let decl = EvolvableDecl {
            name: "step",
            signature: "fn step(counter: &mut usize)",
            full_source: "/// Should increment the counter by a value in the range 5..20\n\
                          #[unsafe(no_mangle)]\n\
                          pub fn step(counter: &mut usize) {\n    \
                              *counter += 1;\n\
                          }\n",
            fn_ptr: &PTR,
        };

        let pretty = format!("{decl:#?}");
        // Source lines should be present verbatim (no `\n` escapes).
        assert!(pretty.contains("\n        #[unsafe(no_mangle)]\n"));
        assert!(pretty.contains("\n        pub fn step(counter: &mut usize) {\n"));
        assert!(pretty.contains("\n            *counter += 1;\n"));
        assert!(!pretty.contains(r"\n"));
    }

    #[test]
    fn full_source_vec_pretty_prints_each_entry() {
        let sources = vec![
            FullSource("#[unsafe(no_mangle)]\npub fn a() {}\n"),
            FullSource("#[unsafe(no_mangle)]\npub fn b() {\n    todo!()\n}\n"),
        ];

        let pretty = format!("{sources:#?}");
        assert!(pretty.contains("\n        #[unsafe(no_mangle)]\n"));
        assert!(pretty.contains("\n        pub fn a() {}"));
        assert!(pretty.contains("\n        pub fn b() {\n"));
        assert!(pretty.contains("\n            todo!()\n"));
        assert!(!pretty.contains(r"\n"));
    }

    #[test]
    fn full_source_as_str() {
        let source = FullSource("Hello");
        assert_eq!(&source.to_string(), "Hello");
    }
}
