//! The shared context-provider identity contract.

/// Boxed error cause that keeps this crate free of transport dependencies.
pub type ErrorCause = Box<dyn std::error::Error + Send + Sync>;

/// Failure of a context-provider operation.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ProviderError {
    /// The network/HTTP layer failed before a usable response arrived.
    #[error("transport failure while {context}")]
    Transport {
        /// What the adapter was doing, e.g. `"fetching METARs"`.
        context: String,
        /// Underlying transport error.
        #[source]
        cause: ErrorCause,
    },
    /// The provider answered, but with an error (HTTP status or in-band
    /// fault).
    #[error("provider error while {context}: {message}")]
    Provider {
        /// What the adapter was doing.
        context: String,
        /// Provider-supplied error message.
        message: String,
        /// HTTP status code, when the error surfaced at the HTTP layer.
        status: Option<u16>,
    },
    /// The upstream rejected the configured credential (HTTP 401/403).
    ///
    /// Kept apart from [`ProviderError::Provider`]: an entitlement lapse
    /// demands a different response than a vendor outage, and a consumer
    /// that cannot tell them apart silently falls back to less data.
    #[error("credentials rejected while {context} (HTTP {status})")]
    AuthRejected {
        /// What the adapter was doing.
        context: String,
        /// The rejecting HTTP status, 401 or 403.
        status: u16,
    },
    /// The response arrived but could not be decoded.
    #[error("undecodable response while {context}")]
    Decode {
        /// What the adapter was doing.
        context: String,
        /// Underlying decode error.
        #[source]
        cause: ErrorCause,
    },
    /// This source cannot serve the requested shape.
    #[error("unsupported request: {reason}")]
    Unsupported {
        /// Why the request cannot be served, e.g. an [`crate::Area`]
        /// variant the provider has no equivalent for.
        reason: String,
    },
}

impl ProviderError {
    /// The flat, wire-friendly class of this failure.
    #[must_use]
    pub fn kind(&self) -> ProviderErrorKind {
        match self {
            Self::Transport { .. } => ProviderErrorKind::Transport,
            Self::Provider { .. } => ProviderErrorKind::Provider,
            Self::AuthRejected { .. } => ProviderErrorKind::AuthRejected,
            Self::Decode { .. } => ProviderErrorKind::Decode,
            Self::Unsupported { .. } => ProviderErrorKind::Unsupported,
        }
    }
}

/// Flat failure classes for boundaries that cannot carry a Rust error chain
/// (agent reports, FFI, wire formats).
///
/// The distinction an agent must act on: `auth-rejected` (fix the
/// credential/entitlement) is not `transport`/`provider` (the source is down
/// or answering badly), and neither is an empty-but-successful answer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum ProviderErrorKind {
    /// The network/HTTP layer failed before a usable response arrived.
    Transport,
    /// The provider answered with an error.
    Provider,
    /// The upstream rejected the configured credential.
    AuthRejected,
    /// The response arrived but could not be decoded.
    Decode,
    /// This source cannot serve the requested shape.
    Unsupported,
}

impl ProviderErrorKind {
    /// The kebab-case wire string, identical to the serde form.
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Transport => "transport",
            Self::Provider => "provider",
            Self::AuthRejected => "auth-rejected",
            Self::Decode => "decode",
            Self::Unsupported => "unsupported",
        }
    }
}

/// The shared identity of every context provider.
///
/// A provider implements this once, then one capability trait per kind of
/// context it serves: [`crate::WeatherBriefingProvider`] today, and the
/// stubbed [`crate::OwnshipStatusProvider`] / [`crate::ControlCommandProvider`]
/// later. Splitting by capability (not by provider) keeps each consumer
/// depending only on the context it actually needs, and lets a new source
/// implement just the capabilities it offers.
pub trait ContextProvider: Send + Sync {
    /// Stable, lowercase identifier for this provider, e.g. `"awc"`.
    fn name(&self) -> &str;
}

#[cfg(test)]
mod tests;
