//! Typed failures for the MCP tool surface, mapped to the protocol's
//! error type at the boundary.

use chrono::NaiveDate;
use rmcp::ErrorData;

/// Failure of an aerocontext MCP tool.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ToolError {
    /// A tool parameter failed validation.
    #[error("invalid parameter: {message}")]
    InvalidParam {
        /// What was wrong with the parameter.
        message: String,
    },
    /// The navdata store has no blob effective today.
    #[error(
        "navdata store {store} has no snapshot effective {date}; \
         run the navdata_sync tool first or `aerocontext-navdata sync`"
    )]
    StoreEmpty {
        /// Store directory.
        store: String,
        /// The date checked.
        date: NaiveDate,
    },
    /// A store blob could not be read.
    #[error("could not read navdata blob {path}")]
    BlobRead {
        /// The blob file.
        path: String,
        /// Underlying failure.
        #[source]
        cause: std::io::Error,
    },
    /// A store blob did not decode.
    #[error("undecodable navdata blob {path}")]
    BlobDecode {
        /// The blob file.
        path: String,
        /// Underlying failure.
        #[source]
        cause: aerocontext_navdata::BlobError,
    },
    /// Enumerating the navdata store failed.
    #[error(transparent)]
    Store(#[from] aerocontext_navdata::SyncError),
    /// A navdata command failed.
    #[error(transparent)]
    Navdata(#[from] aerocontext_navdata::cli::CliError),
    /// A weather client could not be constructed.
    #[error("could not construct the {provider} weather client")]
    WeatherClient {
        /// Provider name, e.g. `"awc"`.
        provider: String,
        /// Underlying failure.
        #[source]
        cause: Box<dyn std::error::Error + Send + Sync>,
    },
    /// The briefing fan-out task could not be hosted or crashed.
    #[error("area briefing task failed")]
    BriefingTask {
        /// Underlying failure.
        #[source]
        cause: Box<dyn std::error::Error + Send + Sync>,
    },
}

/// Render an error with its full source chain, since `Display` alone
/// drops the `#[source]` context the variants carry.
pub fn render_chain(error: &dyn std::error::Error) -> String {
    let mut text = error.to_string();
    let mut source = error.source();
    while let Some(cause) = source {
        text.push_str(": ");
        text.push_str(&cause.to_string());
        source = cause.source();
    }
    text
}

impl From<ToolError> for ErrorData {
    fn from(error: ToolError) -> Self {
        let message = render_chain(&error);
        match error {
            ToolError::InvalidParam { .. } => ErrorData::invalid_params(message, None),
            _ => ErrorData::internal_error(message, None),
        }
    }
}

#[cfg(test)]
mod tests;
