#[cfg(feature = "mongodb")]
use ::mongodb::{ClientSession, SessionCursor};
use revolt_result::{Result, ToRevoltError};
use serde::Deserialize;

#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum ChunkedDatabaseGenerator<T> {
    #[cfg(feature = "mongodb")]
    MongoDb {
        session: ClientSession,
        cursor: SessionCursor<T>,
    },

    Reference {
        offset: usize,
        data: Vec<T>,
    },
}

impl<T: for<'d> Deserialize<'d> + Clone> ChunkedDatabaseGenerator<T> {
    #[cfg(feature = "mongodb")]
    pub fn new_mongo(session: ClientSession, cursor: SessionCursor<T>) -> Self {
        Self::MongoDb { session, cursor }
    }

    pub fn new_reference(data: Vec<T>) -> Self {
        Self::Reference { offset: 0, data }
    }

    pub async fn next(&mut self) -> Result<Option<T>> {
        match self {
            #[cfg(feature = "mongodb")]
            Self::MongoDb { session, cursor } => {
                cursor.next(session).await.transpose().to_internal_error()
            }
            Self::Reference { offset, data } => {
                if let Some(value) = data.get(*offset) {
                    *offset += 1;
                    Ok(Some(value.clone()))
                } else {
                    Ok(None)
                }
            }
        }
    }

    pub async fn next_n(&mut self, n: usize) -> Result<Option<Vec<T>>> {
        let mut docs = Vec::new();

        while docs.len() < n {
            if let Some(doc) = self.next().await? {
                docs.push(doc);
            } else if docs.is_empty() {
                return Ok(None);
            } else {
                break;
            }
        }

        Ok(Some(docs))
    }
}
