use crate::disk_loc::DiskLoc;
use crate::entry::EntryHeader;
use crate::error::DbResult;
use crate::shard::ShardInner;

/// Outcome of applying a replicated entry to a target.
#[derive(Debug)]
pub enum ApplyOutcome {
    /// CRC mismatch or key_len mismatch — entry did not belong to this target.
    NotMatched,
    /// Fresh insert (no prior key). No dead bytes.
    Inserted,
    /// Existing key overwritten; old DiskLoc retired.
    Replaced(DiskLoc),
    /// Tombstone removed a live key; old DiskLoc retired.
    TombstoneRemoved(DiskLoc),
}

/// Trait for trees that can receive replicated entries.
pub trait ReplicationTarget: Send + Sync {
    /// Apply entry with known key/value split (streaming mode, O(1) routing).
    /// Returns the outcome so the registry can account dead bytes.
    #[allow(clippy::too_many_arguments)]
    fn apply_entry(
        &self,
        shard_inner: &mut ShardInner,
        shard_id: u8,
        file_id: u32,
        entry_offset: u64,
        header: &EntryHeader,
        key: &[u8],
        value: &[u8],
    ) -> DbResult<ApplyOutcome>;

    /// Try to apply entry with CRC matching (catch-up mode).
    /// Returns `ApplyOutcome::NotMatched` if CRC fails (key belongs to a different target).
    fn try_apply_entry(
        &self,
        shard_inner: &mut ShardInner,
        shard_id: u8,
        file_id: u32,
        entry_offset: u64,
        header: &EntryHeader,
        raw_after_header: &[u8],
    ) -> DbResult<ApplyOutcome>;

    /// The key length (K) for this tree type.
    fn key_len(&self) -> usize;
}

/// Blanket impl: an `Arc<T>` wrapping any `ReplicationTarget` is itself a
/// `ReplicationTarget`. This lets callers hold an `Arc<VarTree<K>>` (or any
/// other tree) for normal read/write access while simultaneously handing a
/// `Box<dyn ReplicationTarget>` to the `ReplicationRegistry`.
impl<T: ReplicationTarget + ?Sized> ReplicationTarget for std::sync::Arc<T> {
    #[allow(clippy::too_many_arguments)]
    fn apply_entry(
        &self,
        shard_inner: &mut ShardInner,
        shard_id: u8,
        file_id: u32,
        entry_offset: u64,
        header: &EntryHeader,
        key: &[u8],
        value: &[u8],
    ) -> DbResult<ApplyOutcome> {
        (**self).apply_entry(
            shard_inner,
            shard_id,
            file_id,
            entry_offset,
            header,
            key,
            value,
        )
    }

    fn try_apply_entry(
        &self,
        shard_inner: &mut ShardInner,
        shard_id: u8,
        file_id: u32,
        entry_offset: u64,
        header: &EntryHeader,
        raw_after_header: &[u8],
    ) -> DbResult<ApplyOutcome> {
        (**self).try_apply_entry(
            shard_inner,
            shard_id,
            file_id,
            entry_offset,
            header,
            raw_after_header,
        )
    }

    fn key_len(&self) -> usize {
        (**self).key_len()
    }
}
