//! Bridge from `WalWriter` trait to a live `WalHandle`.
//!
//! Lives in `db/` because it depends on both the `wal` layer (for `WalSender`)
//! and the `signals` layer (for `WalWriter`). Placing it here keeps the
//! `wal` and `signals` modules free of cross-layer dependencies.

use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};

use crate::schema::{DurabilityError, EntityId, Timestamp};
use crate::signals::{SignalTypeId, WalWriter};
use crate::wal::{SignalEvent, WalSender};

/// Implements [`WalWriter`] by forwarding signal events to a live WAL.
///
/// Holds a [`WalSender`] (a cloneable, `Send + Sync` channel handle) rather
/// than the full `WalHandle` so that the bridge can be shared across threads
/// without preventing the owning `TidalDb` from shutting down the WAL.
///
/// After each successful append, the bridge updates the shared `last_seq`
/// counter so `TidalDb::shutdown()` knows the highest WAL sequence to include
/// in the ledger checkpoint marker.
pub struct WalHandleWriter {
    sender: WalSender,
    last_seq: Arc<AtomicU64>,
}

impl WalHandleWriter {
    #[allow(clippy::missing_const_for_fn)] // Arc field prevents const in practice
    pub fn new(sender: WalSender, last_seq: Arc<AtomicU64>) -> Self {
        Self { sender, last_seq }
    }
}

impl WalWriter for WalHandleWriter {
    fn append_signal(
        &self,
        signal_type_id: SignalTypeId,
        entity_id: EntityId,
        weight: f64,
        timestamp: Timestamp,
    ) -> crate::Result<()> {
        // Signal type IDs are u16 but WAL wire format stores them as u8.
        // Schemas with > 255 signal types are disallowed (checked here at runtime).
        let signal_type_u8 = u8::try_from(signal_type_id.as_u16()).map_err(|_| {
            crate::TidalError::internal(
                "wal_append",
                format!(
                    "signal type id {} exceeds u8 max (255); reduce signal count",
                    signal_type_id.as_u16()
                ),
            )
        })?;

        let event = SignalEvent {
            entity_id: entity_id.as_u64(),
            signal_type: signal_type_u8,
            // f64 → f32 precision loss is acceptable for WAL storage;
            // the in-memory hot tier retains f64 precision.
            #[allow(clippy::cast_possible_truncation)]
            weight: weight as f32,
            timestamp_nanos: timestamp.as_nanos(),
        };

        let seq = self.sender.append(event).map_err(|e| {
            crate::TidalError::Durability(DurabilityError {
                message: e.to_string(),
            })
        })?;

        // Track the highest committed WAL sequence for checkpoint metadata.
        // seq == 0 means the event was deduplicated; do not update last_seq.
        if seq > 0 {
            // Release on success establishes a happens-before edge with the
            // Acquire load in shutdown_inner. Without this, on weakly-ordered
            // architectures (ARM, RISC-V) the checkpoint thread could observe
            // a stale sequence and write a checkpoint marker that references
            // an older WAL seq than the last committed event, causing spurious
            // event re-replay on the next open.
            let mut current = self.last_seq.load(Ordering::Relaxed);
            loop {
                if seq <= current {
                    break;
                }
                match self.last_seq.compare_exchange_weak(
                    current,
                    seq,
                    Ordering::Release, // publish new seq to shutdown thread
                    Ordering::Relaxed, // failure: no sync needed
                ) {
                    Ok(_) => break,
                    Err(actual) => current = actual,
                }
            }
        }

        Ok(())
    }
}
