use_prelude!();

use collection::ScopedCollection;

use super::collection::document_id::DocumentId;
mod collection;

/// Use lifetime parameters as type-level IDs (generative lifetimes),
/// so as to prevent the following anti-pattern from compiling:
///
/// ```no_run
/// use ::dittolive_ditto::prelude::*;
/// use ::std::path::Path;
/// use ::serde_json::json;
///
/// let dir1 = Path::new("...");
/// let mut ditto1 = Ditto::builder().build().unwrap();
///
/// ditto1.store().with_batched_write(|mut tx| {
///     // API requires that this closure return:
///     // - either `tx.commit_changes()`
///     // - or `tx.revert_changes()`
///     let mut coll = tx.collection("collection");
///     coll
///        .find_with_args("color == $args.color", json!({"color": "yellow"}))
///        .remove()
///        .unwrap();
///     tx.commit_changes()
/// });
/// ```
use marker::InvariantLifetime as TypeLevelId;

// Note, in practice `'batch` is `'txn`, but we hide this from the signatures to hide this
// implementation detail to the users.
pub struct ScopedStore<'batch> {
    // " 'batch == 'txn "
    txn: &'batch mut ffi_sdk::CWriteTransaction,

    // This could be 'store (which is ≥ 'txn), but for all effects and purposes
    // shrinking this to `'txn` is just as useful, and simplifies / reduces
    // the number of lifetime parameters.
    store: &'batch Store, // a Ref to the REAL store not the scoped store

    results: &'batch mut Vec<WriteTransactionResult>,

    // make sure the `'batch` lifetime is invariant so that it can be used as
    // a type-level unique id.
    _txn_id: TypeLevelId<'batch>,
}

pub(super) fn with_batched_write<F>(
    store: &Store, // Has lifetime of 'store
    f: F,
) -> Result<Vec<WriteTransactionResult>, super::DittoError>
where
    for<'batch> F: FnOnce(ScopedStore<'batch>) -> Action<'batch>,
{
    use super::*;

    let hint: Option<char_p::Ref<'_>> = None;
    let mut txn = unsafe { ffi_sdk::ditto_write_transaction(&*store.ditto, hint).ok()? };
    let mut results = vec![];

    let batch = ScopedStore {
        txn: &mut *txn,
        store, // The &Store contains an Arc to the root Ditto instance
        results: &mut results,
        _txn_id: TypeLevelId::default(),
    };
    match f(batch).0 {
        ActionKind::Commit => {
            let status = unsafe { ffi_sdk::ditto_write_transaction_commit(&*store.ditto, txn) };
            if status != 0 {
                return Err(DittoError::from_ffi(ErrorKind::Internal));
            }
        }
        ActionKind::Rollback => {
            unsafe { ffi_sdk::ditto_write_transaction_rollback(&*store.ditto, txn) };
        }
    }
    Ok(results)
}

/// Identifier for changes
pub struct Action<'batch>(ActionKind, TypeLevelId<'batch>);

enum ActionKind {
    Commit,
    Rollback,
}

impl<'batch> ScopedStore<'batch> {
    /// Commit changes done in this ScopedStore.
    pub fn commit_changes(self: ScopedStore<'batch>) -> Action<'batch> {
        Action(ActionKind::Commit, TypeLevelId::default())
    }

    /// Revert changes done in this ScopedStore.
    pub fn revert_changes(self: ScopedStore<'batch>) -> Action<'batch> {
        Action(ActionKind::Rollback, TypeLevelId::default())
    }

    /// Return an handle to change a single [`Collection`].
    pub fn collection<'coll>(
        self: &'coll mut ScopedStore<'batch>,
        collection_name: &'_ str,
    ) -> ScopedCollection<'coll, 'batch> {
        let c_name = char_p::new(collection_name);
        ScopedCollection {
            batch: self,
            collection_name: c_name,
        }
    }
}

/// Provides information about the result of an operation on a document
/// that was part of a write transaction.
pub struct WriteTransactionResult {
    /// The name of the collection that the operation took place in.
    pub collection_name: char_p::Box,

    /// The Id of the document that the write transaction result applies to.
    pub doc_id: DocumentId,

    /// The type of write transaction result.
    pub kind: DocChangeKind,
}

#[derive(Debug, PartialEq, Eq)]

/// The types of write transaction result.
pub enum DocChangeKind {
    Inserted,
    Removed,
    Evicted,
    Updated,
}
