//! Resolved filesystem paths for a single tidalDB instance.
//!
//! [`Paths`] is the single source of truth for directory layout. Both the
//! [`TidalDbBuilder`](super::TidalDbBuilder) and the future CLI use it to
//! resolve subdirectory locations from a base path.

use std::path::{Path, PathBuf};

/// Resolved filesystem paths for a single tidalDB instance.
///
/// All paths derive from a single base directory. Use [`Paths::new`] to
/// construct from a base path. Directories are NOT created automatically --
/// call [`Paths::ensure_all`] to create them.
///
/// # Directory Layout
///
/// | Directory         | Purpose                                           |
/// |-------------------|---------------------------------------------------|
/// | `{base}/wal`      | Write-ahead log segments                          |
/// | `{base}/items`    | fjall keyspace for item entities                  |
/// | `{base}/users`    | fjall keyspace for user entities                  |
/// | `{base}/creators` | fjall keyspace for creator entities                |
/// | `{base}/cache`    | Materialized views and secondary indexes (future) |
///
/// # Examples
///
/// ```
/// use tidaldb::Paths;
///
/// let paths = Paths::new("/data/tidaldb");
/// assert!(paths.wal_dir().ends_with("wal"));
/// assert!(paths.items_dir().ends_with("items"));
/// ```
pub struct Paths {
    base: PathBuf,
}

impl Paths {
    /// Create a new `Paths` from the given base directory.
    ///
    /// No filesystem operations are performed. Call [`ensure_all`](Self::ensure_all)
    /// to create the directories.
    #[must_use]
    pub fn new(base: impl Into<PathBuf>) -> Self {
        Self { base: base.into() }
    }

    /// The base directory from which all subdirectories are derived.
    #[must_use]
    pub fn base(&self) -> &Path {
        &self.base
    }

    /// Path to the write-ahead log directory: `{base}/wal`.
    #[must_use]
    pub fn wal_dir(&self) -> PathBuf {
        self.base.join("wal")
    }

    /// Path to the item entities directory: `{base}/items`.
    #[must_use]
    pub fn items_dir(&self) -> PathBuf {
        self.base.join("items")
    }

    /// Path to the user entities directory: `{base}/users`.
    #[must_use]
    pub fn users_dir(&self) -> PathBuf {
        self.base.join("users")
    }

    /// Path to the creator entities directory: `{base}/creators`.
    #[must_use]
    pub fn creators_dir(&self) -> PathBuf {
        self.base.join("creators")
    }

    /// Path to the cache directory: `{base}/cache`.
    #[must_use]
    pub fn cache_dir(&self) -> PathBuf {
        self.base.join("cache")
    }

    /// Create all subdirectories under the base path.
    ///
    /// Idempotent: does not fail if directories already exist. Uses
    /// `std::fs::create_dir_all` so intermediate directories are also created.
    ///
    /// # Errors
    ///
    /// Returns `std::io::Error` if directory creation fails (e.g., permission
    /// denied, disk full).
    pub fn ensure_all(&self) -> Result<(), std::io::Error> {
        let dirs = [
            self.wal_dir(),
            self.items_dir(),
            self.users_dir(),
            self.creators_dir(),
            self.cache_dir(),
        ];
        for dir in &dirs {
            std::fs::create_dir_all(dir)?;
        }
        Ok(())
    }
}

impl std::fmt::Debug for Paths {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Paths")
            .field("base", &self.base)
            .field("wal", &self.wal_dir())
            .field("items", &self.items_dir())
            .field("users", &self.users_dir())
            .field("creators", &self.creators_dir())
            .field("cache", &self.cache_dir())
            .finish()
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;

    #[test]
    fn paths_derive_from_base() {
        let paths = Paths::new("/data/tidaldb");
        assert_eq!(paths.base(), Path::new("/data/tidaldb"));
        assert_eq!(paths.wal_dir(), PathBuf::from("/data/tidaldb/wal"));
        assert_eq!(paths.items_dir(), PathBuf::from("/data/tidaldb/items"));
        assert_eq!(paths.users_dir(), PathBuf::from("/data/tidaldb/users"));
        assert_eq!(
            paths.creators_dir(),
            PathBuf::from("/data/tidaldb/creators")
        );
        assert_eq!(paths.cache_dir(), PathBuf::from("/data/tidaldb/cache"));
    }

    #[test]
    fn paths_from_pathbuf() {
        let base = PathBuf::from("/tmp/test");
        let paths = Paths::new(base.clone());
        assert_eq!(paths.base(), base.as_path());
    }

    #[test]
    fn paths_debug_includes_all_dirs() {
        let paths = Paths::new("/data/tidaldb");
        let debug = format!("{paths:?}");
        assert!(debug.contains("base"), "Debug should show base");
        assert!(debug.contains("wal"), "Debug should show wal");
        assert!(debug.contains("items"), "Debug should show items");
        assert!(debug.contains("users"), "Debug should show users");
        assert!(debug.contains("creators"), "Debug should show creators");
        assert!(debug.contains("cache"), "Debug should show cache");
    }

    #[test]
    fn paths_ensure_all_creates_directories() {
        let tmp = tempfile::tempdir().unwrap();
        let paths = Paths::new(tmp.path());
        paths.ensure_all().unwrap();

        assert!(paths.wal_dir().is_dir());
        assert!(paths.items_dir().is_dir());
        assert!(paths.users_dir().is_dir());
        assert!(paths.creators_dir().is_dir());
        assert!(paths.cache_dir().is_dir());
    }

    #[test]
    fn paths_ensure_all_is_idempotent() {
        let tmp = tempfile::tempdir().unwrap();
        let paths = Paths::new(tmp.path());
        paths.ensure_all().unwrap();
        // Second call should not error.
        paths.ensure_all().unwrap();
    }

    #[test]
    fn paths_trailing_slash_handled() {
        let paths = Paths::new("/data/tidaldb/");
        // PathBuf::join handles trailing slashes correctly.
        assert_eq!(paths.wal_dir(), PathBuf::from("/data/tidaldb/wal"));
    }
}
