//! 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,
    atomic::{AtomicU64, Ordering},
};

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

/// 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 = Self::signal_type_u8(signal_type_id)?;

        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.
            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(),
            })
        })?;

        self.bump_last_seq(seq);
        Ok(())
    }

    fn append_signal_scoped(
        &self,
        signal_type_id: SignalTypeId,
        entity_id: EntityId,
        weight: f64,
        timestamp: Timestamp,
        scope: u8,
        writer_agent: u16,
        share_policy_version: u16,
        membership_epoch: u32,
    ) -> crate::Result<()> {
        let signal_type_u8 = Self::signal_type_u8(signal_type_id)?;

        let record = EventRecord::scoped(
            entity_id.as_u64(),
            signal_type_u8,
            // f64 → f32 precision loss is acceptable for WAL storage.
            weight as f32,
            timestamp.as_nanos(),
            scope,
            writer_agent,
            share_policy_version,
            membership_epoch,
        );

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

        self.bump_last_seq(seq);
        Ok(())
    }
}

impl WalHandleWriter {
    /// Narrow a `SignalTypeId` (u16) to the WAL wire's u8, rejecting schemas
    /// with more than 256 signal types.
    fn signal_type_u8(signal_type_id: SignalTypeId) -> crate::Result<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()
                ),
            )
        })
    }

    /// Advance the shared `last_seq` high-water mark to `seq` (no-op for seq 0,
    /// the dedup sentinel).
    ///
    /// The `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.
    fn bump_last_seq(&self, seq: u64) {
        if seq == 0 {
            return;
        }
        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,
            }
        }
    }
}
