use std::path::PathBuf;
use anyhow::Context;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use tracing::trace;
use crate::{
    format::collection::Collection,
    store::{BaoBlobSize, ExportFormat, ExportMode, MapEntry, Store as BaoStore},
    util::progress::{IdGenerator, ProgressSender},
    Hash,
};
pub async fn export<D: BaoStore>(
    db: &D,
    hash: Hash,
    outpath: PathBuf,
    format: ExportFormat,
    mode: ExportMode,
    progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
) -> anyhow::Result<()> {
    match format {
        ExportFormat::Blob => export_blob(db, hash, outpath, mode, progress).await,
        ExportFormat::Collection => export_collection(db, hash, outpath, mode, progress).await,
    }
}
pub async fn export_collection<D: BaoStore>(
    db: &D,
    hash: Hash,
    outpath: PathBuf,
    mode: ExportMode,
    progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
) -> anyhow::Result<()> {
    tokio::fs::create_dir_all(&outpath).await?;
    let collection = Collection::load_db(db, &hash).await?;
    for (name, hash) in collection.into_iter() {
        #[allow(clippy::needless_borrow)]
        let path = outpath.join(pathbuf_from_name(&name));
        export_blob(db, hash, path, mode, progress.clone()).await?;
    }
    Ok(())
}
pub async fn export_blob<D: BaoStore>(
    db: &D,
    hash: Hash,
    outpath: PathBuf,
    mode: ExportMode,
    progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
) -> anyhow::Result<()> {
    if let Some(parent) = outpath.parent() {
        tokio::fs::create_dir_all(parent).await?;
    }
    trace!("exporting blob {} to {}", hash, outpath.display());
    let id = progress.new_id();
    let entry = db.get(&hash).await?.context("entry not there")?;
    progress
        .send(ExportProgress::Found {
            id,
            hash,
            outpath: outpath.clone(),
            size: entry.size(),
            meta: None,
        })
        .await?;
    let progress1 = progress.clone();
    db.export(
        hash,
        outpath,
        mode,
        Box::new(move |offset| Ok(progress1.try_send(ExportProgress::Progress { id, offset })?)),
    )
    .await?;
    progress.send(ExportProgress::Done { id }).await?;
    Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExportProgress {
    Found {
        id: u64,
        hash: Hash,
        size: BaoBlobSize,
        outpath: PathBuf,
        meta: Option<Bytes>,
    },
    Progress {
        id: u64,
        offset: u64,
    },
    Done {
        id: u64,
    },
    AllDone,
    Abort(serde_error::Error),
}
fn pathbuf_from_name(name: &str) -> PathBuf {
    let mut path = PathBuf::new();
    for part in name.split('/') {
        path.push(part);
    }
    path
}