//! Core types and logic required to permissively parse Bitcoin / lightning
//! payment addresses and URIs. For actually *resolving* a [`PaymentUri`] into a
//! [`PaymentMethod`], which frequently requires accessing a network, see the
//! [`lexe-payment-uri`] crate.
//!
//! # Permissive parsing
//!
//! This crate parses various BTC-related payment methods permissively.
//! That means we accept inputs that are not strictly well-formed.
//!
//! Other wallet parsers for comparison:
//! + [ACINQ/phoenix - Parser](https://github.com/ACINQ/phoenix/blob/master/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/Parser.kt)
//! + [breez/breez-sdk - input_parser.rs](https://github.com/breez/breez-sdk-greenlight/blob/main/libs/sdk-common/src/input_parser.rs)
//! + [MutinyWallet/bitcoin_waila (unmaintained)](https://github.com/MutinyWallet/bitcoin-waila/blob/master/waila/src/lib.rs)
//!
//! # Prompts
//!
//! ```md
//! Please read these URI parser impls:
//! - `~/lexe/github/breez-sdk-greenlight/libs/sdk-common/src/input_parser.rs`
//! - `~/lexe/github/phoenix/phoenix-shared/src/commonMain/kotlin/fr.acinq.phoenix/utils/Parser.kt` (and files nearby)
//! - `~/lexe/github/bitcoin-waila/waila/src/lib.rs`
//! ```
//!
//! [`PaymentUri`]: payment_uri::PaymentUri
//! [`PaymentMethod`]: payment_method::PaymentMethod

// `proptest_derive::Arbitrary` issue. This will hard-error for edition 2024 so
// hopefully it gets fixed soon...
// See: <https://github.com/proptest-rs/proptest/issues/447>
#![allow(non_local_definitions)]

use std::{
    borrow::Cow,
    fmt::{self, Display},
};

use lexe_api_core::types::{invoice, offer};

/// Export all public types so they are accessible via the crate root.
/// The containing modules are used only for internal organization, and are
/// intentionally private so crate users have a simple, flat namespace.
pub use crate::{
    bip321_uri::Bip321Uri,
    email_like::EmailLikeAddress,
    lightning_uri::LightningUri,
    lnurl::Lnurl,
    payment_method::{OfferWithAmount, Onchain, PaymentMethod},
    payment_uri::PaymentUri,
};

/// Helper functions and utilities; some of these are public.
pub mod helpers;

/// BIP321 / BIP21 parsing and formatting.
mod bip321_uri;
/// Email-like payment URIs, including Lightning Addresses and BIP353.
mod email_like;
/// "lightning:" URIs, containing a BOLT 11 invoice or BOLT12 offer.
mod lightning_uri;
/// LNURLs.
mod lnurl;
/// `PaymentMethod` and subtypes, representing a resolved payment method.
mod payment_method;
/// Top level `PaymentUri` representing a parsed payment URI or address.
mod payment_uri;
/// Low level URI building blocks: `Uri`, `UriParam`, `UriParamKey`
mod uri;

#[derive(Clone, Debug, PartialEq)]
pub enum Error {
    /// [`PaymentUri`] parsing errors.
    InvalidPaymentUri(Cow<'static, str>),
    /// [`Bip321Uri`] parsing errors.
    InvalidBip321Uri(Cow<'static, str>),
    /// [`LightningUri`] parsing errors.
    InvalidLightningUri(Cow<'static, str>),
    // `uri::Uri`
    InvalidUri(Cow<'static, str>),
    /// [`EmailLikeAddress`] parsing errors.
    InvalidEmailLike(Cow<'static, str>),
    InvalidLnurl(Cow<'static, str>),
    InvalidInvoice(invoice::ParseError),
    InvalidOffer(offer::ParseError),
    InvalidBtcAddress(bitcoin::address::ParseError),
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidUri(msg) => write!(f, "Invalid URI: {msg}"),
            Self::InvalidPaymentUri(msg) =>
                write!(f, "Invalid payment URI: {msg}"),
            Self::InvalidBip321Uri(msg) =>
                write!(f, "Invalid 'bitcoin:' URI: {msg}"),
            Self::InvalidLightningUri(msg) =>
                write!(f, "Invalid 'lightning:' URI: {msg}"),
            Self::InvalidEmailLike(msg) =>
                write!(f, "Invalid BIP353 / Lightning Address: {msg}"),
            Self::InvalidLnurl(msg) => write!(f, "Invalid LNURL: {msg}"),
            Self::InvalidInvoice(err) => Display::fmt(err, f),
            Self::InvalidOffer(err) => Display::fmt(err, f),
            Self::InvalidBtcAddress(err) =>
                write!(f, "Invalid on-chain address: {err}"),
        }
    }
}
