//! Broadcast channels and op-tracking statics for the CRDT state layer.
//!
//! Provides the outbound sync channel ([`SYNC_TX`]), the event broadcast
//! channel ([`CRDT_EVENT_TX`]), and the in-memory op journal
//! ([`ALL_OPS`] / [`VECTOR_CLOCK`]) that tracks every applied op for
//! delta-sync.
//!
//! In `cfg(test)`, the op journal and vector clock are stored in
//! thread-local `OnceLock`s (mirroring [`super::CRDT_STATE_TL`]) so parallel
//! tests do not share `ALL_OPS` — preventing one test's `apply_compaction`
//! from pruning another test's freshly-written ops.

use std::sync::atomic::AtomicUsize;
use std::sync::{Mutex, OnceLock};

use bft_json_crdt::json_crdt::SignedOp;
use tokio::sync::broadcast;

use super::super::VectorClock;
use super::super::hex;
use super::super::types::CrdtEvent;

/// Count of ops queued in the persistence channel that have not yet been written to SQLite.
///
/// Incremented when an op is sent into the channel; decremented after the
/// persistence task commits it.  Exposed via `dump_crdt_state` as
/// `pending_persist_ops_count` so operators can tell whether there is a flush
/// backlog before calling `rebuild_and_restart`.
pub(crate) static PERSIST_PENDING: AtomicUsize = AtomicUsize::new(0);

/// Broadcast channel for CRDT events (stage transitions, etc.).
pub(super) static CRDT_EVENT_TX: OnceLock<broadcast::Sender<CrdtEvent>> = OnceLock::new();

/// Broadcast channel for outbound ops to sync peers.
pub(in crate::crdt_state) static SYNC_TX: OnceLock<broadcast::Sender<SignedOp>> = OnceLock::new();

/// All persisted ops as JSON strings, in causal (insertion) order.
///
/// Pub(crate) so that `crdt_snapshot` can access it for compaction.
pub(crate) static ALL_OPS: OnceLock<Mutex<Vec<String>>> = OnceLock::new();

/// Live vector clock tracking op counts per author.
///
/// Updated in lockstep with `ALL_OPS` — every time an op is appended to the
/// journal, the corresponding author's count is incremented here.  This avoids
/// re-parsing all ops when a peer requests `our_vector_clock()`.
pub(crate) static VECTOR_CLOCK: OnceLock<Mutex<VectorClock>> = OnceLock::new();

#[cfg(test)]
thread_local! {
    /// Per-thread op journal for test isolation.  Each test thread sees its
    /// own ALL_OPS so parallel tests cannot prune each other's ops via
    /// `apply_compaction`.  Set up by `init_for_test`.
    pub(in crate::crdt_state) static ALL_OPS_TL: OnceLock<Mutex<Vec<String>>> = const { OnceLock::new() };
    /// Per-thread vector clock for test isolation.  See [`ALL_OPS_TL`].
    pub(in crate::crdt_state) static VECTOR_CLOCK_TL: OnceLock<Mutex<VectorClock>> = const { OnceLock::new() };
}

/// Return the mutex guarding the op journal, if initialised.
///
/// In production: the global `ALL_OPS`.  In `cfg(test)`: the per-thread
/// `ALL_OPS_TL`, so parallel tests do not share the journal.
pub(crate) fn all_ops_lock() -> Option<&'static Mutex<Vec<String>>> {
    #[cfg(not(test))]
    {
        ALL_OPS.get()
    }
    #[cfg(test)]
    {
        let ptr = ALL_OPS_TL.with(|lock| lock as *const OnceLock<Mutex<Vec<String>>>);
        // SAFETY: the thread-local lives as long as the spawning thread,
        // which outlives any test code using it.  We only need 'static for
        // the return type; consumers never hold the reference past the test.
        unsafe { &*ptr }.get()
    }
}

/// Return the mutex guarding the vector clock, if initialised.
///
/// In production: the global `VECTOR_CLOCK`.  In `cfg(test)`: the per-thread
/// `VECTOR_CLOCK_TL`.
pub(crate) fn vector_clock_lock() -> Option<&'static Mutex<VectorClock>> {
    #[cfg(not(test))]
    {
        VECTOR_CLOCK.get()
    }
    #[cfg(test)]
    {
        let ptr = VECTOR_CLOCK_TL.with(|lock| lock as *const OnceLock<Mutex<VectorClock>>);
        // SAFETY: see all_ops_lock above.
        unsafe { &*ptr }.get()
    }
}

/// Append an op's JSON to `ALL_OPS` and bump the author's count in `VECTOR_CLOCK`.
///
/// Centralises the bookkeeping that must stay in sync between the two statics.
pub(in crate::crdt_state) fn track_op(signed: &SignedOp, json: String) {
    if let Some(all) = all_ops_lock()
        && let Ok(mut v) = all.lock()
    {
        v.push(json);
    }
    if let Some(vc) = vector_clock_lock()
        && let Ok(mut clock) = vc.lock()
    {
        let author_hex = hex::encode(&signed.author());
        *clock.entry(author_hex).or_insert(0) += 1;
    }
}
