//! tape โ€” Typed Annotated Payload Exchange
//!
//! Frame format: `0x1F | sigil | render | varint | payload`
//!
//! - 0x1F  ASCII Unit Separator (never appears in valid UTF-8, spare in 256-symbol map)
//! - sigil  one byte from cybermark's 13 sigils (semantic role / T)
//! - render one ASCII letter from the 14-language render set (perception type / A)
//! - varint LEB128 unsigned integer (payload length in bytes / P)
//! - payload N bytes; may contain nested frames for composition chunks / E

pub use bytes;

pub mod molecule;
pub use molecule::Molecule;

use std::io::{self, Read, Write};

pub mod sigil {
    //! Cybermark sigil bytes โ€” semantic role of a chunk.
    pub const HAX: u8 = b'#'; // 0x23 โ€” content / particle identity
    pub const PAT: u8 = b'@'; // 0x40 โ€” identity / agent / neuron
    pub const SIG: u8 = b'~'; // 0x7E โ€” annotation / label / side-info
    pub const FAS: u8 = b'/'; // 0x2F โ€” scope / containment / structure
    pub const BUC: u8 = b'$'; // 0x24 โ€” economic / value-bearing
    pub const KET: u8 = b'^'; // 0x5E โ€” lift / abstract / establish
    pub const ZAP: u8 = b'!'; // 0x21 โ€” effect / imperative / action
    pub const DOT: u8 = b'.'; // 0x2E โ€” transform / pipeline / apply
    pub const BAR: u8 = b'|'; // 0x7C โ€” composition / code-with-data
    pub const TIS: u8 = b'='; // 0x3D โ€” binding / equivalence
    pub const WUT: u8 = b'?'; // 0x3F โ€” test / decision / input request
    pub const COL: u8 = b':'; // 0x3A โ€” pair / key-value
    pub const LUS: u8 = b'+'; // 0x2B โ€” augment / increment

    pub const ALL: [u8; 13] = [HAX, PAT, SIG, FAS, BUC, KET, ZAP, DOT, BAR, TIS, WUT, COL, LUS];

    pub fn name(byte: u8) -> &'static str {
        match byte {
            HAX => "hax", PAT => "pat", SIG => "sig", FAS => "fas",
            BUC => "buc", KET => "ket", ZAP => "zap", DOT => "dot",
            BAR => "bar", TIS => "tis", WUT => "wut", COL => "col",
            LUS => "lus", _ => "unknown",
        }
    }

    pub fn is_valid(byte: u8) -> bool {
        ALL.contains(&byte)
    }
}

pub mod render {
    //! Render type bytes โ€” perception type / how to display a chunk.
    pub const STRUCT:    u8 = b's'; // collapsible tree (Nox)
    pub const BINARY:    u8 = b'b'; // raster image / pixels (Bt)
    pub const TEXT:      u8 = b't'; // prose / code (Rs)
    pub const FORMULA:   u8 = b'f'; // math notation (Tri)
    pub const VECTOR:    u8 = b'v'; // SVG / 2D / 3D (Arc/Ren/Dif)
    pub const MOVIE:     u8 = b'm'; // video frames (Seq)
    pub const TABLE:     u8 = b'T'; // 2D grid of records (Inf)
    pub const AUDIO:     u8 = b'a'; // waveform (Wav)
    pub const COMPONENT: u8 = b'c'; // nested composition (Ten)
    pub const TOKEN:     u8 = b'k'; // ledger / balance (Tok)
    // meta / stream-control
    pub const PROGRESS:  u8 = b'p'; // live progress bar
    pub const LOG:       u8 = b'l'; // structured log line
    pub const ERROR:     u8 = b'e'; // typed error with source location
    pub const STATUS:    u8 = b'x'; // end-of-command sentinel (exit code)
    pub const INPUT:     u8 = b'i'; // user โ†’ producer response

    pub const ALL: [u8; 15] = [
        STRUCT, BINARY, TEXT, FORMULA, VECTOR, MOVIE, TABLE, AUDIO,
        COMPONENT, TOKEN, PROGRESS, LOG, ERROR, STATUS, INPUT,
    ];

    pub fn name(byte: u8) -> &'static str {
        match byte {
            STRUCT    => "struct",   BINARY   => "binary",  TEXT     => "text",
            FORMULA   => "formula",  VECTOR   => "vector",  MOVIE    => "movie",
            TABLE     => "table",    AUDIO    => "audio",   COMPONENT => "component",
            TOKEN     => "token",    PROGRESS => "progress",LOG      => "log",
            ERROR     => "error",    STATUS   => "status",  INPUT    => "input",
            _ => "unknown",
        }
    }

    pub fn is_valid(byte: u8) -> bool {
        ALL.contains(&byte)
    }
}

/// Frame marker byte. ASCII Unit Separator (0x1F).
/// - Never appears in valid UTF-8 (multibyte sequences use 0xC0-0xFF leads)
/// - Not in the nox ISA (which uses 0x01-0x1E)
/// - Not a printable ASCII byte (0x20-0x7E)
pub const MARKER: u8 = 0x1F;

/// Opaque chunk id for in-place updates (e.g. progress dedup).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChunkId(pub u64);

/// A parsed cyb-stream chunk.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Chunk {
    pub sigil:   u8,
    pub render:  u8,
    pub payload: bytes::Bytes,
    pub id:      Option<ChunkId>,
}

impl Chunk {
    /// Create a plain UTF-8 text chunk `(#, t)`.
    pub fn text(s: &str) -> Self {
        Self::new(sigil::HAX, render::TEXT, bytes::Bytes::copy_from_slice(s.as_bytes()))
    }

    /// Create an annotation / label text chunk `(~, t)`.
    pub fn annotation(s: &str) -> Self {
        Self::new(sigil::SIG, render::TEXT, bytes::Bytes::copy_from_slice(s.as_bytes()))
    }

    /// Create a typed error chunk `(!, e)` โ€” nested `(=, s)` key-value pairs.
    pub fn error(message: &str) -> Self {
        let payload = encode_nested(&[
            kv("level",   Self::text("error")),
            kv("source",  Self::text("")),
            kv("message", Self::text(message)),
        ]);
        Self::new(sigil::ZAP, render::ERROR, payload)
    }

    /// Create a log line chunk `(., l)` โ€” nested `(=, s)` key-value pairs.
    pub fn log(level: &str, source: &str, message: &str) -> Self {
        let payload = encode_nested(&[
            kv("level",   Self::text(level)),
            kv("source",  Self::text(source)),
            kv("message", Self::text(message)),
        ]);
        Self::new(sigil::DOT, render::LOG, payload)
    }

    /// Create a progress chunk `(., p)` โ€” nested `(=, s)` key-value pairs.
    pub fn progress(id: u64, label: &str, current: u64, total: u64) -> Self {
        let payload = encode_nested(&[
            kv("id",      Self::text(&id.to_string())),
            kv("label",   Self::text(label)),
            kv("current", Self::text(&current.to_string())),
            kv("total",   Self::text(&total.to_string())),
        ]);
        let mut chunk = Self::new(sigil::DOT, render::PROGRESS, payload);
        chunk.id = Some(ChunkId(id));
        chunk
    }

    /// Create an end-of-command status chunk `(., x)` โ€” nested `(=, s)` key-value pair.
    pub fn status(code: i32) -> Self {
        let payload = encode_nested(&[kv("code", Self::text(&code.to_string()))]);
        Self::new(sigil::DOT, render::STATUS, payload)
    }

    /// Create a scope / breadcrumb chunk `(/, c)` with nested payload bytes.
    pub fn scope(nested: bytes::Bytes) -> Self {
        Self::new(sigil::FAS, render::COMPONENT, nested)
    }

    /// Create a composition container `(|, c)` with nested payload bytes.
    pub fn compose(nested: bytes::Bytes) -> Self {
        Self::new(sigil::BAR, render::COMPONENT, nested)
    }

    /// Base constructor.
    pub fn new(sigil: u8, render: u8, payload: bytes::Bytes) -> Self {
        Self { sigil, render, payload, id: None }
    }

    pub fn with_id(mut self, id: ChunkId) -> Self {
        self.id = Some(id);
        self
    }

    /// Encode this chunk to bytes (no id encoding โ€” ids are transport-layer metadata).
    pub fn encode(&self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(3 + 9 + self.payload.len());
        buf.push(MARKER);
        buf.push(self.sigil);
        buf.push(self.render);
        encode_varint(self.payload.len() as u64, &mut buf);
        buf.extend_from_slice(&self.payload);
        buf
    }
}

// โ”€โ”€ varint โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Encode an unsigned 64-bit integer as LEB128 into `buf`.
pub fn encode_varint(mut n: u64, buf: &mut Vec<u8>) {
    loop {
        let low7 = (n & 0x7F) as u8;
        n >>= 7;
        if n == 0 {
            buf.push(low7);
            break;
        }
        buf.push(low7 | 0x80);
    }
}

/// Decode an unsigned LEB128 from `bytes` starting at `pos`.
/// Returns `(value, new_pos)` or `None` if the stream is truncated.
pub fn decode_varint(bytes: &[u8], pos: usize) -> Option<(u64, usize)> {
    let mut value: u64 = 0;
    let mut shift: u32 = 0;
    let mut cur = pos;
    loop {
        if cur >= bytes.len() {
            return None;
        }
        let b = bytes[cur];
        cur += 1;
        let low7 = (b & 0x7F) as u64;
        value |= low7 << shift;
        shift += 7;
        if b & 0x80 == 0 {
            return Some((value, cur));
        }
        if shift >= 64 {
            return None; // overflow
        }
    }
}

// โ”€โ”€ Writer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Framing writer. Wraps any `io::Write` and emits cyb-stream frames.
pub struct Writer<W: Write> {
    inner: W,
}

impl<W: Write> Writer<W> {
    pub fn new(inner: W) -> Self {
        Self { inner }
    }

    pub fn write_chunk(&mut self, chunk: &Chunk) -> io::Result<()> {
        let mut hdr = Vec::with_capacity(3 + 9);
        hdr.push(MARKER);
        hdr.push(chunk.sigil);
        hdr.push(chunk.render);
        encode_varint(chunk.payload.len() as u64, &mut hdr);
        self.inner.write_all(&hdr)?;
        self.inner.write_all(&chunk.payload)
    }

    pub fn into_inner(self) -> W {
        self.inner
    }
}

// โ”€โ”€ Reader โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Result of a single `Reader::next_chunk` call.
#[derive(Debug)]
pub enum ReadResult {
    /// A complete, valid chunk.
    Chunk(Chunk),
    /// Not enough bytes yet โ€” call again when more data is available.
    Pending,
    /// End of stream (clean EOF with no partial frame).
    Eof,
}

/// Framing reader state โ€” buffers incoming bytes and emits chunks.
///
/// Designed for incremental feeding: call `feed()` as bytes arrive, then
/// drain with `next_chunk()` until `ReadResult::Pending`.
pub struct Reader {
    buf: Vec<u8>,
    pos: usize,
}

impl Reader {
    pub fn new() -> Self {
        Self { buf: Vec::new(), pos: 0 }
    }

    /// Append newly received bytes to the internal buffer.
    pub fn feed(&mut self, data: &[u8]) {
        if self.pos > 4096 && self.pos > self.buf.len() / 2 {
            // Compact: discard already-consumed bytes
            self.buf.drain(..self.pos);
            self.pos = 0;
        }
        self.buf.extend_from_slice(data);
    }

    /// Try to parse the next chunk from the buffer.
    pub fn next_chunk(&mut self) -> ReadResult {
        loop {
            let remaining = &self.buf[self.pos..];
            if remaining.is_empty() {
                return ReadResult::Eof;
            }

            // Scan for the 0x1F marker
            let marker_offset = match remaining.iter().position(|&b| b == MARKER) {
                Some(o) => o,
                None => {
                    // No marker anywhere โ€” consume all, signal pending
                    self.pos = self.buf.len();
                    return ReadResult::Pending;
                }
            };

            if marker_offset > 0 {
                // Skip non-frame bytes before the marker
                self.pos += marker_offset;
                continue;
            }

            // We're at a 0x1F byte. Need: marker + sigil + render + at least 1 varint byte
            if remaining.len() < 4 {
                return ReadResult::Pending;
            }

            let sigil  = remaining[1];
            let render = remaining[2];

            // Decode varint starting at byte 3
            let varint_start = self.pos + 3;
            match decode_varint(&self.buf, varint_start) {
                None => return ReadResult::Pending,
                Some((payload_len, payload_start)) => {
                    let payload_end = payload_start + payload_len as usize;
                    if payload_end > self.buf.len() {
                        return ReadResult::Pending;
                    }
                    let payload = bytes::Bytes::copy_from_slice(&self.buf[payload_start..payload_end]);
                    self.pos = payload_end;

                    // Skip frames with unknown sigil/render rather than hard-erroring.
                    // (forward-compatibility: newer producers may emit chunk types we don't
                    //  know yet; still advance the cursor so we don't re-scan forever)
                    let chunk = Chunk { sigil, render, payload, id: None };
                    return ReadResult::Chunk(chunk);
                }
            }
        }
    }

    /// Convenience: parse all chunks from a `Read` source at once (non-streaming).
    pub fn read_all<R: Read>(mut r: R) -> io::Result<Vec<Chunk>> {
        let mut data = Vec::new();
        r.read_to_end(&mut data)?;
        let mut reader = Self::new();
        reader.feed(&data);
        let mut chunks = Vec::new();
        loop {
            match reader.next_chunk() {
                ReadResult::Chunk(c) => chunks.push(c),
                ReadResult::Pending | ReadResult::Eof => break,
            }
        }
        Ok(chunks)
    }
}

impl Default for Reader {
    fn default() -> Self {
        Self::new()
    }
}

// โ”€โ”€ convenience: write chunks into a nested payload buffer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Encode a slice of chunks into a `Bytes` suitable as a component payload.
pub fn encode_nested(chunks: &[Chunk]) -> bytes::Bytes {
    let mut buf = Vec::new();
    for c in chunks {
        buf.extend_from_slice(&c.encode());
    }
    bytes::Bytes::from(buf)
}

/// Decode all chunks from a nested payload (component, table, etc.).
pub fn decode_nested(payload: &[u8]) -> Vec<Chunk> {
    let mut reader = Reader::new();
    reader.feed(payload);
    let mut out = Vec::new();
    loop {
        match reader.next_chunk() {
            ReadResult::Chunk(c) => out.push(c),
            ReadResult::Pending | ReadResult::Eof => break,
        }
    }
    out
}

// โ”€โ”€ key-value helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Build a `key = value` binding: `(=, s)` containing `(~, t)[key]` + value chunk.
///
/// This is the cyb-stream native equivalent of a TOML `key = value` pair.
/// Used in meta chunk payloads (progress, log, error, status) and anywhere
/// a named binding needs to be expressed in the stream.
pub fn kv(key: &str, value: Chunk) -> Chunk {
    let inner = encode_nested(&[Chunk::annotation(key), value]);
    Chunk::new(sigil::TIS, render::STRUCT, inner)
}

/// Read all `(=, s)` key-value pairs from a nested payload.
/// Returns a map from key text to value chunk.
pub fn read_kv(payload: &[u8]) -> std::collections::HashMap<String, Chunk> {
    decode_nested(payload)
        .into_iter()
        .filter(|c| c.sigil == sigil::TIS && c.render == render::STRUCT)
        .filter_map(|pair| {
            let mut inner = decode_nested(&pair.payload);
            if inner.len() >= 2 {
                let value = inner.remove(1);
                let key = String::from_utf8_lossy(&inner[0].payload).into_owned();
                Some((key, value))
            } else {
                None
            }
        })
        .collect()
}

// โ”€โ”€ table helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Build a `(#, T)` table chunk from column headers and rows of cell chunks.
///
/// Schema row: `(/, s)` containing `(~, t)` for each header.
/// Data rows:  `(:, s)` containing one cell chunk per column.
pub fn table_chunk(headers: &[&str], rows: Vec<Vec<Chunk>>) -> Chunk {
    let mut inner = Vec::new();

    // Schema row
    let header_chunks: Vec<Chunk> = headers.iter()
        .map(|h| Chunk::annotation(h))
        .collect();
    let schema = Chunk::new(sigil::FAS, render::STRUCT, encode_nested(&header_chunks));
    inner.push(schema);

    // Data rows
    for row in rows {
        let row_chunk = Chunk::new(sigil::COL, render::STRUCT, encode_nested(&row));
        inner.push(row_chunk);
    }

    Chunk::new(sigil::HAX, render::TABLE, encode_nested(&inner))
}

// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Tests
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

#[cfg(test)]
mod tests {
    use super::*;

    // โ”€โ”€ varint roundtrip โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    fn varint_rt(n: u64) -> u64 {
        let mut buf = Vec::new();
        encode_varint(n, &mut buf);
        let (v, _) = decode_varint(&buf, 0).expect("decode");
        v
    }

    #[test]
    fn varint_roundtrip() {
        for n in [0u64, 1, 127, 128, 255, 256, 16383, 16384, u32::MAX as u64, u64::MAX] {
            assert_eq!(varint_rt(n), n, "varint failed for {n}");
        }
    }

    #[test]
    fn varint_single_byte_for_small() {
        let mut buf = Vec::new();
        encode_varint(63, &mut buf);
        assert_eq!(buf.len(), 1);
    }

    #[test]
    fn varint_two_bytes_for_128() {
        let mut buf = Vec::new();
        encode_varint(128, &mut buf);
        assert_eq!(buf.len(), 2);
    }

    // โ”€โ”€ chunk encode / decode roundtrip โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    fn roundtrip(chunk: Chunk) -> Chunk {
        let encoded = chunk.encode();
        let chunks = Reader::read_all(encoded.as_slice()).unwrap();
        assert_eq!(chunks.len(), 1, "expected 1 chunk, got {}", chunks.len());
        chunks.into_iter().next().unwrap()
    }

    #[test]
    fn text_chunk_roundtrip() {
        let c = Chunk::text("hello, cyber");
        let r = roundtrip(c.clone());
        assert_eq!(r.sigil, sigil::HAX);
        assert_eq!(r.render, render::TEXT);
        assert_eq!(&r.payload[..], b"hello, cyber");
    }

    #[test]
    fn error_chunk_roundtrip() {
        let c = Chunk::error("something went wrong");
        let r = roundtrip(c);
        assert_eq!(r.sigil, sigil::ZAP);
        assert_eq!(r.render, render::ERROR);
        let m = read_kv(&r.payload);
        assert_eq!(m.get("message").map(|c| &c.payload[..]), Some(b"something went wrong".as_ref()));
        assert_eq!(m.get("level").map(|c| &c.payload[..]), Some(b"error".as_ref()));
    }

    #[test]
    fn log_chunk_roundtrip() {
        let c = Chunk::log("info", "compiler", "build complete");
        let r = roundtrip(c);
        assert_eq!(r.sigil, sigil::DOT);
        assert_eq!(r.render, render::LOG);
    }

    #[test]
    fn progress_chunk_roundtrip() {
        let c = Chunk::progress(42, "building", 30, 100);
        let r = roundtrip(c.clone());
        assert_eq!(r.sigil, sigil::DOT);
        assert_eq!(r.render, render::PROGRESS);
    }

    #[test]
    fn status_chunk_roundtrip() {
        let c = Chunk::status(0);
        let r = roundtrip(c);
        assert_eq!(r.sigil, sigil::DOT);
        assert_eq!(r.render, render::STATUS);
        let m = read_kv(&r.payload);
        assert_eq!(m.get("code").map(|c| &c.payload[..]), Some(b"0".as_ref()));
    }

    #[test]
    fn kv_roundtrip() {
        let pair = kv("level", Chunk::text("error"));
        assert_eq!(pair.sigil, sigil::TIS);
        assert_eq!(pair.render, render::STRUCT);

        let map_bytes = encode_nested(&[
            kv("level",   Chunk::text("warn")),
            kv("source",  Chunk::text("compiler")),
            kv("message", Chunk::text("unused variable")),
        ]);
        let m = read_kv(&map_bytes);
        assert_eq!(m.get("level").map(|c| &c.payload[..]),   Some(b"warn".as_ref()));
        assert_eq!(m.get("source").map(|c| &c.payload[..]),  Some(b"compiler".as_ref()));
        assert_eq!(m.get("message").map(|c| &c.payload[..]), Some(b"unused variable".as_ref()));
    }

    #[test]
    fn annotation_chunk_roundtrip() {
        let c = Chunk::annotation("~/cyber/cyb");
        let r = roundtrip(c);
        assert_eq!(r.sigil, sigil::SIG);
        assert_eq!(r.render, render::TEXT);
    }

    #[test]
    fn empty_payload_chunk() {
        let c = Chunk::new(sigil::DOT, render::STATUS, bytes::Bytes::new());
        let r = roundtrip(c);
        assert!(r.payload.is_empty());
    }

    #[test]
    fn large_payload_roundtrip() {
        let big = "x".repeat(100_000);
        let c = Chunk::text(&big);
        let r = roundtrip(c);
        assert_eq!(r.payload.len(), 100_000);
    }

    // โ”€โ”€ v0 chunk catalog โ€” every defined type โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    #[test]
    fn all_v0_chunk_types() {
        let catalog: &[(u8, u8)] = &[
            (sigil::HAX, render::TEXT),
            (sigil::HAX, render::TABLE),
            (sigil::PAT, render::TEXT),
            (sigil::SIG, render::TEXT),
            (sigil::FAS, render::COMPONENT),
            (sigil::ZAP, render::COMPONENT),
            (sigil::WUT, render::COMPONENT),
            (sigil::WUT, render::INPUT),
            (sigil::BUC, render::TEXT),
            (sigil::BUC, render::TOKEN),
            (sigil::LUS, render::TEXT),
            (sigil::BAR, render::COMPONENT),
            (sigil::DOT, render::COMPONENT),
            (sigil::COL, render::STRUCT),
            (sigil::FAS, render::STRUCT),
            (sigil::DOT, render::PROGRESS),
            (sigil::DOT, render::LOG),
            (sigil::ZAP, render::ERROR),
            (sigil::DOT, render::STATUS),
        ];
        for &(s, r) in catalog {
            let payload = bytes::Bytes::from_static(b"test");
            let c = Chunk::new(s, r, payload);
            let enc = c.encode();
            assert_eq!(enc[0], MARKER);
            assert_eq!(enc[1], s);
            assert_eq!(enc[2], r);
            let back = roundtrip(c);
            assert_eq!(&back.payload[..], b"test",
                "roundtrip failed for ({}, {})", sigil::name(s), render::name(r));
        }
    }

    // โ”€โ”€ nested chunks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    #[test]
    fn nested_component_roundtrip() {
        let inner_text  = Chunk::text("hello");
        let inner_table = table_chunk(&["col1", "col2"], vec![
            vec![Chunk::text("a"), Chunk::text("b")],
            vec![Chunk::text("c"), Chunk::text("d")],
        ]);
        let nested_bytes = encode_nested(&[inner_text, inner_table]);
        let outer = Chunk::compose(nested_bytes);

        let enc = outer.encode();
        let decoded = Reader::read_all(enc.as_slice()).unwrap();
        assert_eq!(decoded.len(), 1);

        let inner_chunks = decode_nested(&decoded[0].payload);
        assert_eq!(inner_chunks.len(), 2);
        assert_eq!(inner_chunks[0].render, render::TEXT);
        assert_eq!(inner_chunks[1].render, render::TABLE);
    }

    #[test]
    fn table_chunk_structure() {
        let t = table_chunk(
            &["name", "size"],
            vec![
                vec![Chunk::text("Cargo.toml"), Chunk::text("512")],
            ],
        );
        assert_eq!(t.sigil, sigil::HAX);
        assert_eq!(t.render, render::TABLE);
        let rows = decode_nested(&t.payload);
        // First inner chunk: schema row (/, s)
        assert_eq!(rows[0].sigil, sigil::FAS);
        assert_eq!(rows[0].render, render::STRUCT);
        // Second inner chunk: data row (:, s)
        assert_eq!(rows[1].sigil, sigil::COL);
        assert_eq!(rows[1].render, render::STRUCT);
        // Cell in data row
        let cells = decode_nested(&rows[1].payload);
        assert_eq!(cells.len(), 2);
        assert_eq!(&cells[0].payload[..], b"Cargo.toml");
    }

    // โ”€โ”€ partial / truncated frames โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    #[test]
    fn truncated_frame_returns_pending() {
        let full = Chunk::text("hello").encode();
        // Feed only the first 3 bytes (marker + sigil + render, no varint/payload)
        let mut reader = Reader::new();
        reader.feed(&full[..3]);
        assert!(matches!(reader.next_chunk(), ReadResult::Pending));
    }

    #[test]
    fn truncated_payload_returns_pending() {
        let full = Chunk::text("hello world").encode();
        // Feed everything except the last byte
        let mut reader = Reader::new();
        reader.feed(&full[..full.len() - 1]);
        assert!(matches!(reader.next_chunk(), ReadResult::Pending));
    }

    #[test]
    fn incremental_feeding_assembles_chunk() {
        let full = Chunk::text("streaming data").encode();
        let mut reader = Reader::new();
        // Feed byte by byte
        for (i, &byte) in full.iter().enumerate() {
            reader.feed(&[byte]);
            if i < full.len() - 1 {
                assert!(matches!(reader.next_chunk(), ReadResult::Pending),
                    "should be Pending after {i} bytes");
            }
        }
        // After all bytes, should get the chunk
        match reader.next_chunk() {
            ReadResult::Chunk(c) => assert_eq!(&c.payload[..], b"streaming data"),
            other => panic!("expected Chunk, got {other:?}"),
        }
    }

    // โ”€โ”€ malformed frames โ€” skip to next 0x1F โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    #[test]
    fn garbage_before_marker_is_skipped() {
        let mut data = vec![0x01, 0x02, 0x03, 0xFF, 0xAB]; // junk
        data.extend_from_slice(&Chunk::text("found").encode());
        let chunks = Reader::read_all(data.as_slice()).unwrap();
        assert_eq!(chunks.len(), 1);
        assert_eq!(&chunks[0].payload[..], b"found");
    }

    #[test]
    fn multiple_chunks_sequential() {
        let mut data = Vec::new();
        data.extend_from_slice(&Chunk::text("first").encode());
        data.extend_from_slice(&Chunk::status(0).encode());
        data.extend_from_slice(&Chunk::error("oops").encode());
        let chunks = Reader::read_all(data.as_slice()).unwrap();
        assert_eq!(chunks.len(), 3);
        assert_eq!(&chunks[0].payload[..], b"first");
        assert_eq!(chunks[1].sigil, sigil::DOT);
        assert_eq!(chunks[2].sigil, sigil::ZAP);
    }

    #[test]
    fn writer_matches_encode() {
        let chunk = Chunk::text("via writer");
        let mut buf = Vec::new();
        let mut w = Writer::new(&mut buf);
        w.write_chunk(&chunk).unwrap();
        assert_eq!(buf, chunk.encode());
    }

    // โ”€โ”€ constants sanity โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    #[test]
    fn marker_byte_is_0x1f() {
        assert_eq!(MARKER, 0x1F);
    }

    #[test]
    fn sigil_bytes_are_ascii() {
        for &s in &sigil::ALL {
            assert!(s.is_ascii(), "sigil 0x{s:02X} is not ASCII");
            assert!(sigil::name(s) != "unknown", "sigil 0x{s:02X} has no name");
        }
    }

    #[test]
    fn render_bytes_are_ascii_letters() {
        for &r in &render::ALL {
            assert!(r.is_ascii_alphabetic(), "render 0x{r:02X} ({}) is not ASCII alphabetic", r as char);
            assert!(render::name(r) != "unknown", "render 0x{r:02X} has no name");
        }
    }
}

Homonyms

cyb/optica/src/lib.rs
soft3/strata/src/lib.rs
cyb/honeycrisp/src/lib.rs
warriors/trisha/honeycrisp/lib.rs
warriors/trisha/wgpu/lib.rs
soft3/glia/import/lib.rs
soft3/foculus/src/lib.rs
soft3/nox/rs/lib.rs
soft3/cybergraph/src/lib.rs
soft3/tru/rs/lib.rs
soft3/mudra/src/lib.rs
soft3/glia/run/lib.rs
cyb/prysm/rs/lib.rs
warriors/trisha/rs/lib.rs
cyb/src-tauri/src/lib.rs
soft3/mir/src/lib.rs
soft3/lens/src/lib.rs
neural/trident/src/lib.rs
neural/rune/rs/subject/lib.rs
cyb/cyb/cyb-services/src/lib.rs
soft3/strata/nebu/rs/lib.rs
soft3/lens/core/src/lib.rs
neural/rs/mir-format/src/lib.rs
soft3/zheng/rs/src/lib.rs
neural/rune/rs/interp/lib.rs
soft3/radio/iroh-willow/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/eidos/rs/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
soft3/bbg/rs/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/lens/ikat/src/lib.rs
neural/rune/rs/lex/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/hemera/rs/src/lib.rs
neural/rune/rs/ast/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
soft3/lens/porphyry/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
neural/rune/rs/compile/lib.rs
neural/rune/rs/parse-pure/lib.rs
neural/rs/codegen/src/lib.rs
soft3/lens/binius/src/lib.rs
neural/rune/rs/prysm/lib.rs
neural/rs/link/src/lib.rs
neural/rune/rs/mold/lib.rs
soft3/strata/proof/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/lens/assayer/src/lib.rs
neural/rs/core/src/lib.rs
neural/rs/macros/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
soft3/strata/compute/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
neural/rune/rs/lower/lib.rs
soft3/strata/ext/src/lib.rs
soft3/strata/core/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
soft3/radio/iroh/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
neural/inf/rs/parse/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
neural/trident/editor/zed/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
neural/inf/rs/plan/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
neural/inf/rs/source/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
neural/inf/rs/lex/src/lib.rs
neural/inf/rs/ast/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/lower/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/graph-filter/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std/src/lib.rs

Graph