//! Embedded key-value storage engine optimized for NVMe.
//!
//! Single process, multi-threaded. Sync read/write API.
//! Each tree/map owns its storage — one tree = one database directory.
//!
//! # Durability backends
//!
//! Collections are generic over `D: Durability` (default: [`Bitcask`](durability::Bitcask)):
//!
//! - **Bitcask** — append-only log + compaction. Good for general workloads.
//! - **FixedStore** ([`Fixed`](durability::Fixed)) — fixed-slot pwrite, no compaction.
//!   Optimized for frequent updates of fixed-size values (counters, metrics, sessions).
//!
//! [`FixedTree`] and [`FixedMap`] are type aliases for `ConstTree<K, V, Fixed>` and
//! `ConstMap<K, V, Fixed>`. [`ZeroTree`] and [`ZeroMap`] also accept `Fixed` as backend.
//!
//! # Collection types
//!
//! | Type | Index | Values | Ordered | FixedStore | Feature |
//! |------|-------|--------|---------|:----------:|---------|
//! | [`ConstTree`] | SkipList | inline `[u8; V]` | yes | yes | *(default)* |
//! | [`ConstMap`] | HashMap | inline `[u8; V]` | no | yes | *(default)* |
//! | [`TypedTree`] | SkipList | typed `T` (in-memory, [`TypedRef`]) | yes | no | `typed-tree` |
//! | [`TypedMap`] | HashMap | typed `T` (in-memory, [`TypedRef`]) | no | no | `typed-tree` |
//! | [`ZeroTree`] | SkipList | zerocopy `T` (in-memory) | yes | yes | `typed-tree` |
//! | [`ZeroMap`] | HashMap | zerocopy `T` (in-memory) | no | yes | `typed-tree` |
//! | [`VarTree`] | SkipList | [`ByteView`] (disk + cache) | yes | no | `var-collections` |
//! | [`VarMap`] | HashMap | [`ByteView`] (disk + cache) | no | no | `var-collections` |
//! | `VarTypedTree` | SkipList | typed `T` (disk + cache) | yes | no | `typed-tree` + `var-collections` |
//! | `VarTypedMap` | HashMap | typed `T` (disk + cache) | no | no | `typed-tree` + `var-collections` |
//!
//! # Usage
//!
//! ```ignore
//! // Bitcask backend (default)
//! let tree = ConstTree::<[u8; 16], 64>::open("data/users", Config::balanced().build())?;
//!
//! // FixedStore backend
//! let tree = FixedTree::<[u8; 16], 64>::open("data/counters", FixedConfig::balanced().build())?;
//!
//! tree.put(&key, &value)?;
//! let val = tree.get(&key);
//! tree.close()?;
//! ```
//!
//! # Durability
//!
//! ## Bitcask
//!
//! Writes are buffered in memory (`write_buffer_size` per shard, default 1 MB).
//! `put()` returns as soon as the entry is copied into the buffer and the
//! in-memory index is updated — **no disk I/O on the write path**.
//!
//! This means unflushed data is lost on crash. To control durability:
//!
//! - [`flush_buffers()`](ConstTree::flush_buffers) — flush write buffers to disk (no fsync)
//! - [`close()`](ConstTree::close) — flush + fsync + write hint files
//!
//! ## FixedStore
//!
//! Each `put()` does a `pwrite` through the page cache (~200ns).
//! `fdatasync` is called in batches (configurable interval/count), **after**
//! releasing the shard lock. With `enable_fsync = true`, an additional
//! per-write `fdatasync` runs inside the shard lock for immediate durability.
//! No compaction, no write amplification, 1x space usage.
//!
//! # Thread safety
//!
//! All tree/map types are `Send + Sync`. Share via `Arc` for concurrent access:
//!
//! ```ignore
//! let tree = Arc::new(ConstTree::<[u8; 8], 8>::open("data/mydb", Config::balanced().build())?);
//! let t = tree.clone();
//! std::thread::spawn(move || { t.put(&key, &value).unwrap(); });
//! ```
//!
//! **Reads:**
//! - `ConstTree` / `ZeroTree` / `TypedTree` — **lock-free**: the index is a lock-free
//!   SkipList (`TypedTree` resolves the value through a `seize` RCU guard, no mutex).
//! - `VarTree` — lock-free SkipList lookup; on a block-cache **miss** a brief shard lock
//!   fetches the file handles, then the `pread` runs **outside** the lock.
//! - `ConstMap` / `ZeroMap` / `TypedMap` — a **brief per-shard mutex** guards the HashMap
//!   index. Values are inline for `Const`/`Zero`; `TypedMap` reads a pointer under the lock,
//!   then accesses the value lock-free via a `seize` guard.
//! - `VarMap` / `VarTypedMap` — brief per-shard mutex on the index; a `pread` on a cache
//!   miss runs outside the lock.
//!
//! **Writes** acquire the target key's **per-shard mutex** (write buffer + index update), so
//! writes to *different shards* never contend on the buffer or durability I/O. Tree writes
//! additionally link the new node into the shared SkipList under its internal linking lock —
//! briefly contended across shards, but the critical section is O(node height), not the full
//! O(log n) search.
//!
//! # Encryption at rest (feature `encryption`)
//!
//! Page-level AES-256-GCM encryption. Key from env or direct config:
//!
//! ```ignore
//! let key = PageCipher::key_from_env("ARMDB_KEY")?;
//! let config = Config::balanced().encryption_key(Some(key)).build();
//! let tree = ConstTree::<[u8; 16], 64>::open("data/encrypted", config)?;
//! // all data is transparently encrypted on disk
//! ```
//!
//! # Write hooks (secondary indexes)
//!
//! Generic `WriteHook<K>` / `TypedWriteHook<K, T>` parameter for synchronous
//! write and init notifications. Zero overhead when unused (`NoHook` default).
//!
//! - `on_write` — fires on every `put`/`insert`/`delete`/`cas`/`compare_delete`/`update`/`fetch_update`,
//!   including mutations inside `atomic()` (buffered under the shard lock and replayed once
//!   it is released, in application order). If the closure returns `Err`, hooks still fire
//!   for operations that were applied. On `Const*`/`Zero*` collections replay happens after
//!   the `sync()?` seam; Var/Typed collections have no per-write sync.
//! - `on_init` — fires once per live entry during collection open (after
//!   recovery). Enable via `NEEDS_INIT = true`. See [`armdb/docs/hooks.md`]
//!   for details.
//! - `NEEDS_OLD_VALUE` — only affects VarTree/VarMap (skips disk I/O for
//!   old value when `false`). In-memory collections (Const/Typed/Zero) always
//!   provide the old value at zero cost.
//!
//! ```ignore
//! impl WriteHook<[u8; 16]> for MyIndex {
//!     const NEEDS_OLD_VALUE: bool = true;
//!     const NEEDS_INIT: bool = true;
//!
//!     fn on_write(&self, key: &[u8; 16], old: Option<&[u8]>, new: Option<&[u8]>) {
//!         // update secondary index incrementally
//!     }
//!
//!     fn on_init(&self, key: &[u8; 16], value: &[u8]) {
//!         // populate secondary index at startup
//!     }
//! }
//!
//! let tree = ConstTree::<[u8; 16], 64, MyIndex>::open_hooked("data/indexed", config, my_index)?;
//! tree.migrate(|_, _| MigrateAction::Keep)?; // triggers on_init for all entries
//! ```
//!
//! # Compaction
//!
//! Compaction is **not automatic**. Dead bytes accumulate as entries are
//! overwritten or deleted. Use [`Compactor`] to run it in the background:
//!
//! ```ignore
//! use std::sync::Arc;
//! use std::time::Duration;
//!
//! let tree = Arc::new(ConstTree::<[u8; 16], 64>::open("data/mydb", Config::balanced().build())?);
//! let t = tree.clone();
//! let _compactor = Compactor::start(move || t.compact(), Duration::from_secs(60));
//! ```
//!
//! Or call `tree.compact()` manually when needed.
//!
//! # Iteration
//!
//! All Tree types provide `iter()`, `range()`, and `prefix_iter()` methods.
//! Map types (HashMap index) **do not support iteration**.
//!
//! Each method returns a dedicated iterator implementing `Iterator` + `DoubleEndedIterator`:
//!
//! | Tree | Iterator | Item |
//! |------|----------|------|
//! | [`ConstTree`] | [`ConstIter`] | `(K, [u8; V])` — copy |
//! | [`VarTree`] | [`VarIter`] | `(K, ByteView)` — RC, possible disk I/O |
//! | [`TypedTree`] | [`TypedIter`] | `(K, &T)` — reference, zero I/O |
//! | [`ZeroTree`] | [`ZeroIter`] | `(K, T)` — copy, zero I/O |
//!
//! | Method | Description |
//! |--------|-------------|
//! | `iter()` | All entries in index order |
//! | `range(start, end)` | Entries in `[start, end)` — start inclusive, end exclusive |
//! | `range_bounds(start, end)` | Entries with custom `Bound` — `Included`, `Excluded`, or `Unbounded` |
//! | `prefix_iter(prefix)` | Entries whose key starts with `prefix` |
//!
//! ```ignore
//! for (key, value) in tree.iter() { /* ... */ }
//!
//! let latest = tree.prefix_iter(&user_id).take(20).collect::<Vec<_>>();
//!
//! for (key, value) in tree.range(&start_key, &end_key) { /* ... */ }
//!
//! // Custom bounds: (5, 10] — exclude 5, include 10
//! use std::ops::Bound;
//! let entries: Vec<_> = tree.range_bounds(
//!     Bound::Excluded(&5u64.to_be_bytes()),
//!     Bound::Included(&10u64.to_be_bytes()),
//! ).collect();
//!
//! // DoubleEndedIterator — .rev() or .next_back()
//! let oldest = tree.prefix_iter(&user_id).rev().take(10);
//! ```
//!
//! ## Complexity
//!
//! | Operation | Complexity | Notes |
//! |-----------|------------|-------|
//! | `next()` | **O(1)** | follows SkipList level-0 forward pointer |
//! | `next_back()` | **O(log n)** | calls `find_last_lt()` — SkipList search from top |
//! | `iter()` / `range()` / `prefix_iter()` setup | O(log n) | initial SkipList search |
//!
//! `VarIter`: both `next()` and `next_back()` may additionally perform a
//! `pread` on block-cache miss. Use `warmup()` to pre-populate the cache.
//!
//! ## Weakly-consistent semantics
//!
//! Iterators do **not** create a snapshot. They are weakly-consistent:
//!
//! - Concurrent inserts/updates **may be visible** during iteration
//! - Deleted entries (marked nodes) are **automatically skipped**
//! - The `seize` guard prevents memory reclamation for the lifetime of the
//!   iterator — no use-after-free, but the index is not frozen
//!
//! # Ordering: `Config::reversed`
//!
//! `Config::reversed` controls the SkipList comparator direction.
//!
//! | `reversed` | `iter()` / `prefix_iter()` | `.rev()` / `next_back()` |
//! |:----------:|:--------------------------:|:------------------------:|
//! | `true` (**default**) | **DESC** (newest first) | ASC (oldest first) |
//! | `false` | **ASC** (oldest first) | DESC (newest first) |
//!
//! ```ignore
//! // reversed=true (default) — DESC: идеально для "newest first" пагинации
//! let tree = ConstTree::<[u8; 16], 64>::open("data/posts", Config::balanced().build())?;
//! let latest = tree.prefix_iter(&user_id).take(20);    // newest 20
//! let oldest = tree.prefix_iter(&user_id).rev().take(5); // oldest 5
//!
//! // reversed=false — ASC: естественный порядок ключей
//! let config = Config::balanced().reversed(false).build();
//! let tree = ConstTree::<[u8; 16], 64>::open("data/logs", config)?;
//! for (key, value) in tree.iter() { /* ascending order */ }
//! ```
//!
//! Keys are stored on disk as-is. `reversed` can be changed between restarts
//! without migration — it only affects in-memory index ordering.
//!
//! FixedStore collections take the same knob via `FixedConfig::reversed`
//! (default `true` → DESC); it behaves identically and is likewise in-memory
//! only.
//!
//! # Prefix sharding
//!
//! ```ignore
//! let config = Config::balanced().shard_prefix_bits(32).build();
//! let tree = ConstTree::<[u8; 16], 64>::open("data/users", config)?;
//! ```
//!
//! # Caveats
//!
//! - **Iterators are weakly-consistent, not snapshot.** Concurrent inserts and
//!   updates may be visible during iteration. Deleted entries are skipped.
//!   The `seize` guard prevents use-after-free but does not freeze the index.
//! - **CAS/update holds shard lock during possible disk I/O.** `VarTree::cas`,
//!   `VarTree::update`, `VarMap::cas`, and `VarMap::update` read the current value
//!   under the shard mutex. On a block-cache miss this issues a `pread` — blocking
//!   all writes to that shard until the read completes. Pre-warm the cache or size
//!   it to cover the working set.
//! - **`migrate()` on HashMap trees allocates `O(keys/shards)` memory.**
//!   `ConstMap::migrate` and `VarMap::migrate` collect all shard keys into a `Vec`
//!   before iterating. For very large shards this causes a transient memory spike.
//!   SkipList trees (`ConstTree`, `VarTree`) are not affected.
//!
//! # Shutdown
//!
//! ```ignore
//! // 1. Stop background tasks first
//! compactor.stop();
//! // 2. Close the tree (writes hint files, flushes, fsyncs)
//! Arc::try_unwrap(tree).expect("no other references").close()?;
//! ```
//!
//! If `close()` is not called (e.g. the tree is dropped via `Arc::drop`),
//! `Shard::Drop` still flushes write buffers, fsyncs, and writes hint files
//! automatically — no data loss and no slow recovery on next startup.
//!
//! # Features
//!
//! - `typed-tree` — enables [`TypedTree`], [`TypedMap`], [`ZeroTree`], [`ZeroMap`],
//!   [`Codec`] and codec implementations
//! - `var-collections` — enables [`VarTree`], [`VarMap`] (variable-length values with LRU cache)
//! - `rapira-codec` — [`RapiraCodec`] for rapira serialization (implies `typed-tree`)
//! - `bytemuck-codec` — [`BytemuckCodec`] / [`BytemuckSliceCodec`] (implies `typed-tree`)
//! - `bitcode-codec` — [`BitcodeCodec`] (implies `typed-tree`)
//! - `postcard-codec` — `PostcardCodec` for postcard/serde serialization (implies `typed-tree`)
//! - `encryption` — AES-256-GCM page-level encryption at rest
//! - `replication` — leader/follower log-shipping replication
//! - `armour` — integration with armour ecosystem: [`Db`](armour::Db),
//!   schema-versioned migrations, binary RPC server (TCP/UDS).
//!   See [`armour`] module docs.
//! - `rpc` — TCP/UDS RPC server (implies `armour`)
//! - `solana` — Solana pubkey/signature key types (implies `armour`)
//! - `hot-path-tracing` — per-operation `tracing::trace!` calls
//!
//! Internal/test-only: `parking_lot`, `loom`, `proptest`.

#![warn(clippy::unwrap_used)]

mod batch;
#[cfg(feature = "var-collections")]
mod byte_view;
#[cfg(feature = "var-collections")]
mod cache;
#[cfg(feature = "typed-tree")]
pub mod codec;
pub mod compaction;
mod config;
mod const_map;
mod const_tree;
#[cfg(feature = "encryption")]
mod crypto;
mod disk_loc;
pub mod durability;
mod engine;
mod entry;
mod error;
pub mod fixed;
mod flusher;
mod hint;
mod hook;
mod io;
mod key;
mod map_index;
mod meta;
mod recovery;
mod shard;
mod shutdown;
#[cfg(any(feature = "loom", miri, feature = "bench-internals"))]
pub mod skiplist;
#[cfg(not(any(feature = "loom", miri, feature = "bench-internals")))]
mod skiplist;
mod sync;
#[cfg(all(test, feature = "typed-tree"))]
mod test_faults;
mod tree_meta;
#[cfg(feature = "var-collections")]
mod value_cache;
#[cfg(feature = "var-collections")]
mod var_map;
#[cfg(feature = "var-collections")]
mod var_tree;
#[cfg(feature = "typed-tree")]
mod zero_map;
#[cfg(feature = "typed-tree")]
mod zero_tree;

#[cfg(feature = "typed-tree")]
mod typed_map;
#[cfg(feature = "typed-tree")]
mod typed_tree;

#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
mod var_typed_map;
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
mod var_typed_tree;

#[cfg(feature = "replication")]
pub mod replication;

#[cfg(feature = "replication")]
pub mod fixed_replication;

pub mod docs;

#[cfg(feature = "armour")]
pub mod armour;

#[cfg(feature = "schema")]
pub mod schema;

#[cfg(feature = "armour")]
pub use armour_core;
pub use batch::{Applied, BatchWrite};
#[cfg(feature = "var-collections")]
pub use byte_view::ByteView;
#[cfg(feature = "bitcode-codec")]
pub use codec::BitcodeCodec;
#[cfg(feature = "typed-tree")]
pub use codec::Codec;
#[cfg(feature = "postcard-codec")]
pub use codec::PostcardCodec;
#[cfg(feature = "rapira-codec")]
pub use codec::RapiraCodec;
#[cfg(feature = "typed-tree")]
pub use codec::ZerocopyCodec;
#[cfg(feature = "bytemuck-codec")]
pub use codec::{BytemuckCodec, BytemuckSliceCodec, BytemuckVec};
pub use compaction::Compactor;
#[cfg(feature = "var-collections")]
pub use config::CacheConfig;
pub use config::Config;
pub use config::IoBackend;
pub use config::Workload;
pub use const_map::{ConstMap, ConstMapIterView, ConstMapShard};
pub use const_tree::{ConstIter, ConstShard, ConstTree};
#[cfg(feature = "encryption")]
pub use crypto::PageCipher;
pub use error::{DbError, DbResult, SchemaMismatchKind};
pub use fixed::{FixedConfig, FixedMap, FixedTree, FixedWorkload};
pub use flusher::Flusher;
#[cfg(feature = "typed-tree")]
pub use hook::TypedWriteHook;
pub use hook::{NoHook, WriteHook};
pub use key::{Key, Location};
pub use map_index::{KeyPage, Page};
pub use shutdown::ShutdownSignal;
pub use tree_meta::{CollectionMeta, TreeMeta};
#[cfg(feature = "typed-tree")]
pub use typed_map::{TypedMap, TypedMapIterView, TypedMapShard};
#[cfg(feature = "typed-tree")]
pub use typed_tree::{TypedIter, TypedRef, TypedShard, TypedTree};
#[cfg(feature = "var-collections")]
pub use var_map::{VarMap, VarMapShard};
#[cfg(feature = "var-collections")]
pub use var_tree::{VarIter, VarShard, VarTree};
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
pub use var_typed_map::{VarTypedMap, VarTypedMapIterView, VarTypedMapShard};
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
pub use var_typed_tree::{VarTypedIter, VarTypedShard, VarTypedTree};
#[cfg(feature = "typed-tree")]
pub use zero_map::{ZeroMap, ZeroMapIterView, ZeroMapShard};
#[cfg(feature = "typed-tree")]
pub use zero_tree::{ZeroIter, ZeroShard, ZeroTree};

#[doc(hidden)]
pub use disk_loc::DiskLoc;

#[doc(hidden)]
pub use entry::{
    EntryHeader, FLAGS_MASK, SEQUENCE_MASK, SOFT_DELETE_BIT, TOMBSTONE_BIT, compute_crc32,
    entry_size, serialize_entry,
};
#[doc(hidden)]
pub use hint::{HintEntry, hint_entry_size, parse_hint_entries};

use std::mem::size_of;

pub(crate) fn shard_for_key<K: Key>(
    key: &K,
    shard_prefix_bits: usize,
    shard_count: usize,
) -> usize {
    if shard_prefix_bits == 0 || shard_prefix_bits >= size_of::<K>() * 8 {
        let hash = xxhash_rust::xxh3::xxh3_64(key.as_bytes());
        return (hash as usize) % shard_count;
    }

    let full_bytes = shard_prefix_bits / 8;
    let extra_bits = shard_prefix_bits % 8;

    let hash = if extra_bits == 0 {
        xxhash_rust::xxh3::xxh3_64(&key.as_bytes()[..full_bytes])
    } else {
        let mut buf = K::zeroed();
        buf.as_bytes_mut()[..full_bytes + 1].copy_from_slice(&key.as_bytes()[..full_bytes + 1]);
        let mask = !((1u8 << (8 - extra_bits)) - 1);
        buf.as_bytes_mut()[full_bytes] = key.as_bytes()[full_bytes] & mask;
        xxhash_rust::xxh3::xxh3_64(&buf.as_bytes()[..full_bytes + 1])
    };

    (hash as usize) % shard_count
}

/// Action returned by the `migrate()` callback for each entry.
pub enum MigrateAction<V> {
    /// Leave this entry unchanged.
    Keep,
    /// Replace the value.
    Update(V),
    /// Delete this entry.
    Delete,
}

/// Expands to `ZeroTree<<V as CollectionMeta>::SelfId, { size_of::<V>() }, V [, H, D]>`.
#[cfg(feature = "typed-tree")]
#[macro_export]
macro_rules! zero_tree {
    ($V:ty) => {
        $crate::ZeroTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
        >
    };
    ($V:ty, fixed) => {
        $crate::ZeroTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
            $crate::NoHook,
            $crate::durability::Fixed,
        >
    };
    ($V:ty, $H:ty) => {
        $crate::ZeroTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
            $H,
            $crate::durability::Bitcask,
        >
    };
    ($V:ty, $H:ty, fixed) => {
        $crate::ZeroTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
            $H,
            $crate::durability::Fixed,
        >
    };
}

/// Expands to `ZeroMap<<V as CollectionMeta>::SelfId, { size_of::<V>() }, V [, H, D]>`.
#[cfg(feature = "typed-tree")]
#[macro_export]
macro_rules! zero_map {
    ($V:ty) => {
        $crate::ZeroMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
        >
    };
    ($V:ty, fixed) => {
        $crate::ZeroMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
            $crate::NoHook,
            $crate::durability::Fixed,
        >
    };
    ($V:ty, $H:ty) => {
        $crate::ZeroMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
            $H,
            $crate::durability::Bitcask,
        >
    };
    ($V:ty, $H:ty, fixed) => {
        $crate::ZeroMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $V,
            $H,
            $crate::durability::Fixed,
        >
    };
}

/// Expands to `ConstTree<<V as CollectionMeta>::SelfId, { size_of::<V>() } [, H, D]>`.
#[macro_export]
macro_rules! const_tree {
    ($V:ty) => {
        $crate::ConstTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
        >
    };
    ($V:ty, fixed) => {
        $crate::ConstTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $crate::NoHook,
            $crate::durability::Fixed,
        >
    };
    ($V:ty, $H:ty) => {
        $crate::ConstTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $H,
            $crate::durability::Bitcask,
        >
    };
    ($V:ty, $H:ty, fixed) => {
        $crate::ConstTree<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $H,
            $crate::durability::Fixed,
        >
    };
}

/// Expands to `ConstMap<<V as CollectionMeta>::SelfId, { size_of::<V>() } [, H, D]>`.
#[macro_export]
macro_rules! const_map {
    ($V:ty) => {
        $crate::ConstMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
        >
    };
    ($V:ty, fixed) => {
        $crate::ConstMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $crate::NoHook,
            $crate::durability::Fixed,
        >
    };
    ($V:ty, $H:ty) => {
        $crate::ConstMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $H,
            $crate::durability::Bitcask,
        >
    };
    ($V:ty, $H:ty, fixed) => {
        $crate::ConstMap<
            <$V as $crate::CollectionMeta>::SelfId,
            { ::std::mem::size_of::<$V>() },
            $H,
            $crate::durability::Fixed,
        >
    };
}

/// Expands to `TypedTree<<V as CollectionMeta>::SelfId, V, C [, H]>`.
#[cfg(feature = "typed-tree")]
#[macro_export]
macro_rules! typed_tree {
    ($V:ty, $C:ty) => {
        $crate::TypedTree<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
        >
    };
    ($V:ty, $C:ty, $H:ty) => {
        $crate::TypedTree<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
            $H,
        >
    };
}

/// Expands to `TypedMap<<V as CollectionMeta>::SelfId, V, C [, H]>`.
#[cfg(feature = "typed-tree")]
#[macro_export]
macro_rules! typed_map {
    ($V:ty, $C:ty) => {
        $crate::TypedMap<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
        >
    };
    ($V:ty, $C:ty, $H:ty) => {
        $crate::TypedMap<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
            $H,
        >
    };
}

/// Expands to `VarTree<<V as CollectionMeta>::SelfId [, H]>`.
#[cfg(feature = "var-collections")]
#[macro_export]
macro_rules! var_tree {
    ($V:ty) => {
        $crate::VarTree<<$V as $crate::CollectionMeta>::SelfId>
    };
    ($V:ty, $H:ty) => {
        $crate::VarTree<<$V as $crate::CollectionMeta>::SelfId, $H>
    };
}

/// Expands to `VarMap<<V as CollectionMeta>::SelfId [, H]>`.
#[cfg(feature = "var-collections")]
#[macro_export]
macro_rules! var_map {
    ($V:ty) => {
        $crate::VarMap<<$V as $crate::CollectionMeta>::SelfId>
    };
    ($V:ty, $H:ty) => {
        $crate::VarMap<<$V as $crate::CollectionMeta>::SelfId, $H>
    };
}

/// Expands to `VarTypedTree<<V as CollectionMeta>::SelfId, V, C [, H]>`.
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
#[macro_export]
macro_rules! var_typed_tree {
    ($V:ty, $C:ty) => {
        $crate::VarTypedTree<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
        >
    };
    ($V:ty, $C:ty, $H:ty) => {
        $crate::VarTypedTree<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
            $H,
        >
    };
}

/// Expands to `VarTypedMap<<V as CollectionMeta>::SelfId, V, C [, H]>`.
#[cfg(all(feature = "typed-tree", feature = "var-collections"))]
#[macro_export]
macro_rules! var_typed_map {
    ($V:ty, $C:ty) => {
        $crate::VarTypedMap<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
        >
    };
    ($V:ty, $C:ty, $H:ty) => {
        $crate::VarTypedMap<
            <$V as $crate::CollectionMeta>::SelfId,
            $V,
            $C,
            $H,
        >
    };
}
