use std::path::PathBuf;

use thiserror::Error;

/// Result type used by this crate.
pub type Result<T> = std::result::Result<T, Error>;

/// Error returned by Ghidra bridge and API operations.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
    #[error("{operation}: {source}")]
    Runtime {
        operation: &'static str,
        #[source]
        source: crate::runtime::Error,
    },

    #[error("JNI error: {0}")]
    Jni(#[from] jni::errors::Error),

    #[error("{operation}: {class_name}: {message}")]
    JavaException {
        operation: &'static str,
        class_name: String,
        message: String,
        stack_trace: String,
    },

    #[error("{handle_type} handle is already closed")]
    ClosedHandle { handle_type: &'static str },

    #[error("{operation}: {source}")]
    Json {
        operation: String,
        #[source]
        source: serde_json::Error,
    },

    #[error("{operation}: {path}: {source}")]
    Io {
        operation: &'static str,
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },

    #[error("{message}")]
    InvalidInput { message: String },
}

impl Error {
    pub(crate) fn runtime(operation: &'static str, source: crate::runtime::Error) -> Self {
        Self::Runtime { operation, source }
    }

    pub(crate) fn json(operation: impl Into<String>, source: serde_json::Error) -> Self {
        Self::Json {
            operation: operation.into(),
            source,
        }
    }

    pub(crate) fn invalid_input(message: impl Into<String>) -> Self {
        Self::InvalidInput {
            message: message.into(),
        }
    }

    pub(crate) fn closed_handle(handle_type: &'static str) -> Self {
        Self::ClosedHandle { handle_type }
    }
}
