use arrow_schema::ArrowError;
use snafu::{Location, Snafu};
type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
pub fn box_error(e: impl std::error::Error + Send + Sync + 'static) -> BoxedError {
    Box::new(e)
}
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum Error {
    #[snafu(display("Invalid user input: {source}, {location}"))]
    InvalidInput {
        source: BoxedError,
        location: Location,
    },
    #[snafu(display("Dataset already exists: {uri}, {location}"))]
    DatasetAlreadyExists { uri: String, location: Location },
    #[snafu(display("Append with different schema:"))]
    SchemaMismatch {},
    #[snafu(display("Dataset at path {path} was not found: {source}, {location}"))]
    DatasetNotFound {
        path: String,
        source: BoxedError,
        location: Location,
    },
    #[snafu(display("Encountered corrupt file {path}: {source}, {location}"))]
    CorruptFile {
        path: object_store::path::Path,
        source: BoxedError,
        location: Location,
        },
    #[snafu(display("Not supported: {source}, {location}"))]
    NotSupported {
        source: BoxedError,
        location: Location,
    },
    #[snafu(display("Commit conflict for version {version}: {source}, {location}"))]
    CommitConflict {
        version: u64,
        source: BoxedError,
        location: Location,
    },
    #[snafu(display("Encountered internal error. Please file a bug report at https://github.com/lancedb/lance/issues. {message}, {location}"))]
    Internal { message: String, location: Location },
    #[snafu(display("A prerequisite task failed: {message}, {location}"))]
    PrerequisiteFailed { message: String, location: Location },
    #[snafu(display("LanceError(Arrow): {message}, {location}"))]
    Arrow { message: String, location: Location },
    #[snafu(display("LanceError(Schema): {message}, {location}"))]
    Schema { message: String, location: Location },
    #[snafu(display("Not found: {uri}, {location}"))]
    NotFound { uri: String, location: Location },
    #[snafu(display("LanceError(IO): {message}, {location}"))]
    IO { message: String, location: Location },
    #[snafu(display("LanceError(Index): {message}, {location}"))]
    Index { message: String, location: Location },
    #[snafu(display("Cannot infer storage location from: {message}"))]
    InvalidTableLocation { message: String },
    Stop,
}
impl Error {
    pub fn corrupt_file(
        path: object_store::path::Path,
        message: impl Into<String>,
        location: Location,
    ) -> Self {
        let message: String = message.into();
        Self::CorruptFile {
            path,
            source: message.into(),
            location,
        }
    }
    pub fn invalid_input(message: impl Into<String>, location: Location) -> Self {
        let message: String = message.into();
        Self::InvalidInput {
            source: message.into(),
            location,
        }
    }
}
trait ToSnafuLocation {
    fn to_snafu_location(&'static self) -> snafu::Location;
}
impl ToSnafuLocation for std::panic::Location<'static> {
    fn to_snafu_location(&'static self) -> snafu::Location {
        snafu::Location::new(self.file(), self.line(), self.column())
    }
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<ArrowError> for Error {
    #[track_caller]
    fn from(e: ArrowError) -> Self {
        Self::Arrow {
            message: e.to_string(),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<&ArrowError> for Error {
    #[track_caller]
    fn from(e: &ArrowError) -> Self {
        Self::Arrow {
            message: e.to_string(),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<std::io::Error> for Error {
    #[track_caller]
    fn from(e: std::io::Error) -> Self {
        Self::IO {
            message: (e.to_string()),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<object_store::Error> for Error {
    #[track_caller]
    fn from(e: object_store::Error) -> Self {
        Self::IO {
            message: (e.to_string()),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<prost::DecodeError> for Error {
    #[track_caller]
    fn from(e: prost::DecodeError) -> Self {
        Self::IO {
            message: (e.to_string()),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<tokio::task::JoinError> for Error {
    #[track_caller]
    fn from(e: tokio::task::JoinError) -> Self {
        Self::IO {
            message: (e.to_string()),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<object_store::path::Error> for Error {
    #[track_caller]
    fn from(e: object_store::path::Error) -> Self {
        Self::IO {
            message: (e.to_string()),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<url::ParseError> for Error {
    #[track_caller]
    fn from(e: url::ParseError) -> Self {
        Self::IO {
            message: (e.to_string()),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<serde_json::Error> for Error {
    #[track_caller]
    fn from(e: serde_json::Error) -> Self {
        Self::Arrow {
            message: e.to_string(),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
#[track_caller]
fn arrow_io_error_from_msg(message: String) -> ArrowError {
    ArrowError::IoError(
        message.clone(),
        std::io::Error::new(std::io::ErrorKind::Other, message),
    )
}
impl From<Error> for ArrowError {
    fn from(value: Error) -> Self {
        match value {
            Error::Arrow { message, .. } => arrow_io_error_from_msg(message), Error::IO { message, .. } => arrow_io_error_from_msg(message),
            Error::Schema { message, .. } => Self::SchemaError(message),
            Error::Index { message, .. } => arrow_io_error_from_msg(message),
            Error::Stop => arrow_io_error_from_msg("early stop".to_string()),
            e => arrow_io_error_from_msg(e.to_string()), }
    }
}
impl From<datafusion_sql::sqlparser::parser::ParserError> for Error {
    #[track_caller]
    fn from(e: datafusion_sql::sqlparser::parser::ParserError) -> Self {
        Self::IO {
            message: e.to_string(),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<datafusion_sql::sqlparser::tokenizer::TokenizerError> for Error {
    #[track_caller]
    fn from(e: datafusion_sql::sqlparser::tokenizer::TokenizerError) -> Self {
        Self::IO {
            message: e.to_string(),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<Error> for datafusion_common::DataFusionError {
    #[track_caller]
    fn from(e: Error) -> Self {
        Self::Execution(e.to_string())
    }
}
impl From<datafusion_common::DataFusionError> for Error {
    #[track_caller]
    fn from(e: datafusion_common::DataFusionError) -> Self {
        Self::IO {
            message: e.to_string(),
            location: std::panic::Location::caller().to_snafu_location(),
        }
    }
}
impl From<Error> for object_store::Error {
    fn from(err: Error) -> Self {
        Self::Generic {
            store: "N/A",
            source: Box::new(err),
        }
    }
}
#[track_caller]
pub fn get_caller_location() -> &'static std::panic::Location<'static> {
    std::panic::Location::caller()
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_caller_location_capture() {
        let current_fn = get_caller_location();
        let f: Box<dyn Fn() -> Result<()>> = Box::new(|| {
            Err(object_store::Error::Generic {
                store: "",
                source: "".into(),
            })?;
            Ok(())
        });
        match f().unwrap_err() {
            Error::IO { location, .. } => {
                assert_eq!(location.line, current_fn.line() + 4, "{}", location)
            }
            #[allow(unreachable_patterns)]
            _ => panic!("expected ObjectStore error"),
        }
    }
}