//! # Rapira
//!
//! A fast, safe binary serialization library — an alternative to `bincode`,
//! `borsh` and `speedy`, tuned for compactness and speed with built-in safety
//! checks. Supports `no_std` (disable default features, enable `alloc` as needed).
//!
//! ## Quick start
//!
//! ```
//! use rapira::{Rapira, serialize, deserialize};
//!
//! #[derive(Rapira, PartialEq, Debug)]
//! struct Point {
//!     x: u32,
//!     y: u32,
//! }
//!
//! let bytes = serialize(&Point { x: 1, y: 2 });
//! let decoded: Point = deserialize(&bytes).unwrap();
//! assert_eq!(decoded, Point { x: 1, y: 2 });
//! ```
//!
//! ## The [`Rapira`](trait@Rapira) trait
//!
//! The core trait. Key members:
//! - `size(&self) -> usize` — serialized byte length.
//! - `check_bytes(slice) -> Result<()>` — validate **untrusted** bytes (UTF-8,
//!   collection bounds, `NonZero`, float finiteness) before decoding.
//! - `from_slice(slice) -> Result<Self>` — safe decode (checks capacity limits).
//! - `convert_to_bytes(&self, slice, cursor)` — encode into a preallocated buffer.
//! - `STATIC_SIZE: Option<usize>` — `Some(n)` for fixed-size types, `None` for dynamic.
//!
//! ### Deserialization safety tiers
//!
//! | Method | Checks | Use for |
//! |---|---|---|
//! | `from_slice` | capacity limits | untrusted / external data |
//! | `from_slice_unchecked` | cursor bounds only (skips UTF-8 / float / `NonZero`) | trusted data |
//! | `from_slice_unsafe` | none | only **after** `check_bytes` |
//!
//! ## Top-level API
//!
//! [`serialize`] / [`deserialize`], [`check_bytes`], [`extend_vec`], and the
//! context-aware [`serialize_ctx`] / [`deserialize_ctx`] (which carry
//! [`RapiraFlags`] — e.g. the id-encryption flag used at a wire boundary).
//!
//! ## Wire format
//!
//! - Little-endian integers; `usize` as `u32`, `isize` as `i64`.
//! - Collection / string lengths as `u32` ([`LEN_SIZE`] = 4 bytes).
//! - `f32` / `f64` must be finite (NaN / Inf → error).
//! - `Option<T>`: 1-byte discriminant + payload. Enums: 1-byte tag + payload.
//! - `Duration`: seconds only (nanoseconds discarded).
//!
//! ## Derive macros
//!
//! `#[derive(Rapira)]` generates the trait impl. The crate also provides
//! `#[derive(FromU8)]` and `#[derive(PrimitiveFromEnum)]` for enum ↔ byte
//! conversion (see [`FromU8`](trait@FromU8)).
//!
//! ### `#[derive(Rapira)]` attributes
//!
//! **Container (struct / enum):**
//!
//! | Attribute | Effect |
//! |---|---|
//! | `#[rapira(static_size = expr)]` | override the computed `STATIC_SIZE` |
//! | `#[rapira(min_size = expr)]` | override the computed minimum size |
//! | `#[rapira(version = N)]` | (struct) enable versioned deserialization; pairs with field `since` (see [`from_slice_versioned`](Rapira::from_slice_versioned)) |
//! | `#[rapira(peer)]` | (feature `schema`) generate a [`PeerRead`] impl for forward-compatible decoding; the type must also implement `GetType` |
//! | `#[rapira(debug)]` | print the generated code at compile time |
//! | `#[primitive(PrimEnum)]` | (complex enum) take discriminants from a sibling unit enum |
//!
//! **Field:**
//!
//! | Attribute | Effect |
//! |---|---|
//! | `#[rapira(with = path)]` | (de)serialize via module fns (e.g. [`byte_rapira`], [`bytes_rapira`], [`str_rapira`], `rapira::zero`) instead of the field's `Rapira` impl |
//! | `#[rapira(skip)]` | skip the field; `Default::default()` on decode |
//! | `#[rapira(since = N)]` | field added in schema version `N` (requires `version` on the struct; the field type must implement `Default`) |
//!
//! **Enum variant:**
//!
//! | Attribute | Effect |
//! |---|---|
//! | `#[idx = N]` | set the variant's wire tag explicitly |
//! | `#[rapira(unknown)]` | (feature `schema`, on a `#[rapira(peer)]` enum) one **unit** variant used as the fallback for variants an older reader doesn't know; not allowed on `#[primitive]` enums |
//!
//! ```rust,ignore
//! #[derive(Rapira)]
//! #[rapira(version = 2)]
//! struct User {
//!     id: u64,
//!     #[rapira(with = rapira::byte_rapira)]
//!     kind: u8,                   // encoded via byte_rapira, not the u8 impl
//!     #[rapira(since = 2)]
//!     nickname: Option<String>,   // added in v2; v1 data decodes it as None
//!     #[rapira(skip)]
//!     cached: u32,                // never on the wire; Default on decode
//! }
//! ```
//!
//! ## Schema-aware forward-compat (feature `schema`, default)
//!
//! [`PeerRead`] decodes a value against a sender-provided runtime schema
//! (`armour_typ::SchemaTyp`) so additive changes (new fields, new enum variants)
//! don't break older readers. Scalars, ids and collections implement it already;
//! a domain `struct` / `enum` opts in with `#[rapira(peer)]`, and an enum may mark
//! one unit variant `#[rapira(unknown)]` as the unknown-variant fallback. Entry
//! point: [`deserialize_with_peer`] / [`deserialize_with_peer_ctx`]. Dispatch on
//! the schema hash (`armour_typ::schema_hash`): equal hashes → plain
//! [`deserialize`]; differing → pass the peer schema. See `docs/rapira.md` for the
//! full guide (including which types get `PeerRead` automatically vs by opt-in).
//!
//! ## Feature flags
//!
//! Default: `std`, `zerocopy`, `serde` / `serde_json`, `arrayvec`, `bytes`,
//! `time`, `schema`. Optional: `smallvec`, `indexmap`, `rust_decimal`,
//! `compact_str`, `smol_str`, `ecow`, `uuid`, `bytemuck`, `inline-array`,
//! `fjall`, `byteview`, `postcard`, `solana`, `rmp`.

// #![feature(trace_macros)]
// #![feature(log_syntax)]
#![cfg_attr(not(feature = "std"), no_std)]

mod allocated;
pub mod error;
mod from_u8;
pub mod funcs;
mod implements;
#[cfg(feature = "std")]
mod macros;
pub mod max_cap;
mod primitive;

#[cfg(feature = "schema")]
pub mod schema;
#[cfg(feature = "schema")]
mod schema_wire;

pub use error::{RapiraError, Result};
pub use from_u8::{EnumFromU8Error, FromU8};
#[cfg(feature = "postcard")]
pub use implements::postcard;
#[cfg(feature = "zerocopy")]
pub use implements::zero;
pub use primitive::{byte_rapira, bytes_rapira, str_rapira};

#[cfg(feature = "alloc")]
extern crate alloc;

pub use funcs::{
    check_bytes, deser_unchecked, deser_unsafe, deserialize, deserialize_ctx,
    deserialize_versioned, size, size_ctx,
};
#[cfg(feature = "schema")]
pub use funcs::{deserialize_with_peer, deserialize_with_peer_ctx};
#[cfg(feature = "alloc")]
pub use funcs::{extend_vec, serialize, serialize_ctx};
pub use rapira_derive::{FromU8, PrimitiveFromEnum, Rapira};

/// Bitflags for context-aware serialization.
/// Bits 0–7 are reserved for rapira. External crates use bits 8+.
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct RapiraFlags(pub u64);

impl RapiraFlags {
    pub const NONE: Self = Self(0);

    #[inline]
    pub const fn new(flags: u64) -> Self {
        Self(flags)
    }

    #[inline]
    pub const fn has(self, flag: u64) -> bool {
        self.0 & flag != 0
    }

    #[inline]
    pub const fn with(self, flag: u64) -> Self {
        Self(self.0 | flag)
    }
}

pub trait Rapira {
    const STATIC_SIZE: Option<usize> = None;
    const MIN_SIZE: usize;

    /// size of bytes for serialize
    fn size(&self) -> usize;

    /// check bytes, collections len, check utf-8, NonZero, f32 and others...
    fn check_bytes(slice: &mut &[u8]) -> Result<()>;

    /// this is safe, but not check collections capacity!
    /// recommend use only for safe data (example: from DB), not external data.
    fn from_slice(slice: &mut &[u8]) -> Result<Self>
    where
        Self: Sized;

    #[inline]
    fn debug_from_slice(slice: &mut &[u8]) -> Result<Self>
    where
        Self: Sized + std::fmt::Debug,
    {
        let len = slice.len();
        Self::from_slice(slice)
            .inspect(|item| {
                println!("len: {len}, item: {item:?}");
            })
            .inspect_err(|err| {
                println!("len: {len}, err: {err:?}");
            })
    }

    /// # Safety
    ///
    /// this mean not unsafe, but unchecked
    /// utf-8 strings, NonZero, float numbers not check
    #[inline]
    unsafe fn from_slice_unchecked(slice: &mut &[u8]) -> Result<Self>
    where
        Self: Sized,
    {
        Self::from_slice(slice)
    }

    /// # Safety
    ///
    /// This is unsafe, but maybe safe after check_bytes fn
    unsafe fn from_slice_unsafe(slice: &mut &[u8]) -> Result<Self>
    where
        Self: Sized,
    {
        Self::from_slice(slice)
    }

    /// Deserialize with schema version awareness.
    ///
    /// Enables backward-compatible deserialization: new code can read old data
    /// that is missing fields added in later versions. The version number is
    /// stored **externally** (e.g. in DB metadata), not inside the serialized bytes.
    ///
    /// Default implementation delegates to [`from_slice`](Rapira::from_slice).
    /// The derive macro generates an override when `#[rapira(version = N)]` is
    /// placed on a struct — fields annotated with `#[rapira(since = M)]` are
    /// only read from the slice when `version >= M`, otherwise
    /// [`Default::default()`] is used.
    ///
    /// # Derive usage
    ///
    /// ```rust,ignore
    /// #[derive(Rapira)]
    /// #[rapira(version = 2)]
    /// struct User {
    ///     name: String,           // present since v1 (no attribute needed)
    ///     age: u32,               // present since v1
    ///     #[rapira(since = 2)]
    ///     email: Option<String>,  // added in v2, defaults to None for v1 data
    /// }
    /// ```
    ///
    /// # Reading old data
    ///
    /// ```rust,ignore
    /// // version comes from DB metadata, not from the bytes themselves
    /// let user: User = rapira::deserialize_versioned(&bytes, schema_version)?;
    /// ```
    ///
    /// # Rules
    ///
    /// - Serialization (`convert_to_bytes`, `size`) always writes **all** fields
    ///   (current version). Only deserialization is affected.
    /// - `from_slice` always reads all fields regardless of version (use for
    ///   current-version data).
    /// - Fields with `#[rapira(since = M)]` must implement [`Default`].
    /// - `since = 0` is invalid (versions start at 1).
    /// - `since` value must not exceed the struct's `version`.
    /// - `#[rapira(since)]` requires `#[rapira(version)]` on the struct.
    /// - `#[rapira(since)]` and `#[rapira(skip)]` cannot be combined.
    /// - Structs without `#[rapira(version)]` use the default impl (delegates
    ///   to `from_slice`), so existing code is unaffected.
    /// - Version is propagated through collections (`Vec<T>`, `Option<T>`,
    ///   `Box<T>`, `BTreeMap`, tuples, arrays, `SmallVec`, `ArrayVec`).
    #[inline]
    fn from_slice_versioned(slice: &mut &[u8], _version: u8) -> Result<Self>
    where
        Self: Sized,
    {
        Self::from_slice(slice)
    }

    #[inline]
    fn try_convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) -> Result<()> {
        self.convert_to_bytes(slice, cursor);
        Ok(())
    }

    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize);

    /// Context-aware serialization. Default: delegates to `convert_to_bytes`.
    #[inline]
    fn convert_to_bytes_ctx(&self, slice: &mut [u8], cursor: &mut usize, _flags: RapiraFlags) {
        self.convert_to_bytes(slice, cursor)
    }

    /// Context-aware deserialization. Default: delegates to `from_slice`.
    #[inline]
    fn from_slice_ctx(slice: &mut &[u8], _flags: RapiraFlags) -> Result<Self>
    where
        Self: Sized,
    {
        Self::from_slice(slice)
    }

    /// Context-aware size calculation. Default: delegates to `size`.
    #[inline]
    fn size_ctx(&self, _flags: RapiraFlags) -> usize {
        self.size()
    }
}

#[cfg(feature = "schema")]
/// Schema-aware deserialization for types that can decode bytes using a
/// peer-provided runtime schema.
///
/// Implement this alongside [`Rapira`] for concrete types that support
/// forward-compatible decoding against [`armour_typ::SchemaTyp`]. The peer
/// schema describes the sender's wire layout.
pub trait PeerRead: Rapira + armour_typ::GetType {
    /// Deserializes `Self` from `slice` using the peer schema and explicit
    /// context flags.
    fn from_slice_with_peer_ctx(
        slice: &mut &[u8],
        peer: &armour_typ::SchemaTyp,
        flags: RapiraFlags,
    ) -> Result<Self>
    where
        Self: Sized;

    /// Deserializes `Self` from `slice` using the peer schema and
    /// [`RapiraFlags::NONE`].
    #[inline]
    fn from_slice_with_peer(slice: &mut &[u8], peer: &armour_typ::SchemaTyp) -> Result<Self>
    where
        Self: Sized,
    {
        Self::from_slice_with_peer_ctx(slice, peer, RapiraFlags::NONE)
    }
}

pub const LEN_SIZE: usize = 4;

#[inline]
pub fn push(slice: &mut [u8], cursor: &mut usize, item: u8) {
    let s = slice.get_mut(*cursor).unwrap();
    *s = item;
    *cursor += 1;
}

#[inline]
pub fn try_push(slice: &mut [u8], cursor: &mut usize, item: u8) -> Result<()> {
    let s = slice.get_mut(*cursor).ok_or(RapiraError::SliceLen)?;
    *s = item;
    *cursor += 1;

    Ok(())
}

#[inline]
pub fn extend(slice: &mut [u8], cursor: &mut usize, items: &[u8]) {
    let end = *cursor + items.len();
    let s = slice.get_mut(*cursor..end).unwrap();
    s.copy_from_slice(items);
    *cursor = end;
}

#[inline]
pub fn try_extend(slice: &mut [u8], cursor: &mut usize, items: &[u8]) -> Result<()> {
    let end = *cursor + items.len();
    let s = slice.get_mut(*cursor..end).ok_or(RapiraError::SliceLen)?;
    s.copy_from_slice(items);
    *cursor = end;

    Ok(())
}

pub const fn static_size<const N: usize>(arr: [Option<usize>; N]) -> Option<usize> {
    let mut i = 0;
    let mut size = 0;
    while i < arr.len() {
        let item = arr[i];
        match item {
            Some(s) => {
                size += s;
            }
            None => {
                return None;
            }
        }
        i += 1;
    }
    Some(size)
}

pub const fn enum_size<const N: usize>(arr: [Option<usize>; N]) -> Option<usize> {
    let mut i = 0;
    let mut size = 0;
    let mut is_init = false;
    while i < arr.len() {
        let item = arr[i];
        match item {
            Some(s) => {
                if !is_init {
                    size = s;
                    is_init = true;
                } else if s != size {
                    return None;
                }
            }
            None => {
                return None;
            }
        }
        i += 1;
    }
    Some(size + 1)
}

pub const fn min_size(arr: &'static [usize]) -> usize {
    let mut i = 0;
    let mut size = 0;
    while i < arr.len() {
        let item = arr[i];
        size += item;
        i += 1;
    }
    size
}

pub const fn enum_min_size(arr: &'static [usize]) -> usize {
    let mut i = 0;
    let mut size = 0;
    let mut is_init = false;
    while i < arr.len() {
        let item = arr[i];
        if !is_init {
            size = item;
            is_init = true;
        } else if size < item {
            size = item;
        }
        i += 1;
    }
    size + 1
}
