//! Error types for the aurora-semantic crate.

use std::path::PathBuf;
use thiserror::Error;

use crate::types::WorkspaceId;

/// Main error type for the aurora-semantic crate.
#[derive(Error, Debug)]
pub enum Error {
    /// Configuration error.
    #[error("Configuration error: {0}")]
    Config(String),

    /// Workspace not found.
    #[error("Workspace not found: {0}")]
    WorkspaceNotFound(WorkspaceId),

    /// Workspace already exists.
    #[error("Workspace already exists: {0}")]
    WorkspaceExists(WorkspaceId),

    /// File not found.
    #[error("File not found: {0}")]
    FileNotFound(PathBuf),

    /// Directory not found.
    #[error("Directory not found: {0}")]
    DirectoryNotFound(PathBuf),

    /// IO error.
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    /// Parsing error.
    #[error("Parsing error in {file}: {message}")]
    Parse {
        /// File that failed to parse.
        file: PathBuf,
        /// Error message describing the parse failure.
        message: String,
    },

    /// Index error.
    #[error("Index error: {0}")]
    Index(String),

    /// Search error.
    #[error("Search error: {0}")]
    Search(String),

    /// Embedding error.
    #[error("Embedding error: {0}")]
    Embedding(String),

    /// Storage error.
    #[error("Storage error: {0}")]
    Storage(String),

    /// Serialization error.
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// Tantivy error.
    #[error("Tantivy error: {0}")]
    Tantivy(#[from] tantivy::TantivyError),

    /// Tantivy query parser error.
    #[error("Query parser error: {0}")]
    QueryParser(#[from] tantivy::query::QueryParserError),

    /// Model loading error.
    #[error("Model loading error: {0}")]
    ModelLoad(String),

    /// Invalid query.
    #[error("Invalid query: {0}")]
    InvalidQuery(String),

    /// Operation cancelled.
    #[error("Operation cancelled")]
    Cancelled,

    /// Internal error.
    #[error("Internal error: {0}")]
    Internal(String),
}

/// Result type alias using our Error.
pub type Result<T> = std::result::Result<T, Error>;

impl Error {
    /// Create a configuration error.
    pub fn config(msg: impl Into<String>) -> Self {
        Self::Config(msg.into())
    }

    /// Create a parsing error.
    pub fn parse(file: impl Into<PathBuf>, msg: impl Into<String>) -> Self {
        Self::Parse {
            file: file.into(),
            message: msg.into(),
        }
    }

    /// Create an index error.
    pub fn index(msg: impl Into<String>) -> Self {
        Self::Index(msg.into())
    }

    /// Create a search error.
    pub fn search(msg: impl Into<String>) -> Self {
        Self::Search(msg.into())
    }

    /// Create an embedding error.
    pub fn embedding(msg: impl Into<String>) -> Self {
        Self::Embedding(msg.into())
    }

    /// Create a storage error.
    pub fn storage(msg: impl Into<String>) -> Self {
        Self::Storage(msg.into())
    }

    /// Create a serialization error.
    pub fn serialization(msg: impl Into<String>) -> Self {
        Self::Serialization(msg.into())
    }

    /// Create a model loading error.
    pub fn model_load(msg: impl Into<String>) -> Self {
        Self::ModelLoad(msg.into())
    }

    /// Create an invalid query error.
    pub fn invalid_query(msg: impl Into<String>) -> Self {
        Self::InvalidQuery(msg.into())
    }

    /// Create an internal error.
    pub fn internal(msg: impl Into<String>) -> Self {
        Self::Internal(msg.into())
    }

    /// Check if this error is recoverable.
    pub fn is_recoverable(&self) -> bool {
        matches!(
            self,
            Self::FileNotFound(_)
                | Self::Parse { .. }
                | Self::InvalidQuery(_)
                | Self::Cancelled
        )
    }

    /// Check if this error should trigger a retry.
    pub fn is_retryable(&self) -> bool {
        matches!(self, Self::Io(_) | Self::Storage(_))
    }
}

impl From<serde_json::Error> for Error {
    fn from(e: serde_json::Error) -> Self {
        Self::Serialization(e.to_string())
    }
}

impl From<bincode::Error> for Error {
    fn from(e: bincode::Error) -> Self {
        Self::Serialization(e.to_string())
    }
}
