//! Entry-level m2dir coroutines and their shared types.
//!
//! An entry is a single message file inside an m2dir, named
//! `<date>,<checksum>.<nonce>` per the specification. This module holds
//! the [`M2dirEntry`] handle and its companions, shared by the store,
//! get, list and delete coroutines below.

pub mod delete;
pub mod get;
pub mod list;
pub mod store;

use alloc::{string::String, vec::Vec};

use thiserror::Error;

use crate::{flag::M2dirFlags, path::M2dirPath};

/// Errors that can occur while parsing or validating an entry
/// filename.
#[derive(Clone, Debug, Error)]
pub enum M2dirEntryParseError {
    /// The given path is not a regular file.
    #[error("path {0} is not a regular file")]
    NotFile(M2dirPath),
    /// The path has no final filename component.
    #[error("path {0} is missing a filename")]
    MissingFilename(M2dirPath),
    /// The filename does not match the m2dir specification.
    #[error("entry {path} does not match filename spec: {reason}")]
    InvalidFilename {
        path: M2dirPath,
        reason: &'static str,
    },
    /// The checksum embedded in the filename does not match the file
    /// contents.
    #[error("invalid checksum for {path}: expected {expected:?}, got {got:?}")]
    InvalidChecksum {
        path: M2dirPath,
        expected: String,
        got: String,
    },
}

/// A single entry inside an [`M2dir`](crate::m2dir::M2dir).
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct M2dirEntry {
    id: String,
    path: M2dirPath,
}

impl M2dirEntry {
    /// Builds an [`M2dirEntry`] from a path and its unique id without
    /// checking the on-disk checksum. Used by coroutines that have
    /// just delivered the entry and trust their own checksum.
    pub fn from_parts(id: impl Into<String>, path: impl Into<M2dirPath>) -> Self {
        Self {
            id: id.into(),
            path: path.into(),
        }
    }

    /// Returns the path to the entry file.
    pub fn path(&self) -> &M2dirPath {
        &self.path
    }

    /// Returns the unique identifier of the entry (the
    /// `<checksum>.<nonce>` portion of the filename).
    pub fn id(&self) -> &str {
        &self.id
    }

    /// Returns the checksum portion of the id (the chunk before the
    /// last `.`).
    pub fn checksum(&self) -> &str {
        self.id.rsplit_once('.').map(|(c, _)| c).unwrap_or(&self.id)
    }
}

/// An [`M2dirEntry`] paired with its file contents and flags metadata.
///
/// Produced by the bulk reads on
/// [`M2dirClient`](crate::client::M2dirClient), which fetch an entry's
/// bytes and its `.flags` sidecar in one pass.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct M2dirFullEntry {
    entry: M2dirEntry,
    contents: Vec<u8>,
    flags: M2dirFlags,
}

impl M2dirFullEntry {
    /// Builds a full entry from its resolved handle, file contents and
    /// flags set.
    pub fn from_parts(entry: M2dirEntry, contents: Vec<u8>, flags: M2dirFlags) -> Self {
        Self {
            entry,
            contents,
            flags,
        }
    }

    /// Returns the underlying entry handle (id and on-disk path).
    pub fn entry(&self) -> &M2dirEntry {
        &self.entry
    }

    /// Returns the raw bytes read from the entry file.
    pub fn contents(&self) -> &[u8] {
        &self.contents
    }

    /// Returns the flags read from the entry's `.flags` sidecar.
    pub fn flags(&self) -> &M2dirFlags {
        &self.flags
    }
}
