use crate::db::raw_def::v9::RawModuleDefV9Builder;
use crate::db::raw_def::RawTableDefV8;
use anyhow::Context;
use sats::typespace::TypespaceBuilder;
use spacetimedb_sats::{impl_serialize, WithTypespace};
use std::any::TypeId;
use std::collections::{btree_map, BTreeMap};
pub mod address;
pub mod db;
pub mod error;
pub mod identity;
pub mod operator;
pub mod relation;
pub mod scheduler;
pub mod version;
pub mod type_def {
    pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
}
pub mod type_value {
    pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
}
pub use address::Address;
pub use identity::Identity;
pub use scheduler::ScheduleAt;
pub use spacetimedb_sats::hash::{self, hash_bytes, Hash};
pub use spacetimedb_sats::SpacetimeType;
pub use spacetimedb_sats::__make_register_reftype;
pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser};
pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
pub const MODULE_ABI_MAJOR_VERSION: u16 = 10;
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
pub struct VersionTuple {
    pub major: u16,
    pub minor: u16,
}
impl VersionTuple {
    pub const fn new(major: u16, minor: u16) -> Self {
        Self { major, minor }
    }
    #[inline]
    pub const fn eq(self, other: Self) -> bool {
        self.major == other.major && self.minor == other.minor
    }
    #[inline]
    pub const fn supports(self, module_version: VersionTuple) -> bool {
        self.major == module_version.major && self.minor >= module_version.minor
    }
    #[inline]
    pub const fn from_u32(v: u32) -> Self {
        let major = (v >> 16) as u16;
        let minor = (v & 0xFF) as u16;
        Self { major, minor }
    }
    #[inline]
    pub const fn to_u32(self) -> u32 {
        (self.major as u32) << 16 | self.minor as u32
    }
}
impl std::fmt::Display for VersionTuple {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self { major, minor } = *self;
        write!(f, "{major}.{minor}")
    }
}
extern crate self as spacetimedb_lib;
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
#[sats(crate = crate)]
pub struct TableDesc {
    pub schema: RawTableDefV8,
    pub data: sats::AlgebraicTypeRef,
}
impl TableDesc {
    pub fn into_table_def(table: WithTypespace<'_, TableDesc>) -> anyhow::Result<RawTableDefV8> {
        let schema = table
            .map(|t| &t.data)
            .resolve_refs()
            .context("recursive types not yet supported")?;
        let schema = schema.into_product().ok().context("table not a product type?")?;
        let table = table.ty();
        anyhow::ensure!(
            table.schema.columns.len() == schema.elements.len(),
            "mismatched number of columns"
        );
        Ok(table.schema.clone())
    }
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
pub struct ReducerDef {
    pub name: Box<str>,
    pub args: Vec<ProductTypeElement>,
}
impl ReducerDef {
    pub fn encode(&self, writer: &mut impl buffer::BufWriter) {
        bsatn::to_writer(writer, self).unwrap()
    }
    pub fn serialize_args<'a>(ty: sats::WithTypespace<'a, Self>, value: &'a ProductValue) -> impl ser::Serialize + 'a {
        ReducerArgsWithSchema { value, ty }
    }
    pub fn deserialize(
        ty: sats::WithTypespace<'_, Self>,
    ) -> impl for<'de> de::DeserializeSeed<'de, Output = ProductValue> + '_ {
        ReducerDeserialize(ty)
    }
}
struct ReducerDeserialize<'a>(sats::WithTypespace<'a, ReducerDef>);
impl<'de> de::DeserializeSeed<'de> for ReducerDeserialize<'_> {
    type Output = ProductValue;
    fn deserialize<D: de::Deserializer<'de>>(self, deserializer: D) -> Result<Self::Output, D::Error> {
        deserializer.deserialize_product(self)
    }
}
impl<'de> de::ProductVisitor<'de> for ReducerDeserialize<'_> {
    type Output = ProductValue;
    fn product_name(&self) -> Option<&str> {
        Some(&self.0.ty().name)
    }
    fn product_len(&self) -> usize {
        self.0.ty().args.len()
    }
    fn product_kind(&self) -> de::ProductKind {
        de::ProductKind::ReducerArgs
    }
    fn visit_seq_product<A: de::SeqProductAccess<'de>>(self, tup: A) -> Result<Self::Output, A::Error> {
        de::visit_seq_product(self.0.map(|r| &*r.args), &self, tup)
    }
    fn visit_named_product<A: de::NamedProductAccess<'de>>(self, tup: A) -> Result<Self::Output, A::Error> {
        de::visit_named_product(self.0.map(|r| &*r.args), &self, tup)
    }
}
struct ReducerArgsWithSchema<'a> {
    value: &'a ProductValue,
    ty: sats::WithTypespace<'a, ReducerDef>,
}
impl_serialize!([] ReducerArgsWithSchema<'_>, (self, ser) => {
    use itertools::Itertools;
    use ser::SerializeSeqProduct;
    let mut seq = ser.serialize_seq_product(self.value.elements.len())?;
    for (value, elem) in self.value.elements.iter().zip_eq(&self.ty.ty().args) {
        seq.serialize_element(&self.ty.with(&elem.algebraic_type).with_value(value))?;
    }
    seq.end()
});
#[derive(Debug, Clone, Default, SpacetimeType)]
#[sats(crate = crate)]
pub struct RawModuleDefV8 {
    pub typespace: sats::Typespace,
    pub tables: Vec<TableDesc>,
    pub reducers: Vec<ReducerDef>,
    pub misc_exports: Vec<MiscModuleExport>,
}
impl RawModuleDefV8 {
    pub fn builder() -> ModuleDefBuilder {
        ModuleDefBuilder::default()
    }
    pub fn with_builder(f: impl FnOnce(&mut ModuleDefBuilder)) -> Self {
        let mut builder = Self::builder();
        f(&mut builder);
        builder.finish()
    }
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[non_exhaustive]
pub enum RawModuleDef {
    V8BackCompat(RawModuleDefV8),
    V9(db::raw_def::v9::RawModuleDefV9),
    }
#[derive(Default)]
pub struct ModuleDefBuilder {
    module: RawModuleDefV8,
    type_map: BTreeMap<TypeId, sats::AlgebraicTypeRef>,
}
impl ModuleDefBuilder {
    pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
        TypespaceBuilder::add_type::<T>(self)
    }
    #[cfg(feature = "test")]
    pub fn add_type_for_tests(&mut self, name: &str, ty: AlgebraicType) -> spacetimedb_sats::AlgebraicTypeRef {
        let slot_ref = self.module.typespace.add(ty);
        self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
            name: name.to_owned(),
            ty: slot_ref,
        }));
        slot_ref
    }
    #[cfg(feature = "test")]
    pub fn add_table_for_tests(&mut self, schema: RawTableDefV8) -> spacetimedb_sats::AlgebraicTypeRef {
        let ty: ProductType = schema
            .columns
            .iter()
            .map(|c| ProductTypeElement {
                name: Some(c.col_name.clone()),
                algebraic_type: c.col_type.clone(),
            })
            .collect();
        let data = self.module.typespace.add(ty.into());
        self.add_type_alias(TypeAlias {
            name: schema.table_name.clone().into(),
            ty: data,
        });
        self.add_table(TableDesc { schema, data });
        data
    }
    pub fn add_table(&mut self, table: TableDesc) {
        self.module.tables.push(table)
    }
    pub fn add_reducer(&mut self, reducer: ReducerDef) {
        self.module.reducers.push(reducer)
    }
    #[cfg(feature = "test")]
    pub fn add_reducer_for_tests(&mut self, name: impl Into<Box<str>>, args: ProductType) {
        self.add_reducer(ReducerDef {
            name: name.into(),
            args: args.elements.to_vec(),
        });
    }
    pub fn add_misc_export(&mut self, misc_export: MiscModuleExport) {
        self.module.misc_exports.push(misc_export)
    }
    pub fn add_type_alias(&mut self, type_alias: TypeAlias) {
        self.add_misc_export(MiscModuleExport::TypeAlias(type_alias))
    }
    pub fn typespace(&self) -> &sats::Typespace {
        &self.module.typespace
    }
    pub fn finish(self) -> RawModuleDefV8 {
        self.module
    }
}
impl TypespaceBuilder for ModuleDefBuilder {
    fn add(
        &mut self,
        typeid: TypeId,
        name: Option<&'static str>,
        make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
    ) -> AlgebraicType {
        let r = match self.type_map.entry(typeid) {
            btree_map::Entry::Occupied(o) => *o.get(),
            btree_map::Entry::Vacant(v) => {
                let slot_ref = self.module.typespace.add(AlgebraicType::unit());
                v.insert(slot_ref);
                if let Some(name) = name {
                    self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
                        name: name.to_owned(),
                        ty: slot_ref,
                    }));
                }
                let ty = make_ty(self);
                self.module.typespace[slot_ref] = ty;
                slot_ref
            }
        };
        AlgebraicType::Ref(r)
    }
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
pub enum MiscModuleExport {
    TypeAlias(TypeAlias),
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
pub struct TypeAlias {
    pub name: String,
    pub ty: sats::AlgebraicTypeRef,
}
pub fn from_hex_pad<R: hex::FromHex<Error = hex::FromHexError>, T: AsRef<[u8]>>(
    hex: T,
) -> Result<R, hex::FromHexError> {
    let hex = hex.as_ref();
    let hex = if hex.starts_with(b"0x") {
        &hex[2..]
    } else if hex.starts_with(b"X'") {
        &hex[2..hex.len()]
    } else {
        hex
    };
    hex::FromHex::from_hex(hex)
}
pub fn resolved_type_via_v9<T: SpacetimeType>() -> AlgebraicType {
    let mut builder = RawModuleDefV9Builder::new();
    let ty = T::make_type(&mut builder);
    let module = builder.finish();
    WithTypespace::new(&module.typespace, &ty)
        .resolve_refs()
        .expect("recursive types not supported")
}