//! The implementation of creating an archive from a worktree stream, similar to `git archive`.
//!
//! ## Deviation
//!
//! This implementation is early and just does the basics. Git does more to support more context when filtering and to keep
//! more information about entries in the various archive formats.
//! `tar` is implemented in a very basic fashion only.
//!
//! ## Feature Flags
//! All features are related to which container formats are available.
#![cfg_attr(
    all(doc, feature = "document-features"),
    doc = ::document_features::document_features!()
)]
#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))]
#![deny(rust_2018_idioms, missing_docs)]
#![forbid(unsafe_code)]

use bstr::BString;

/// The error returned by [`write_stream()`].
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error(transparent)]
    NextStreamEntry(#[from] gix_worktree_stream::entry::Error),
    #[error("The internal format cannot be used as an archive, it's merely a debugging tool")]
    InternalFormatMustNotPersist,
    #[error("Support for the format '{wanted:?}' was not compiled in")]
    SupportNotCompiledIn { wanted: Format },
    #[error("Cannot create a zip archive if output stream does not support seek")]
    ZipWithoutSeek,
    #[error("Cannot use modification as it is not within the supported bounds")]
    InvalidModificationTime(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}

/// The supported container formats for use in [`write_stream()`].
#[derive(Default, PartialEq, Eq, Copy, Clone, Debug)]
pub enum Format {
    /// An internal format that is suitable only for intra-process communication.
    ///
    /// All transformations in the options are ignored. Calling [`write_stream`] is disallowed
    /// as it's more efficient to call [gix_worktree_stream::Stream::into_read()] right away.
    /// It is provided here as a basis available without extra dependencies, and as a debugging tool.
    #[default]
    InternalTransientNonPersistable,
    /// A standard `tar` archive.
    ///
    /// Use it as well if a custom container format is desired. The idea is to decode it on a separate thread
    /// to rewrite the data to the desired format.
    Tar,
    /// A convenience format that will `gzip` deflate the `tar` stream.
    TarGz {
        /// If `None`, use the default compression level. Otherwise use the given one which
        /// ranges from 0-9 for the deflate algorithm.
        compression_level: Option<u8>,
    },
    /// A standard `zip` archive. Note that this format silently converts illformed UTF-8 to UTF-8, which will
    /// equal a change of path.
    ///
    /// Requires the `zip` feature toggle to have an effect.
    ///
    /// ### Shortcoming
    ///
    /// Even though symlinks are stored as such, for some reason at least on MacOS those aren't restored. That works,
    /// however, when letting `git` create the archive.
    Zip {
        /// If `None`, use the default compression level. Otherwise use the given one which
        /// ranges from 0-9 for the deflate algorithm.
        compression_level: Option<u8>,
    },
}

/// Options for configuring [`write_stream()`].
#[derive(Clone, Debug)]
pub struct Options {
    /// The archive's format.
    pub format: Format,
    /// Given a `path`, originating in the git tree, to place into the archive, put `<prefix>/path` in front of it.
    ///
    /// Note that that `/` should be used as separator, and that a prefix directory has to end with `/`.
    pub tree_prefix: Option<BString>,
    /// The modification time for all entries in the archive as seen since UNIX epoch.
    ///
    /// Defaults to the current time. The caller may set this to the commit time if available.
    pub modification_time: gix_date::SecondsSinceUnixEpoch,
}

impl Default for Options {
    fn default() -> Self {
        Options {
            format: Default::default(),
            tree_prefix: None,
            modification_time: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .map(|t| t.as_secs() as i64)
                .unwrap_or_default(),
        }
    }
}

mod write;
pub use write::{write_stream, write_stream_seek};
