use serde::{Deserialize, Serialize};

// Re-export Lazy from crate root for submodule use via `super::Lazy`
pub use crate::Lazy;

/// AT Protocol URI (at://) types and validation
pub mod aturi;
/// Blob references for binary data
pub mod blob;
/// Content Identifier (CID) types for IPLD
pub mod cid;
/// Repository collection trait for records
pub mod collection;
pub mod crypto;
/// AT Protocol datetime string type
pub mod datetime;
/// Decentralized Identifier (DID) types and validation
pub mod did;
/// DID Document types and helpers
pub mod did_doc;
/// AT Protocol handle types and validation
pub mod handle;
/// AT Protocol identifier types (handle or DID)
pub mod ident;
pub mod integer;
/// Language tag types per BCP 47
pub mod language;
/// Namespaced Identifier (NSID) types and validation
pub mod nsid;
/// Record key types and validation
pub mod recordkey;
/// String types with format validation
pub mod string;
/// Timestamp Identifier (TID) types and generation
pub mod tid;
/// URI types with scheme validation
pub mod uri;
/// Generic data value types for lexicon data model
pub mod value;

/// Trait for a constant string literal type
pub trait Literal: Clone + Copy + PartialEq + Eq + Send + Sync + 'static {
    /// The string literal
    const LITERAL: &'static str;
}

/// top-level domains which are not allowed in at:// handles or dids
pub const DISALLOWED_TLDS: &[&str] = &[
    ".local",
    ".arpa",
    ".invalid", // NOTE: if someone has a screwed up handle, this is what's returned
    ".localhost",
    ".internal",
    ".example",
    ".alt",
    // policy could concievably change on ".onion" some day
    ".onion",
    // NOTE: .test is allowed in testing and devopment. In practical terms
    // "should" "never" actually resolve and get registered in production
];

/// checks if a string ends with anything from the provided list of strings.
pub fn ends_with(string: impl AsRef<str>, list: &[&str]) -> bool {
    let string = string.as_ref();
    for item in list {
        if string.ends_with(item) {
            return true;
        }
    }
    false
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "kebab-case")]
/// Valid types in the AT protocol [data model](https://atproto.com/specs/data-model). Type marker only, used in concert with `[Data<'_>]`.
pub enum DataModelType {
    /// Null type. IPLD type `null`, JSON type `Null`, CBOR Special Value (major 7)
    Null,
    /// Boolean type. IPLD type `boolean`, JSON type Boolean, CBOR Special Value (major 7)
    Boolean,
    /// Integer type. IPLD type `integer`, JSON type Number, CBOR Special Value (major 7)
    Integer,
    /// Byte type. IPLD type `bytes`, in JSON a `{ "$bytes": bytes }` Object, CBOR Byte String (major 2)
    Bytes,
    /// CID (content identifier) link. IPLD type `link`, in JSON a `{ "$link": cid }` Object, CBOR CID (tag 42)
    CidLink,
    /// Blob type. No special IPLD type. in JSON a `{ "$type": "blob" }` Object. in CBOR a `{ "$type": "blob" }` Map.
    Blob,
    /// Array type. IPLD type `list`. JSON type `Array`, CBOR type Array (major 4)
    Array,
    /// Object type. IPLD type `map`. JSON type `Object`, CBOR type Map (major 5). keys are always SmolStr.
    Object,
    #[serde(untagged)]
    /// String type (lots of variants). JSON String, CBOR UTF-8 String (major 3)
    String(LexiconStringType),
}

impl core::fmt::Display for DataModelType {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        match self {
            DataModelType::Null => write!(f, "null"),
            DataModelType::Boolean => write!(f, "boolean"),
            DataModelType::Integer => write!(f, "integer"),
            DataModelType::Bytes => write!(f, "bytes"),
            DataModelType::CidLink => write!(f, "cid-link"),
            DataModelType::Blob => write!(f, "blob"),
            DataModelType::Array => write!(f, "array"),
            DataModelType::Object => write!(f, "object"),
            DataModelType::String(s) => write!(f, "{}", s),
        }
    }
}

/// Lexicon string format types for typed strings in the AT Protocol data model
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum LexiconStringType {
    /// ISO 8601 datetime string
    Datetime,
    /// AT Protocol URI (at://)
    AtUri,
    /// Decentralized Identifier
    Did,
    /// AT Protocol handle
    Handle,
    /// Handle or DID
    AtIdentifier,
    /// Namespaced Identifier
    Nsid,
    /// Content Identifier
    Cid,
    /// BCP 47 language tag
    Language,
    /// Timestamp Identifier
    Tid,
    /// Record key
    RecordKey,
    /// URI with type constraint
    Uri(UriType),
    /// Plain string
    #[serde(untagged)]
    String,
}

impl core::fmt::Display for LexiconStringType {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        match self {
            LexiconStringType::Datetime => write!(f, "datetime"),
            LexiconStringType::AtUri => write!(f, "at-uri"),
            LexiconStringType::Did => write!(f, "did"),
            LexiconStringType::Handle => write!(f, "handle"),
            LexiconStringType::AtIdentifier => write!(f, "at-identifier"),
            LexiconStringType::Nsid => write!(f, "nsid"),
            LexiconStringType::Cid => write!(f, "cid"),
            LexiconStringType::Language => write!(f, "language"),
            LexiconStringType::Tid => write!(f, "tid"),
            LexiconStringType::RecordKey => write!(f, "record-key"),
            LexiconStringType::Uri(u) => write!(f, "uri({})", u),
            LexiconStringType::String => write!(f, "string"),
        }
    }
}

/// URI scheme types for lexicon URI format constraints
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum UriType {
    /// DID URI (did:)
    Did,
    /// AT Protocol URI (at://)
    At,
    /// HTTPS URI
    Https,
    /// WebSocket Secure URI
    Wss,
    /// CID URI
    Cid,
    /// DNS name
    Dns,
    /// Any valid URI
    Any,
}

impl core::fmt::Display for UriType {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        match self {
            UriType::Did => write!(f, "did"),
            UriType::At => write!(f, "at"),
            UriType::Https => write!(f, "https"),
            UriType::Wss => write!(f, "wss"),
            UriType::Cid => write!(f, "cid"),
            UriType::Dns => write!(f, "dns"),
            UriType::Any => write!(f, "any"),
        }
    }
}
