//! Error types for [`Hydrate`](crate::Hydrate) and [`Reconcile`](crate::Reconcile) operations.
//!
//! Both error types wrap [`loro::LoroError`] and add domain-specific variants
//! for type mismatches, missing properties, overflow, and JSON failures.

#[derive(Debug, thiserror::Error)]
pub enum HydrateError {
    #[error(transparent)]
    Loro(#[from] loro::LoroError),

    #[error("expected {expected}, found {found}")]
    Unexpected {
        expected: &'static str,
        found: &'static str,
    },

    #[error("missing required property: {key}")]
    Missing { key: String },

    #[error("json deserialization failed for {key}: {source}")]
    Json {
        key: String,
        #[source]
        source: serde_json::Error,
    },

    #[error("integer overflow: {value} doesn't fit in {target_type}")]
    Overflow {
        value: i64,
        target_type: &'static str,
    },
}

impl HydrateError {
    pub fn unexpected(expected: &'static str, found: &'static str) -> Self {
        Self::Unexpected { expected, found }
    }

    pub fn missing(key: impl Into<String>) -> Self {
        Self::Missing { key: key.into() }
    }
}

/// Extension trait for `Result<T, HydrateError>`.
pub trait HydrateResultExt<T> {
    /// Convert `Unexpected` errors to `None`, propagating all other errors.
    /// Useful for trying multiple type interpretations.
    fn strip_unexpected(self) -> Result<Option<T>, HydrateError>;
}

impl<T> HydrateResultExt<T> for Result<T, HydrateError> {
    fn strip_unexpected(self) -> Result<Option<T>, HydrateError> {
        match self {
            Ok(v) => Ok(Some(v)),
            Err(HydrateError::Unexpected { .. }) => Ok(None),
            Err(e) => Err(e),
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ReconcileError {
    #[error(transparent)]
    Loro(#[from] loro::LoroError),

    #[error("json serialization failed: {0}")]
    Json(#[from] serde_json::Error),

    #[error("type mismatch: expected {expected} container, found {found}")]
    TypeMismatch {
        expected: &'static str,
        found: &'static str,
    },

    #[error("stale heads: document was modified during reconciliation")]
    StaleHeads,
}
