//! Error type for voa-core

use std::path::PathBuf;

/// Error type for voa-core
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// Identifier is illegal because it consists of the empty string
    #[error("Identifier is empty")]
    IdentifierIsEmpty,

    /// Identifier contains illegal characters
    #[error("Identifier contains illegal character ({0})")]
    IdentifierIllegalChars(char),

    /// Identifier is illegal
    #[error("Identifier is illegal: {context}")]
    IllegalIdentifier {
        /// Context for the error
        context: &'static str,
    },

    /// Expected a directory, but found a different entry
    #[error("Expected a directory at {path}")]
    ExpectedDirectory {
        /// The path at which the error occurred.
        path: PathBuf,
    },

    /// Expected a file, but found a different entry
    #[error("Expected a file at {path}")]
    ExpectedFile {
        /// The path at which the error occurred.
        path: PathBuf,
    },

    /// Illegal symlink found
    #[error("Illegal symlink found at {path}: {context}")]
    IllegalSymlink {
        /// The path at which the error occurred.
        path: PathBuf,

        /// Context for the error
        context: &'static str,
    },

    /// Illegal symlink target found during canonicalization
    #[error("Illegal symlink target {path} found during canonicalization")]
    IllegalSymlinkTarget {
        /// The path at which the error occurred.
        path: PathBuf,
    },

    /// Cyclic symlinks found during canonicalization
    #[error("Cyclic symlinks found during canonicalization at {path}")]
    CyclicSymlinks {
        /// The path at which the error occurred.
        path: PathBuf,
    },

    /// An I/O error occurred at a path.
    #[error("I/O error at path {path} while {context}:\n{source}")]
    IoPath {
        /// The path at which the error occurred.
        path: PathBuf,

        /// The context in which the error occurred.
        ///
        /// This is meant to complete the sentence "I/O error at path XXX while ...".
        context: &'static str,

        /// The source error.
        source: std::io::Error,
    },

    /// A path is not a valid segment path.
    #[error("The path {path:?} is not a valid segment path, because {context}")]
    InvalidSegmentPath {
        /// The illegal segment path.
        path: PathBuf,

        /// The context in which the error occurred.
        ///
        /// This is meant to complete the sentence "The segment path {path} is not valid, because".
        context: String,
    },

    /// An internal error signals an inconsistency in voa-core.
    /// Those should never happen, they signal a logic error in the implementation.
    #[error("Internal error: {context}")]
    InternalError {
        /// A description of the problem
        context: String,
    },

    /// A winnow parser for a type failed and produced and error.
    #[error("Parser error:\n{0}")]
    ParseError(String),

    /// A write error occurred.
    ///
    /// Use this variant for generic errors when writing verifiers to VOA hierarchies.
    #[error("Write error:\n{0}")]
    WriteError(Box<dyn std::error::Error + Send + Sync>),
}

impl<'a> From<winnow::error::ParseError<&'a str, winnow::error::ContextError>> for Error {
    /// Converts a [`winnow::error::ParseError`] into an [`Error::ParseError`].
    fn from(value: winnow::error::ParseError<&'a str, winnow::error::ContextError>) -> Self {
        Self::ParseError(value.to_string())
    }
}
