use core::{hash::Hash, str::FromStr};
use std::{fmt::Debug, marker::PhantomData};

use bytemuck::{Pod, TransparentWrapper, Zeroable};
use rapira::{Rapira, RapiraError};
use rend::u64_be;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};

use crate::{
    GetType, IdStr, ScalarTyp, Typ,
    enc::IdHasher,
    error::ArmourError,
    key_part::{KeyPart, SEQ64_BITS},
    key_type::KeyType,
    num_ops::g8bits,
};

type Result<T, E = ArmourError> = core::result::Result<T, E>;

/// - 64bit sequence id
/// - Blowfish for encryption (64bit block)
/// - ZBASE32 for encoding into 13 length string
#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[repr(transparent)]
#[transparent(u64_be)]
pub struct Id64<T>(pub u64_be, PhantomData<T>);

unsafe impl<T> Zeroable for Id64<T> {}
unsafe impl<T: 'static> Pod for Id64<T> {}

impl<T> Clone for Id64<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for Id64<T> {}

impl<T> PartialOrd for Id64<T> {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> Ord for Id64<T> {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.0.cmp(&other.0)
    }
}

impl<T> PartialEq for Id64<T> {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl<T> Eq for Id64<T> {}

impl<T> PartialEq<[u8; 8]> for Id64<T> {
    fn eq(&self, other: &[u8; 8]) -> bool {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes == other
    }
}

impl<T> PartialEq<Id64<T>> for [u8; 8] {
    fn eq(&self, other: &Id64<T>) -> bool {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(other);
        bytes == self
    }
}

impl<T> PartialEq<Id64<T>> for Option<Id64<T>> {
    fn eq(&self, other: &Id64<T>) -> bool {
        match self {
            Some(id) => id == other,
            None => false,
        }
    }
}

impl<T> PartialEq<Option<Id64<T>>> for Id64<T> {
    fn eq(&self, other: &Option<Id64<T>>) -> bool {
        match other {
            Some(id) => id == self,
            None => false,
        }
    }
}

impl<T> AsRef<Self> for Id64<T> {
    fn as_ref(&self) -> &Self {
        self
    }
}

impl<T> AsRef<[u8; 8]> for Id64<T> {
    fn as_ref(&self) -> &[u8; 8] {
        zerocopy::transmute_ref!(self)
    }
}

impl<T> Id64<T> {
    /// get LE u64
    #[inline]
    pub fn get(self) -> u64 {
        self.0.to_native()
    }

    /// Create a new ID64 from a native (LE) u64.
    #[inline]
    pub fn new(id: u64) -> Self {
        Id64(u64_be::from_native(id), PhantomData)
    }

    #[inline]
    pub fn to_le_bytes(self) -> [u8; 8] {
        self.0.to_native().to_le_bytes()
    }

    #[inline]
    pub fn to_be_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    #[inline]
    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }

    /// to BE bytes
    #[inline]
    pub fn to_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    /// from BE bytes
    #[inline]
    pub fn from_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }

    /// to BE u64
    #[inline]
    pub fn to_u64(self) -> u64 {
        zerocopy::transmute!(self)
    }

    /// from BE u64
    #[inline]
    pub fn from_u64(bytes: u64) -> Self {
        zerocopy::transmute!(bytes)
    }

    /// get native (LE) u32
    #[inline]
    pub fn get_u32(self) -> Option<u32> {
        let native = self.0.to_native();
        if native > u32::MAX as u64 {
            None
        } else {
            Some(native as u32)
        }
    }

    #[inline]
    pub fn increment(mut self) -> Self {
        self.0 += 1;
        self
    }

    /// group_id for this Id64
    #[inline]
    pub fn group_id(&self) -> u32 {
        let id = self.get();
        g8bits(id, SEQ64_BITS)
    }
}

impl<T: IdHasher> Id64<T> {
    #[inline]
    pub fn deser(s: &str) -> Result<Id64<T>> {
        let v = T::deser(s)?;
        Ok(zerocopy::transmute!(v))
    }

    #[inline]
    pub fn ser(self) -> IdStr {
        let u: u64 = zerocopy::transmute!(self);
        T::ser(u)
    }
}

impl<T: IdHasher> TryFrom<&str> for Id64<T> {
    type Error = ArmourError;
    fn try_from(val: &str) -> Result<Id64<T>> {
        Self::deser(val)
    }
}

impl TryFrom<&str> for Id64<()> {
    type Error = ArmourError;
    fn try_from(val: &str) -> Result<Self> {
        let val = u64::from_str(val)?;
        Ok(Self(u64_be::from_native(val), PhantomData))
    }
}

impl<T: IdHasher> FromStr for Id64<T> {
    type Err = ArmourError;
    fn from_str(s: &str) -> Result<Self> {
        Self::deser(s)
    }
}

impl FromStr for Id64<()> {
    type Err = ArmourError;
    fn from_str(s: &str) -> Result<Self> {
        let val = u64::from_str(s)?;
        Ok(Self(u64_be::from_native(val), PhantomData))
    }
}

impl<T> From<Id64<T>> for u64 {
    #[inline(always)]
    fn from(id: Id64<T>) -> Self {
        id.get()
    }
}

impl<T: IdHasher> std::fmt::Display for Id64<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.ser())
    }
}

impl<T> Debug for Id64<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("ID").field(&self.0).finish()
    }
}

impl<T> Hash for Id64<T> {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

#[cfg(feature = "std")]
impl<T: IdHasher> Serialize for Id64<T> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        let s = self.ser();
        serializer.serialize_str(&s)
    }
}

#[cfg(feature = "std")]
impl<'de, T: IdHasher> Deserialize<'de> for Id64<T> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;
        let s: &str = Deserialize::deserialize(deserializer)?;
        let a = Id64::<T>::deser(s).map_err(|_| D::Error::custom("id value error"))?;
        Ok(a)
    }
}

#[cfg(feature = "std")]
impl Serialize for Id64<()> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        let s = self.get();
        serializer.serialize_u64(s)
    }
}

#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for Id64<()> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: u64 = Deserialize::deserialize(deserializer)?;
        Ok(Id64(u64_be::from_native(s), PhantomData))
    }
}

impl<T: IdHasher> Rapira for Id64<T> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
    where
        Self: Sized,
    {
        let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;

        if bytes == [0u8; 8] {
            return Err(RapiraError::NonZero);
        }

        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        let id = Self::from_bytes(bytes);
        Ok(id)
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }

    #[inline]
    fn convert_to_bytes_ctx(
        &self,
        slice: &mut [u8],
        cursor: &mut usize,
        flags: rapira::RapiraFlags,
    ) {
        if flags.has(crate::ID_ENC_FLAG) {
            let u: u64 = zerocopy::transmute!(*self);
            let id = T::encrypt(u);
            id.convert_to_bytes(slice, cursor);
        } else {
            self.convert_to_bytes(slice, cursor)
        }
    }

    #[inline]
    fn from_slice_ctx(slice: &mut &[u8], flags: rapira::RapiraFlags) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        if flags.has(crate::ID_ENC_FLAG) {
            let id = u64::from_slice(slice)?;
            let id = T::decrypt(id);
            Ok(zerocopy::transmute!(id))
        } else {
            Self::from_slice(slice)
        }
    }
}

impl Rapira for Id64<()> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
    where
        Self: Sized,
    {
        let bytes: &[u8] = slice.get(..8).ok_or(RapiraError::SliceLen)?;

        if bytes == [0u8; 8] {
            return Err(RapiraError::NonZero);
        }

        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        let id = Self::from_bytes(bytes);
        Ok(id)
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }
}

impl<T> GetType for Id64<T> {
    const TYPE: Typ = Typ::Scalar(ScalarTyp::Id64);
}

impl<T: armour_typ::IdBrand> armour_typ::GetRefs for Id64<T> {
    fn refs() -> Vec<armour_typ::FieldRef> {
        vec![armour_typ::FieldRef::leaf(T::BRAND)]
    }
    fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
        f(T::BRAND, self.get());
    }
}

impl<T: armour_typ::IdBrand> armour_typ::GetRefs for OptId64<T> {
    fn refs() -> Vec<armour_typ::FieldRef> {
        vec![armour_typ::FieldRef::leaf(T::BRAND)]
    }
    fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
        if let Some(id) = self.get() {
            f(T::BRAND, id.get());
        }
    }
}

impl<H> KeyPart for Id64<H> {
    const TY: KeyType = KeyType::U64;
    const PREFIX_BITS: u32 = SEQ64_BITS;
}

#[cfg(feature = "ts-rs")]
impl<T> ts_rs::TS for Id64<T> {
    type WithoutGenerics = Id64<()>;
    type OptionInnerType = Self;
    fn name(_: &ts_rs::Config) -> String {
        "Id64".to_owned()
    }
    fn decl_concrete(c: &ts_rs::Config) -> String {
        format!("type {} = {};", Self::name(c), Self::inline(c))
    }
    fn decl(c: &ts_rs::Config) -> String {
        let inline = <Id64<()> as ::ts_rs::TS>::inline(c);
        format!("type {} = {};", Self::name(c), inline)
    }
    fn inline(_: &ts_rs::Config) -> String {
        "string".to_owned()
    }
    fn inline_flattened(c: &ts_rs::Config) -> String {
        panic!("{} cannot be flattened", Self::name(c))
    }
    fn output_path() -> Option<std::path::PathBuf> {
        Some(std::path::PathBuf::from("id64.ts"))
    }
}

#[cfg(feature = "facet")]
unsafe impl<'facet, T: 'static> facet::Facet<'facet> for Id64<T> {
    const SHAPE: &'static facet::Shape = &const {
        const VTABLE: facet::VTableDirect = facet::vtable_direct!(Id64<()> =>
            Debug,
            Hash,
            PartialEq,
            PartialOrd,
            Ord,
        );

        facet::ShapeBuilder::for_sized::<Id64<T>>("Id64")
            .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
                repr: facet::Repr::transparent(),
                kind: facet::StructKind::TupleStruct,
                fields: &const {
                    [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
                },
            })))
            .inner(<u64 as facet::Facet>::SHAPE)
            .def(facet::Def::Scalar)
            .vtable_direct(&VTABLE)
            .eq()
            .copy()
            .send()
            .sync()
            .build()
    };
}

#[cfg(feature = "fake")]
impl<H, T> fake::Dummy<T> for Id64<H> {
    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
        use fake::Fake;

        let u = (0..100_000_000).fake::<u64>();
        Self::new(u)
    }
}

// ---------------------------------------------------------------------------
// OptId64
// ---------------------------------------------------------------------------

/// Optional 64-bit ID with the same 8-byte representation as [`Id64`].
///
/// Zero (`[0u8; 8]`) is interpreted as `None`. Use in zerocopy-compatible
/// structs where `Option<Id64>` would break alignment (16+ bytes).
///
/// ```ignore
/// #[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Copy, Clone)]
/// #[repr(C)]
/// struct Follower {
///     follow: Id64<H>,       // 8 bytes
///     follower: OptId64<H>,  // 8 bytes, zero = None
/// }
/// ```
#[derive(IntoBytes, FromBytes, Immutable, KnownLayout, TransparentWrapper)]
#[cfg_attr(
    feature = "rkyv",
    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "bitcode", derive(bitcode::Encode, bitcode::Decode))]
#[repr(transparent)]
#[transparent(u64_be)]
pub struct OptId64<T>(u64_be, PhantomData<T>);

unsafe impl<T> Zeroable for OptId64<T> {}
unsafe impl<T: 'static> Pod for OptId64<T> {}

impl<T> Clone for OptId64<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for OptId64<T> {}

impl<T> OptId64<T> {
    // SAFETY: OptId64 is Zeroable — all-zero bytes is valid and means None.
    pub const NONE: Self = unsafe { core::mem::zeroed() };

    #[inline]
    pub fn some(id: Id64<T>) -> Self {
        Self(id.0, PhantomData)
    }

    #[inline]
    pub fn get(self) -> Option<Id64<T>> {
        if self.0.to_native() == 0 {
            None
        } else {
            Some(Id64(self.0, PhantomData))
        }
    }

    #[inline]
    pub fn is_some(self) -> bool {
        self.0.to_native() != 0
    }

    #[inline]
    pub fn is_none(self) -> bool {
        self.0.to_native() == 0
    }

    /// get LE u64 (0 for None)
    #[inline]
    pub fn get_raw(self) -> u64 {
        self.0.to_native()
    }

    /// to BE bytes
    #[inline]
    pub fn to_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    /// from BE bytes
    #[inline]
    pub fn from_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }

    #[inline]
    pub fn to_be_bytes(self) -> [u8; 8] {
        zerocopy::transmute!(self)
    }

    #[inline]
    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
        zerocopy::transmute!(bytes)
    }
}

impl<T> Default for OptId64<T> {
    fn default() -> Self {
        Self::NONE
    }
}

impl<T> From<Id64<T>> for OptId64<T> {
    fn from(id: Id64<T>) -> Self {
        Self::some(id)
    }
}

impl<T> From<Option<Id64<T>>> for OptId64<T> {
    fn from(opt: Option<Id64<T>>) -> Self {
        match opt {
            Some(id) => Self::some(id),
            None => Self::NONE,
        }
    }
}

impl<T> From<OptId64<T>> for Option<Id64<T>> {
    fn from(opt: OptId64<T>) -> Self {
        opt.get()
    }
}

impl<T> PartialEq for OptId64<T> {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl<T> Eq for OptId64<T> {}

impl<T> PartialOrd for OptId64<T> {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> Ord for OptId64<T> {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        self.0.cmp(&other.0)
    }
}

impl<T> Hash for OptId64<T> {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl<T> PartialEq<Id64<T>> for OptId64<T> {
    fn eq(&self, other: &Id64<T>) -> bool {
        self.0 == other.0
    }
}

impl<T> PartialEq<OptId64<T>> for Id64<T> {
    fn eq(&self, other: &OptId64<T>) -> bool {
        self.0 == other.0
    }
}

impl<T> AsRef<[u8; 8]> for OptId64<T> {
    fn as_ref(&self) -> &[u8; 8] {
        zerocopy::transmute_ref!(self)
    }
}

impl<T> Debug for OptId64<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.0.to_native() {
            0 => write!(f, "OptID(None)"),
            _ => f.debug_tuple("OptID").field(&self.0).finish(),
        }
    }
}

impl<T: IdHasher> Rapira for OptId64<T> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
    where
        Self: Sized,
    {
        if slice.len() < 8 {
            return Err(RapiraError::SliceLen);
        }
        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        Ok(Self::from_bytes(bytes))
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }

    #[inline]
    fn convert_to_bytes_ctx(
        &self,
        slice: &mut [u8],
        cursor: &mut usize,
        flags: rapira::RapiraFlags,
    ) {
        if flags.has(crate::ID_ENC_FLAG) && self.is_some() {
            let u: u64 = zerocopy::transmute!(*self);
            let id = T::encrypt(u);
            id.convert_to_bytes(slice, cursor);
        } else {
            self.convert_to_bytes(slice, cursor)
        }
    }

    #[inline]
    fn from_slice_ctx(
        slice: &mut &[u8],
        flags: rapira::RapiraFlags,
    ) -> core::result::Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        if flags.has(crate::ID_ENC_FLAG) {
            let bytes = <[u8; 8]>::from_slice(slice)?;
            if bytes == [0u8; 8] {
                Ok(Self::NONE)
            } else {
                let id = u64::from_le_bytes(bytes);
                let id = T::decrypt(id);
                Ok(zerocopy::transmute!(id))
            }
        } else {
            Self::from_slice(slice)
        }
    }
}

impl Rapira for OptId64<()> {
    const STATIC_SIZE: Option<usize> = Some(8);
    const MIN_SIZE: usize = 8;

    #[inline]
    fn size(&self) -> usize {
        8
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> core::result::Result<(), rapira::RapiraError>
    where
        Self: Sized,
    {
        if slice.len() < 8 {
            return Err(RapiraError::SliceLen);
        }
        *slice = unsafe { slice.get_unchecked(8..) };
        Ok(())
    }

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> core::result::Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let bytes = <[u8; 8]>::from_slice(slice)?;
        Ok(Self::from_bytes(bytes))
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let bytes: &[u8; 8] = zerocopy::transmute_ref!(self);
        bytes.convert_to_bytes(slice, cursor);
    }
}

impl<T> GetType for OptId64<T> {
    const TYPE: Typ = Typ::Scalar(ScalarTyp::Id64);
}

#[cfg(feature = "std")]
impl<T: IdHasher> Serialize for OptId64<T> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        match self.get() {
            Some(id) => serializer.serialize_str(&id.ser()),
            None => serializer.serialize_none(),
        }
    }
}

#[cfg(feature = "std")]
impl<'de, T: IdHasher> Deserialize<'de> for OptId64<T> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        use serde::de::Error;
        let s: Option<&str> = Deserialize::deserialize(deserializer)?;
        match s {
            Some(s) => {
                let id = Id64::<T>::deser(s).map_err(|_| D::Error::custom("id value error"))?;
                Ok(Self::some(id))
            }
            None => Ok(Self::NONE),
        }
    }
}

#[cfg(feature = "std")]
impl Serialize for OptId64<()> {
    fn serialize<S: Serializer>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> {
        match self.get() {
            Some(id) => serializer.serialize_some(&id.get()),
            None => serializer.serialize_none(),
        }
    }
}

#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for OptId64<()> {
    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: Option<u64> = Deserialize::deserialize(deserializer)?;
        match s {
            Some(v) => Ok(Self::some(Id64::new(v))),
            None => Ok(Self::NONE),
        }
    }
}

#[cfg(feature = "ts-rs")]
impl<T> ts_rs::TS for OptId64<T> {
    type WithoutGenerics = OptId64<()>;
    type OptionInnerType = Self;
    fn name(_: &ts_rs::Config) -> String {
        "OptId64".to_owned()
    }
    fn decl_concrete(c: &ts_rs::Config) -> String {
        format!("type OptId64 = {};", Self::inline(c))
    }
    fn decl(c: &ts_rs::Config) -> String {
        let inline = <OptId64<()> as ::ts_rs::TS>::inline(c);
        format!("type OptId64 = {inline};")
    }
    fn inline(_: &ts_rs::Config) -> String {
        "string | null".to_owned()
    }
    fn inline_flattened(c: &ts_rs::Config) -> String {
        panic!("{} cannot be flattened", Self::name(c))
    }
    fn output_path() -> Option<std::path::PathBuf> {
        Some(std::path::PathBuf::from("opt_id64.ts"))
    }
}

#[cfg(feature = "facet")]
unsafe impl<'facet, T: 'static> facet::Facet<'facet> for OptId64<T> {
    const SHAPE: &'static facet::Shape = &const {
        const VTABLE: facet::VTableDirect = facet::vtable_direct!(OptId64<()> =>
            Debug,
            Hash,
            PartialEq,
            PartialOrd,
            Ord,
        );

        facet::ShapeBuilder::for_sized::<OptId64<T>>("OptId64")
            .ty(facet::Type::User(facet::UserType::Struct(facet::StructType {
                repr: facet::Repr::transparent(),
                kind: facet::StructKind::TupleStruct,
                fields: &const {
                    [facet::FieldBuilder::new("0", facet::shape_of::<u64>, 0).build()]
                },
            })))
            .inner(<u64 as facet::Facet>::SHAPE)
            .def(facet::Def::Scalar)
            .vtable_direct(&VTABLE)
            .eq()
            .copy()
            .send()
            .sync()
            .build()
    };
}

#[cfg(feature = "fake")]
impl<H, T> fake::Dummy<T> for OptId64<H> {
    fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &T, _: &mut R) -> Self {
        use fake::Fake;
        let u = (0..100_000_000).fake::<u64>();
        Self::some(Id64::new(u))
    }
}

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

    #[test]
    fn test_increment_basic() {
        let id = Id64::<()>::new(42);
        let incremented = id.increment();
        assert_eq!(incremented.get(), 43);
    }

    #[test]
    fn test_increment_multiple() {
        let id = Id64::<()>::new(100);
        let id = id.increment();
        assert_eq!(id.get(), 101);
        let id = id.increment();
        assert_eq!(id.get(), 102);
        let id = id.increment();
        assert_eq!(id.get(), 103);
    }

    #[test]
    fn test_increment_zero() {
        let id = Id64::<()>::new(0);
        let incremented = id.increment();
        assert_eq!(incremented.get(), 1);
    }

    #[test]
    fn test_increment_large_value() {
        let id = Id64::<()>::new(u64::MAX - 1);
        let incremented = id.increment();
        assert_eq!(incremented.get(), u64::MAX);
    }

    #[test]
    #[cfg(debug_assertions)]
    #[should_panic(expected = "attempt to add with overflow")]
    fn test_increment_overflow_debug() {
        let id = Id64::<()>::new(u64::MAX);
        let _ = id.increment(); // This will panic in debug mode
    }

    #[test]
    #[cfg(not(debug_assertions))]
    fn test_increment_overflow_release() {
        let id = Id64::<()>::new(u64::MAX);
        let incremented = id.increment();
        // In release mode, this will wrap around to 0
        assert_eq!(incremented.get(), 0);
    }

    #[test]
    fn test_increment_endianness() {
        // Test that increment works correctly with big-endian representation
        let id = Id64::<()>::new(255); // 0xFF
        let incremented = id.increment();
        assert_eq!(incremented.get(), 256); // 0x100

        // Verify the big-endian bytes are correct
        let bytes = incremented.to_be_bytes();
        assert_eq!(bytes, [0, 0, 0, 0, 0, 0, 1, 0]);
    }

    // -- OptId64 tests --

    #[test]
    fn test_opt_id64_none() {
        let opt = OptId64::<()>::NONE;
        assert!(opt.is_none());
        assert!(!opt.is_some());
        assert_eq!(opt.get(), None);
        assert_eq!(opt.to_bytes(), [0u8; 8]);
    }

    #[test]
    fn test_opt_id64_some() {
        let id = Id64::<()>::new(42);
        let opt = OptId64::some(id);
        assert!(opt.is_some());
        assert!(!opt.is_none());
        assert_eq!(opt.get(), Some(id));
    }

    #[test]
    fn test_opt_id64_conversions() {
        let id = Id64::<()>::new(42);

        let opt: OptId64<()> = id.into();
        assert_eq!(opt.get(), Some(id));

        let opt: OptId64<()> = None.into();
        assert!(opt.is_none());

        let opt: OptId64<()> = Some(id).into();
        let back: Option<Id64<()>> = opt.into();
        assert_eq!(back, Some(id));
    }

    #[test]
    fn test_opt_id64_size() {
        assert_eq!(size_of::<OptId64<()>>(), 8);
        assert_eq!(size_of::<OptId64<()>>(), size_of::<Id64<()>>());
    }

    #[test]
    fn test_opt_id64_eq_with_id64() {
        let id = Id64::<()>::new(42);
        let opt = OptId64::some(id);
        assert_eq!(opt, id);
        assert_eq!(id, opt);
    }

    #[test]
    fn test_opt_id64_default() {
        let opt = OptId64::<()>::default();
        assert!(opt.is_none());
    }

    #[test]
    fn test_id64_key_part_ty() {
        assert_eq!(<Id64<()> as KeyPart>::TY, KeyType::U64);
    }

    // -- Rapira _ctx round-trip tests --

    use crate::enc::{Cipher, IdHasher};

    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
    pub struct Hasher;

    impl IdHasher for Hasher {
        const HASHER: Cipher = Cipher::new(
            "_mKbKGF2IrkGvIJvl97HuCgWjgt6QRZ7Ye8DHBQ2anvyi18BdMz8uN6Ej3YJApooY6qDu0obqq4",
        );
    }

    #[test]
    fn test_id64_ctx_roundtrip() {
        let id = Id64::<Hasher>::new(123456789);
        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);

        let encrypted = rapira::serialize_ctx(&id, flags);
        let plain = rapira::serialize(&id);
        assert_ne!(encrypted, plain);

        let decoded: Id64<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
        assert_eq!(decoded, id);
    }

    #[test]
    fn test_id64_ctx_no_flag_same_as_plain() {
        let id = Id64::<Hasher>::new(123456789);
        let plain = rapira::serialize(&id);
        let ctx_none = rapira::serialize_ctx(&id, rapira::RapiraFlags::NONE);
        assert_eq!(plain, ctx_none);
    }

    #[test]
    fn test_opt_id64_ctx_none_roundtrip() {
        let opt = OptId64::<Hasher>::NONE;
        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);
        let bytes = rapira::serialize_ctx(&opt, flags);
        assert_eq!(bytes, [0u8; 8]);
        let decoded: OptId64<Hasher> = rapira::deserialize_ctx(&bytes, flags).unwrap();
        assert!(decoded.is_none());
    }

    #[test]
    fn test_opt_id64_ctx_some_roundtrip() {
        let id = Id64::<Hasher>::new(123456789);
        let opt = OptId64::some(id);
        let flags = rapira::RapiraFlags::new(crate::ID_ENC_FLAG);

        let encrypted = rapira::serialize_ctx(&opt, flags);
        let plain = rapira::serialize(&opt);
        assert_ne!(encrypted, plain);

        let decoded: OptId64<Hasher> = rapira::deserialize_ctx(&encrypted, flags).unwrap();
        assert_eq!(decoded.get(), Some(id));
    }
}
