//! Sealed — a compile-time guaranteed literal valve.
//!
//! Sealed wraps a value that can only be unwrapped inside the mad-hatter crate.
//! Engine code can hold, clone, and debug-print a Sealed value, but cannot extract
//! or compare its inner value. The value flows one-way: from bindings declaration
//! to IO boundaries (DB write, prompt render, HTTP response).

use std::fmt;

/// A sealed value. Only mad-hatter internals can access the inner value.
///
/// # Guarantees
///
/// - `new()` is public — bindings can construct
/// - `value()` is pub(crate) — only mad-hatter can read
/// - No PartialEq, Display, AsRef, Deref, or Into — engine cannot compare or extract
/// - Orphan rule prevents engine from adding these traits
///
/// # Type parameter
///
/// Default type is `&'static str` for backward compatibility.
/// Use `Sealed<String>` for dynamic values (e.g. formatted error messages).
#[derive(Debug, Clone)]
pub struct Sealed<T = &'static str>(T);

/// Result of discriminating a candidate value against a sealed literal.
///
/// `Match` means the candidate matched the sealed value (consumed, not accessible).
/// `Other(T)` returns the candidate unchanged for further processing.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Discriminated<T> {
    /// The candidate matches the sealed literal (value consumed)
    Match,
    /// The candidate does not match — returned unchanged
    Other(T),
}

impl<T> Sealed<T> {
    /// Create a new sealed value. Called from bindings declarations.
    pub const fn new(value: T) -> Self {
        Self(value)
    }

    /// Access the inner value. This is the **only** way to unseal.
    ///
    /// Used at IO boundaries (DB write, prompt render, HTTP response)
    /// and inside mad-hatter trait impls (Serialize, ToSql, LogParam, etc.).
    ///
    /// In engine code, guardian scans `.unseal()` calls — only persist/ is exempt.
    pub fn unseal(&self) -> &T {
        &self.0
    }
}

impl<T: AsRef<str>> Sealed<T> {
    /// Discriminate a borrowed candidate against this sealed value.
    pub fn discriminate<'a>(&self, candidate: &'a str) -> Discriminated<&'a str> {
        if self.unseal().as_ref() == candidate {
            Discriminated::Match
        } else {
            Discriminated::Other(candidate)
        }
    }

    /// Discriminate an owned candidate against this sealed value.
    /// Used by Parseable derive macro during parse phase.
    pub fn discriminate_owned(&self, candidate: String) -> Discriminated<String> {
        if self.unseal().as_ref() == candidate.as_str() {
            Discriminated::Match
        } else {
            Discriminated::Other(candidate)
        }
    }
}

impl<T> Discriminated<T> {
    /// Returns true if this is a Match.
    pub fn is_match(&self) -> bool {
        matches!(self, Discriminated::Match)
    }
}

impl<T: fmt::Display> fmt::Display for Discriminated<T> {
    #[allow(clippy::string_slice)]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Discriminated::Match => write!(f, "[match]"),
            Discriminated::Other(v) => write!(f, "{}", v),
        }
    }
}

/// Format a template string by replacing `{}` placeholders with sealed values.
///
/// Called from derive-macro-generated code when a `#[prompt("..{}", SEALED)]`
/// attribute references sealed constants. Each `{}` is replaced positionally
/// with the corresponding Sealed value.
///
/// This runs inside the mad-hatter crate where `value()` is accessible.
#[allow(clippy::string_slice)]
pub fn format_sealed<T: AsRef<str>>(template: &str, values: &[&Sealed<T>]) -> Sealed<String> {
    let mut result = String::with_capacity(template.len());
    let mut rest = template;
    let mut idx = 0;
    while let Some(pos) = rest.find("{}") {
        result.push_str(&rest[..pos]);
        if idx < values.len() {
            result.push_str(values[idx].unseal().as_ref());
            idx += 1;
        } else {
            result.push_str("{}");
        }
        rest = &rest[pos + 2..];
    }
    result.push_str(rest);
    Sealed(result)
}

/// Display implementation for Sealed values.
///
/// Sealed's core invariant is **controlled construction** (values come from
/// legitimate declaration sites), not output suppression. At IO boundaries
/// (DB write, prompt render, HTTP response, serde), the inner value is already
/// exposed via ToSql, Serialize, Promptable, etc. Display is the same class
/// of IO output — making it available simplifies MadError humanize templates
/// and general formatting without weakening the construction guarantee.
impl<T: fmt::Display> fmt::Display for Sealed<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

/// Write a Sealed value's Display output to a formatter.
///
/// Used by `MadError` derive macro — generated code calls this function
/// instead of `value()` directly, since `value()` is pub(crate).
/// The value never leaks: it goes straight into the formatter.
#[doc(hidden)]
pub fn __mad_error_display<T: std::fmt::Display>(
    sealed: &Sealed<T>,
    f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
    write!(f, "{}", sealed.unseal())
}

/// Serialize implementation for Sealed values.
///
/// Delegates to the inner value's Serialize impl, allowing Sealed fields
/// to be transparently serialized via serde without exposing the raw value.
/// Engine code derives Serialize on structs containing Sealed fields,
/// and serde automatically calls this impl at serialization time.
impl<T: serde::Serialize> serde::Serialize for Sealed<T> {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.unseal().serialize(serializer)
    }
}

// ===== sqlx integration =====
// Sealed<String> can be used directly in sqlx queries.
// The inner value is automatically extracted at the IO boundary (DB write),
// and automatically re-sealed at the IO boundary (DB read).

impl sqlx::Type<sqlx::Sqlite> for Sealed<String> {
    fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
        <String as sqlx::Type<sqlx::Sqlite>>::type_info()
    }

    fn compatible(ty: &sqlx::sqlite::SqliteTypeInfo) -> bool {
        <String as sqlx::Type<sqlx::Sqlite>>::compatible(ty)
    }
}

impl<'q> sqlx::Encode<'q, sqlx::Sqlite> for Sealed<String> {
    fn encode_by_ref(
        &self,
        args: &mut Vec<sqlx::sqlite::SqliteArgumentValue<'q>>,
    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
        <String as sqlx::Encode<'q, sqlx::Sqlite>>::encode_by_ref(self.unseal(), args)
    }
}

impl<'r> sqlx::Decode<'r, sqlx::Sqlite> for Sealed<String> {
    fn decode(
        value: sqlx::sqlite::SqliteValueRef<'r>,
    ) -> Result<Self, sqlx::error::BoxDynError> {
        let s = <String as sqlx::Decode<'r, sqlx::Sqlite>>::decode(value)?;
        Ok(Sealed(s))
    }
}

// ===== Serde Deserialize =====
// Symmetric with Serialize: Serialize unwraps at IO exit, Deserialize re-seals at IO entry.

impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Sealed<T> {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let value = T::deserialize(deserializer)?;
        Ok(Sealed(value))
    }
}
// ===== Promptable integration =====
// Sealed<String> can participate in prompt rendering (ActionView fields, BeatRequest).
// The inner value is automatically extracted at the IO boundary (prompt render).

impl crate::llm::Reflectable for Sealed<String> {}

impl crate::llm::Promptable for Sealed<String> {
    fn to_markdown_depth(&self, _depth: usize) -> String {
        self.unseal().clone()
    }
    fn to_markdown_item(&self) -> String {
        self.unseal().clone()
    }
}
