//! Pins the upstream `automerge::sync` protocol invariant that
//! peat-mesh's `initiate_delta_sync` depends on (peat-mesh#136 /
//! defenseunicorns/peat#873): once a fresh `SyncState` has been
//! driven through a successful round trip with a peer,
//! `SyncDoc::generate_sync_message` returns `None` — the protocol-
//! defined "already in sync" terminal, not an error.
//!
//! peat-mesh treats that `None` as `Ok(())` (see the
//! `initiate_delta_sync_treats_no_sync_message_as_success` static
//! pin in `src/storage/automerge_sync.rs`). The static pin guards
//! against a source-level revert; this integration test guards
//! against the *upstream contract* changing under us — e.g. a future
//! Automerge release deciding to return `Some(empty_message)` or to
//! signal "already in sync" via a different return shape.
//!
//! Issue tracking: peat-mesh#138 (M2). Companion structural pin lives
//! in the unit-test module of `automerge_sync.rs`.

#![cfg(feature = "automerge-backend")]

use automerge::sync::{State as SyncState, SyncDoc};
use automerge::transaction::Transactable;
use automerge::{Automerge, ROOT};

/// Drive two `SyncState`s to convergence against the same document
/// contents. Returns the round count on success; panics if the
/// protocol doesn't settle within a generous bound (real-world
/// 1-field convergence is typically 2-4 rounds).
fn drive_to_convergence(
    doc_a: &mut Automerge,
    state_a: &mut SyncState,
    doc_b: &mut Automerge,
    state_b: &mut SyncState,
) -> usize {
    const MAX_ROUNDS: usize = 32;
    for round in 0..MAX_ROUNDS {
        let mut activity = false;
        if let Some(msg) = SyncDoc::generate_sync_message(doc_a, state_a) {
            SyncDoc::receive_sync_message(doc_b, state_b, msg).expect("a→b receive must succeed");
            activity = true;
        }
        if let Some(msg) = SyncDoc::generate_sync_message(doc_b, state_b) {
            SyncDoc::receive_sync_message(doc_a, state_a, msg).expect("b→a receive must succeed");
            activity = true;
        }
        if !activity {
            return round;
        }
    }
    panic!(
        "automerge sync did not converge within {MAX_ROUNDS} rounds — \
         upstream protocol may have changed; review before adjusting the cap"
    );
}

/// THE invariant peat-mesh#136 depends on.
///
/// After convergence, calling `generate_sync_message` on either side
/// must return `None` (not `Some(empty)` and not an error path). This
/// is the upstream contract `initiate_delta_sync` reads as "already
/// in sync; nothing to send" and maps to `Ok(())`.
#[test]
fn generate_sync_message_returns_none_once_state_is_converged() {
    let mut doc_a = Automerge::new();
    let mut tx = doc_a.transaction();
    tx.put(ROOT, "field", "value")
        .expect("put on root must succeed");
    tx.commit();
    let mut doc_b = Automerge::new();

    let mut state_a = SyncState::new();
    let mut state_b = SyncState::new();

    let rounds = drive_to_convergence(&mut doc_a, &mut state_a, &mut doc_b, &mut state_b);
    assert!(
        rounds > 0,
        "expected at least one round of activity before convergence"
    );

    // Post-convergence: both sides see "nothing to send."
    assert!(
        SyncDoc::generate_sync_message(&doc_a, &mut state_a).is_none(),
        "after convergence, A.generate_sync_message must return None — \
         peat-mesh#136 maps this to Ok(()); a Some(_) here re-opens the field-noise regression"
    );
    assert!(
        SyncDoc::generate_sync_message(&doc_b, &mut state_b).is_none(),
        "after convergence, B.generate_sync_message must return None — \
         peat-mesh#136 maps this to Ok(()); a Some(_) here re-opens the field-noise regression"
    );
}

/// Follow-on: a *fresh* SyncState against a non-empty document must
/// produce `Some` — confirms the upstream's "I need to send something"
/// signal hasn't shifted. The existing `remote_initial_sync_message`
/// helper in `automerge_sync.rs:4788` already relies on this; pinning
/// it explicitly so a regression is caught here rather than as a
/// downstream `expect` panic in unrelated code.
#[test]
fn generate_sync_message_returns_some_for_fresh_state_against_nonempty_doc() {
    let mut doc = Automerge::new();
    let mut tx = doc.transaction();
    tx.put(ROOT, "field", "value")
        .expect("put on root must succeed");
    tx.commit();

    let mut state = SyncState::new();
    let msg = SyncDoc::generate_sync_message(&doc, &mut state);
    assert!(
        msg.is_some(),
        "fresh SyncState against a non-empty doc must produce an initial sync message — \
         a None here would break peat-mesh's `remote_initial_sync_message` test helper \
         and the first round of every real sync"
    );
}
