pub trait NewId {
    fn new_id() -> Self;

    fn id(&self) -> String;
}

#[macro_export]
macro_rules! sql_uuid_newtype {
    ($newtype:tt) => {
        impl $crate::database::macros::NewId for $newtype {
            fn new_id() -> Self {
                Self(uuid::Uuid::now_v7())
            }

            fn id(&self) -> String {
                self.0.to_string()
            }
        }

        impl rusqlite::types::FromSql for $newtype {
            fn column_result(
                value: rusqlite::types::ValueRef<'_>,
            ) -> rusqlite::types::FromSqlResult<Self> {
                // i have chosen to store the uuids as text, so i could seed the database easier.
                let s = match value {
                    rusqlite::types::ValueRef::Null => todo!(),
                    rusqlite::types::ValueRef::Text(items) => {
                        let s = unsafe { str::from_utf8_unchecked(items) };
                        let uuid = Uuid::parse_str(s).unwrap();

                        uuid
                    }
                    _ => Err(rusqlite::types::FromSqlError::InvalidType)?,
                };

                Ok($newtype(s))
            }
        }

        impl rusqlite::ToSql for $newtype {
            fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
                Ok(rusqlite::types::ToSqlOutput::Owned(
                    rusqlite::types::Value::Text(self.0.to_string()),
                ))
            }
        }

        impl std::fmt::Display for $newtype {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.0.to_string())
            }
        }

        impl std::str::FromStr for $newtype {
            type Err = uuid::Error;

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                let uuid = Uuid::parse_str(s)?;
                let id = $newtype(uuid);
                Ok(id)
            }
        }

        impl $crate::types::user::FromRow for $newtype {
            fn try_from_row(row: &rusqlite::Row) -> Result<Self, rusqlite::Error> {
                let s: Self = row.get(0)?;
                Ok(s)
            }
        }
    };
}
