//! Write path: create, sign, apply, persist, and broadcast a CRDT op.
//!
//! [`apply_and_persist`] is the single entry point for all CRDT mutations.
//! It invokes the caller's op-factory closure, signs the resulting op, applies
//! it to the live document, sends it to the persistence channel, and broadcasts
//! it to sync peers via [`super::SYNC_TX`].

use std::sync::atomic::Ordering;

use bft_json_crdt::json_crdt::JsonValue;
use bft_json_crdt::op::Op;

use super::super::types::CrdtEvent;
use super::{CrdtState, init::PersistMsg, statics};

/// Create a CRDT op via `op_fn`, sign it, apply it, and send it to the
/// persistence channel.  The closure receives `&mut CrdtState` so it can
/// mutably access the CRDT document, while `sign` only needs `&keypair`.
pub(in crate::crdt_state) fn apply_and_persist<F>(state: &mut CrdtState, op_fn: F)
where
    F: FnOnce(&mut CrdtState) -> Op<JsonValue>,
{
    let raw_op = op_fn(state);
    let signed = raw_op.sign(&state.keypair);
    state.crdt.apply(signed.clone());
    if state
        .persist_tx
        .send(PersistMsg::Op(Box::new(signed.clone())))
        .is_ok()
    {
        statics::PERSIST_PENDING.fetch_add(1, Ordering::Relaxed);
    } else {
        let op_type = if signed.inner.is_deleted {
            "Delete"
        } else {
            "Insert"
        };
        let seq = signed.inner.seq;
        crate::slog_warn!(
            "[crdt_persist] persist channel send failed: op_type={op_type} seq={seq}"
        );
    }

    // Track in ALL_OPS + VECTOR_CLOCK, then broadcast to sync peers.
    if let Ok(json) = serde_json::to_string(&signed) {
        statics::track_op(&signed, json);
    }
    if let Some(tx) = statics::SYNC_TX.get() {
        let _ = tx.send(signed);
    }
}

/// Broadcast a CRDT event to all subscribers.
pub(in crate::crdt_state) fn emit_event(event: CrdtEvent) {
    if let Some(tx) = statics::CRDT_EVENT_TX.get() {
        let _ = tx.send(event);
    }
}
