import { AxiosRequestConfig } from 'axios'
import { ILogger } from '../Utils/logger'
import type { Readable } from 'stream'
import type { URL } from 'url'
import { proto } from '../../WAProto'
import { MEDIA_HKDF_KEY_MAPPING } from '../Defaults'
import { BinaryNode } from '../WABinary'
import type { GroupMetadata } from './GroupMetadata'
import { CacheStore } from './Socket'

export { proto as WAProto }

export type WAMessage = proto.IWebMessageInfo

export type WAMessageContent = proto.IMessage

export type WAContactMessage = proto.Message.IContactMessage

export type WAContactsArrayMessage = proto.Message.IContactsArrayMessage

export type WAMessageKey = proto.IMessageKey & {
    newsletter_server_id?: string
}

export type WATextMessage = proto.Message.IExtendedTextMessage

export type WAContextInfo = proto.IContextInfo

export type WAEventMessage = proto.Message.IEventMessage

export type WALocationMessage = proto.Message.ILocationMessage

export type WAOrderMessage = proto.Message.IOrderMessage

export type WAGenericMediaMessage = proto.Message.IVideoMessage | proto.Message.IImageMessage | proto.Message.IAudioMessage | proto.Message.IDocumentMessage | proto.Message.IStickerMessage

export declare const WAMessageStubType = proto.WebMessageInfo.StubType

export declare const WAMessageStatus = proto.WebMessageInfo.Status

export type WAMediaUpload = Buffer | {
    url: URL | string
} | {
    stream: Readable
}

/** Set of message types that are supported by the library */
export type MessageType = keyof proto.Message

export type DownloadableMessage = {
    mediaKey?: Uint8Array | null
    directPath?: string | null
    url?: string | null
}

export type MessageReceiptType = 'read' | 'read-self' | 'hist_sync' | 'peer_msg' | 'sender' | 'inactive' | 'played' | undefined

export type MediaConnInfo = {
    auth: string
    ttl: number
    hosts: {
        hostname: string
        maxContentLengthBytes: number
    }[]
    fetchDate: Date
}

export interface StickerPack {
    stickers: Sticker[]
    cover: WAMediaUpload
    name: string
    publisher: string
    description?: string
    packId?: string
}

export interface Carousel {
    image?: WAMediaUpload
    video?: WAMediaUpload
    product?: WASendableProduct
    title?: string
    body?: string
    footer?: string
    buttons?: proto.Message.InteractiveMessage.NativeFlowMessage.NativeFlowButton[]
}

export interface WAUrlInfo {
    'canonical-url': string
    'matched-text': string
    title: string
    description?: string
    jpegThumbnail?: Buffer
    highQualityThumbnail?: proto.Message.IImageMessage
    originalThumbnailUrl?: string
}

type Mentionable = {
    /** list of jids that are mentioned in the accompanying text */
    mentions?: string[]
}

type Contextable = {
    /** add contextInfo to the message */
    contextInfo?: proto.IContextInfo
}

type Ephemeral = {
    ephemeral?: boolean
}

type ViewOnce = {
    viewOnce?: boolean
}

type ViewOnceV2 = {
    viewOnceV2?: boolean
}

type ViewOnceV2Ext = {
    viewOnceV2Ext?: boolean
}

type Buttonable = {
    /** add buttons to the message  */
    buttons?: proto.Message.ButtonsMessage.IButton[]
}

type Templatable = {
    /** add buttons to the message (conflicts with normal buttons)*/
    templateButtons?: proto.IHydratedTemplateButton[]
    footer?: string
}

type Interactiveable = {
   /** add buttons to the message (conflicts with normal buttons)*/
   interactiveButtons?: proto.Message.InteractiveMessage.NativeFlowMessage.INativeFlowButton[]
   title?: string
   subtitle?: string
   footer?: string
   hasMediaAttachment?: boolean
}

type Shopable = {
   shop?: proto.Message.InteractiveMessage.ShopMessage
   title?: string
   subtitle?: string
   footer?: string
   hasMediaAttachment?: boolean
}

type Collectionable = {
   collection?: proto.Message.InteractiveMessage.CollectionMessage
   title?: string
   subtitle?: string
   footer?: string
   hasMediaAttachment?: boolean
}

type Listable = {
    /** Sections of the List */
    sections?: proto.Message.ListMessage.ISection[]
    /** Title of a List Message only */
    title?: string
    /** Text of the button on the list (required) */
    buttonText?: string
    /** ListType of a List Message only */
    listType?: proto.Message.ListMessage.ListType
}

type Cardsable = {
    cards?: Carousel[]
    title?: string
    subtitle?: string
    footer?: string
}

type Editable = {
    edit?: WAMessageKey
}

type WithDimensions = {
    width?: number
    height?: number
}

export type Sticker = { 
    data: WAMediaUpload
    emojis?: string[]
    accessibilityLabel?: string
}

export type PollMessageOptions = {
    name: string
    selectableCount?: number
    values: string[]
    /** 32 byte message secret to encrypt poll selections */
    messageSecret?: Uint8Array
    toAnnouncementGroup?: boolean
}

export type PollResultOptions = {
    name: string 
    values: string[]
}

type SharePhoneNumber = {
    sharePhoneNumber: boolean
}

type RequestPhoneNumber = {
    requestPhoneNumber: boolean
}

export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING

export type AnyMediaMessageContent = (({
    image: WAMediaUpload
    caption?: string
    jpegThumbnail?: string
} & Mentionable & Contextable & Buttonable & Templatable & Interactiveable & WithDimensions) | ({
    video: WAMediaUpload
    caption?: string
    gifPlayback?: boolean
    jpegThumbnail?: string
} & Mentionable & Contextable & Buttonable & Templatable & Interactiveable & WithDimensions) | {
    audio: WAMediaUpload
    /** if set to true, will send as a `voice note` */
    ptt?: boolean
    /** optionally tell the duration of the audio */
    seconds?: number
} | ({
    sticker: WAMediaUpload
    isAnimated?: boolean
} & WithDimensions) | ({
    document: WAMediaUpload
    mimetype: string
    fileName?: string
    caption?: string
} & Contextable & Buttonable & Templatable & Interactiveable )) & {
    mimetype?: string
} & Editable

export type ButtonReplyInfo = {
    displayText: string
    id: string
    index: number
}

export type GroupInviteInfo = {
    code: string
    expiration: number
    caption: string
    jid: string
    name: string
    jpegThumbnail?: string
}

export type CallCreationInfo = {
    time?: number
    type?: number
    name: string
}

export type AdminInviteInfo = {
    jid: string
    name: string
    caption: string
    expiration: number
    jpegThumbnail: string
}

export type PinInfo = {
    key: WAMessageKey 
    type?: number
    time?: number
}

export type KeepInfo = {
    key: WAMessageKey
    type: number
}

export type PaymentInfo = {
    note: string
    currency?: string
    offset?: number
    amount?: number
    expiry?: number
    from?: string
    image?: {
        placeholderArgb: number
        textArgb: number
        subtextArgb: number
    }
}

export type WASendableProduct = Omit<proto.Message.ProductMessage.IProductSnapshot, 'productImage'> & {
    productImage: WAMediaUpload
}

export type AnyRegularMessageContent = (({
    text: string
    linkPreview?: WAUrlInfo | null
} & Mentionable & Contextable & Buttonable & Templatable & Interactiveable & Shopable & Collectionable & Cardsable & Listable & Editable & WithDimensions) | AnyMediaMessageContent | ({
    poll: PollMessageOptions
} & Mentionable & Contextable & Buttonable & Templatable & Interactiveable & Shopable & Collectionable & Cardsable & Listable & Editable & WithDimensions) | {
    contacts: {
        displayName?: string
        contacts: proto.Message.IContactMessage[]
    }
} | {
    location: WALocationMessage
} | {
    react: proto.Message.IReactionMessage
} | {
    buttonReply: ButtonReplyInfo
    type: 'template' | 'plain' | 'list' | 'interactive'
} | {
     groupInvite: GroupInviteInfo
} | {
     pin: PinInfo
} | {
     keep: KeepInfo
} | {
     call: CallCreationInfo
} | {
     event: WAEventMessage
} | {
     adminInvite: AdminInviteInfo
} | {
     payment: PaymentInfo
} | {
    paymentInvite: {
       type: number 
       expiry: number
    }
} | {
    pollResult: PollResultOptions
} | {
    order: WAOrderMessage
} | {
    product: WASendableProduct
    businessOwnerJid?: string
    body?: string
    footer?: string
} | {
    stickerPack: StickerPack
} | SharePhoneNumber | RequestPhoneNumber) & ViewOnce & ViewOnceV2 & ViewOnceV2Ext

export type AnyMessageContent = AnyRegularMessageContent | {
    forward: WAMessage
    force?: boolean
} | {
    /** Delete your message or anyone's message in a group (admin required) */
    delete: WAMessageKey
} | {
    disappearingMessagesInChat: boolean | number
}

export type GroupMetadataParticipants = Pick<GroupMetadata, 'participants'>

type MinimalRelayOptions = {
    /** override the message ID with a custom provided string */
    messageId?: string
    /** should we use group metadata cache, or fetch afresh from the server default assumed to be "true" */
    useCachedGroupMetadata?: boolean
}

export type MessageRelayOptions = MinimalRelayOptions & {
    /** only send to a specific participant used when a message decryption fails for a single user */
    participant?: {
        jid: string
        count: number
    }
    /** additional attributes to add to the WA binary node */
    additionalAttributes?: {
        [_: string]: string
    }
    additionalNodes?: BinaryNode[]
    /** should we use the devices cache, or fetch afresh from the server default assumed to be "true" */
    useUserDevicesCache?: boolean
    /** jid list of participants for status@broadcast */
    statusJidList?: string[]
    newsletter?: boolean
}

export type MiscMessageGenerationOptions = MinimalRelayOptions & {
    /** optional, if you want to manually set the timestamp of the message */
    timestamp?: Date
    /** the message you want to quote */
    quoted?: WAMessage
    /** disappearing messages settings */
    ephemeralExpiration?: number | string
    /** timeout for media upload to WA server */
    mediaUploadTimeoutMs?: number
    /** jid list of participants for status@broadcast */
    statusJidList?: string[]
    /** backgroundcolor for status */
    backgroundColor?: string
    /** font type for status */
    font?: number
    /** if it is broadcast */
    broadcast?: boolean
    newsletter?: boolean
    additionalNodes?: BinaryNode[]
}

export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions & {
    userJid: string
}

export type WAMediaUploadFunctionOpts = {
    fileEncSha256B64: string
    mediaType: MediaType
    newsletter?: boolean
    timeoutMs?: number
}

export type WAMediaUploadFunction = (encFilePath: string | Buffer, opts: WAMediaUploadFunctionOpts) => Promise<{
    mediaUrl: string
    directPath: string
    handle?: string
}>

export type MediaGenerationOptions = {
    logger?: ILogger
    mediaTypeOverride?: MediaType
    upload: WAMediaUploadFunction
    /** cache media so it does not have to be uploaded again */
    mediaCache?: CacheStore
    mediaUploadTimeoutMs?: number
    options?: AxiosRequestConfig
    backgroundColor?: string
    font?: number
    /** The message is for newsletter? */
    newsletter?: boolean
}

export type MessageContentGenerationOptions = MediaGenerationOptions & {
    getUrlInfo?: (text: string) => Promise<WAUrlInfo | undefined>
    getProfilePicUrl?: (jid: string) => Promise<string | undefined>
}

export type MessageGenerationOptions = MessageContentGenerationOptions & MessageGenerationOptionsFromContent
/**
 * Type of message upsert
 * 1. notify => notify the user, this message was just received
 * 2. append => append the message to the chat history, no notification required
 */
export type MessageUpsertType = 'append' | 'notify'

export type MessageUserReceipt = proto.IUserReceipt

export type WAMessageUpdate = {
    update: Partial<WAMessage>
    key: proto.IMessageKey
}

export type WAMessageCursor = {
    before: WAMessageKey | undefined
} | {
    after: WAMessageKey | undefined
}

export type MessageUserReceiptUpdate = {
    key: proto.IMessageKey
    receipt: MessageUserReceipt
}

export type MediaDecryptionKeyInfo = {
    iv: Buffer
    cipherKey: Buffer
    macKey?: Buffer
}

export type MinimalMessage = Pick<proto.IWebMessageInfo, 'key' | 'messageTimestamp'>