//! Failure-only infrastructure for callable API shims.
//!
//! Fallible synchronous and asynchronous members return [`Error::NotImplemented`].
//! Infallible members, trait methods, and constructors use
//! [`unimplemented_api!`](crate::unimplemented_api). Stream and callback
//! factories fail before returning a stream, registering a callback, or storing
//! a trait object; an empty stream or no-op callback would be plausible behavior.

use std::fmt;

const PANIC_PREFIX: &str = "unimplemented C# API member: ";

/// Typed failure returned by a fallible API shim.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct NotImplemented {
    csharp_member: &'static str,
}

impl NotImplemented {
    /// Creates a failure for one stable C# documentation ID.
    #[must_use]
    pub const fn new(csharp_member: &'static str) -> Self {
        Self { csharp_member }
    }

    /// Returns the stable C# documentation ID of the unimplemented member.
    #[must_use]
    pub const fn csharp_member(self) -> &'static str {
        self.csharp_member
    }
}

impl fmt::Display for NotImplemented {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "C# API member {} is not implemented",
            self.csharp_member
        )
    }
}

impl std::error::Error for NotImplemented {}

/// Shared error contract for translated fallible APIs.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Error {
    /// The API surface exists but its production body has not been ported yet.
    NotImplemented(NotImplemented),
    /// A required reference-like argument was absent.
    ArgumentNull,
    /// An argument value or length was invalid.
    Argument,
    /// The requested operation is invalid in the current object state.
    InvalidOperation,
    /// An index or destination buffer boundary was exceeded.
    IndexOutOfRange,
    /// An operation observed a requested cancellation.
    Cancelled,
    /// An HTTP request completed with an unsuccessful response.
    HttpRequest,
}

impl Error {
    /// Returns the stable C# member ID for a failure-only shim.
    #[must_use]
    pub const fn csharp_member(self) -> Option<&'static str> {
        match self {
            Self::NotImplemented(error) => Some(error.csharp_member()),
            Self::ArgumentNull
            | Self::Argument
            | Self::InvalidOperation
            | Self::IndexOutOfRange
            | Self::Cancelled
            | Self::HttpRequest => None,
        }
    }
}

impl From<NotImplemented> for Error {
    fn from(error: NotImplemented) -> Self {
        Self::NotImplemented(error)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::NotImplemented(error) => error.fmt(formatter),
            Self::ArgumentNull => formatter.write_str("required argument was null"),
            Self::Argument => formatter.write_str("argument was invalid"),
            Self::InvalidOperation => {
                formatter.write_str("operation was invalid for the current state")
            }
            Self::IndexOutOfRange => formatter.write_str("index was out of range"),
            Self::Cancelled => formatter.write_str("operation was cancelled"),
            Self::HttpRequest => formatter.write_str("HTTP request failed"),
        }
    }
}

impl std::error::Error for Error {}

/// Returns the standardized typed failure without constructing a fake value.
///
/// # Errors
///
/// Always returns [`Error::NotImplemented`] with the supplied stable C# member ID.
pub const fn not_implemented<T>(csharp_member: &'static str) -> Result<T, Error> {
    Err(Error::NotImplemented(NotImplemented::new(csharp_member)))
}

/// Panics for an infallible API whose signature cannot return [`NotImplemented`].
///
/// # Panics
///
/// Always, with a marker and the stable C# member ID.
#[doc(hidden)]
#[cold]
#[track_caller]
pub fn panic_unimplemented(csharp_member: &'static str) -> ! {
    panic!("{PANIC_PREFIX}{csharp_member}")
}

/// Fails an infallible API shim with its stable C# documentation ID.
///
/// This is deliberately a panic rather than a default value. Translated tests
/// expecting a domain error therefore cannot mistake an unimplemented API for
/// the expected `Err` variant.
///
/// # Panics
///
/// Always, with the supplied stable C# member ID.
#[macro_export]
macro_rules! unimplemented_api {
    ($csharp_member:expr $(,)?) => {{ $crate::shim::panic_unimplemented($csharp_member) }};
}

#[cfg(test)]
mod tests {
    use super::{Error, NotImplemented, PANIC_PREFIX, not_implemented};

    const MEMBER: &str = "M:LibreMetaverse.GridClient.Login(System.String)";

    #[test]
    fn error_formats_and_preserves_member_id() {
        let error = NotImplemented::new(MEMBER);
        assert_eq!(error.csharp_member(), MEMBER);
        assert_eq!(
            error.to_string(),
            format!("C# API member {MEMBER} is not implemented")
        );
        assert_eq!(
            not_implemented::<bool>(MEMBER),
            Err(Error::NotImplemented(error))
        );
    }

    #[test]
    fn panic_placeholder_is_not_an_expected_error_result() {
        let panic = std::panic::catch_unwind(|| crate::unimplemented_api!(MEMBER))
            .expect_err("the infallible placeholder must panic");
        let message = panic
            .downcast_ref::<String>()
            .expect("placeholder panic must retain its visible marker");
        assert_eq!(message, &format!("{PANIC_PREFIX}{MEMBER}"));
        assert!(not_implemented::<()>(MEMBER).is_err());
    }
}
