//! Frame and Input Codec — encode/decode complete messages.
//!
//! A message = header (16 bytes) + payload (variable length).

use crate::protocol::*;

// ─── Stream Parser ───

/// Accumulates bytes from a transport and yields complete messages.
///
/// Handles partial reads: feed arbitrary chunks in, get complete
/// `(FrameHeader, Vec<u8>)` tuples out.
///
/// ```ignore
/// let mut parser = StreamParser::new();
/// parser.feed(&data_from_websocket);
/// while let Some((header, payload)) = parser.next_message() {
///     // process message
/// }
/// ```
pub struct StreamParser {
    buf: Vec<u8>,
}

impl StreamParser {
    pub fn new() -> Self {
        Self { buf: Vec::with_capacity(4096) }
    }

    /// Feed raw bytes into the parser.
    pub fn feed(&mut self, data: &[u8]) {
        self.buf.extend_from_slice(data);
    }

    /// Try to extract the next complete message.
    /// Returns `Some((header, payload))` if a complete message is available.
    pub fn next_message(&mut self) -> Option<(FrameHeader, Vec<u8>)> {
        if self.buf.len() < HEADER_SIZE {
            return None;
        }
        let header = FrameHeader::decode(&self.buf)?;
        let total = HEADER_SIZE + header.length as usize;
        if self.buf.len() < total {
            return None;
        }
        let payload = self.buf[HEADER_SIZE..total].to_vec();
        self.buf.drain(..total);
        Some((header, payload))
    }

    /// Number of bytes currently buffered.
    pub fn buffered(&self) -> usize {
        self.buf.len()
    }

    /// Clear the internal buffer.
    pub fn clear(&mut self) {
        self.buf.clear();
    }
}

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

// ─── Frame Encoder ───

/// Encode a complete frame message: header + payload.
pub fn encode_frame(
    frame_type: FrameType,
    seq: u16,
    timestamp: u32,
    width: u16,
    height: u16,
    flags: u8,
    payload: &[u8],
) -> Vec<u8> {
    let header = FrameHeader {
        frame_type: frame_type as u8,
        flags,
        seq,
        timestamp,
        width,
        height,
        length: payload.len() as u32,
    };
    let mut buf = Vec::with_capacity(HEADER_SIZE + payload.len());
    buf.extend_from_slice(&header.encode());
    buf.extend_from_slice(payload);
    buf
}

/// Encode an input event message: header + payload, with FLAG_INPUT set.
pub fn encode_input(
    input_type: InputType,
    seq: u16,
    timestamp: u32,
    payload: &[u8],
) -> Vec<u8> {
    let header = FrameHeader {
        frame_type: input_type as u8,
        flags: FLAG_INPUT,
        seq,
        timestamp,
        width: 0,
        height: 0,
        length: payload.len() as u32,
    };
    let mut buf = Vec::with_capacity(HEADER_SIZE + payload.len());
    buf.extend_from_slice(&header.encode());
    buf.extend_from_slice(payload);
    buf
}

// ─── Frame Decoder ───

/// Decoded message: header + payload reference.
#[derive(Debug)]
pub struct DecodedMessage<'a> {
    pub header: FrameHeader,
    pub payload: &'a [u8],
}

/// Decode a complete message from bytes.
/// Returns None if the buffer is too short for the header or payload.
pub fn decode_message(buf: &[u8]) -> Option<DecodedMessage<'_>> {
    let header = FrameHeader::decode(buf)?;
    let payload_start = HEADER_SIZE;
    let payload_end = payload_start + header.length as usize;
    if buf.len() < payload_end {
        return None;
    }
    Some(DecodedMessage {
        header,
        payload: &buf[payload_start..payload_end],
    })
}

/// Total message size from a header.
pub fn message_size(header: &FrameHeader) -> usize {
    HEADER_SIZE + header.length as usize
}

// ─── Delta Compression ───

/// Compute XOR delta between two frames.
/// Returns a delta buffer where unchanged pixels are zero bytes.
pub fn compute_delta(current: &[u8], previous: &[u8]) -> Vec<u8> {
    assert_eq!(current.len(), previous.len(), "frames must be same size");
    current.iter().zip(previous.iter()).map(|(c, p)| c ^ p).collect()
}

/// Apply XOR delta to reconstruct current frame from previous + delta.
pub fn apply_delta(previous: &[u8], delta: &[u8]) -> Vec<u8> {
    assert_eq!(previous.len(), delta.len(), "frames must be same size");
    previous.iter().zip(delta.iter()).map(|(p, d)| p ^ d).collect()
}

/// Check if a delta frame is worth sending (vs sending a keyframe).
/// Returns true if the delta is significantly smaller due to zero runs.
pub fn delta_is_worthwhile(delta: &[u8]) -> bool {
    let zero_count = delta.iter().filter(|&&b| b == 0).count();
    zero_count > delta.len() * 3 / 10
}

// ─── RLE Compression for Delta Frames ───

/// RLE-encode a delta buffer for transmission.
///
/// Encoding scheme:
/// - Non-zero byte `b`: emit literally
/// - Run of N zero bytes: emit `0x00` followed by 2-byte LE count
///
/// Optimized for XOR delta frames where most bytes are 0x00 (unchanged pixels).
/// On typical UI frames with 70-95% unchanged content, achieves 5-20x compression.
pub fn rle_encode(data: &[u8]) -> Vec<u8> {
    let mut out = Vec::with_capacity(data.len() / 2);
    let mut i = 0;
    while i < data.len() {
        if data[i] == 0 {
            let start = i;
            while i < data.len() && data[i] == 0 {
                i += 1;
            }
            let count = i - start;
            let mut remaining = count;
            while remaining > 0 {
                let chunk = remaining.min(65535);
                out.push(0x00);
                out.push((chunk & 0xFF) as u8);
                out.push(((chunk >> 8) & 0xFF) as u8);
                remaining -= chunk;
            }
        } else {
            out.push(data[i]);
            i += 1;
        }
    }
    out
}

/// RLE-decode a compressed delta buffer.
///
/// Inverse of `rle_encode`: expands `0x00 count_lo count_hi` markers
/// into zero runs, passes non-zero bytes through literally.
pub fn rle_decode(data: &[u8]) -> Vec<u8> {
    let mut out = Vec::with_capacity(data.len() * 2);
    let mut i = 0;
    while i < data.len() {
        if data[i] == 0 {
            if i + 2 >= data.len() {
                break;
            }
            let count = data[i + 1] as usize | ((data[i + 2] as usize) << 8);
            out.resize(out.len() + count, 0);
            i += 3;
        } else {
            out.push(data[i]);
            i += 1;
        }
    }
    out
}

/// Encode a delta frame with RLE: compute XOR delta, then RLE-compress.
/// Returns compressed bytes and the compression ratio (compressed/original).
pub fn encode_delta_rle(current: &[u8], previous: &[u8]) -> (Vec<u8>, f32) {
    let delta = compute_delta(current, previous);
    let compressed = rle_encode(&delta);
    let ratio = compressed.len() as f32 / delta.len() as f32;
    (compressed, ratio)
}

/// Decode a delta frame with RLE: RLE-decompress, then apply XOR delta.
pub fn decode_delta_rle(compressed: &[u8], previous: &[u8]) -> Vec<u8> {
    let delta = rle_decode(compressed);
    apply_delta(previous, &delta)
}

// ─── Convenience Builders ───

/// Build a pixel frame message from raw RGBA data.
pub fn pixel_frame(seq: u16, timestamp: u32, width: u16, height: u16, rgba_data: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::Pixels, seq, timestamp, width, height, FLAG_KEYFRAME, rgba_data)
}

/// Build a delta frame message with RLE-compressed payload.
pub fn delta_frame(seq: u16, timestamp: u32, width: u16, height: u16, rle_data: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::DeltaPixels, seq, timestamp, width, height, FLAG_COMPRESSED, rle_data)
}

/// Build a signal sync (full state) frame from a JSON payload.
pub fn signal_sync_frame(seq: u16, timestamp: u32, json: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::SignalSync, seq, timestamp, 0, 0, FLAG_KEYFRAME, json)
}

/// Build a signal diff (changed signals only) frame from a JSON payload.
pub fn signal_diff_frame(seq: u16, timestamp: u32, json: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::SignalDiff, seq, timestamp, 0, 0, 0, json)
}

/// Build a schema announcement frame (source → relay → receivers).
pub fn schema_announce_frame(seq: u16, timestamp: u32, json: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::SchemaAnnounce, seq, timestamp, 0, 0, 0, json)
}

/// Build a subscribe filter frame (receiver → relay).
pub fn subscribe_filter_frame(seq: u16, timestamp: u32, json: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::SubscribeFilter, seq, timestamp, 0, 0, 0, json)
}

/// Build a pointer input message.
pub fn pointer_input(seq: u16, timestamp: u32, event: &PointerEvent, input_type: InputType) -> Vec<u8> {
    encode_input(input_type, seq, timestamp, &event.encode())
}

/// Build a touch input message.
pub fn touch_input(seq: u16, timestamp: u32, event: &TouchEvent, input_type: InputType) -> Vec<u8> {
    encode_input(input_type, seq, timestamp, &event.encode())
}

/// Build a gamepad axis input message.
pub fn gamepad_axis_input(seq: u16, timestamp: u32, event: &GamepadAxisEvent) -> Vec<u8> {
    encode_input(InputType::GamepadAxis, seq, timestamp, &event.encode())
}

/// Build a gamepad button input message.
pub fn gamepad_button_input(seq: u16, timestamp: u32, event: &GamepadButtonEvent) -> Vec<u8> {
    encode_input(InputType::GamepadButton, seq, timestamp, &event.encode())
}

/// Build a ping/heartbeat message.
pub fn ping(seq: u16, timestamp: u32) -> Vec<u8> {
    encode_frame(FrameType::Ping, seq, timestamp, 0, 0, 0, &[])
}

/// Build a stream-end message.
pub fn stream_end(seq: u16, timestamp: u32) -> Vec<u8> {
    encode_frame(FrameType::End, seq, timestamp, 0, 0, 0, &[])
}

// ─── Neural Frame Builders ───

/// Build a neural frame message (model output tensor as pixels).
pub fn neural_frame(seq: u16, timestamp: u32, width: u16, height: u16, tensor_data: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::NeuralFrame, seq, timestamp, width, height, 0, tensor_data)
}

/// Build a neural audio synthesis output message.
pub fn neural_audio(seq: u16, timestamp: u32, audio_data: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::NeuralAudio, seq, timestamp, 0, 0, 0, audio_data)
}

/// Build a neural actuator command message (learned motor control).
pub fn neural_actuator(seq: u16, timestamp: u32, command_data: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::NeuralActuator, seq, timestamp, 0, 0, 0, command_data)
}

/// Build a neural latent space representation message.
pub fn neural_latent(seq: u16, timestamp: u32, latent_data: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::NeuralLatent, seq, timestamp, 0, 0, 0, latent_data)
}

// ─── Audio Frame Builder ───

/// Build an Opus-compressed audio frame message.
pub fn audio_compressed(seq: u16, timestamp: u32, channels: u16, sample_rate_div100: u16, opus_data: &[u8]) -> Vec<u8> {
    encode_frame(FrameType::AudioCompressed, seq, timestamp, channels, sample_rate_div100, FLAG_COMPRESSED, opus_data)
}

// ─── Remaining Input Builders ───

/// Build a MIDI input message.
pub fn midi_input(seq: u16, timestamp: u32, event: &MidiEvent) -> Vec<u8> {
    encode_input(InputType::Midi, seq, timestamp, &event.encode())
}

/// Build a voice/audio input message (header + PCM payload).
pub fn voice_input(seq: u16, timestamp: u32, event: &VoiceInputEvent, pcm_data: &[u8]) -> Vec<u8> {
    let header_bytes = event.encode();
    let mut payload = Vec::with_capacity(header_bytes.len() + pcm_data.len());
    payload.extend_from_slice(&header_bytes);
    payload.extend_from_slice(pcm_data);
    encode_input(InputType::VoiceInput, seq, timestamp, &payload)
}

/// Build a camera frame input message (header + frame payload).
pub fn camera_input(seq: u16, timestamp: u32, event: &CameraInputEvent, frame_data: &[u8]) -> Vec<u8> {
    let header_bytes = event.encode();
    let mut payload = Vec::with_capacity(header_bytes.len() + frame_data.len());
    payload.extend_from_slice(&header_bytes);
    payload.extend_from_slice(frame_data);
    encode_input(InputType::CameraInput, seq, timestamp, &payload)
}

/// Build a sensor telemetry input message.
pub fn sensor_input(seq: u16, timestamp: u32, event: &SensorInputEvent) -> Vec<u8> {
    encode_input(InputType::SensorInput, seq, timestamp, &event.encode())
}

/// Build a BCI (brain-computer interface) input message (header + samples payload).
pub fn bci_input(seq: u16, timestamp: u32, event: &BciInputEvent, samples: &[u8]) -> Vec<u8> {
    let header_bytes = event.encode();
    let mut payload = Vec::with_capacity(header_bytes.len() + samples.len());
    payload.extend_from_slice(&header_bytes);
    payload.extend_from_slice(samples);
    encode_input(InputType::BciInput, seq, timestamp, &payload)
}

// ─── ACK Frame Builder ───

/// Build an ACK message (receiver → source for RTT measurement).
pub fn ack_frame(seq: u16, timestamp: u32, event: &AckEvent) -> Vec<u8> {
    encode_frame(FrameType::Ack, seq, timestamp, 0, 0, 0, &event.encode())
}

// ─── Compression Helpers ───

/// Compute compression ratio and savings percentage.
/// Returns `(ratio, savings_percent)` where ratio = compressed/original
/// and savings_percent = (1 - ratio) * 100.
pub fn compression_ratio(original_len: usize, compressed_len: usize) -> (f32, f32) {
    if original_len == 0 {
        return (1.0, 0.0);
    }
    let ratio = compressed_len as f32 / original_len as f32;
    let savings = (1.0 - ratio) * 100.0;
    (ratio, savings)
}

// ─── v0.3: Compressed Pixel Frame ───

/// Build a CompressedPixels frame with format byte prepended.
/// `format`: 0=PNG, 1=WebP, 2=JPEG (see `CompressedPixelFormat`).
pub fn compressed_pixel_frame(
    seq: u16, timestamp: u32, width: u16, height: u16,
    format: CompressedPixelFormat, image_data: &[u8],
) -> Vec<u8> {
    let mut payload = Vec::with_capacity(1 + image_data.len());
    payload.push(format as u8);
    payload.extend_from_slice(image_data);
    encode_frame(FrameType::CompressedPixels, seq, timestamp, width, height, FLAG_COMPRESSED, &payload)
}

// ─── v0.3: Recording File I/O ───

/// Magic bytes for `.dsrec` recording files: "DS\x01"
pub const RECORDING_MAGIC: [u8; 3] = [0x44, 0x53, 0x01]; // 'D', 'S', version=1

/// Writes ds-stream messages to a `.dsrec` file.
///
/// File format:
/// ```text
/// [magic: 3 bytes "DS\x01"]
/// [entry]*
///
/// Each entry:
/// [timestamp_us: u64 LE]  — microsecond timestamp
/// [msg_len: u32 LE]       — message length in bytes
/// [msg: msg_len bytes]    — raw ds-stream message (header + payload)
/// ```
pub struct RecordingWriter<W: std::io::Write> {
    writer: W,
    entry_count: u64,
}

impl<W: std::io::Write> RecordingWriter<W> {
    /// Create a new recording writer. Writes the magic header immediately.
    pub fn new(mut writer: W) -> std::io::Result<Self> {
        writer.write_all(&RECORDING_MAGIC)?;
        Ok(Self { writer, entry_count: 0 })
    }

    /// Write a single message entry with timestamp.
    pub fn write_entry(&mut self, timestamp_us: u64, message: &[u8]) -> std::io::Result<()> {
        self.writer.write_all(&timestamp_us.to_le_bytes())?;
        self.writer.write_all(&(message.len() as u32).to_le_bytes())?;
        self.writer.write_all(message)?;
        self.entry_count += 1;
        Ok(())
    }

    /// Flush the underlying writer.
    pub fn flush(&mut self) -> std::io::Result<()> {
        self.writer.flush()
    }

    /// Number of entries written.
    pub fn entry_count(&self) -> u64 {
        self.entry_count
    }
}

/// Reads ds-stream messages from a `.dsrec` file.
pub struct RecordingReader<R: std::io::Read> {
    reader: R,
    entries_read: u64,
}

impl<R: std::io::Read> RecordingReader<R> {
    /// Create a new recording reader. Validates the magic header.
    pub fn new(mut reader: R) -> std::io::Result<Self> {
        let mut magic = [0u8; 3];
        reader.read_exact(&mut magic)?;
        if magic != RECORDING_MAGIC {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "Invalid .dsrec magic bytes",
            ));
        }
        Ok(Self { reader, entries_read: 0 })
    }

    /// Read the next entry. Returns `None` at end of file.
    /// Returns `Some((timestamp_us, message_bytes))`.
    pub fn next_entry(&mut self) -> std::io::Result<Option<(u64, Vec<u8>)>> {
        let mut ts_buf = [0u8; 8];
        match self.reader.read_exact(&mut ts_buf) {
            Ok(_) => {}
            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
            Err(e) => return Err(e),
        }
        let timestamp_us = u64::from_le_bytes(ts_buf);

        let mut len_buf = [0u8; 4];
        self.reader.read_exact(&mut len_buf)?;
        let msg_len = u32::from_le_bytes(len_buf) as usize;

        let mut message = vec![0u8; msg_len];
        self.reader.read_exact(&mut message)?;

        self.entries_read += 1;
        Ok(Some((timestamp_us, message)))
    }

    /// Number of entries read so far.
    pub fn entries_read(&self) -> u64 {
        self.entries_read
    }

    /// Read all remaining entries into a Vec.
    pub fn read_all(&mut self) -> std::io::Result<Vec<(u64, Vec<u8>)>> {
        let mut entries = Vec::new();
        while let Some(entry) = self.next_entry()? {
            entries.push(entry);
        }
        Ok(entries)
    }
}


// ─── v0.4: Adaptive Quality ───

/// Quality tier based on measured RTT.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QualityTier {
    /// RTT < 50ms: full quality, all frames
    Full = 0,
    /// RTT 50-150ms: reduced quality, skip some frames
    Reduced = 1,
    /// RTT > 150ms: minimal — signal diffs only, no pixels
    Minimal = 2,
}

/// Adaptive quality controller that tracks RTT and recommends quality settings.
pub struct AdaptiveQuality {
    rtt_history: Vec<u16>,
    max_history: usize,
    current_tier: QualityTier,
}

impl AdaptiveQuality {
    pub fn new() -> Self {
        Self {
            rtt_history: Vec::new(),
            max_history: 30,
            current_tier: QualityTier::Full,
        }
    }

    /// Record an ACK RTT measurement and update the quality tier.
    pub fn record_ack(&mut self, rtt_ms: u16) {
        self.rtt_history.push(rtt_ms);
        if self.rtt_history.len() > self.max_history {
            self.rtt_history.remove(0);
        }
        self.current_tier = self.compute_tier();
    }

    /// Current quality tier.
    pub fn current_tier(&self) -> QualityTier {
        self.current_tier
    }

    /// Suggested JPEG/WebP quality (0-100).
    pub fn suggested_quality(&self) -> u8 {
        match self.current_tier {
            QualityTier::Full => 80,
            QualityTier::Reduced => 50,
            QualityTier::Minimal => 0,
        }
    }

    /// Suggested number of frames to skip between sends.
    pub fn suggested_skip_frames(&self) -> u32 {
        match self.current_tier {
            QualityTier::Full => 0,
            QualityTier::Reduced => 2,
            QualityTier::Minimal => u32::MAX,
        }
    }

    /// Average RTT over the history window (or 0 if empty).
    pub fn avg_rtt(&self) -> u16 {
        if self.rtt_history.is_empty() { return 0; }
        let sum: u32 = self.rtt_history.iter().map(|&r| r as u32).sum();
        (sum / self.rtt_history.len() as u32) as u16
    }

    fn compute_tier(&self) -> QualityTier {
        let avg = self.avg_rtt();
        if avg < 50 { QualityTier::Full }
        else if avg < 150 { QualityTier::Reduced }
        else { QualityTier::Minimal }
    }
}

/// Sliding window bandwidth estimator.
pub struct BandwidthEstimator {
    /// (timestamp_ms, bytes) entries
    samples: Vec<(u64, usize)>,
    window_ms: u64,
}

impl BandwidthEstimator {
    pub fn new(window_seconds: u32) -> Self {
        Self {
            samples: Vec::new(),
            window_ms: window_seconds as u64 * 1000,
        }
    }

    /// Record bytes sent/received at a given timestamp.
    pub fn record(&mut self, timestamp_ms: u64, bytes: usize) {
        self.samples.push((timestamp_ms, bytes));
        self.prune(timestamp_ms);
    }

    /// Estimated throughput in bytes per second.
    pub fn estimate_bps(&self) -> f64 {
        if self.samples.len() < 2 { return 0.0; }
        let first = self.samples.first().unwrap().0;
        let last = self.samples.last().unwrap().0;
        let elapsed = (last - first) as f64 / 1000.0;
        if elapsed <= 0.0 { return 0.0; }
        let total: usize = self.samples.iter().map(|s| s.1).sum();
        total as f64 / elapsed
    }

    /// Check if bandwidth is below a threshold.
    pub fn is_congested(&self, threshold_bps: f64) -> bool {
        self.estimate_bps() > 0.0 && self.estimate_bps() < threshold_bps
    }

    fn prune(&mut self, now: u64) {
        let cutoff = now.saturating_sub(self.window_ms);
        self.samples.retain(|s| s.0 >= cutoff);
    }
}

// ─── v0.5: Recording Metadata ───

/// Metadata header for .dsrec files. Written after magic bytes.
/// Total size: 24 bytes.
#[derive(Debug, Clone, PartialEq)]
pub struct RecordingMetadata {
    pub version: u16,
    pub created_at: u64,   // unix timestamp seconds
    pub duration_ms: u32,
    pub frame_count: u32,
    pub width: u16,
    pub height: u16,
}

impl RecordingMetadata {
    pub const SIZE: usize = 24;

    pub fn encode(&self) -> [u8; Self::SIZE] {
        let mut buf = [0u8; Self::SIZE];
        buf[0..2].copy_from_slice(&self.version.to_le_bytes());
        buf[2..10].copy_from_slice(&self.created_at.to_le_bytes());
        buf[10..14].copy_from_slice(&self.duration_ms.to_le_bytes());
        buf[14..18].copy_from_slice(&self.frame_count.to_le_bytes());
        buf[18..20].copy_from_slice(&self.width.to_le_bytes());
        buf[20..22].copy_from_slice(&self.height.to_le_bytes());
        // bytes 22-23 reserved
        buf
    }

    pub fn decode(buf: &[u8]) -> Option<Self> {
        if buf.len() < Self::SIZE { return None; }
        Some(Self {
            version: u16::from_le_bytes([buf[0], buf[1]]),
            created_at: u64::from_le_bytes([buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9]]),
            duration_ms: u32::from_le_bytes([buf[10], buf[11], buf[12], buf[13]]),
            frame_count: u32::from_le_bytes([buf[14], buf[15], buf[16], buf[17]]),
            width: u16::from_le_bytes([buf[18], buf[19]]),
            height: u16::from_le_bytes([buf[20], buf[21]]),
        })
    }
}

// ─── v0.5: Auth Frame Builders ───

/// Build an auth challenge frame (server → client).
pub fn auth_challenge_frame(seq: u16, nonce: &[u8; 8]) -> Vec<u8> {
    let payload = AuthPayload {
        phase: 0,
        nonce: *nonce,
        token: Vec::new(),
    };
    let encoded = payload.encode();
    encode_frame(FrameType::Auth, seq, 0, 0, 0, 0, &encoded)
}

/// Build an auth response frame (client → server).
pub fn auth_response_frame(seq: u16, nonce: &[u8; 8], token: &[u8]) -> Vec<u8> {
    let payload = AuthPayload {
        phase: 1,
        nonce: *nonce,
        token: token.to_vec(),
    };
    let encoded = payload.encode();
    encode_frame(FrameType::Auth, seq, 0, 0, 0, 0, &encoded)
}

// ─── v0.10: Stream Multiplexing ───

/// Multiplex a frame onto a logical stream by embedding stream_id in the flags byte.
pub fn mux_frame(stream_id: u8, frame: &[u8]) -> Vec<u8> {
    if frame.len() < HEADER_SIZE { return frame.to_vec(); }
    let mut out = frame.to_vec();
    out[1] = embed_stream_id(out[1], stream_id);
    out
}

/// Demultiplex: extract stream_id and strip it from the flags byte.
/// Returns (stream_id, cleaned_frame).
pub fn demux_frame(frame: &[u8]) -> (u8, Vec<u8>) {
    if frame.len() < HEADER_SIZE { return (0, frame.to_vec()); }
    let stream_id = extract_stream_id(frame[1]);
    let mut out = frame.to_vec();
    out[1] &= FLAGS_MASK;
    (stream_id, out)
}

// ─── v0.10: Frame Priority ───

/// Priority levels for frame types.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FramePriority {
    Critical = 0,
    High = 1,
    Normal = 2,
    Low = 3,
}

/// Determine the priority of a frame type.
pub fn prioritize_frame(frame_type: u8) -> FramePriority {
    match FrameType::from_u8(frame_type) {
        Some(FrameType::Keyframe) | Some(FrameType::Auth) | Some(FrameType::End) => FramePriority::Critical,
        Some(FrameType::Pixels) | Some(FrameType::CompressedPixels) | Some(FrameType::DeltaPixels) |
        Some(FrameType::NeuralFrame) => FramePriority::High,
        Some(FrameType::SignalSync) | Some(FrameType::SignalDiff) |
        Some(FrameType::AudioPcm) | Some(FrameType::AudioCompressed) => FramePriority::Normal,
        Some(FrameType::Ping) | Some(FrameType::Ack) => FramePriority::Low,
        _ => FramePriority::Normal,
    }
}

// ─── v0.10: Bandwidth Allocator ───

/// Per-stream bandwidth allocation.
#[derive(Debug, Clone)]
pub struct StreamAllocation {
    pub stream_id: u8,
    pub priority: FramePriority,
    pub byte_limit: u32,
}

/// Allocate bytes across streams based on priority.
/// Higher-priority streams get proportionally more bandwidth.
pub fn allocate_bandwidth(total_bps: u32, streams: &[(u8, FramePriority)]) -> Vec<StreamAllocation> {
    if streams.is_empty() { return Vec::new(); }
    // Priority weights: Critical=8, High=4, Normal=2, Low=1
    let weight = |p: &FramePriority| -> u32 {
        match p {
            FramePriority::Critical => 8,
            FramePriority::High => 4,
            FramePriority::Normal => 2,
            FramePriority::Low => 1,
        }
    };
    let total_weight: u32 = streams.iter().map(|(_, p)| weight(p)).sum();
    if total_weight == 0 { return Vec::new(); }
    streams.iter().map(|(id, p)| {
        StreamAllocation {
            stream_id: *id,
            priority: *p,
            byte_limit: (total_bps as u64 * weight(p) as u64 / total_weight as u64) as u32,
        }
    }).collect()
}

// ─── v0.10: Frame Drop Policy ───

/// Congestion level (0-3). 0 = no congestion, 3 = severe.
pub type CongestionLevel = u8;

/// Determine if a frame should be dropped based on priority and congestion.
/// Returns true if the frame should be dropped.
pub fn should_drop(priority: FramePriority, congestion: CongestionLevel) -> bool {
    match congestion {
        0 => false,                     // No drops
        1 => priority == FramePriority::Low,  // Drop low-priority only
        2 => priority >= FramePriority::Normal, // Drop normal and low
        _ => priority >= FramePriority::High,   // Drop everything except critical
    }
}

// ─── Tests ───

// ─── v0.11: Frame Stats ───

/// Accumulates per-stream frame statistics.
#[derive(Debug, Clone)]
pub struct FrameStats {
    pub frame_count: u64,
    pub total_bytes: u64,
    pub dropped_count: u64,
    pub min_size: u32,
    pub max_size: u32,
}

impl FrameStats {
    pub fn new() -> Self {
        FrameStats {
            frame_count: 0,
            total_bytes: 0,
            dropped_count: 0,
            min_size: u32::MAX,
            max_size: 0,
        }
    }

    /// Record a frame (len in bytes, dropped = true if skipped).
    pub fn record(&mut self, len: u32, dropped: bool) {
        if dropped {
            self.dropped_count += 1;
        } else {
            self.frame_count += 1;
            self.total_bytes += len as u64;
            self.min_size = self.min_size.min(len);
            self.max_size = self.max_size.max(len);
        }
    }

    /// Average frame size in bytes.
    pub fn avg_size(&self) -> f64 {
        if self.frame_count == 0 { 0.0 } else { self.total_bytes as f64 / self.frame_count as f64 }
    }

    /// Reset all counters.
    pub fn reset(&mut self) {
        *self = FrameStats::new();
    }
}

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

// ─── v0.11: Jitter Buffer ───

/// Reorders frames by sequence number with configurable depth.
pub struct JitterBuffer {
    buffer: Vec<(u16, Vec<u8>)>,
    depth: usize,
    next_seq: u16,
}

impl JitterBuffer {
    pub fn new(depth: usize) -> Self {
        JitterBuffer {
            buffer: Vec::with_capacity(depth),
            depth: depth.max(1),
            next_seq: 0,
        }
    }

    /// Push a frame with its sequence number.
    pub fn push(&mut self, seq: u16, frame: Vec<u8>) {
        if self.buffer.len() >= self.depth * 2 {
            // Prevent unbounded growth: flush oldest
            self.buffer.sort_by_key(|(s, _)| *s);
            self.buffer.drain(..self.depth);
        }
        self.buffer.push((seq, frame));
    }

    /// Pop the next in-order frame, if available.
    pub fn pop(&mut self) -> Option<Vec<u8>> {
        if let Some(pos) = self.buffer.iter().position(|(s, _)| *s == self.next_seq) {
            let (_, frame) = self.buffer.remove(pos);
            self.next_seq = self.next_seq.wrapping_add(1);
            Some(frame)
        } else if self.buffer.len() >= self.depth {
            // Buffer full, force pop oldest
            self.buffer.sort_by_key(|(s, _)| *s);
            let (seq, frame) = self.buffer.remove(0);
            self.next_seq = seq.wrapping_add(1);
            Some(frame)
        } else {
            None
        }
    }

    /// Number of buffered frames.
    pub fn len(&self) -> usize { self.buffer.len() }
    pub fn is_empty(&self) -> bool { self.buffer.is_empty() }
}

// ─── v0.11: Latency Tracker ───

/// Tracks round-trip time samples for latency analysis.
#[derive(Debug, Clone)]
pub struct LatencyTracker {
    samples: Vec<f64>,
    max_samples: usize,
}

impl LatencyTracker {
    pub fn new(max_samples: usize) -> Self {
        LatencyTracker {
            samples: Vec::with_capacity(max_samples),
            max_samples: max_samples.max(1),
        }
    }

    /// Record an RTT sample in milliseconds.
    pub fn record_rtt(&mut self, ms: f64) {
        if self.samples.len() >= self.max_samples {
            self.samples.remove(0);
        }
        self.samples.push(ms);
    }

    /// Average RTT in ms.
    pub fn avg_rtt(&self) -> f64 {
        if self.samples.is_empty() { return 0.0; }
        self.samples.iter().sum::<f64>() / self.samples.len() as f64
    }

    /// P95 RTT in ms.
    pub fn p95_rtt(&self) -> f64 {
        if self.samples.is_empty() { return 0.0; }
        let mut sorted = self.samples.clone();
        sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
        let idx = ((sorted.len() as f64 * 0.95) as usize).min(sorted.len() - 1);
        sorted[idx]
    }

    /// Jitter: standard deviation of RTT samples.
    pub fn jitter(&self) -> f64 {
        if self.samples.len() < 2 { return 0.0; }
        let avg = self.avg_rtt();
        let variance = self.samples.iter().map(|s| (s - avg).powi(2)).sum::<f64>() / self.samples.len() as f64;
        variance.sqrt()
    }

    pub fn sample_count(&self) -> usize { self.samples.len() }
}

// ─── v0.12: State Snapshot ───

/// Encode application state as a frame for initial sync on reconnect.
/// Format: [version:u32][state_bytes...]
pub fn encode_state(version: u32, state: &[u8]) -> Vec<u8> {
    let mut out = Vec::with_capacity(4 + state.len());
    out.extend_from_slice(&version.to_le_bytes());
    out.extend_from_slice(state);
    out
}

/// Decode state snapshot. Returns (version, state_bytes).
pub fn decode_state(data: &[u8]) -> Option<(u32, Vec<u8>)> {
    if data.len() < 4 { return None; }
    let version = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
    Some((version, data[4..].to_vec()))
}

// ─── v0.12: Gap Detector ───

/// Result of feeding a sequence number to the gap detector.
#[derive(Debug, Clone, PartialEq)]
pub enum GapResult {
    Ok,
    Gap { expected: u16, got: u16 },
    Duplicate,
}

/// Detects gaps and duplicates in sequence numbers.
pub struct GapDetector {
    next_expected: u16,
    seen: Vec<u16>,
    max_seen: usize,
}

impl GapDetector {
    pub fn new() -> Self {
        GapDetector { next_expected: 0, seen: Vec::new(), max_seen: 256 }
    }

    /// Feed a sequence number. Returns gap/dup/ok status.
    pub fn feed(&mut self, seq: u16) -> GapResult {
        // Check duplicate
        if self.seen.contains(&seq) {
            return GapResult::Duplicate;
        }
        if self.seen.len() >= self.max_seen {
            self.seen.drain(..self.max_seen / 2);
        }
        self.seen.push(seq);

        if seq == self.next_expected {
            self.next_expected = self.next_expected.wrapping_add(1);
            GapResult::Ok
        } else {
            let expected = self.next_expected;
            self.next_expected = seq.wrapping_add(1);
            GapResult::Gap { expected, got: seq }
        }
    }

    pub fn expected(&self) -> u16 { self.next_expected }
}

// ─── v0.12: Connection Handshake ───

/// Build a handshake hello message.
/// Format: [0x48][version:u16][cap_count:u8][capabilities...]
pub fn handshake_hello(version: u16, capabilities: &[u8]) -> Vec<u8> {
    let mut out = Vec::with_capacity(4 + capabilities.len());
    out.push(0x48); // 'H'
    out.extend_from_slice(&version.to_le_bytes());
    out.push(capabilities.len() as u8);
    out.extend_from_slice(capabilities);
    out
}

/// Build a handshake accept response.
/// Format: [0x41][version:u16]
pub fn handshake_accept(version: u16) -> Vec<u8> {
    let mut out = Vec::with_capacity(3);
    out.push(0x41); // 'A'
    out.extend_from_slice(&version.to_le_bytes());
    out
}

/// Parse a handshake hello. Returns (version, capabilities).
pub fn parse_handshake_hello(data: &[u8]) -> Option<(u16, Vec<u8>)> {
    if data.len() < 4 || data[0] != 0x48 { return None; }
    let version = u16::from_le_bytes([data[1], data[2]]);
    let cap_count = data[3] as usize;
    if data.len() < 4 + cap_count { return None; }
    Some((version, data[4..4 + cap_count].to_vec()))
}

// ─── v0.13: Replay Buffer ───

/// Circular replay buffer for DVR-style rewind.
pub struct ReplayBuffer {
    frames: Vec<Vec<u8>>,
    capacity: usize,
    write_pos: usize,
    count: usize,
}

impl ReplayBuffer {
    pub fn new(capacity: usize) -> Self {
        ReplayBuffer {
            frames: Vec::with_capacity(capacity),
            capacity: capacity.max(1),
            write_pos: 0,
            count: 0,
        }
    }

    /// Push a frame into the buffer (overwrites oldest if full).
    pub fn push(&mut self, frame: Vec<u8>) {
        if self.frames.len() < self.capacity {
            self.frames.push(frame);
        } else {
            self.frames[self.write_pos] = frame;
        }
        self.write_pos = (self.write_pos + 1) % self.capacity;
        self.count += 1;
    }

    /// Get frames in the range [start, end) from oldest to newest.
    pub fn get_range(&self, start: usize, end: usize) -> Vec<Vec<u8>> {
        let len = self.frames.len();
        if len == 0 || start >= len { return vec![]; }
        let end = end.min(len);
        if self.count <= self.capacity {
            self.frames[start..end].to_vec()
        } else {
            let mut result = Vec::new();
            for i in start..end {
                let idx = (self.write_pos + i) % self.capacity;
                result.push(self.frames[idx].clone());
            }
            result
        }
    }

    /// Snapshot all buffered frames.
    pub fn snapshot(&self) -> Vec<Vec<u8>> {
        self.get_range(0, self.frames.len())
    }

    pub fn len(&self) -> usize { self.frames.len() }
    pub fn is_empty(&self) -> bool { self.frames.is_empty() }
    pub fn total_pushed(&self) -> usize { self.count }
}

// ─── v0.13: Header Serialization ───

/// Serialize a frame header to 16 bytes.
pub fn serialize_header(frame_type: u8, flags: u8, seq: u16, timestamp: u32, width: u16, height: u16, payload_len: u32) -> Vec<u8> {
    let mut out = Vec::with_capacity(HEADER_SIZE);
    out.push(frame_type);
    out.push(flags);
    out.extend_from_slice(&seq.to_le_bytes());
    out.extend_from_slice(&timestamp.to_le_bytes());
    out.extend_from_slice(&width.to_le_bytes());
    out.extend_from_slice(&height.to_le_bytes());
    out.extend_from_slice(&payload_len.to_le_bytes());
    out
}

/// Deserialize a 16-byte header. Returns (type, flags, seq, timestamp, width, height, len).
pub fn deserialize_header(data: &[u8]) -> Option<(u8, u8, u16, u32, u16, u16, u32)> {
    if data.len() < HEADER_SIZE { return None; }
    let frame_type = data[0];
    let flags = data[1];
    let seq = u16::from_le_bytes([data[2], data[3]]);
    let timestamp = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
    let width = u16::from_le_bytes([data[8], data[9]]);
    let height = u16::from_le_bytes([data[10], data[11]]);
    let payload_len = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
    Some((frame_type, flags, seq, timestamp, width, height, payload_len))
}

// ─── v0.13: Codec Registry ───

/// Named codec entry.
#[derive(Debug, Clone)]
pub struct CodecEntry {
    pub name: String,
    pub encode_id: u8,
    pub decode_id: u8,
}

/// Registry for dynamically loaded codecs.
pub struct CodecRegistry {
    codecs: Vec<CodecEntry>,
}

impl CodecRegistry {
    pub fn new() -> Self { CodecRegistry { codecs: Vec::new() } }

    pub fn register(&mut self, name: &str, encode_id: u8, decode_id: u8) {
        self.codecs.push(CodecEntry {
            name: name.to_string(),
            encode_id,
            decode_id,
        });
    }

    pub fn list(&self) -> Vec<String> {
        self.codecs.iter().map(|c| c.name.clone()).collect()
    }

    pub fn get(&self, name: &str) -> Option<&CodecEntry> {
        self.codecs.iter().find(|c| c.name == name)
    }

    pub fn count(&self) -> usize { self.codecs.len() }
}

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

// ─── v0.14: Frame Compressor (RLE) ───

/// Simple run-length encoding compressor.
pub struct FrameCompressor {
    total_in: usize,
    total_out: usize,
}

impl FrameCompressor {
    pub fn new() -> Self { FrameCompressor { total_in: 0, total_out: 0 } }

    /// RLE compress: [byte, count, byte, count, ...]
    pub fn compress(&mut self, data: &[u8]) -> Vec<u8> {
        self.total_in += data.len();
        let mut out = Vec::new();
        if data.is_empty() { return out; }
        let mut i = 0;
        while i < data.len() {
            let val = data[i];
            let mut count: u8 = 1;
            while i + (count as usize) < data.len() && data[i + count as usize] == val && count < 255 {
                count += 1;
            }
            out.push(val);
            out.push(count);
            i += count as usize;
        }
        self.total_out += out.len();
        out
    }

    /// RLE decompress.
    pub fn decompress(&self, data: &[u8]) -> Vec<u8> {
        let mut out = Vec::new();
        let mut i = 0;
        while i + 1 < data.len() {
            let val = data[i];
            let count = data[i + 1] as usize;
            for _ in 0..count { out.push(val); }
            i += 2;
        }
        out
    }

    /// Compression ratio (lower = better).
    pub fn compression_ratio(&self) -> f32 {
        if self.total_in == 0 { return 1.0; }
        self.total_out as f32 / self.total_in as f32
    }
}

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

// ─── v0.14: Multi-Client Sync ───

/// Per-client state.
#[derive(Debug, Clone)]
pub struct ClientState {
    pub id: String,
    pub last_seq: u16,
    pub last_ack: u16,
    pub drift: i32, // seq - ack difference
}

/// Tracks per-client sync state.
pub struct MultiClientSync {
    clients: Vec<ClientState>,
}

impl MultiClientSync {
    pub fn new() -> Self { MultiClientSync { clients: Vec::new() } }

    pub fn register_client(&mut self, id: &str) {
        self.clients.push(ClientState {
            id: id.to_string(), last_seq: 0, last_ack: 0, drift: 0,
        });
    }

    pub fn update_seq(&mut self, id: &str, seq: u16) {
        if let Some(c) = self.clients.iter_mut().find(|c| c.id == id) {
            c.last_seq = seq;
            c.drift = seq as i32 - c.last_ack as i32;
        }
    }

    pub fn update_ack(&mut self, id: &str, ack: u16) {
        if let Some(c) = self.clients.iter_mut().find(|c| c.id == id) {
            c.last_ack = ack;
            c.drift = c.last_seq as i32 - ack as i32;
        }
    }

    /// Get the slowest consumer (highest drift). Returns client id or empty.
    pub fn slowest_client(&self) -> String {
        self.clients.iter().max_by_key(|c| c.drift)
            .map(|c| c.id.clone()).unwrap_or_default()
    }

    pub fn client_count(&self) -> usize { self.clients.len() }

    pub fn get_drift(&self, id: &str) -> i32 {
        self.clients.iter().find(|c| c.id == id).map(|c| c.drift).unwrap_or(0)
    }
}

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

// ─── v0.14: Bandwidth Throttle ───

/// Rate limiter for bandwidth.
pub struct BandwidthThrottle {
    limit_bps: usize,
    consumed: usize,
    window_start: u64, // timestamp ms
}

impl BandwidthThrottle {
    pub fn new(limit_bps: usize) -> Self {
        BandwidthThrottle { limit_bps, consumed: 0, window_start: 0 }
    }

    /// Set bandwidth limit (bytes per second).
    pub fn set_limit(&mut self, bps: usize) { self.limit_bps = bps; }

    /// Check if a frame of given size can be sent. Advances consumed counter if yes.
    pub fn should_send(&mut self, frame_size: usize, now_ms: u64) -> bool {
        // Reset window every second
        if now_ms - self.window_start >= 1000 {
            self.consumed = 0;
            self.window_start = now_ms;
        }
        if self.consumed + frame_size <= self.limit_bps {
            self.consumed += frame_size;
            true
        } else {
            false
        }
    }

    pub fn consumed(&self) -> usize { self.consumed }
    pub fn limit(&self) -> usize { self.limit_bps }
}

// ─── v0.15: Adaptive Bitrate ───

/// Adaptive bitrate controller based on RTT and loss.
pub struct AdaptiveBitrate {
    rtt_samples: Vec<f32>,
    loss_samples: Vec<f32>,
    max_samples: usize,
}

impl AdaptiveBitrate {
    pub fn new() -> Self {
        AdaptiveBitrate { rtt_samples: Vec::new(), loss_samples: Vec::new(), max_samples: 30 }
    }

    pub fn feed_rtt(&mut self, ms: f32) {
        if self.rtt_samples.len() >= self.max_samples { self.rtt_samples.remove(0); }
        self.rtt_samples.push(ms);
    }

    pub fn feed_loss(&mut self, pct: f32) {
        if self.loss_samples.len() >= self.max_samples { self.loss_samples.remove(0); }
        self.loss_samples.push(pct);
    }

    fn avg_rtt(&self) -> f32 {
        if self.rtt_samples.is_empty() { return 0.0; }
        self.rtt_samples.iter().sum::<f32>() / self.rtt_samples.len() as f32
    }

    fn avg_loss(&self) -> f32 {
        if self.loss_samples.is_empty() { return 0.0; }
        self.loss_samples.iter().sum::<f32>() / self.loss_samples.len() as f32
    }

    /// Recommended quality tier 0-4 (0=lowest, 4=highest).
    pub fn recommended_tier(&self) -> u8 {
        let rtt = self.avg_rtt();
        let loss = self.avg_loss();
        if rtt > 200.0 || loss > 10.0 { 0 }
        else if rtt > 100.0 || loss > 5.0 { 1 }
        else if rtt > 50.0 || loss > 2.0 { 2 }
        else if rtt > 20.0 || loss > 0.5 { 3 }
        else { 4 }
    }
}

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

// ─── v0.15: Metrics Snapshot ───

/// Frozen snapshot of stream metrics.
#[derive(Debug, Clone)]
pub struct MetricsSnapshot {
    pub fps: f32,
    pub bitrate_bps: usize,
    pub clients: usize,
    pub avg_drift: f32,
    pub health: f32,
    pub timestamp_ms: u64,
}

impl MetricsSnapshot {
    pub fn capture(fps: f32, bitrate_bps: usize, clients: usize, avg_drift: f32, health: f32) -> Self {
        MetricsSnapshot {
            fps, bitrate_bps, clients, avg_drift, health,
            timestamp_ms: 0, // caller sets
        }
    }

    pub fn to_json(&self) -> String {
        format!(
            r#"{{"fps":{:.1},"bitrate":{},"clients":{},"drift":{:.1},"health":{:.2}}}"#,
            self.fps, self.bitrate_bps, self.clients, self.avg_drift, self.health,
        )
    }
}

// ─── v0.15: Frame Pipeline ───

/// Named processing stage.
#[derive(Debug, Clone)]
pub struct PipelineStage {
    pub name: String,
}

/// Chain of named processing stages.
pub struct FramePipeline {
    stages: Vec<PipelineStage>,
}

impl FramePipeline {
    pub fn new() -> Self { FramePipeline { stages: Vec::new() } }

    pub fn add_stage(&mut self, name: &str) {
        self.stages.push(PipelineStage { name: name.to_string() });
    }

    /// Process data through all stages (identity for now — stages are named hooks).
    pub fn process(&self, data: Vec<u8>) -> Vec<u8> {
        // Each stage is a named hook; actual transforms would be registered callbacks.
        // For now, pipeline is a named pass-through.
        data
    }

    pub fn stage_count(&self) -> usize { self.stages.len() }

    pub fn stage_names(&self) -> Vec<String> {
        self.stages.iter().map(|s| s.name.clone()).collect()
    }
}

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

// ─── v0.16: Frame Encryptor ───

/// XOR cipher with rotating key.
pub struct FrameEncryptor;

impl FrameEncryptor {
    /// XOR encrypt data with key (symmetric).
    pub fn encrypt(data: &[u8], key: &[u8]) -> Vec<u8> {
        if key.is_empty() { return data.to_vec(); }
        data.iter().enumerate().map(|(i, &b)| b ^ key[i % key.len()]).collect()
    }

    /// XOR decrypt (same as encrypt for XOR).
    pub fn decrypt(data: &[u8], key: &[u8]) -> Vec<u8> {
        Self::encrypt(data, key)
    }
}

// ─── v0.16: Stream Migration ───

/// State transfer for stream migration between servers.
pub struct StreamMigration {
    state: Vec<u8>,
}

impl StreamMigration {
    pub fn new() -> Self { StreamMigration { state: Vec::new() } }

    /// Serialize current state for handoff.
    pub fn prepare_handoff(&mut self, seq: u16, clients: usize, fps: f32) -> Vec<u8> {
        let mut buf = Vec::with_capacity(8);
        buf.extend_from_slice(&seq.to_le_bytes());
        buf.extend_from_slice(&(clients as u16).to_le_bytes());
        buf.extend_from_slice(&fps.to_le_bytes());
        self.state = buf.clone();
        buf
    }

    /// Accept state from another server.
    pub fn accept_handoff(&mut self, state: &[u8]) -> Option<(u16, u16, f32)> {
        if state.len() < 8 { return None; }
        let seq = u16::from_le_bytes([state[0], state[1]]);
        let clients = u16::from_le_bytes([state[2], state[3]]);
        let fps = f32::from_le_bytes([state[4], state[5], state[6], state[7]]);
        self.state = state.to_vec();
        Some((seq, clients, fps))
    }
}

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

// ─── v0.16: Frame Dedup ───

/// Hash-based frame deduplication.
pub struct FrameDedup {
    seen_hashes: Vec<u64>,
    max_entries: usize,
    bytes_saved: usize,
}

impl FrameDedup {
    pub fn new(max_entries: usize) -> Self {
        FrameDedup { seen_hashes: Vec::new(), max_entries, bytes_saved: 0 }
    }

    fn hash(data: &[u8]) -> u64 {
        let mut h: u64 = 0xcbf29ce484222325;
        for &b in data {
            h ^= b as u64;
            h = h.wrapping_mul(0x100000001b3);
        }
        h
    }

    /// Check if frame is duplicate. Returns true if already seen.
    pub fn is_duplicate(&mut self, data: &[u8]) -> bool {
        let h = Self::hash(data);
        if self.seen_hashes.contains(&h) {
            self.bytes_saved += data.len();
            return true;
        }
        if self.seen_hashes.len() >= self.max_entries { self.seen_hashes.remove(0); }
        self.seen_hashes.push(h);
        false
    }

    pub fn dedup_savings(&self) -> usize { self.bytes_saved }
}

// ─── v0.16: Priority Queue ───

/// Priority-sorted frame queue.
pub struct PriorityQueue<T> {
    items: Vec<(T, u8)>, // (item, priority)
}

impl<T> PriorityQueue<T> {
    pub fn new() -> Self { PriorityQueue { items: Vec::new() } }

    pub fn enqueue(&mut self, item: T, priority: u8) {
        self.items.push((item, priority));
        self.items.sort_by(|a, b| b.1.cmp(&a.1));
    }

    pub fn dequeue(&mut self) -> Option<T> {
        if self.items.is_empty() { None }
        else { Some(self.items.remove(0).0) }
    }

    pub fn len(&self) -> usize { self.items.len() }
    pub fn is_empty(&self) -> bool { self.items.is_empty() }
}

impl<T> Default for PriorityQueue<T> {
    fn default() -> Self { Self::new() }
}

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

    #[test]
    fn frame_encode_decode_roundtrip() {
        let payload = vec![0xFF; 100];
        let msg = encode_frame(FrameType::Pixels, 1, 5000, 320, 240, FLAG_KEYFRAME, &payload);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::Pixels as u8);
        assert_eq!(decoded.header.seq, 1);
        assert_eq!(decoded.header.timestamp, 5000);
        assert_eq!(decoded.header.width, 320);
        assert_eq!(decoded.header.height, 240);
        assert_eq!(decoded.header.length, 100);
        assert!(decoded.header.is_keyframe());
        assert_eq!(decoded.payload, &payload[..]);
    }

    #[test]
    fn input_encode_decode_roundtrip() {
        let ptr = PointerEvent { x: 100, y: 200, buttons: 1 };
        let msg = pointer_input(5, 1234, &ptr, InputType::Pointer);
        let decoded = decode_message(&msg).unwrap();
        assert!(decoded.header.is_input());
        assert_eq!(decoded.header.frame_type, InputType::Pointer as u8);
        let ptr2 = PointerEvent::decode(decoded.payload).unwrap();
        assert_eq!(ptr2.x, 100);
        assert_eq!(ptr2.y, 200);
    }

    #[test]
    fn delta_compression() {
        let frame1 = vec![10, 20, 30, 40, 50, 60, 70, 80];
        let frame2 = vec![10, 20, 30, 40, 55, 65, 70, 80];
        let delta = compute_delta(&frame2, &frame1);
        assert_eq!(delta[0], 0);
        assert_eq!(delta[1], 0);
        assert_eq!(delta[6], 0);
        assert_eq!(delta[7], 0);
        let reconstructed = apply_delta(&frame1, &delta);
        assert_eq!(reconstructed, frame2);
    }

    #[test]
    fn delta_worthwhile_check() {
        assert!(delta_is_worthwhile(&vec![0u8; 100]));
        assert!(!delta_is_worthwhile(&vec![0xFF; 100]));
    }

    #[test]
    fn ping_message() {
        let msg = ping(99, 5000);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::Ping as u8);
        assert_eq!(decoded.header.seq, 99);
        assert_eq!(decoded.payload.len(), 0);
    }

    #[test]
    fn partial_buffer_returns_none() {
        let msg = encode_frame(FrameType::Pixels, 0, 0, 10, 10, 0, &[1, 2, 3]);
        assert!(decode_message(&msg[..HEADER_SIZE + 2]).is_none());
    }

    #[test]
    fn message_size_calculation() {
        let header = FrameHeader {
            frame_type: 0, flags: 0, seq: 0, timestamp: 0,
            width: 0, height: 0, length: 1024,
        };
        assert_eq!(message_size(&header), HEADER_SIZE + 1024);
    }

    // ─── RLE Tests ───

    #[test]
    fn rle_encode_all_zeros() {
        let data = vec![0u8; 1000];
        let encoded = rle_encode(&data);
        assert_eq!(encoded.len(), 3);
        let decoded = rle_decode(&encoded);
        assert_eq!(decoded, data);
    }

    #[test]
    fn rle_encode_no_zeros() {
        let data: Vec<u8> = (1..=255).collect();
        let encoded = rle_encode(&data);
        assert_eq!(encoded.len(), data.len());
        let decoded = rle_decode(&encoded);
        assert_eq!(decoded, data);
    }

    #[test]
    fn rle_encode_mixed() {
        let data = vec![0, 0, 0, 0xAA, 0xBB, 0, 0, 0, 0, 0, 0xCC];
        let encoded = rle_encode(&data);
        let decoded = rle_decode(&encoded);
        assert_eq!(decoded, data);
        assert!(encoded.len() < data.len());
    }

    #[test]
    fn rle_delta_roundtrip() {
        let frame1: Vec<u8> = (0..100).map(|i| (i * 7 % 256) as u8).collect();
        let mut frame2 = frame1.clone();
        for i in (0..10).map(|x| x * 10) {
            frame2[i] = frame2[i].wrapping_add(1);
        }
        let (compressed, ratio) = encode_delta_rle(&frame2, &frame1);
        assert!(ratio < 0.5, "RLE should compress well: ratio={}", ratio);
        let reconstructed = decode_delta_rle(&compressed, &frame1);
        assert_eq!(reconstructed, frame2);
    }

    #[test]
    fn rle_large_zero_run() {
        let data = vec![0u8; 70000];
        let encoded = rle_encode(&data);
        let decoded = rle_decode(&encoded);
        assert_eq!(decoded, data);
        assert_eq!(encoded.len(), 6); // 2 markers: 65535 + 4465
    }

    // ─── Signal Frame Tests ───

    #[test]
    fn signal_sync_frame_roundtrip() {
        let json = br#"{"count":42,"name":"hello"}"#;
        let msg = signal_sync_frame(1, 1000, json);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::SignalSync as u8);
        assert!(decoded.header.is_keyframe());
        assert_eq!(decoded.payload, json);
    }

    #[test]
    fn signal_diff_frame_roundtrip() {
        let json = br#"{"count":43}"#;
        let msg = signal_diff_frame(2, 2000, json);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::SignalDiff as u8);
        assert!(!decoded.header.is_keyframe());
        assert_eq!(decoded.payload, json);
    }

    #[test]
    fn delta_frame_builder() {
        let rle_data = rle_encode(&[0, 0, 0, 0, 0xFF, 0xAA, 0, 0]);
        let msg = delta_frame(5, 3000, 320, 240, &rle_data);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::DeltaPixels as u8);
        assert_eq!(decoded.header.flags & FLAG_COMPRESSED, FLAG_COMPRESSED);
    }

    #[test]
    fn stream_end_message() {
        let msg = stream_end(100, 99999);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::End as u8);
    }

    #[test]
    fn touch_input_builder() {
        let evt = TouchEvent { id: 1, x: 300, y: 400, phase: 0 };
        let msg = touch_input(1, 100, &evt, InputType::Touch);
        let decoded = decode_message(&msg).unwrap();
        assert!(decoded.header.is_input());
        let evt2 = TouchEvent::decode(decoded.payload).unwrap();
        assert_eq!(evt2.id, 1);
        assert_eq!(evt2.x, 300);
    }

    #[test]
    fn gamepad_input_builders() {
        let axis = GamepadAxisEvent { axis: 0, value: 16000 };
        let msg = gamepad_axis_input(1, 100, &axis);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, InputType::GamepadAxis as u8);

        let btn = GamepadButtonEvent { button: 3, pressed: 1, analog: 255 };
        let msg = gamepad_button_input(2, 200, &btn);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, InputType::GamepadButton as u8);
    }

    #[test]
    fn neural_frame_builders() {
        let tensor = vec![0.5f32, 0.3, 0.8, 0.1].iter()
            .flat_map(|f| f.to_le_bytes()).collect::<Vec<u8>>();
        let msg = neural_frame(1, 100, 2, 2, &tensor);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::NeuralFrame as u8);
        assert_eq!(decoded.header.width, 2);
        assert_eq!(decoded.header.height, 2);
        assert_eq!(decoded.payload, &tensor[..]);
    }

    #[test]
    fn neural_audio_builder() {
        let audio = vec![0u8; 256];
        let msg = neural_audio(1, 200, &audio);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::NeuralAudio as u8);
        assert_eq!(decoded.payload.len(), 256);
    }

    #[test]
    fn neural_actuator_builder() {
        let cmd = vec![1, 128, 0, 64]; // actuator command
        let msg = neural_actuator(1, 300, &cmd);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::NeuralActuator as u8);
    }

    #[test]
    fn neural_latent_builder() {
        let latent = vec![0u8; 512]; // latent vector
        let msg = neural_latent(1, 400, &latent);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::NeuralLatent as u8);
        assert_eq!(decoded.payload.len(), 512);
    }

    #[test]
    fn audio_compressed_builder() {
        let opus = vec![0xAA; 128]; // fake opus data
        let msg = audio_compressed(1, 500, 2, 480, &opus);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::AudioCompressed as u8);
        assert_eq!(decoded.header.width, 2); // channels
        assert_eq!(decoded.header.height, 480); // sample_rate/100
        assert_eq!(decoded.header.flags & FLAG_COMPRESSED, FLAG_COMPRESSED);
    }

    #[test]
    fn midi_input_builder() {
        let evt = MidiEvent { status: 0x90, data1: 60, data2: 100 };
        let msg = midi_input(1, 100, &evt);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, InputType::Midi as u8);
        assert!(decoded.header.is_input());
        let evt2 = MidiEvent::decode(decoded.payload).unwrap();
        assert_eq!(evt2.status, 0x90);
    }

    #[test]
    fn sensor_input_builder() {
        let evt = SensorInputEvent { sensor_type: 0, x: 100, y: -200, z: 9800 };
        let msg = sensor_input(1, 100, &evt);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, InputType::SensorInput as u8);
        let evt2 = SensorInputEvent::decode(decoded.payload).unwrap();
        assert_eq!(evt2.z, 9800);
    }

    #[test]
    fn voice_input_builder() {
        let header = VoiceInputEvent { sample_rate: 48000, channels: 1, format: 0 };
        let pcm = vec![0u8; 960]; // 10ms of mono f32 at 48kHz
        let msg = voice_input(1, 100, &header, &pcm);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, InputType::VoiceInput as u8);
        assert_eq!(decoded.payload.len(), VoiceInputEvent::SIZE + 960);
    }

    #[test]
    fn camera_input_builder() {
        let header = CameraInputEvent { width: 640, height: 480, format: 1 };
        let jpeg = vec![0xFFu8; 1024];
        let msg = camera_input(1, 100, &header, &jpeg);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, InputType::CameraInput as u8);
        assert_eq!(decoded.payload.len(), CameraInputEvent::SIZE + 1024);
    }

    #[test]
    fn bci_input_builder() {
        let header = BciInputEvent { channel_count: 8, sample_rate: 256 };
        let samples = vec![0u8; 32]; // 8 channels × 1 f32 sample
        let msg = bci_input(1, 100, &header, &samples);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, InputType::BciInput as u8);
        assert_eq!(decoded.payload.len(), BciInputEvent::SIZE + 32);
    }

    // ─── v0.2 Tests ───

    #[test]
    fn stream_parser_single_message() {
        let mut parser = StreamParser::new();
        let msg = signal_diff_frame(1, 100, b"{\"x\":1}");
        parser.feed(&msg);
        let (header, payload) = parser.next_message().unwrap();
        assert_eq!(header.frame_type, FrameType::SignalDiff as u8);
        assert_eq!(payload, b"{\"x\":1}");
        assert!(parser.next_message().is_none());
    }

    #[test]
    fn stream_parser_multi_message() {
        let mut parser = StreamParser::new();
        let msg1 = ping(1, 100);
        let msg2 = signal_sync_frame(2, 200, b"{\"a\":1}");
        let msg3 = stream_end(3, 300);

        let mut combined = Vec::new();
        combined.extend_from_slice(&msg1);
        combined.extend_from_slice(&msg2);
        combined.extend_from_slice(&msg3);

        parser.feed(&combined);

        let (h1, _) = parser.next_message().unwrap();
        assert_eq!(h1.frame_type, FrameType::Ping as u8);

        let (h2, p2) = parser.next_message().unwrap();
        assert_eq!(h2.frame_type, FrameType::SignalSync as u8);
        assert_eq!(p2, b"{\"a\":1}");

        let (h3, _) = parser.next_message().unwrap();
        assert_eq!(h3.frame_type, FrameType::End as u8);

        assert!(parser.next_message().is_none());
    }

    #[test]
    fn stream_parser_partial_feed() {
        let mut parser = StreamParser::new();
        let msg = signal_diff_frame(1, 100, b"{\"x\":42}");

        // Feed in two halves
        let mid = msg.len() / 2;
        parser.feed(&msg[..mid]);
        assert!(parser.next_message().is_none());

        parser.feed(&msg[mid..]);
        let (header, payload) = parser.next_message().unwrap();
        assert_eq!(header.seq, 1);
        assert_eq!(payload, b"{\"x\":42}");
    }

    #[test]
    fn ack_frame_roundtrip() {
        let evt = AckEvent { ack_seq: 99, rtt_ms: 12 };
        let msg = ack_frame(5, 5000, &evt);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::Ack as u8);
        let ack = AckEvent::decode(decoded.payload).unwrap();
        assert_eq!(ack.ack_seq, 99);
        assert_eq!(ack.rtt_ms, 12);
    }

    #[test]
    fn compression_ratio_calc() {
        let (ratio, savings) = compression_ratio(1000, 300);
        assert!((ratio - 0.3).abs() < 0.001);
        assert!((savings - 70.0).abs() < 0.1);

        let (ratio_zero, _) = compression_ratio(0, 0);
        assert!((ratio_zero - 1.0).abs() < 0.001);
    }

    // ─── v0.3 Tests ───

    #[test]
    fn compressed_pixel_frame_roundtrip() {
        let fake_jpeg = vec![0xFF, 0xD8, 0xFF, 0xE0]; // JPEG header
        let msg = compressed_pixel_frame(1, 100, 320, 240, CompressedPixelFormat::Jpeg, &fake_jpeg);
        let decoded = decode_message(&msg).unwrap();
        assert_eq!(decoded.header.frame_type, FrameType::CompressedPixels as u8);
        assert_eq!(decoded.header.width, 320);
        assert_eq!(decoded.header.height, 240);
        assert_eq!(decoded.header.flags & FLAG_COMPRESSED, FLAG_COMPRESSED);
        // First payload byte is format
        assert_eq!(decoded.payload[0], CompressedPixelFormat::Jpeg as u8);
        assert_eq!(&decoded.payload[1..], &fake_jpeg);
    }

    #[test]
    fn recording_writer_reader_roundtrip() {
        let mut buf = Vec::new();

        // Write
        {
            let mut writer = RecordingWriter::new(&mut buf).unwrap();
            let msg1 = signal_diff_frame(1, 100, b"{\"x\":1}");
            let msg2 = ping(2, 200);
            writer.write_entry(1000, &msg1).unwrap();
            writer.write_entry(2000, &msg2).unwrap();
            assert_eq!(writer.entry_count(), 2);
            writer.flush().unwrap();
        }

        // Read
        {
            let cursor = std::io::Cursor::new(&buf);
            let mut reader = RecordingReader::new(cursor).unwrap();
            let entries = reader.read_all().unwrap();
            assert_eq!(entries.len(), 2);
            assert_eq!(entries[0].0, 1000); // timestamp
            assert_eq!(entries[1].0, 2000);

            // Verify the messages are intact
            let decoded = decode_message(&entries[0].1).unwrap();
            assert_eq!(decoded.header.frame_type, FrameType::SignalDiff as u8);
            assert_eq!(reader.entries_read(), 2);
        }
    }

    #[test]
    fn recording_reader_invalid_magic() {
        let buf = vec![0x00, 0x00, 0x00]; // wrong magic
        let result = RecordingReader::new(std::io::Cursor::new(&buf));
        assert!(result.is_err());
    }

    #[test]
    fn compressed_pixel_format_roundtrip() {
        assert_eq!(CompressedPixelFormat::from_u8(0), Some(CompressedPixelFormat::Png));
        assert_eq!(CompressedPixelFormat::from_u8(1), Some(CompressedPixelFormat::WebP));
        assert_eq!(CompressedPixelFormat::from_u8(2), Some(CompressedPixelFormat::Jpeg));
        assert_eq!(CompressedPixelFormat::from_u8(3), None);
        assert_eq!(CompressedPixelFormat::Jpeg.name(), "JPEG");
        assert_eq!(CompressedPixelFormat::WebP.mime_type(), "image/webp");
    }

    // ─── v0.4 Tests ───

    #[test]
    fn adaptive_quality_tiers() {
        let mut aq = AdaptiveQuality::new();
        assert_eq!(aq.current_tier(), QualityTier::Full);
        assert_eq!(aq.suggested_quality(), 80);
        assert_eq!(aq.suggested_skip_frames(), 0);

        // Push RTT into reduced range
        for _ in 0..10 { aq.record_ack(80); }
        assert_eq!(aq.current_tier(), QualityTier::Reduced);
        assert_eq!(aq.suggested_quality(), 50);
        assert_eq!(aq.suggested_skip_frames(), 2);

        // Push into minimal
        for _ in 0..30 { aq.record_ack(200); }
        assert_eq!(aq.current_tier(), QualityTier::Minimal);
        assert_eq!(aq.suggested_quality(), 0);

        // Recover
        for _ in 0..30 { aq.record_ack(10); }
        assert_eq!(aq.current_tier(), QualityTier::Full);
    }

    #[test]
    fn bandwidth_estimator_basic() {
        let mut bw = BandwidthEstimator::new(5);
        bw.record(0, 1000);
        bw.record(1000, 1000);
        bw.record(2000, 1000);
        // 3000 bytes over 2 seconds = 1500 bps
        let bps = bw.estimate_bps();
        assert!((bps - 1500.0).abs() < 1.0, "BPS should be ~1500: {}", bps);
        assert!(!bw.is_congested(1000.0));
        assert!(bw.is_congested(2000.0));
    }

    #[test]
    fn audio_header_roundtrip() {
        let header = AudioHeader {
            format: AudioFormat::Opus,
            channels: 2,
            sample_rate: 48000,
            frame_size: 960,
        };
        let encoded = header.encode();
        let decoded = AudioHeader::decode(&encoded).unwrap();
        assert_eq!(decoded.format, AudioFormat::Opus);
        assert_eq!(decoded.channels, 2);
        assert_eq!(decoded.sample_rate, 48000);
        assert_eq!(decoded.frame_size, 960);
    }

    #[test]
    fn audio_format_enum() {
        assert_eq!(AudioFormat::from_u8(0), Some(AudioFormat::Pcm));
        assert_eq!(AudioFormat::from_u8(1), Some(AudioFormat::Opus));
        assert_eq!(AudioFormat::from_u8(2), Some(AudioFormat::Aac));
        assert_eq!(AudioFormat::from_u8(3), None);
        assert_eq!(AudioFormat::Opus.name(), "Opus");
    }

    // ─── v0.5 Tests ───

    #[test]
    fn auth_payload_roundtrip() {
        let ap = AuthPayload {
            phase: 1,
            nonce: [1, 2, 3, 4, 5, 6, 7, 8],
            token: b"secret-token".to_vec(),
        };
        let encoded = ap.encode();
        let decoded = AuthPayload::decode(&encoded).unwrap();
        assert_eq!(decoded, ap);
    }

    #[test]
    fn xor_scramble_roundtrip() {
        let data = b"hello world";
        let key = b"mykey";
        let scrambled = scramble_payload(data, key);
        assert_ne!(&scrambled, data);
        let restored = descramble_payload(&scrambled, key);
        assert_eq!(&restored, data);
    }

    #[test]
    fn recording_metadata_roundtrip() {
        let meta = RecordingMetadata {
            version: 1,
            created_at: 1710000000,
            duration_ms: 5000,
            frame_count: 150,
            width: 1920,
            height: 1080,
        };
        let encoded = meta.encode();
        let decoded = RecordingMetadata::decode(&encoded).unwrap();
        assert_eq!(decoded, meta);
    }

    #[test]
    fn auth_frame_builders() {
        let nonce = [1u8; 8];
        let challenge = auth_challenge_frame(1, &nonce);
        assert_eq!(challenge[0], FrameType::Auth as u8);
        let payload = AuthPayload::decode(&challenge[HEADER_SIZE..]).unwrap();
        assert_eq!(payload.phase, 0);
        assert_eq!(payload.nonce, nonce);

        let response = auth_response_frame(2, &nonce, b"token123");
        let payload = AuthPayload::decode(&response[HEADER_SIZE..]).unwrap();
        assert_eq!(payload.phase, 1);
        assert_eq!(payload.token, b"token123");
    }

    // ─── v0.10 Tests ───

    #[test]
    fn mux_demux_roundtrip() {
        let frame = encode_frame(FrameType::Pixels, 1, 0, 320, 240, 0, &[0xAA; 10]);
        let muxed = mux_frame(5, &frame);
        assert_eq!(extract_stream_id(muxed[1]), 5);
        let (stream_id, demuxed) = demux_frame(&muxed);
        assert_eq!(stream_id, 5);
        assert_eq!(demuxed[1] & STREAM_ID_MASK, 0); // stream cleared
    }

    #[test]
    fn frame_priority_levels() {
        assert_eq!(prioritize_frame(FrameType::Keyframe as u8), FramePriority::Critical);
        assert_eq!(prioritize_frame(FrameType::Auth as u8), FramePriority::Critical);
        assert_eq!(prioritize_frame(FrameType::Pixels as u8), FramePriority::High);
        assert_eq!(prioritize_frame(FrameType::SignalDiff as u8), FramePriority::Normal);
        assert_eq!(prioritize_frame(FrameType::Ping as u8), FramePriority::Low);
    }

    #[test]
    fn bandwidth_allocation() {
        let streams = vec![
            (0, FramePriority::High),
            (1, FramePriority::Low),
        ];
        let allocs = allocate_bandwidth(5000, &streams);
        assert_eq!(allocs.len(), 2);
        // High (weight 4) should get 4x more than Low (weight 1)
        assert!(allocs[0].byte_limit > allocs[1].byte_limit);
        assert_eq!(allocs[0].byte_limit + allocs[1].byte_limit, 5000);
    }

    #[test]
    fn frame_drop_policy() {
        assert!(!should_drop(FramePriority::Low, 0));      // No congestion: no drops
        assert!(should_drop(FramePriority::Low, 1));       // Mild: drop low
        assert!(!should_drop(FramePriority::High, 1));     // Mild: keep high
        assert!(should_drop(FramePriority::Normal, 2));    // Moderate: drop normal
        assert!(!should_drop(FramePriority::Critical, 3)); // Severe: keep critical
        assert!(should_drop(FramePriority::High, 3));      // Severe: drop high
    }

    #[test]
    fn stream_id_embed_extract() {
        let flags = FLAG_KEYFRAME; // 0x01
        let combined = embed_stream_id(flags, 12);
        assert_eq!(extract_stream_id(combined), 12);
        assert_eq!(combined & FLAGS_MASK, FLAG_KEYFRAME);
    }

    // ─── v0.11 Tests ───

    #[test]
    fn frame_stats_accumulator() {
        let mut stats = FrameStats::new();
        stats.record(1000, false);
        stats.record(2000, false);
        stats.record(500, true); // dropped
        assert_eq!(stats.frame_count, 2);
        assert_eq!(stats.dropped_count, 1);
        assert_eq!(stats.total_bytes, 3000);
        assert!((stats.avg_size() - 1500.0).abs() < 0.1);
        assert_eq!(stats.min_size, 1000);
        assert_eq!(stats.max_size, 2000);
        stats.reset();
        assert_eq!(stats.frame_count, 0);
    }

    #[test]
    fn jitter_buffer_ordering() {
        let mut jb = JitterBuffer::new(4);
        jb.push(2, vec![2]);
        jb.push(0, vec![0]);
        jb.push(1, vec![1]);
        let f0 = jb.pop().unwrap();
        assert_eq!(f0, vec![0]);
        let f1 = jb.pop().unwrap();
        assert_eq!(f1, vec![1]);
        let f2 = jb.pop().unwrap();
        assert_eq!(f2, vec![2]);
    }

    #[test]
    fn latency_tracker_stats() {
        let mut lt = LatencyTracker::new(100);
        for i in 0..10 {
            lt.record_rtt(10.0 + i as f64);
        }
        assert_eq!(lt.sample_count(), 10);
        assert!((lt.avg_rtt() - 14.5).abs() < 0.1);
        assert!(lt.p95_rtt() >= 18.0);
        assert!(lt.jitter() > 0.0);
    }

    #[test]
    fn stream_health_computation() {
        // Good conditions
        let good = compute_health(1000, 50.0, 100_000.0, 0.01);
        assert!(good > 0.7, "Good health: {}", good);
        // Bad conditions
        let bad = compute_health(100, 500.0, 10_000.0, 0.3);
        assert!(bad < 0.5, "Bad health: {}", bad);
    }

    // ─── v0.12 Tests ───

    #[test]
    fn state_snapshot_roundtrip() {
        let state = b"hello world state";
        let encoded = encode_state(42, state);
        let (version, decoded) = decode_state(&encoded).unwrap();
        assert_eq!(version, 42);
        assert_eq!(decoded, state);
    }

    #[test]
    fn gap_detector_basic() {
        let mut gd = GapDetector::new();
        assert_eq!(gd.feed(0), GapResult::Ok);
        assert_eq!(gd.feed(1), GapResult::Ok);
        assert_eq!(gd.feed(3), GapResult::Gap { expected: 2, got: 3 });
        assert_eq!(gd.feed(3), GapResult::Duplicate);
        assert_eq!(gd.feed(4), GapResult::Ok);
    }

    #[test]
    fn handshake_roundtrip() {
        let caps = vec![1, 2, 3];
        let hello = handshake_hello(12, &caps);
        let (version, parsed_caps) = parse_handshake_hello(&hello).unwrap();
        assert_eq!(version, 12);
        assert_eq!(parsed_caps, caps);
        let accept = handshake_accept(12);
        assert_eq!(accept[0], 0x41);
        assert_eq!(u16::from_le_bytes([accept[1], accept[2]]), 12);
    }

    #[test]
    fn state_decode_too_short() {
        assert!(decode_state(&[0, 1, 2]).is_none());
    }

    // ─── v0.13 Tests ───

    #[test]
    fn replay_buffer_circular() {
        let mut rb = ReplayBuffer::new(3);
        rb.push(vec![1]);
        rb.push(vec![2]);
        rb.push(vec![3]);
        assert_eq!(rb.len(), 3);
        rb.push(vec![4]); // overwrites oldest
        assert_eq!(rb.len(), 3);
        assert_eq!(rb.total_pushed(), 4);
        let snap = rb.snapshot();
        assert_eq!(snap.len(), 3);
    }

    #[test]
    fn header_serde_roundtrip() {
        let header = serialize_header(0x02, 0x01, 42, 1000, 800, 600, 256);
        let (ft, fl, seq, ts, w, h, len) = deserialize_header(&header).unwrap();
        assert_eq!(ft, 0x02);
        assert_eq!(fl, 0x01);
        assert_eq!(seq, 42);
        assert_eq!(ts, 1000);
        assert_eq!(w, 800);
        assert_eq!(h, 600);
        assert_eq!(len, 256);
    }

    #[test]
    fn codec_registry_basic() {
        let mut reg = CodecRegistry::new();
        reg.register("jpeg", 1, 1);
        reg.register("webp", 2, 2);
        assert_eq!(reg.count(), 2);
        assert_eq!(reg.list(), vec!["jpeg", "webp"]);
        let codec = reg.get("jpeg").unwrap();
        assert_eq!(codec.encode_id, 1);
    }

    #[test]
    fn replay_buffer_get_range() {
        let mut rb = ReplayBuffer::new(10);
        for i in 0..5 {
            rb.push(vec![i]);
        }
        let range = rb.get_range(1, 3);
        assert_eq!(range.len(), 2);
        assert_eq!(range[0], vec![1]);
        assert_eq!(range[1], vec![2]);
    }

    // ─── v0.14 Tests ───

    #[test]
    fn rle_compress_decompress() {
        let mut comp = FrameCompressor::new();
        let data = vec![0, 0, 0, 1, 1, 2, 2, 2, 2];
        let compressed = comp.compress(&data);
        let decompressed = comp.decompress(&compressed);
        assert_eq!(decompressed, data);
        assert!(comp.compression_ratio() < 1.0, "Should compress: {}", comp.compression_ratio());
    }

    #[test]
    fn multi_client_sync() {
        let mut sync = MultiClientSync::new();
        sync.register_client("a");
        sync.register_client("b");
        sync.update_seq("a", 10);
        sync.update_seq("b", 10);
        sync.update_ack("a", 8);
        sync.update_ack("b", 3);
        assert_eq!(sync.slowest_client(), "b");
        assert_eq!(sync.get_drift("b"), 7);
    }

    #[test]
    fn bandwidth_throttle() {
        let mut throttle = BandwidthThrottle::new(1000);
        assert!(throttle.should_send(500, 0));
        assert!(throttle.should_send(400, 0));
        assert!(!throttle.should_send(200, 0)); // over limit
        assert!(throttle.should_send(200, 1000)); // new window
    }

    #[test]
    fn rle_empty_data() {
        let mut comp = FrameCompressor::new();
        let compressed = comp.compress(&[]);
        assert!(compressed.is_empty());
        let decompressed = comp.decompress(&compressed);
        assert!(decompressed.is_empty());
    }

    // ─── v0.15 Tests ───

    #[test]
    fn adaptive_bitrate_tiers() {
        let mut ab = AdaptiveBitrate::new();
        // Good connection
        for _ in 0..10 { ab.feed_rtt(10.0); ab.feed_loss(0.0); }
        assert_eq!(ab.recommended_tier(), 4);
        // Bad connection
        for _ in 0..30 { ab.feed_rtt(250.0); ab.feed_loss(15.0); }
        assert_eq!(ab.recommended_tier(), 0);
    }

    #[test]
    fn metrics_snapshot_json() {
        let snap = MetricsSnapshot::capture(30.0, 500_000, 3, 2.5, 0.95);
        let json = snap.to_json();
        assert!(json.contains("30.0"), "Has fps: {}", json);
        assert!(json.contains("500000"), "Has bitrate: {}", json);
    }

    #[test]
    fn frame_pipeline_stages() {
        let mut pipe = FramePipeline::new();
        pipe.add_stage("encode");
        pipe.add_stage("compress");
        pipe.add_stage("encrypt");
        assert_eq!(pipe.stage_count(), 3);
        let names = pipe.stage_names();
        assert_eq!(names, vec!["encode", "compress", "encrypt"]);
        let data = vec![1, 2, 3];
        let out = pipe.process(data.clone());
        assert_eq!(out, data); // identity pass-through
    }

    // ─── v0.16 Tests ───

    #[test]
    fn xor_encrypt_decrypt() {
        let data = b"hello world";
        let key = b"secret";
        let encrypted = FrameEncryptor::encrypt(data, key);
        assert_ne!(encrypted, data.to_vec());
        let decrypted = FrameEncryptor::decrypt(&encrypted, key);
        assert_eq!(decrypted, data.to_vec());
    }

    #[test]
    fn stream_migration_handoff() {
        let mut src = StreamMigration::new();
        let state = src.prepare_handoff(42, 5, 30.0);
        let mut dst = StreamMigration::new();
        let (seq, clients, fps) = dst.accept_handoff(&state).unwrap();
        assert_eq!(seq, 42);
        assert_eq!(clients, 5);
        assert!((fps - 30.0).abs() < 0.01);
    }

    #[test]
    fn frame_dedup() {
        let mut dedup = FrameDedup::new(100);
        let frame = vec![1, 2, 3, 4, 5];
        assert!(!dedup.is_duplicate(&frame));
        assert!(dedup.is_duplicate(&frame));
        assert_eq!(dedup.dedup_savings(), 5);
    }

    #[test]
    fn priority_queue_ordering() {
        let mut pq: PriorityQueue<&str> = PriorityQueue::new();
        pq.enqueue("low", 1);
        pq.enqueue("high", 10);
        pq.enqueue("mid", 5);
        assert_eq!(pq.dequeue(), Some("high"));
        assert_eq!(pq.dequeue(), Some("mid"));
        assert_eq!(pq.dequeue(), Some("low"));
    }
}

