//! Output sinks for observability.
//!
//! A `Sink` represents a fallible destination for human-readable text. It avoids
//! allocation and aims to be interrupt-safe when possible. Typical implementations:
//! - UART writer
//! - Memory ring buffer (provided)
//! - Semihosting or debugger console
//!
//! Sinks are intentionally minimal: they accept string slices and may truncate.

use core::sync::atomic::{AtomicUsize, Ordering};

/// Minimal output destination for text.
///
/// Implementors should avoid panics and prefer bounded, non-blocking behavior. The
/// API is string-oriented to cooperate with `core::fmt::Write`.
pub trait Sink {
    /// Attempts to write the entire string into the sink.
    ///
    /// Returns the number of bytes successfully written (which may be less than
    /// `s.len()` if the sink is at capacity).
    fn write_str(&self, s: &str) -> usize;
}

/// Lock-free, no-alloc ring buffer sink.
///
/// This sink stores bytes in a fixed-capacity circular buffer using atomics for
/// the head pointer. It is single-writer friendly and multi-reader safe when readers
/// only snapshot contents. Writers never block; they overwrite old data when full.
pub struct RingBufferSink<const N: usize> {
    buf: [core::cell::UnsafeCell<u8>; N],
    head: AtomicUsize,
}

// Safety: We never hand out references to interior elements, and writes are done
// with atomics coordinating indices. Readers only snapshot by copying out bytes.
unsafe impl<const N: usize> Send for RingBufferSink<N> {}
unsafe impl<const N: usize> Sync for RingBufferSink<N> {}

impl<const N: usize> RingBufferSink<N> {
    /// Creates a new empty ring buffer sink.
    pub const fn new() -> Self {
        const ZERO: core::cell::UnsafeCell<u8> = core::cell::UnsafeCell::new(0);
        Self {
            buf: [ZERO; N],
            head: AtomicUsize::new(0),
        }
    }

    /// Current logical write position modulo capacity.
    #[inline]
    pub fn head(&self) -> usize {
        self.head.load(Ordering::Relaxed)
    }

    /// Capacity in bytes.
    #[inline]
    pub const fn capacity(&self) -> usize {
        N
    }

    /// Internal: writes a single byte to the buffer, advancing the head.
    #[inline]
    fn push_byte(&self, b: u8) {
        let pos = self.head.fetch_add(1, Ordering::Relaxed);
        // Wrap to capacity
        let idx = pos % N;
        unsafe {
            // Safe: idx within bounds, exclusive writer semantics via atomic head.
            (*self.buf[idx].get()) = b;
        }
        // Ordering::Release on a newline to hint end of record boundaries for readers.
        if b == b'\n' {
            self.head.fetch_add(0, Ordering::Release);
        }
    }

    /// Copies the buffer contents into `out`, starting from the oldest byte and
    /// preserving logical order. Returns the number of bytes copied.
    ///
    /// This is intended for host-side testing or exporting diagnostics.
    pub fn snapshot(&self, out: &mut [u8]) -> usize {
        let head = self.head.load(Ordering::Acquire);
        let start = head.saturating_sub(N);
        let mut copied = 0usize;
        for i in 0..N {
            if copied >= out.len() {
                break;
            }
            let idx = (start + i) % N;
            unsafe {
                out[copied] = *self.buf[idx].get();
            }
            copied += 1;
        }
        copied
    }
}

impl<const N: usize> Sink for RingBufferSink<N> {
    fn write_str(&self, s: &str) -> usize {
        let mut written = 0usize;
        for &b in s.as_bytes() {
            self.push_byte(b);
            written += 1;
        }
        written
    }
}

