//! Error types for RETRIEVE query construction, validation, and execution.

/// Errors arising from query construction, validation, or execution.
///
/// Replaces the Milestone 1 stub `QueryError { message }`. Each variant
/// carries structured context so callers can programmatically handle
/// specific failure modes.
#[derive(Debug, thiserror::Error)]
pub enum QueryError {
    /// The named ranking profile does not exist in the registry.
    #[error("profile '{0}' not found")]
    ProfileNotFound(String),
    /// A filter references an invalid field or has an unsupported value.
    #[error("invalid filter on '{field}': {reason}")]
    InvalidFilter { field: String, reason: String },
    /// The requested limit is outside the allowed range.
    #[error("limit {requested} out of range [{min}, {max}]")]
    InvalidLimit {
        requested: usize,
        min: usize,
        max: usize,
    },
    /// A required index (vector, text, bitmap) is not available.
    #[error("index '{0}' not available")]
    IndexNotAvailable(String),
    /// The underlying storage engine returned an error.
    #[error("storage error: {0}")]
    StorageError(String),
    /// A pagination cursor could not be decoded.
    #[error("invalid cursor: {0}")]
    InvalidCursor(String),
    /// The query requires a candidate strategy not yet implemented.
    #[error("unsupported strategy: {0}")]
    UnsupportedStrategy(String),
    /// The FOR SESSION clause references a session that does not exist.
    #[error("session not found: {0}")]
    SessionNotFound(String),
    /// A referenced entity (cohort, collection, etc.) was not found.
    #[error("not found: {0}")]
    NotFound(String),
}

// Manual `From` because this is a lossy conversion: `StorageError` is
// flattened to its `Display` string. The variant holds `String`, not
// `StorageError`, so `#[from]` cannot be used.
impl From<crate::storage::StorageError> for QueryError {
    fn from(e: crate::storage::StorageError) -> Self {
        Self::StorageError(e.to_string())
    }
}

// ── Tests ───────────────────────────────────────────────────────────────────

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use crate::storage::StorageError;

    #[test]
    fn query_error_display_profile_not_found() {
        let e = QueryError::ProfileNotFound("trending".into());
        assert!(e.to_string().contains("trending"));
        assert!(e.to_string().contains("not found"));
    }

    #[test]
    fn query_error_display_invalid_filter() {
        let e = QueryError::InvalidFilter {
            field: "category".into(),
            reason: "unknown value".into(),
        };
        assert!(e.to_string().contains("category"));
    }

    #[test]
    fn query_error_display_invalid_limit() {
        let e = QueryError::InvalidLimit {
            requested: 0,
            min: 1,
            max: 500,
        };
        assert!(e.to_string().contains('0'));
    }

    #[test]
    fn query_error_display_invalid_cursor() {
        let e = QueryError::InvalidCursor("bad data".into());
        assert!(e.to_string().contains("bad data"));
    }

    #[test]
    fn query_error_display_unsupported_strategy() {
        let e = QueryError::UnsupportedStrategy("Hybrid requires M3".into());
        assert!(e.to_string().contains("Hybrid"));
    }

    #[test]
    fn query_error_from_storage_error() {
        let storage_err = StorageError::Closed;
        let query_err: QueryError = storage_err.into();
        assert!(matches!(query_err, QueryError::StorageError(_)));
        assert!(query_err.to_string().contains("storage"));
    }
}
