//! Goals:
//! - No need for downstream libraries to depend upon libsignal-service-rs or presage.
//! - Make messages sent from others and from oneselves on other devices look identical (except for --- of course --- the sender), instead of having a distinct SyncMessage.
//! - Downstream libraries should not need to parse fields of messages to find out e.g. which message was quoted.
//! - There should be a clear differentiation between fields used by consumers of the library, fields required by the backend to work (`secrets` and `metadata`).
//!
//! Conventions:
//! - Types ending in "Id" references something that is likely available locally and does not need to be fetched.
//! - Types ending in "Pointer" references something that likely needs to be fetched from the internet.

pub mod convert;
pub mod error;
mod store;
pub use error::{Error, ErrorKind, Result};
use libsignal_service::prelude::AttachmentIdentifier;
pub use store::Store;

use std::{fmt::Display, num::NonZero};

// Take those from elsewhere.
type ServiceId = libsignal_service::protocol::ServiceId;
type GroupV2Id = libsignal_service::sender::GroupV2Id;
pub type PhoneNumber = phonenumber::PhoneNumber;
type ProfileKey = libsignal_service::prelude::ProfileKey;
const GROUP_MASTER_KEY_LEN: usize = 32;
type GroupMasterKey = [u8; GROUP_MASTER_KEY_LEN];
type DateTime<T> = chrono::DateTime<T>;
type Utc = chrono::Utc;
type Duration = chrono::TimeDelta;
type Uuid = uuid::Uuid;
pub type DeviceId = libsignal_service::protocol::DeviceId;
pub type SignalServers = libsignal_service::configuration::SignalServers;

// TODO:
type PendingMember = ();
type RequestingMember = ();
type Preview = ();
type ContactInformation = ();
type AccessControl = ();

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct AccountId(pub ServiceId);

impl AccountId {
    pub fn from_raw(uuid: uuid::Uuid) -> Self {
        Self(libsignal_service::protocol::ServiceId::Aci(uuid.into()))
    }
}

impl std::fmt::Display for AccountId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Debug::fmt(&self.0, f)
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ContactId {
    pub account_id: AccountId,
    pub in_group: Option<GroupId>,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct GroupId(pub GroupV2Id);

impl std::fmt::Debug for GroupId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("GroupId")
            .field(&hex::encode(self.0))
            .finish()
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ThreadId {
    Contact(AccountId),
    Group(GroupId),
}

impl ThreadId {
    pub fn group(&self) -> Option<GroupId> {
        match self {
            ThreadId::Contact(_) => None,
            ThreadId::Group(group_id) => Some(*group_id),
        }
    }

    pub fn contact(&self) -> Option<AccountId> {
        match self {
            ThreadId::Contact(account_id) => Some(*account_id),
            ThreadId::Group(_) => None,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Contact {
    pub id: ContactId,

    pub phone_number: Option<PhoneNumber>,
    pub username: Option<String>,
    pub email: Option<String>,
    pub is_blocked: bool,

    pub given_name: Option<String>,
    pub family_name: Option<String>,
    pub about: Option<String>,
    pub about_emoji: Option<String>,
    // Note: No avatar pointer; those are fetched from the secrets.
    pub disappearing_messages_timer: Timer,

    pub group_member_data: Option<Member>,

    pub secrets: ContactSecrets,
}

impl Contact {
    pub fn title(&self) -> Option<String> {
        self.username
            .clone()
            .or_else(|| {
                self.given_name
                    .as_ref()
                    .and_then(|g| self.family_name.as_ref().map(|f| format!("{g} {f}")))
            })
            .or_else(|| self.given_name.clone())
            .or_else(|| self.family_name.clone())
            .or_else(|| self.phone_number.as_ref().map(|p| p.to_string()))
    }
}

#[derive(Copy, Clone)]
pub struct ContactSecrets {
    pub profile_key: Option<ProfileKey>,
}

impl std::fmt::Debug for ContactSecrets {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ContactSecrets")
            .field(
                "profile_key",
                if self.profile_key.is_some() {
                    &Some("...")
                } else {
                    &None::<&str>
                },
            )
            .finish()
    }
}

#[derive(Clone, Debug)]
pub struct Group {
    pub id: GroupId,

    pub name: String,
    pub description: Option<String>,
    // Note: No avatar pointer; those are fetched from the secrets.
    pub announcement_only: bool,
    pub disappearing_messages_timer: Timer,

    pub members: Vec<Member>,
    pub pending_members: Vec<PendingMember>,
    pub requesting_members: Vec<RequestingMember>,
    pub invite_link_password: Vec<u8>,

    pub access_control: Option<AccessControl>,

    pub secrets: GroupSecrets,
}

#[derive(Copy, Clone)]
pub struct GroupSecrets {
    pub master_key: GroupMasterKey,
    pub revision: u32,
}

impl std::fmt::Debug for GroupSecrets {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GroupSecrets")
            .field("master_key", &"...")
            .field("revision", &self.revision)
            .finish()
    }
}

#[derive(Clone, Debug)]
pub enum Thread {
    Contact(Contact),
    Group(Group),
}

impl From<Contact> for Thread {
    fn from(val: Contact) -> Self {
        Thread::Contact(val)
    }
}

impl From<Group> for Thread {
    fn from(val: Group) -> Self {
        Thread::Group(val)
    }
}

impl Thread {
    pub fn id(&self) -> ThreadId {
        match self {
            Thread::Contact(contact) => ThreadId::Contact(contact.id.account_id),
            Thread::Group(group) => ThreadId::Group(group.id),
        }
    }

    pub fn is_contact(&self) -> bool {
        matches!(self, Self::Contact(_))
    }

    pub fn is_group(&self) -> bool {
        matches!(self, Self::Group(_))
    }

    pub fn contact(&self) -> Option<&Contact> {
        match self {
            Thread::Contact(contact) => Some(contact),
            Thread::Group(_) => None,
        }
    }

    pub fn group(&self) -> Option<&Group> {
        match self {
            Thread::Contact(_) => None,
            Thread::Group(group) => Some(group),
        }
    }

    pub fn title(&self) -> Option<String> {
        match self {
            Thread::Contact(contact) => contact.title(),
            Thread::Group(group) => Some(group.name.clone()),
        }
    }

    pub fn description(&self) -> Option<String> {
        match self {
            Thread::Contact(contact) => match (&contact.about_emoji, &contact.about) {
                (None, None) => None,
                (None, Some(about)) => Some(about.to_string()),
                (Some(emoji), None) => Some(emoji.to_string()),
                (Some(emoji), Some(about)) => Some(format!("{emoji} {about}")),
            },
            Thread::Group(group) => group.description.clone(),
        }
    }

    pub fn disappearing_messages_timer(&self) -> Timer {
        match self {
            Thread::Contact(contact) => contact.disappearing_messages_timer,
            Thread::Group(group) => group.disappearing_messages_timer,
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Timer {
    Off,
    On(Duration),
}

// While Signal messages are usually identified only by the sender and timestamp, a single Signal message may contain multiple MessageContent as defined in this crate.
// Differentiate such messages by "Sub IDs".
// Note that only one message may be quoteable (or referred to in other ways by other messages), such a message is always assigned the primary message sub ID.
// Other messages have an other sub ID.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum MessageSubId {
    Primary,
    Other(NonZero<u64>),
}

impl std::fmt::Display for MessageSubId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MessageSubId::Primary => write!(f, "primary message"),
            MessageSubId::Other(non_zero) => write!(f, "message {}", non_zero),
        }
    }
}

impl MessageSubId {
    fn next(&self) -> Self {
        match self {
            MessageSubId::Primary => MessageSubId::Other(NonZero::new(1).expect("1 to be NonZero")),
            MessageSubId::Other(i) => MessageSubId::Other(i.saturating_add(1)),
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct MessageId {
    // Note: This is an AccountId instead of a ContactId, as ReceiptMessages
    // don't have information about which thread they were sent in; they need
    // to query this information based on the sender of the ReceiptMessage.
    // One therefore needs to store messages by the sender account ID instead
    // of the contact ID.
    pub sender: AccountId,
    pub timestamp: DateTime<Utc>,
    pub sub_id: MessageSubId,
}

impl MessageId {
    pub fn get_and_next(&mut self) -> MessageId {
        let clone = *self;
        self.sub_id = self.sub_id.next();
        clone
    }
}

#[derive(Clone, Debug)]
pub struct Message<C> {
    pub id: MessageId,

    pub thread: ThreadId,
    pub metadata: MessageMetadata,

    pub content: C,
}

pub type ReceivedMessage = Message<ReceivedMessageContent>;
pub type AmendmentMessage = Message<AmendmentMessageContent>;
pub type RichMessage = Message<RichMessageContent<MainMessageContent>>;

impl<C> Message<C> {
    pub fn sender(&self) -> ContactId {
        ContactId {
            account_id: self.id.sender,
            in_group: self.thread.group(),
        }
    }

    pub fn map<F: FnOnce(C) -> D, D>(self, f: F) -> Message<D> {
        Message {
            id: self.id,
            thread: self.thread,
            metadata: self.metadata,
            content: f(self.content),
        }
    }

    pub fn with_content<D>(&self, content: D) -> Message<D> {
        self.remove_content().map(|_| content)
    }

    pub fn extract_content(self) -> (Message<()>, C) {
        (self.remove_content(), self.content)
    }

    pub fn remove_content(&self) -> Message<()> {
        Message {
            id: self.id,
            thread: self.thread,
            metadata: self.metadata,
            content: (),
        }
    }
}

impl Message<EditMessage> {
    pub fn as_rich_message(self) -> RichMessage {
        self.map(|e| RichMessageContent {
            inner: MainMessageContent::Text(e.updated),
            reactions: vec![],
            receipts: vec![],
            deletion: None,
            edits: vec![],
            vote: vec![],
            terminate_poll: None,
        })
    }
}

impl Message<ReceivedMessageContent> {
    pub fn as_rich_message(&self) -> Option<RichMessage> {
        match &self.content {
            ReceivedMessageContent::Main(main_message) => {
                Some(self.with_content(RichMessageContent {
                    inner: main_message.clone(),
                    reactions: vec![],
                    receipts: vec![],
                    deletion: None,
                    edits: vec![],
                    vote: vec![],
                    terminate_poll: None,
                }))
            }
            _ => None,
        }
    }

    pub fn as_amendment_message(&self) -> Option<AmendmentMessage> {
        match &self.content {
            ReceivedMessageContent::Amendment(amendment_message) => {
                Some(self.with_content(amendment_message.clone()))
            }
            _ => None,
        }
    }
}

#[derive(Copy, Clone, Debug)]
pub struct MessageMetadata {
    pub needs_receipt: bool,
    pub unidentified_sender: bool,
    pub was_plaintext: bool,
    pub server_guid: Option<Uuid>,
    pub sender_device_id: DeviceId,
}

// TODO:
// - required_protocol_version
// - group_call_update
// - story_context
// - gift_badge
// - sync-message things

#[derive(Clone, Debug)]
pub enum ReceivedMessageContent {
    // An "intuitive" message, e.g. text messages or polls.
    Main(MainMessageContent),
    // Amendments to messages, e.g. reactions, receipts, or poll votes.
    Amendment(AmendmentMessageContent),
    // Changes in the thread, e.g. user typing, or user name update.
    Meta(MetaMessageContent),
}

#[derive(Clone, Debug)]
pub enum MainMessageContent {
    Text(TextMessage),
    CreatePoll(CreatePollMessage),
    Story(StoryMessage),
    Call(CallMessage),
    Payment(PaymentMessage),
    Deleted,
}

#[derive(Clone, Debug)]
pub enum AmendmentMessageContent {
    Reaction(ReactionMessage),
    Receipt(ReceiptMessage),
    Deletion(DeletionMessage),
    EditMessage(EditMessage),
    VotePoll(VotePollMessage),
    TerminatePoll(TerminatePollMessage),
}

#[derive(Clone, Debug)]
pub enum MetaMessageContent {
    Typing(TypingMessage),
    Pin(PinMessage),
    Unpin(UnpinMessage),
    GroupChange(GroupChangeMessage),
    ContactChange(ContactChangeMessage),
    SessionReset,
}

impl AmendmentMessageContent {
    pub fn amends(&self) -> Vec<MessageId> {
        match self {
            AmendmentMessageContent::Reaction(reaction_message) => {
                vec![reaction_message.reaction_to_message]
            }
            AmendmentMessageContent::Receipt(receipt_message) => {
                receipt_message.message_ids.clone()
            }
            AmendmentMessageContent::Deletion(deletion_message) => {
                vec![deletion_message.deleted_message]
            }
            AmendmentMessageContent::EditMessage(edit_message) => vec![edit_message.edited_message],
            AmendmentMessageContent::VotePoll(vote_poll_message) => {
                vec![vote_poll_message.vote_for]
            }
            AmendmentMessageContent::TerminatePoll(terminate_poll_message) => {
                vec![terminate_poll_message.poll]
            }
        }
    }
}

impl Message<AmendmentMessageContent> {
    pub fn amends(&self) -> Vec<MessageId> {
        self.content.amends()
    }
}

#[derive(Clone, Debug)]
pub struct RichMessageContent<C> {
    pub inner: C,

    // Mirrors AmendmentMessage
    pub reactions: Vec<Message<ReactionMessage>>,
    pub receipts: Vec<Message<ReceiptMessage>>,
    pub deletion: Option<Message<DeletionMessage>>,
    pub edits: Vec<Message<EditMessage>>,
    pub vote: Vec<Message<VotePollMessage>>,
    pub terminate_poll: Option<Message<TerminatePollMessage>>,
}

impl Message<RichMessageContent<MainMessageContent>> {
    pub fn amend(mut self, amendment: Message<AmendmentMessageContent>) -> Self {
        let (msg, content) = amendment.extract_content();
        match content {
            AmendmentMessageContent::Reaction(reaction_message) => self
                .content
                .reactions
                .push(msg.with_content(reaction_message)),
            AmendmentMessageContent::Receipt(receipt_message) => {
                if let Some((index, previous_receipt)) =
                    self.content.receipts.iter().enumerate().find(|(_, r)| {
                        r.content.kind == receipt_message.kind && r.id.sender == msg.id.sender
                    })
                {
                    log::trace!(
                        "Tried to amend a duplicated receipt; keeping only the earlier one"
                    );
                    if previous_receipt.id.timestamp < msg.id.timestamp {
                        return self;
                    } else {
                        self.content.receipts.remove(index);
                    }
                }
                self.content
                    .receipts
                    .push(msg.with_content(receipt_message));
            }
            AmendmentMessageContent::Deletion(deletion_message) => {
                self.content.inner = MainMessageContent::Deleted;
                self.content.deletion = Some(msg.with_content(deletion_message));
                self.content.reactions = vec![];
                self.content.reactions = vec![];
                self.content.edits = vec![];
                self.content.vote = vec![];
                self.content.terminate_poll = None;
            }
            AmendmentMessageContent::EditMessage(edit_message) => {
                self.content.edits.push(msg.with_content(edit_message))
            }
            AmendmentMessageContent::VotePoll(vote_poll_message) => {
                self.content.vote.push(msg.with_content(vote_poll_message))
            }
            AmendmentMessageContent::TerminatePoll(terminate_poll_message) => {
                self.content.terminate_poll = Some(msg.with_content(terminate_poll_message))
            }
        }
        self
    }

    pub fn remove_amendment(mut self, amendment: Message<AmendmentMessageContent>) -> Self {
        let (msg, content) = amendment.extract_content();
        match content {
            AmendmentMessageContent::Reaction(_reaction_message) => {
                self.content.reactions.retain(|m| m.id != msg.id)
            }
            AmendmentMessageContent::Receipt(_receipt_message) => {
                self.content.receipts.retain(|m| m.id != msg.id)
            }
            AmendmentMessageContent::Deletion(_deletion_message) => {
                log::warn!("Cannot remove an delete amendment message")
            }
            AmendmentMessageContent::EditMessage(_edit_message) => {
                self.content.edits.retain(|m| m.id != msg.id)
            }
            AmendmentMessageContent::VotePoll(_vote_poll_message) => {
                self.content.vote.retain(|m| m.id != msg.id)
            }
            AmendmentMessageContent::TerminatePoll(_terminate_poll_message) => {
                if self
                    .content
                    .terminate_poll
                    .as_ref()
                    .is_some_and(|m| m.id == msg.id)
                {
                    self.content.terminate_poll = None;
                }
            }
        }
        self
    }

    // Gets the currently active text message, i.e. the last edit if there were edits, or the base message if there were no edits. For non-text main messages, returns `None`.
    pub fn active_text_message(&self) -> Option<&TextMessage> {
        if let Some(edited) = self.content.edits.last() {
            Some(&edited.content.updated)
        } else {
            match &self.content.inner {
                MainMessageContent::Text(text_message) => Some(text_message),
                _ => None,
            }
        }
    }

    pub fn edit_history(&self) -> Vec<Message<EditMessage>> {
        match &self.content.inner {
            MainMessageContent::Text(text) => Some(Message {
                id: self.id,
                thread: self.thread,
                metadata: self.metadata,
                content: EditMessage {
                    edited_message: self.id,
                    updated: text.clone(),
                },
            })
            .into_iter()
            .chain(self.content.edits.clone())
            .collect(),
            _ => vec![],
        }
    }

    // Returns all reactions for the message, except the ones that were removed.
    pub fn active_reactions(&self) -> Vec<Message<ReactionMessage>> {
        let mut result: Vec<Message<ReactionMessage>> = vec![];

        for r in &self.content.reactions {
            if r.content.operation == ReactionOperation::Add {
                result.retain(|v| v.sender() != r.sender());
                result.push(r.clone());
            } else {
                let removed = result.iter().enumerate().rfind(|(_idx, v)| {
                    v.id.sender == r.id.sender && v.content.emoji == r.content.emoji
                });
                if let Some((idx, _match)) = removed {
                    result.remove(idx);
                } else {
                    log::warn!("Got a remove reaction message without an associated add message");
                }
            }
        }

        result
    }

    pub fn has_delivery_receipt(&self) -> bool {
        self.content
            .receipts
            .iter()
            .any(|r| r.content.kind == ReceiptKind::Delivery)
    }

    pub fn has_read_receipt(&self) -> bool {
        self.content
            .receipts
            .iter()
            .any(|r| r.content.kind == ReceiptKind::Read)
    }

    pub fn has_viewed_receipt(&self) -> bool {
        self.content
            .receipts
            .iter()
            .any(|r| r.content.kind == ReceiptKind::Viewed)
    }
}

impl<C: Clone> RichMessageContent<C> {
    pub fn map<F: FnOnce(C) -> D, D>(self, f: F) -> RichMessageContent<D> {
        RichMessageContent {
            inner: f(self.inner),
            reactions: self.reactions,
            receipts: self.receipts,
            deletion: self.deletion,
            edits: self.edits,
            vote: self.vote,
            terminate_poll: self.terminate_poll,
        }
    }

    pub fn with_content<D>(&self, content: D) -> RichMessageContent<D> {
        // TODO: No clone?
        self.clone().remove_content().map(|_| content)
    }

    pub fn extract_content(self) -> (RichMessageContent<()>, C) {
        // TODO: No clone?
        (self.clone().remove_content(), self.inner)
    }

    pub fn remove_content(self) -> RichMessageContent<()> {
        RichMessageContent {
            inner: (),
            reactions: self.reactions,
            receipts: self.receipts,
            deletion: self.deletion,
            edits: self.edits,
            vote: self.vote,
            terminate_poll: self.terminate_poll,
        }
    }
}

#[derive(Clone, Debug)]
pub struct TextMessage {
    pub body: Option<String>,
    pub attachments: Vec<AttachmentOrPointer>,
    pub quote: Option<Quote>,
    pub body_ranges: Vec<BodyRange>,
    pub previews: Vec<Preview>,
    pub contacts: Vec<ContactInformation>,
    pub is_view_once: bool,
}

#[derive(Copy, Clone, Debug)]
pub struct BodyRange {
    pub start: usize,
    pub length: usize,
    pub associated_value: AssociatedValue,
}

#[derive(Copy, Clone, Debug)]
pub enum AssociatedValue {
    Mention(ContactId),
    Style(BodyStyle),
}

// Source: https://github.com/signalapp/Signal-Android/blob/main/lib/libsignal-service/src/main/protowire/SignalService.proto#L981.
#[derive(Copy, Clone, Debug)]
pub enum BodyStyle {
    None,
    Bold,
    Italic,
    Spoiler,
    Strikethrough,
    Monospace,
}

#[derive(Clone, Debug)]
pub struct Quote {
    pub quoted_message: MessageId,
    pub body: Option<String>,
    pub attachments: Vec<AttachmentOrPointer>,
    pub body_ranges: Vec<BodyRange>,
    pub kind: QuoteKind,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum QuoteKind {
    Normal,
    GiftBadge,
    Poll,
}

#[derive(Clone, Debug)]
pub struct ReactionMessage {
    pub reaction_to_message: MessageId,
    pub emoji: String,
    pub operation: ReactionOperation,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ReactionOperation {
    Add,
    Remove,
}

#[derive(Clone, Debug)]
pub struct ReceiptMessage {
    pub message_ids: Vec<MessageId>,
    pub kind: ReceiptKind,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ReceiptKind {
    Delivery,
    Read,
    Viewed,
}

#[derive(Copy, Clone, Debug)]
pub struct TypingMessage {
    pub timestamp: DateTime<Utc>,
    pub action: TypingAction,
}

#[derive(Copy, Clone, Debug)]
pub enum TypingAction {
    Started,
    Stopped,
}

#[derive(Copy, Clone, Debug)]
pub struct DeletionMessage {
    pub deleted_message: MessageId,
}

#[derive(Clone, Debug)]
pub struct EditMessage {
    pub edited_message: MessageId,
    pub updated: TextMessage,
}

#[derive(Clone, Debug)]
pub struct CreatePollMessage {
    pub question: String,
    pub options: Vec<String>,
    pub allow_multiple: bool,
}

#[derive(Clone, Debug)]
pub struct VotePollMessage {
    pub vote_for: MessageId,
    pub options: Vec<u32>,
    // TODO: vote_count?
}

#[derive(Copy, Clone, Debug)]
pub struct TerminatePollMessage {
    pub poll: MessageId,
}

#[derive(Copy, Clone, Debug)]
pub struct PinMessage {
    pub message: MessageId,
    pub duration: Option<PinDuration>,
}

#[derive(Copy, Clone, Debug)]
pub enum PinDuration {
    Forever,
    Limited(Duration),
}

#[derive(Copy, Clone, Debug)]
pub struct UnpinMessage {
    pub message: MessageId,
}

#[derive(Copy, Clone, Debug)]
pub struct ContactChangeMessage {
    pub contact: ContactId,
    pub secrets: ContactSecrets,
}

#[derive(Copy, Clone, Debug)]
pub struct GroupChangeMessage {
    pub group: GroupId,
    pub secrets: GroupSecrets,
}

#[derive(Copy, Clone, Debug)]
pub struct StoryMessage {
    // TODO
}

#[derive(Copy, Clone, Debug)]
pub struct CallMessage {
    // TODO
}

#[derive(Copy, Clone, Debug)]
pub struct PaymentMessage {
    // TODO
}

#[derive(Clone, Debug)]
pub struct RegistrationData {
    pub account: AccountId,
    pub device_id: DeviceId,
    pub device_name: Option<String>,
    pub phone_number: PhoneNumber,
}

#[derive(Clone, Debug)]
pub struct Member {
    pub id: ContactId,
    pub role: Role,
    pub secrets: ContactSecrets,
    pub label: MemberLabel,
}

#[derive(Clone, Debug)]
pub struct MemberLabel {
    pub label: Option<String>,
    pub emoji: Option<String>,
}

impl Display for MemberLabel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match (self.label.as_ref(), self.emoji.as_ref()) {
            (None, None) => f.write_str(""),
            (None, Some(e)) => f.write_str(e),
            (Some(l), None) => f.write_str(l),
            (Some(l), Some(e)) => write!(f, "{e} {l}"),
        }
    }
}

#[derive(Clone, Debug)]
pub enum Role {
    Unknown,
    Default,
    Administrator,
}

#[derive(Clone, Debug)]
pub struct AttachmentPointer {
    pub id: AttachmentIdentifier,
    pub properties: AttachmentProperties,
    pub secrets: AttachmentPointerSecrets,
}

#[derive(Clone, Debug)]
pub struct AttachmentProperties {
    pub content_type: Option<String>,
    pub size: Option<u32>,
    pub file_name: Option<String>,
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub caption: Option<String>,
    pub blur_hash: Option<String>,
    pub uploaded: Option<DateTime<Utc>>,
    pub thumbnail: Option<Vec<u8>>,
}

#[derive(Clone, Debug)]
pub struct AttachmentPointerSecrets {
    pub client_uuid: Option<Vec<u8>>,
    pub key: Option<Vec<u8>>,
    pub digest: Option<Vec<u8>>,
    pub incremental_mac: Option<Vec<u8>>,
    pub chunk_size: Option<u32>,
    pub flags: Option<u32>,
    pub cdn_number: Option<u32>,
}

#[derive(Clone, Debug)]
pub struct Attachment {
    pub properties: AttachmentProperties,
    pub content: AttachmentData,
}

pub type AttachmentData = Vec<u8>;

#[derive(Clone, Debug)]
pub enum AttachmentOrPointer {
    Attachment(Attachment),
    Pointer(AttachmentPointer),
}

impl AttachmentOrPointer {
    pub fn properties(&self) -> &AttachmentProperties {
        match self {
            AttachmentOrPointer::Attachment(attachment) => &attachment.properties,
            AttachmentOrPointer::Pointer(attachment_pointer) => &attachment_pointer.properties,
        }
    }
}

#[derive(Clone, Debug)]
pub struct DeviceInfo {
    pub id: DeviceId,
    pub name: Option<String>,
    pub created_at: DateTime<Utc>,
    pub last_seen: DateTime<Utc>,
    pub secrets: DeviceInfoSecrets,
}

#[derive(Clone, Debug)]
pub struct DeviceInfoSecrets {
    pub registration_id: i32,
}

#[derive(Clone, Debug)]
pub struct CaptchaToken(pub String);
