//! Error types for the ASPIC+ library.
//!
//! Errors are organised in a two-level hierarchy:
//!
//! - [`AspicError`] is the top-level error returned by pipeline operations
//!   and public API methods. It can wrap:
//!   - [`SolverError`] — failures in semantics solvers, SAT backends, or
//!     external solver processes (parse errors, solver not found, powerset
//!     limits, unsupported semantics).
//!   - [`SerialError`] — failures during serialisation or deserialisation
//!     of ASPIC+ systems (unknown contrariness relation, unknown premise kind).
//!   - A [`PipelineStageError`](AspicError::PipelineStageError) variant that
//!     records which [`PipelineStage`] (grounding or evaluation) produced
//!     the underlying error.
//!
//! Most users will only encounter [`AspicError`] through the `Result` types
//! returned by [`AspicPipeline::evaluate`](crate::pipeline::AspicPipeline::evaluate)
//! and related methods.

use std::fmt;

/// Identifies which pipeline stage produced an error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PipelineStage {
    Grounding,
    Evaluation,
}

impl fmt::Display for PipelineStage {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PipelineStage::Grounding => write!(f, "grounding"),
            PipelineStage::Evaluation => write!(f, "evaluation"),
        }
    }
}

// ---- Per-domain error types ------------------------------------------------

/// Errors arising from semantics solvers, SAT backends, or external solver
/// processes.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SolverError {
    #[error("ASPARTIX parse error: {0}")]
    AspartixParse(String),

    #[error("external solver not found: {0}")]
    NotFound(String),

    #[error("external solver failed: {0}")]
    Failed(String),

    #[error(
        "powerset enumeration limit exceeded: {count} elements (limit {limit}); \
         the algorithm is O(2^n) and computation is infeasible at this scale"
    )]
    PowersetLimitExceeded { count: usize, limit: usize },

    #[error("unsupported semantics: {0}")]
    UnsupportedSemantics(String),
}

/// Errors arising during serialisation / deserialisation of ASPIC+ systems.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SerialError {
    #[error("unknown contrariness relation: `{0}`")]
    UnknownContrarinessRelation(String),

    #[error("unknown premise kind: `{0}`")]
    UnknownPremiseKind(String),
}

// ---- Top-level error -------------------------------------------------------

/// Top-level error type for the ASPIC+ library, wrapping solver errors,
/// serialisation errors, and pipeline stage failures.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AspicError {
    #[error("pipeline stage '{stage}' failed: {source}")]
    PipelineStageError {
        stage: PipelineStage,
        #[source]
        source: Box<AspicError>,
    },

    #[error(transparent)]
    Solver(#[from] SolverError),

    #[error(transparent)]
    Serial(#[from] SerialError),
}

impl AspicError {
    /// Wrap this error with pipeline stage context.
    pub fn in_stage(self, stage: PipelineStage) -> Self {
        AspicError::PipelineStageError {
            stage,
            source: Box::new(self),
        }
    }
}
