use std::path::PathBuf;

use thiserror::Error;

pub type Result<T, E = ServerError> = std::result::Result<T, E>;

#[derive(Debug, Error)]
pub enum ServerError {
    /// A filesystem read that knows WHICH path failed — config / topology /
    /// schema file loads carry the path so the operator sees the offending file.
    /// Constructed via [`ServerError::io`]; never `?`-converted (that path lacks
    /// a path to attach).
    #[error("failed to read {path}: {source}")]
    Io {
        path: PathBuf,
        source: std::io::Error,
    },
    #[error("invalid schema config: {0}")]
    SchemaConfig(String),
    #[error("schema build failed: {0}")]
    SchemaBuild(#[from] tidaldb::schema::SchemaError),
    #[error("tidalDB error: {0}")]
    Tidal(#[from] tidaldb::TidalError),
    /// A pathless `std::io::Error` from socket setup / serving (TCP bind,
    /// `local_addr`, `axum::serve`). This is the `?`-conversion target for
    /// `std::io::Error`; the path-tagged file-read case uses [`ServerError::Io`]
    /// instead. Named for its source (the network/serve stack) rather than the
    /// old misleading "Http", which read as an HTTP-protocol error.
    #[error("network/serve I/O error: {0}")]
    Network(#[from] std::io::Error),
    #[error("bad request: {0}")]
    BadRequest(String),
    #[error("cluster mode is experimental and disabled: {0}")]
    ExperimentalDisabled(String),
    #[error("cluster error: {0}")]
    Cluster(String),
    /// The server is shutting down and the cluster fabric has been taken; map to
    /// 503 so a post-shutdown access degrades cleanly instead of panicking.
    #[error("service unavailable: {0}")]
    Unavailable(String),
}

impl ServerError {
    pub fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
        Self::Io {
            path: path.into(),
            source,
        }
    }
}
