use std::fmt;
use std::io;

use obiskio::SKError;

#[derive(Debug)]
pub enum OKIError {
    Io(io::Error),
    Json(serde_json::Error),
    Partition(SKError),
    /// Source index is not in `Indexed` state.
    NotIndexed(std::path::PathBuf),
    /// Source indexes have incompatible configurations (k, m, n_bits).
    IncompatibleConfig,
    /// Count mode requested but a source index lacks count data.
    MismatchedMode,
    /// Two or more sources share the same genome label.
    DuplicateGenomeLabel(String),
    /// Operation not valid for this index configuration.
    InvalidInput(String),
    /// Sources mix exact and approximate evidence, or use incompatible approx parameters.
    IncompatibleEvidence(String),
}

pub type OKIResult<T> = Result<T, OKIError>;

impl fmt::Display for OKIError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OKIError::Io(e)            => write!(f, "I/O error: {e}"),
            OKIError::Json(e)          => write!(f, "JSON error: {e}"),
            OKIError::Partition(e)     => write!(f, "partition error: {e}"),
            OKIError::NotIndexed(p)    => write!(f, "index not fully built: {}", p.display()),
            OKIError::IncompatibleConfig => write!(f, "incompatible index configurations"),
            OKIError::MismatchedMode   => write!(f, "count mode requires all sources to have with_counts=true"),
            OKIError::DuplicateGenomeLabel(l) => write!(f, "duplicate genome label across sources: {l}"),
            OKIError::InvalidInput(m)          => write!(f, "invalid input: {m}"),
            OKIError::IncompatibleEvidence(m)  => write!(f, "incompatible evidence: {m}"),
        }
    }
}

impl std::error::Error for OKIError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            OKIError::Io(e)        => Some(e),
            OKIError::Json(e)      => Some(e),
            OKIError::Partition(e) => Some(e),
            _                      => None, // IncompatibleConfig, MismatchedMode, DuplicateGenomeLabel
        }
    }
}

impl From<io::Error> for OKIError {
    fn from(e: io::Error) -> Self { OKIError::Io(e) }
}

impl From<serde_json::Error> for OKIError {
    fn from(e: serde_json::Error) -> Self { OKIError::Json(e) }
}

impl From<SKError> for OKIError {
    fn from(e: SKError) -> Self { OKIError::Partition(e) }
}
