mod visitors;
extern crate self as sov_universal_wallet;
pub use visitors::display;
#[cfg(feature = "serde")]
pub use visitors::json_to_borsh;

pub mod schema;
pub mod ty;

pub extern crate bech32;

#[cfg(feature = "macros")]
/// Implements the [`UniversalWallet`](schema::UniversalWallet) trait for the
/// annotated struct or enum.
///
/// The schema generated by the trait allows two main features.
/// First, the borsh-encoding of the type to be dispalyed in a human-readable format, with the exact
/// formatting controlled by attributes on the fields of the type.
/// Second, it allows a JSON-encoding of the type to be translated into borsh-encoding, without
/// needing access to the original Rust definition of the type.
///
/// ## Attributes: `#[sov_wallet(bound = "T: Trait")]`
///
/// Tells the proc-macro to add the specified bound to the where clause
/// of the generated implementation instead of adding the default `T: UniversalWallet` (where `T`
/// is the type of the annotated field).
///
/// This annotation may only be applied to fields, not items.
///
/// ## Attributes: #[sov_wallet(hidden)]`
///
/// Causes the field to be hidden from the user during display. This is often used for data
/// that can't be displayed in a human-readable format, such as merkle proofs. If the field is not
/// present in the `borsh` serialization of the type, use `#[sov_wallet(skip)]` instead.
///
/// This annotation may only be applied to fields, not items.
///
/// ```rust
/// use sov_universal_wallet::schema::{Schema, safe_string::SafeString};
/// use sov_universal_wallet::UniversalWallet;
///
/// #[derive(UniversalWallet, borsh::BorshSerialize)]
/// pub struct Unreadable {
///    name: SafeString,
///    #[sov_wallet(hidden)]
///    opaque_contents: Vec<u8>,
/// }
/// let serialized = borsh::to_vec(&Unreadable { name: "foo.txt".try_into().unwrap(), opaque_contents: vec![23, 74, 119, 119, 2, 232, 22]}).unwrap();
/// assert_eq!(Schema::of_single_type::<Unreadable>().unwrap().display(0, &serialized).unwrap(), r#"{ name: "foo.txt" }"#);
/// ```
/// Notice also the use of the SafeString type here - this is to ensure the string can be safely
/// displayed to the user. By default, unconstrained Strings are forbidden in schemas; for blobs of
/// data, use byte arrays/vectors directly. If a String is absolutely required, a newtype wrapper
/// can be used.
///
/// ## Attributes: `#[sov_wallet(as_ty = "path::to::Type")]`
///
/// Inserts the schema of the specified type in place of the schema for the annotated field. Note that the subsituted type
/// must have exactly the same borsh serialization as the original.
///
/// This is useful when you want to display a foreign type that doesn't implement [`UniversalWallet`](schema::UniversalWallet),
/// or when you want to override the default schema for a type in a particular context.
///
/// ```rust
/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
///
/// // A foreign type that doesn't derive UniversalWallet
/// #[derive(borsh::BorshSerialize)]
/// pub struct Foreign(u64);
///
/// #[derive(UniversalWallet, borsh::BorshSerialize)]
/// pub struct Tagged {
///    #[sov_wallet(as_ty = "u64")]
///    data: Foreign,
///    tag: i8,
/// }
/// let serialized = borsh::to_vec(&Tagged { data: Foreign(300_000), tag: -5 }).unwrap();
/// assert_eq!(Schema::of_single_type::<Tagged>().unwrap().display(0, &serialized).unwrap(), r#"{ data: 300000, tag: -5 }"#);
/// ```
///
/// ## Attributes: `#[sov_wallet(skip)]`
///
/// Causes the field to be excluded from the Schema entirely. This should be used if the field is not present in
/// the `borsh` serialization of the type. If the type is present in the serialization but should not be displayed,
/// use `#[sov_wallet(hidden)]` instead.
///
/// ```rust
/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
/// #[derive(UniversalWallet, borsh::BorshSerialize)]
/// pub struct File {
///     #[borsh(skip)]
///     #[sov_wallet(skip)]
///     checksum: Option<[u8;32]>,
///     contents: Vec<u8>,
/// }
/// let serialized = borsh::to_vec(&File { contents: vec![1, 2, 3], checksum: None }).unwrap();
/// assert_eq!(Schema::of_single_type::<File>().unwrap().display(0, &serialized).unwrap(), r#"{ contents: 0x010203 }"#);
/// ```
///
/// ## Attributes: `#[sov_wallet(fixed_point({decimals}))]`
///
/// Specifies fixed-point formatting for an integer field. The decimals specification can be one of
/// the following:
///  - an integer literal: directly specifies the number of decimal places to use
///  - `from_field({n})`: where `n` is an integer denoting a sibling field in the same structure
///    (by index). That field must be an unsigned integer type, and at runtime its value must be at
///    most 255. That field's value will be used as the number of decimal points when formatting the
///    fixed-point number.
///
/// ```rust
/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
/// #[derive(UniversalWallet, borsh::BorshSerialize)]
/// pub struct Coins {
///     #[sov_wallet(fixed_point(from_field(1)))]
///     amount: u128,
///     #[sov_wallet(hidden)]
///     decimals: u8
/// }
/// let serialized = borsh::to_vec(&Coins { amount: 475200, decimals: 3 }).unwrap();
/// assert_eq!(Schema::of_single_type::<Coins>().unwrap().display(0, &serialized).unwrap(), r#"{ amount: 475.2 }"#);
/// ```
///
/// **Security note**: uniquely, this formats the display using user-submitted input. If the
/// accuracy of the displayed string is important for security, it is crucial that the submitted
/// value for the amount of decimals be treated as the source of truth, as that will be what the
/// user will have been presented with.
/// For example, when using the schema to sign on-chain messages referencing cryptocurrency
/// amounts, any message where the decimals field does not match the currency's canonical decimal
/// count **must** be considered invalid and rejected.
///
/// ## Attributes: `#[sov_wallet(display({encoding}))]`
///
/// Specifies the encoding to use when displaying a byte sequence. The encoding can be one of the following:
/// - hex: displays the type as a hexadecimal string with the prefix "0x"
/// - decimal: displays the type as a list of decimal numbers in square brackets
/// - bech32(prefix = "my_prefix_expr"): displays the type as a bech32-encoded string with the specified human-readable part.
/// - bech32m(prefix = "my_prefix_expr"): displays the type as a bech32-encoded string with the specified human-readable part.
///
/// This annotation may only be applied to fields, not items. The field must have type `[u8;N]` or `Vec<u8>` to use this attribute.
///
/// ```rust
/// use sov_universal_wallet::{schema::Schema, UniversalWallet};
///
/// fn prefix() -> &'static str {
///   "celestia"
/// }
///
/// #[derive(UniversalWallet, borsh::BorshSerialize)]
/// pub struct CelestiaAddress(
///   #[sov_wallet(display(bech32(prefix = "prefix()")))]
///   [u8;32],
/// );
/// let serialized = borsh::to_vec(&CelestiaAddress([1; 32])).unwrap();
/// assert_eq!(Schema::of_single_type::<CelestiaAddress>().unwrap().display(0, &serialized).unwrap(), "celestia1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsagv2r7");
/// ```
pub use sov_universal_wallet_macros::UniversalWallet;
