/** Table declarations. */
import {Table} from 'ts-sql-query/Table';

import type {NonceHash, PublicKey} from '~/common/crypto';
import type {RawGroupCallKey} from '~/common/crypto/group-call';
import type {
    DbMessageReactionUid,
    DbContactUid,
    DbConversationUid,
    DbDistributionListUid,
    DbFileDataUid,
    DbGlobalPropertyUid,
    DbGroupMemberUid,
    DbGroupUid,
    DbMessageUid,
    DbNonceUid,
    DbMessageHistoryUid,
    DbStatusMessageUid,
    DbRunningGroupCallUid,
    DbPersistentProtocolStateUid,
    DbEmojiSkinToneUid,
    DbEmojiDataUid,
    DbPollUid,
    DbChoiceUid,
    DbVoteUid,
} from '~/common/db';
import type {
    StatusMessageType,
    AcquaintanceLevel,
    ActivityState,
    BlobDownloadState,
    ContactNotificationTriggerPolicy,
    ConversationCategory,
    ConversationVisibility,
    GlobalPropertyKey,
    GroupNotificationTriggerPolicy,
    GroupUserState,
    IdentityType,
    ImageRenderingType,
    MessageType,
    NonceScope,
    NotificationSoundPolicy,
    ReadReceiptPolicy,
    SyncState,
    TypingIndicatorPolicy,
    VerificationLevel,
    WorkVerificationLevel,
    PersistentProtocolStateType,
    PollMessageType,
    PollState,
    PollAnswerType,
    PollChoicesType,
    PollAnnounceType,
    PollDisplayMode,
} from '~/common/enum';
import type {FileEncryptionKey, FileId} from '~/common/file-storage';
import type {BlobId} from '~/common/network/protocol/blob';
import type {
    DistributionListId,
    EmojiReaction,
    FeatureMask,
    GroupId,
    IdentityString,
    MessageId,
    Nickname,
    PollId,
} from '~/common/network/types';
import type {RawBlobKey} from '~/common/network/types/keys';
import {CUSTOM_TYPES, type DBConnection} from '~/common/node/db/connection';
import type {f64, ReadonlyUint8Array, u8, u53} from '~/common/types';
import type {SingleUnicodeEmoji} from '~/common/utils/emoji';

/**
 * A contact.
 *
 * Constraints:
 *
 * - The identity must be unique
 * - The publicKey must be unique
 */
export const tContact = new (class TContact extends Table<DBConnection, 'TContact'> {
    public uid = this.autogeneratedPrimaryKey<DbContactUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.CONTACT_UID,
    );

    /**
     * The 8-character Threema ID of the contact.
     */
    public identity = this.column<IdentityString>('identity', 'custom', CUSTOM_TYPES.IDENTITY);

    /**
     * Public key bytes.
     */
    public publicKey = this.column<PublicKey>('publicKey', 'custom', CUSTOM_TYPES.PUBLIC_KEY);

    /**
     * Timestamp when this contact was first stored.
     */
    public createdAt = this.column('createdAt', 'localDateTime');

    /**
     * First name of the contact.
     */
    public firstName = this.column('firstName', 'string');

    /**
     * Last name of the contact.
     */
    public lastName = this.column('lastName', 'string');

    /**
     * Nickname of the contact (excluding the `~`).
     */
    public nickname = this.optionalColumn<Nickname>('nickname', 'custom', CUSTOM_TYPES.NICKNAME);

    /**
     * Verification level of the contact.
     */
    public verificationLevel = this.column<VerificationLevel>(
        'verificationLevel',
        'custom',
        CUSTOM_TYPES.VERIFICATION_LEVEL,
    );

    /**
     * Work verification level of the contact.
     */
    public workVerificationLevel = this.column<WorkVerificationLevel>(
        'workVerificationLevel',
        'custom',
        CUSTOM_TYPES.WORK_VERIFICATION_LEVEL,
    );

    /**
     * Identity type of the contact.
     */
    public identityType = this.column<IdentityType>(
        'identityType',
        'custom',
        CUSTOM_TYPES.IDENTITY_TYPE,
    );

    /**
     * Acquaintance level of the contact.
     */
    public acquaintanceLevel = this.column<AcquaintanceLevel>(
        'acquaintanceLevel',
        'custom',
        CUSTOM_TYPES.ACQUAINTANCE_LEVEL,
    );

    /**
     * Activity state of the contact.
     */
    public activityState = this.column<ActivityState>(
        'activityState',
        'custom',
        CUSTOM_TYPES.ACTIVITY_STATE,
    );

    /**
     * Features supported by this contact.
     */
    public featureMask = this.column<FeatureMask>(
        'featureMask',
        'custom',
        CUSTOM_TYPES.FEATURE_MASK,
    );

    /**
     * Contact synchronization state.
     */
    public syncState = this.column<SyncState>('syncState', 'custom', CUSTOM_TYPES.SYNC_STATE);

    /**
     * Typing indicator policy override for the contact.
     */
    public typingIndicatorPolicyOverride = this.optionalColumn<TypingIndicatorPolicy>(
        'typingIndicatorPolicyOverride',
        'custom',
        CUSTOM_TYPES.TYPING_INDICATOR_POLICY,
    );

    /**
     * Read receipt policy override for the contact.
     */
    public readReceiptPolicyOverride = this.optionalColumn<ReadReceiptPolicy>(
        'readReceiptPolicyOverride',
        'custom',
        CUSTOM_TYPES.READ_RECEIPT_POLICY,
    );

    /**
     * Notification trigger policy override for the contact.
     */
    public notificationTriggerPolicyOverride =
        this.optionalColumn<ContactNotificationTriggerPolicy>(
            'notificationTriggerPolicyOverride',
            'custom',
            CUSTOM_TYPES.CONTACT_NOTIFICATION_TRIGGER_POLICY,
        );

    /**
     * Notification trigger policy override expiration date for the contact.
     */
    public notificationTriggerPolicyOverrideExpiresAt = this.optionalColumn(
        'notificationTriggerPolicyOverrideExpiresAt',
        'localDateTime',
    );

    /**
     * Notification sound policy override for the contact.
     */
    public notificationSoundPolicyOverride = this.optionalColumn<NotificationSoundPolicy>(
        'notificationSoundPolicyOverride',
        'custom',
        CUSTOM_TYPES.NOTIFICATION_SOUND_POLICY,
    );

    /**
     * The color lookup index.
     */
    public colorIndex = this.column<u8>('colorIndex', 'custom', CUSTOM_TYPES.U8);

    /**
     * Profile picture set by the contact.
     */
    public profilePictureContactDefined = this.optionalColumn<ReadonlyUint8Array>(
        'profilePictureContactDefined',
        'custom',
        CUSTOM_TYPES.UINT8ARRAY,
    );

    /**
     * Profile picture set by the creator in the Threema Gateway control
     * panel. Only applicable to Threema Gateway IDs (starting with a `*`).
     */
    public profilePictureGatewayDefined = this.optionalColumn<ReadonlyUint8Array>(
        'profilePictureGatewayDefined',
        'custom',
        CUSTOM_TYPES.UINT8ARRAY,
    );

    /**
     * Profile picture set by the user.
     */
    public profilePictureUserDefined = this.optionalColumn<ReadonlyUint8Array>(
        'profilePictureUserDefined',
        'custom',
        CUSTOM_TYPES.UINT8ARRAY,
    );

    /**
     * Blob ID of the last profile picture sent to this contact.
     */
    public profilePictureBlobIdSent = this.optionalColumn<BlobId>(
        'profilePictureBlobIdSent',
        'custom',
        CUSTOM_TYPES.BLOB_ID,
    );

    public constructor() {
        super('contacts'); // Table name in the database
    }
})();

/**
 * A group.
 *
 * Constraints:
 *
 * - The (creatorIdentity, groupId) pair must be unique
 */
export const tGroup = new (class TGroup extends Table<DBConnection, 'TGroup'> {
    public uid = this.autogeneratedPrimaryKey<DbGroupUid>('uid', 'custom', CUSTOM_TYPES.GROUP_UID);

    /**
     * Contact UID of the creator of this group, 8 bytes. Is null iff the user is the creator.
     */
    public creatorUid = this.optionalColumn<DbContactUid>(
        'creatorUid',
        'custom',
        CUSTOM_TYPES.CONTACT_UID,
    );

    /**
     * Group ID, 8 bytes.
     */
    public groupId = this.column<GroupId>('groupId', 'custom', CUSTOM_TYPES.GROUP_ID);

    /**
     * Group name. May be an empty string.
     */
    public name = this.column('name', 'string');

    /**
     * Timestamp when this group was first stored.
     */
    public createdAt = this.column('createdAt', 'localDateTime');

    /**
     * The user's state within the group.
     */
    public userState = this.column<GroupUserState>(
        'userState',
        'custom',
        CUSTOM_TYPES.GROUP_USER_STATE,
    );

    /**
     * Notification trigger policy override for the group.
     */
    public notificationTriggerPolicyOverride = this.optionalColumn<GroupNotificationTriggerPolicy>(
        'notificationTriggerPolicyOverride',
        'custom',
        CUSTOM_TYPES.GROUP_NOTIFICATION_TRIGGER_POLICY,
    );

    /**
     * Notification trigger policy override expiration date for the group.
     */
    public notificationTriggerPolicyOverrideExpiresAt = this.optionalColumn(
        'notificationTriggerPolicyOverrideExpiresAt',
        'localDateTime',
    );

    /**
     * Notification sound policy override for the group.
     */
    public notificationSoundPolicyOverride = this.optionalColumn<NotificationSoundPolicy>(
        'notificationSoundPolicyOverride',
        'custom',
        CUSTOM_TYPES.NOTIFICATION_SOUND_POLICY,
    );

    /**
     * The color lookup index.
     */
    public colorIndex = this.column<u8>('colorIndex', 'custom', CUSTOM_TYPES.U8);

    /**
     * Profile picture set by the group admin.
     */
    public profilePictureAdminDefined = this.optionalColumn<ReadonlyUint8Array>(
        'profilePictureAdminDefined',
        'custom',
        CUSTOM_TYPES.UINT8ARRAY,
    );

    public constructor() {
        super('groups'); // Table name in the database
    }
})();

/**
 * A group membership.
 */
export const tGroupMember = new (class TGropupMembers extends Table<DBConnection, 'TGroupMember'> {
    public uid = this.autogeneratedPrimaryKey<DbGroupMemberUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.GROUP_MEMBER_UID,
    );

    /**
     * Group UID, 8 bytes.
     */
    public groupUid = this.column<DbGroupUid>('groupUid', 'custom', CUSTOM_TYPES.GROUP_UID);

    /**
     * Contact UID, 8 bytes.
     */
    public contactUid = this.column<DbContactUid>('contactUid', 'custom', CUSTOM_TYPES.CONTACT_UID);

    public constructor() {
        super('groupMembers'); // Table name in the database
    }
})();

/**
 * A distribution list.
 */
export const tDistributionList = new (class TDistributionList extends Table<
    DBConnection,
    'TDistributionList'
> {
    public uid = this.primaryKey<DbDistributionListUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.DISTRIBUTION_LIST_UID,
    );

    // TODO(DESK-334): Add distribution list ID

    /**
     * Distribution list ID, 8 bytes.
     */
    public distributionListId = this.column<DistributionListId>(
        'distributionListId',
        'custom',
        CUSTOM_TYPES.DISTRIBUTION_LIST_ID,
    );

    /**
     * Distribution list name. May be an empty string.
     */
    public name = this.column('name', 'string');

    /**
     * Timestamp when this distribution list was first stored.
     */
    public createdAt = this.column('createdAt', 'localDateTime');

    /**
     * The color lookup index.
     */
    public colorIndex = this.column<u8>('colorIndex', 'custom', CUSTOM_TYPES.U8);

    public constructor() {
        super('distributionLists'); // Table name in the database
    }
})();

/**
 * A conversation.
 *
 * Constraints:
 *
 * - Of the three fields (contactUid, groupUid, distributionListUid) exactly one must be set
 * - All three fields (contactUid, groupUid, distributionListUid) must be unique
 */
export const tConversation = new (class TConversation extends Table<DBConnection, 'TConversation'> {
    public uid = this.autogeneratedPrimaryKey<DbConversationUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.CONVERSATION_UID,
    );

    /**
     * Timestamp of the last conversation update (e.g. due to an inbound or outbound message).
     * If not provided, the conversation should not be displayed.
     *
     * This timestamp should never go back in time. For example, if the last incoming message is
     * deleted, the `lastUpdate` timestamp of that message still remains stored.
     */
    public lastUpdate = this.optionalColumn('lastUpdate', 'localDateTime');

    /**
     * Contact associated with this conversation (if any).
     */
    public contactUid = this.optionalColumn<DbContactUid>(
        'contactUid',
        'custom',
        CUSTOM_TYPES.CONTACT_UID,
    );

    /**
     * Group associated with this conversation (if any).
     */
    public groupUid = this.optionalColumn<DbGroupUid>('groupUid', 'custom', CUSTOM_TYPES.GROUP_UID);

    /**
     * Distribution list associated with this conversation (if any).
     */
    public distributionListUid = this.optionalColumn<DbDistributionListUid>(
        'distributionListUid',
        'custom',
        CUSTOM_TYPES.DISTRIBUTION_LIST_UID,
    );

    /**
     * Category of the conversation.
     */
    public category = this.column<ConversationCategory>(
        'category',
        'custom',
        CUSTOM_TYPES.CONVERSATION_CATEGORY,
    );

    /**
     * Visibility of the conversation.
     */
    public visibility = this.column<ConversationVisibility>(
        'visibility',
        'custom',
        CUSTOM_TYPES.CONVERSATION_VISIBILITY,
    );

    public constructor() {
        super('conversations'); // Table name in the database
    }
})();

/**
 * A message in a conversation.
 *
 * Constraints:
 *
 * - Either none or both (lastReaction, lastReactionAt) fields must be set
 * - The contactUid may only be null for outgoing messages
 */
export const tMessage = new (class TMessage extends Table<DBConnection, 'TMessage'> {
    public uid = this.autogeneratedPrimaryKey<DbMessageUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.MESSAGE_UID,
    );

    /**
     * The CSP message ID.
     *
     * Note: This is stored as a BLOB and not an INTEGER because SQLite only has signed integers. If
     *       we wanted to store unsigned 64bit integers in an INTEGER field, we'd have to convert
     *       them to signed integers (Java style), which is really ugly. Instead, we store the raw
     *       bytes (in little-endian order).
     */
    public messageId = this.column<MessageId>('messageId', 'custom', CUSTOM_TYPES.MESSAGE_ID);

    /**
     * The contact that sent this message.
     *
     * Note: If provided, this is an inbound message. If not provided, this is
     *       an outbound message.
     */
    public senderContactUid = this.optionalColumn<DbContactUid>(
        'senderContactUid',
        'custom',
        CUSTOM_TYPES.CONTACT_UID,
    );

    /**
     * The conversation associated with this message.
     */
    public conversationUid = this.column<DbConversationUid>(
        'conversationUid',
        'custom',
        CUSTOM_TYPES.CONVERSATION_UID,
    );

    /**
     * Timestamp for when the message...
     *
     * - Outbound: ...has been created on the local device.
     * - Inbound: ...has been created on the remote device.
     *
     * Note: For inbound messages, this timestamp may have an arbitrary value as it's controlled by
     *       the sender.
     */
    public createdAt = this.column('createdAt', 'localDateTime');

    /**
     * Alias for `createdAt`, but returns numeric timestamp (unix timestamp in milliseconds) instead
     * of a `Date`.
     */
    public createdAtTimestamp = this.virtualColumnFromFragment<u53>(
        'customInt',
        CUSTOM_TYPES.U53,
        (fragment) => fragment.sql`${this.createdAt}`,
    );

    /**
     * Optional timestamp for when the message...
     *
     * - Outbound: ...has been delivered to and acknowledged by the chat server.
     * - Inbound: ...has been received from the chat server and reflected to the mediator server by
     *     the leader device.
     *
     * Note: The value is always known for inbound messages but not known until acknowledged for
     *       outbound messages.
     *
     * Note: In the models, this is split up into the virtual fields `sentAt` and `receivedAt`,
     *       depending on the message direction.
     */
    public processedAt = this.optionalColumn('processedAt', 'localDateTime');

    /**
     * Alias for `processedAt`, but returns numeric timestamp (unix timestamp in milliseconds)
     * instead of a `Date`.
     */
    public processedAtTimestamp = this.virtualColumnFromFragment<u53>(
        'customInt',
        CUSTOM_TYPES.U53,
        (fragment) => fragment.sql`${this.processedAt}`,
    );

    /**
     * Optional timestamp for when the outbound message has been delivered to the recipient and the
     * 'received' delivery receipt was reflected to the mediator server.
     *
     * Note: This value is never set for inbound messages.
     */
    public deliveredAt = this.optionalColumn('deliveredAt', 'localDateTime');

    /**
     * Optional timestamp for when...
     *
     * - Outbound: ...the 'read' delivery receipt message or the 'OutgoingMessageUpdate' with
     *     'update=read' has been reflected to the mediator server.
     * - Inbound: ...the 'read' delivery receipt message has been reflected to the mediator server
     *     by the leader device.
     */
    public readAt = this.optionalColumn('readAt', 'localDateTime');

    /** Optional timestamp for when message was edited.*/
    public lastEditedAt = this.optionalColumn('lastEditedAt', 'localDateTime');

    /** Optional timestamp for when the message was deleted. Defaults to null if this message was not deleted */
    public deletedAt = this.optionalColumn('deletedAt', 'localDateTime');
    /**
     * Unparsed raw body. Only provided for inbound messages.
     */
    public raw = this.optionalColumn<ReadonlyUint8Array>('raw', 'custom', CUSTOM_TYPES.UINT8ARRAY);

    /**
     * Message type, e.g. "text" or "location".
     *
     * Note: Storing this as TEXT instead of an INTEGER because it makes joined queries much easier
     *       to read.
     */
    public messageType = this.column<MessageType>(
        'messageType',
        'custom',
        CUSTOM_TYPES.MESSAGE_TYPE,
    );

    /**
     * Incrementing thread ID used for sorting.
     */
    public threadId = this.column('threadId', 'bigint');

    public constructor() {
        super('messages'); // Table name in the database
    }
})();

/** The message history for every message */
export const tMessageHistory = new (class TMessageHistory extends Table<
    DBConnection,
    'TMessageHistory'
> {
    public uid = this.autogeneratedPrimaryKey<DbMessageHistoryUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.MESSAGE_HISTORY_UID,
    );

    /** The timestamp at which this version was created */
    public editedAt = this.column('editedAt', 'localDateTime');

    /** The text of this version */
    public text = this.optionalColumn('text', 'string');

    /** The message UID. */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);

    public constructor() {
        super('messageHistory');
    }
})();

/**
 * Message data associated with text messages.
 */
export const tMessageTextData = new (class TMessageTextData extends Table<
    DBConnection,
    'TMessageTextData'
> {
    public uid = this.autogeneratedPrimaryKey('uid', 'bigint');

    /**
     * The message UID.
     */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);

    /**
     * The message text.
     */
    public text = this.column('text', 'string');

    /**
     * The optional quoted message id.
     */
    public quotedMessageId = this.optionalColumn<MessageId>(
        'quotedMessageId',
        'custom',
        CUSTOM_TYPES.MESSAGE_ID,
    );

    public constructor() {
        super('messageTextData'); // Table name in the database
    }
})();

/**
 * Message data associated with poll messages.
 */
export const tMessagePollData = new (class TMessagePollData extends Table<
    DBConnection,
    'TMessagePollData'
> {
    public uid = this.autogeneratedPrimaryKey('uid', 'bigint');

    /**
     * The message UID.
     */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);

    /**
     * Poll type.
     */
    public type = this.column<PollMessageType>('type', 'custom', CUSTOM_TYPES.POLL_MESSAGE_TYPE);

    /**
     * The poll UID.
     */
    public pollUid = this.column<DbPollUid>('pollUid', 'custom', CUSTOM_TYPES.POLL_UID);

    public constructor() {
        super('messagePollData'); // Table name in the database
    }
})();

export const tPolls = new (class TPolls extends Table<DBConnection, 'TPolls'> {
    public uid = this.autogeneratedPrimaryKey<DbPollUid>('uid', 'custom', CUSTOM_TYPES.POLL_UID);

    public pollId = this.column<PollId>('pollId', 'custom', CUSTOM_TYPES.POLL_ID);

    /**
     * The conversation associated with this poll.
     */
    public conversationUid = this.column<DbConversationUid>(
        'conversationUid',
        'custom',
        CUSTOM_TYPES.CONVERSATION_UID,
    );

    /**
     * Identity associated with this poll.
     */
    public pollCreatorIdentity = this.column<IdentityString>(
        'pollCreatorIdentity',
        'custom',
        CUSTOM_TYPES.IDENTITY,
    );

    public createdAt = this.column('createdAt', 'localDateTime');

    /**
     * The poll description.
     */
    public description = this.column('description', 'string');

    /**
     * Poll state.
     */
    public pollState = this.column<PollState>('pollState', 'custom', CUSTOM_TYPES.POLL_STATE);

    /**
     * Poll answerType.
     */
    public answerType = this.column<PollAnswerType>(
        'answerType',
        'custom',
        CUSTOM_TYPES.POLL_ANSWER_TYPE,
    );

    /**
     * Poll announceType.
     */
    public announceType = this.column<PollAnnounceType>(
        'announceType',
        'custom',
        CUSTOM_TYPES.POLL_ANNOUNCE_TYPE,
    );

    /**
     * Poll choicesType.
     */
    public choicesType = this.column<PollChoicesType>(
        'choicesType',
        'custom',
        CUSTOM_TYPES.POLL_CHOICES_TYPE,
    );

    /**
     * Poll displayMode.
     */
    public displayMode = this.column<PollDisplayMode>(
        'displayMode',
        'custom',
        CUSTOM_TYPES.POLL_DISPLAY_MODE,
    );

    public constructor() {
        super('polls'); // Table name in the database
    }
})();

export const tPollChoices = new (class TPollChoices extends Table<DBConnection, 'TPollChoices'> {
    public uid = this.autogeneratedPrimaryKey<DbChoiceUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.CHOICE_UID,
    );

    /**
     * Referenced poll UID.
     */
    public pollUid = this.column<DbPollUid>('pollUid', 'custom', CUSTOM_TYPES.POLL_UID);

    /**
     * The per-poll unique choice ID.
     */
    public choiceId = this.column('choiceId', 'int');

    /**
     * The choice description.
     */
    public description = this.column('description', 'string');

    /**
     * The index of the choice object within the choices list.
     */
    public sortKey = this.column('sortKey', 'int');

    /**
     * The total amount of votes for this choice
     */
    public totalAmountVotes = this.column('totalAmountVotes', 'int');

    public constructor() {
        super('pollChoices'); // Table name in the database
    }
})();

export const tPollVotes = new (class TPollVotes extends Table<DBConnection, 'TPollVotes'> {
    public uid = this.autogeneratedPrimaryKey<DbVoteUid>('uid', 'custom', CUSTOM_TYPES.VOTE_UID);

    /**
     * The senderIdentity that voted
     */
    public senderIdentity = this.column<IdentityString>(
        'senderIdentity',
        'custom',
        CUSTOM_TYPES.IDENTITY,
    );

    public choiceUid = this.column<DbChoiceUid>('choiceUid', 'custom', CUSTOM_TYPES.CHOICE_UID);

    public selected = this.column<boolean>('selected', 'custom', CUSTOM_TYPES.BOOLEAN);

    public constructor() {
        super('pollVotes'); // Table name in the database
    }
})();

/**
 * Message data associated with non-media file messages.
 */
export const tMessageFileData = new (class TMessageFileData extends Table<
    DBConnection,
    'TMessageFileData'
> {
    public uid = this.autogeneratedPrimaryKey('uid', 'bigint');
    /**
     * The message UID.
     */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);
    /**
     * Original file Blob ID (used for downloading).
     */
    public blobId = this.optionalColumn<BlobId>('blobId', 'custom', CUSTOM_TYPES.BLOB_ID);
    /**
     * Original thumbnail Blob ID (used for downloading).
     */
    public thumbnailBlobId = this.optionalColumn<BlobId>(
        'thumbnailBlobId',
        'custom',
        CUSTOM_TYPES.BLOB_ID,
    );
    /**
     * The download state for the file blob.
     *
     * Right now, this can only be 'failed' or undefined.
     */
    public blobDownloadState = this.optionalColumn<BlobDownloadState>(
        'blobDownloadState',
        'custom',
        CUSTOM_TYPES.BLOB_DOWNLOAD_STATE,
    );
    /**
     * The download state for the thumbnail blob.
     *
     * Right now, this can only be 'failed' or undefined.
     */
    public thumbnailBlobDownloadState = this.optionalColumn<BlobDownloadState>(
        'thumbnailBlobDownloadState',
        'custom',
        CUSTOM_TYPES.BLOB_DOWNLOAD_STATE,
    );
    /**
     * Encryption key used to decrypt the file and thumbnail blobs.
     */
    public encryptionKey = this.column<RawBlobKey>(
        'encryptionKey',
        'custom',
        CUSTOM_TYPES.BLOB_KEY,
    );
    /**
     * File data UID.
     */
    public fileDataUid = this.optionalColumn<DbFileDataUid>(
        'fileDataUid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );
    /**
     * Thumbnail file data UID.
     */
    public thumbnailFileDataUid = this.optionalColumn<DbFileDataUid>(
        'thumbnailFileDataUid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );
    /**
     * File media type (formerly known as MIME type).
     */
    public mediaType = this.column('mediaType', 'string');
    /**
     * Thumbnail media type (formerly known as MIME type).
     */
    public thumbnailMediaType = this.optionalColumn('thumbnailMediaType', 'string');
    /**
     * The original file name.
     */
    public fileName = this.optionalColumn('fileName', 'string');
    /**
     * The original file size in bytes.
     */
    public fileSize = this.column<u53>('fileSize', 'custom', CUSTOM_TYPES.U53);
    /**
     * Optional caption text.
     */
    public caption = this.optionalColumn('caption', 'string');
    /**
     * Optional Correlation ID.
     */
    public correlationId = this.optionalColumn('correlationId', 'string');

    public constructor() {
        super('messageFileData'); // Table name in the database
    }
})();

/**
 * Message data associated with image messages.
 */
export const tMessageImageData = new (class TMessageImageData extends Table<
    DBConnection,
    'TMessageImageData'
> {
    public uid = this.autogeneratedPrimaryKey('uid', 'bigint');
    /**
     * The message UID.
     */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);
    /**
     * Original file Blob ID (used for downloading).
     */
    public blobId = this.optionalColumn<BlobId>('blobId', 'custom', CUSTOM_TYPES.BLOB_ID);
    /**
     * Original thumbnail Blob ID (used for downloading).
     */
    public thumbnailBlobId = this.optionalColumn<BlobId>(
        'thumbnailBlobId',
        'custom',
        CUSTOM_TYPES.BLOB_ID,
    );
    /**
     * The download state for the file blob.
     *
     * Right now, this can only be 'failed' or undefined.
     */
    public blobDownloadState = this.optionalColumn<BlobDownloadState>(
        'blobDownloadState',
        'custom',
        CUSTOM_TYPES.BLOB_DOWNLOAD_STATE,
    );
    /**
     * The download state for the thumbnail blob.
     *
     * Right now, this can only be 'failed' or undefined.
     */
    public thumbnailBlobDownloadState = this.optionalColumn<BlobDownloadState>(
        'thumbnailBlobDownloadState',
        'custom',
        CUSTOM_TYPES.BLOB_DOWNLOAD_STATE,
    );
    /**
     * Encryption key used to decrypt the file and thumbnail blobs.
     */
    public encryptionKey = this.column<RawBlobKey>(
        'encryptionKey',
        'custom',
        CUSTOM_TYPES.BLOB_KEY,
    );
    /**
     * File data UID.
     */
    public fileDataUid = this.optionalColumn<DbFileDataUid>(
        'fileDataUid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );
    /**
     * Thumbnail file data UID.
     */
    public thumbnailFileDataUid = this.optionalColumn<DbFileDataUid>(
        'thumbnailFileDataUid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );
    /**
     * File media type (formerly known as MIME type).
     */
    public mediaType = this.column('mediaType', 'string');
    /**
     * Thumbnail media type (formerly known as MIME type).
     */
    public thumbnailMediaType = this.optionalColumn('thumbnailMediaType', 'string');
    /**
     * The original file name.
     */
    public fileName = this.optionalColumn('fileName', 'string');
    /**
     * The original file size in bytes.
     */
    public fileSize = this.column<u53>('fileSize', 'custom', CUSTOM_TYPES.U53);
    /**
     * Optional caption text.
     */
    public caption = this.optionalColumn('caption', 'string');
    /**
     * Optional Correlation ID.
     */
    public correlationId = this.optionalColumn('correlationId', 'string');
    /**
     * Rendering type.
     *
     * 1: Regular image, 2: Sticker
     */
    public renderingType = this.column<ImageRenderingType>(
        'renderingType',
        'custom',
        CUSTOM_TYPES.IMAGE_RENDERING_TYPE,
    );
    /**
     * Whether this is an animated image.
     */
    public animated = this.column<boolean>('animated', 'custom', CUSTOM_TYPES.BOOLEAN);
    /**
     * Optional image height (in px).
     */
    public height = this.optionalColumn<u53>('height', 'custom', CUSTOM_TYPES.U53);
    /**
     * Optional image width (in px).
     */
    public width = this.optionalColumn<u53>('width', 'custom', CUSTOM_TYPES.U53);

    public constructor() {
        super('messageImageData'); // Table name in the database
    }
})();

/**
 * Message data associated with video messages.
 */
export const tMessageVideoData = new (class TMessageVideoData extends Table<
    DBConnection,
    'TMessageVideoData'
> {
    public uid = this.autogeneratedPrimaryKey('uid', 'bigint');
    /**
     * The message UID.
     */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);
    /**
     * Original file Blob ID (used for downloading).
     */
    public blobId = this.optionalColumn<BlobId>('blobId', 'custom', CUSTOM_TYPES.BLOB_ID);
    /**
     * Original thumbnail Blob ID (used for downloading).
     */
    public thumbnailBlobId = this.optionalColumn<BlobId>(
        'thumbnailBlobId',
        'custom',
        CUSTOM_TYPES.BLOB_ID,
    );
    /**
     * The download state for the file blob.
     *
     * Right now, this can only be 'failed' or undefined.
     */
    public blobDownloadState = this.optionalColumn<BlobDownloadState>(
        'blobDownloadState',
        'custom',
        CUSTOM_TYPES.BLOB_DOWNLOAD_STATE,
    );
    /**
     * The download state for the thumbnail blob.
     *
     * Right now, this can only be 'failed' or undefined.
     */
    public thumbnailBlobDownloadState = this.optionalColumn<BlobDownloadState>(
        'thumbnailBlobDownloadState',
        'custom',
        CUSTOM_TYPES.BLOB_DOWNLOAD_STATE,
    );
    /**
     * Encryption key used to decrypt the file and thumbnail blobs.
     */
    public encryptionKey = this.column<RawBlobKey>(
        'encryptionKey',
        'custom',
        CUSTOM_TYPES.BLOB_KEY,
    );
    /**
     * File data UID.
     */
    public fileDataUid = this.optionalColumn<DbFileDataUid>(
        'fileDataUid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );
    /**
     * Thumbnail file data UID.
     */
    public thumbnailFileDataUid = this.optionalColumn<DbFileDataUid>(
        'thumbnailFileDataUid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );
    /**
     * File media type (formerly known as MIME type).
     */
    public mediaType = this.column('mediaType', 'string');
    /**
     * Thumbnail media type (formerly known as MIME type).
     */
    public thumbnailMediaType = this.optionalColumn('thumbnailMediaType', 'string');
    /**
     * The original file name.
     */
    public fileName = this.optionalColumn('fileName', 'string');
    /**
     * The original file size in bytes.
     */
    public fileSize = this.column<u53>('fileSize', 'custom', CUSTOM_TYPES.U53);
    /**
     * Optional caption text.
     */
    public caption = this.optionalColumn('caption', 'string');
    /**
     * Optional Correlation ID.
     */
    public correlationId = this.optionalColumn('correlationId', 'string');
    /**
     * Optional image height (in px).
     */
    public height = this.optionalColumn<u53>('height', 'custom', CUSTOM_TYPES.U53);
    /**
     * Optional image width (in px).
     */
    public width = this.optionalColumn<u53>('width', 'custom', CUSTOM_TYPES.U53);
    /**
     * Optional duration (in seconds).
     */
    public duration = this.optionalColumn<f64>('durationSeconds', 'custom', CUSTOM_TYPES.F64);

    public constructor() {
        super('messageVideoData'); // Table name in the database
    }
})();

/**
 * Message data associated with audio messages.
 */
export const tMessageAudioData = new (class TMessageAudioData extends Table<
    DBConnection,
    'TMessageAudioData'
> {
    public uid = this.autogeneratedPrimaryKey('uid', 'bigint');
    /**
     * The message UID.
     */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);
    /**
     * Original file Blob ID (used for downloading).
     */
    public blobId = this.optionalColumn<BlobId>('blobId', 'custom', CUSTOM_TYPES.BLOB_ID);
    /**
     * The download state for the file blob.
     *
     * Right now, this can only be 'failed' or undefined.
     */
    public blobDownloadState = this.optionalColumn<BlobDownloadState>(
        'blobDownloadState',
        'custom',
        CUSTOM_TYPES.BLOB_DOWNLOAD_STATE,
    );
    /**
     * Encryption key used to decrypt the file and thumbnail blobs.
     */
    public encryptionKey = this.column<RawBlobKey>(
        'encryptionKey',
        'custom',
        CUSTOM_TYPES.BLOB_KEY,
    );
    /**
     * File data UID.
     */
    public fileDataUid = this.optionalColumn<DbFileDataUid>(
        'fileDataUid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );
    /**
     * File media type (formerly known as MIME type).
     */
    public mediaType = this.column('mediaType', 'string');
    /**
     * The original file name.
     */
    public fileName = this.optionalColumn('fileName', 'string');
    /**
     * The original file size in bytes.
     */
    public fileSize = this.column<u53>('fileSize', 'custom', CUSTOM_TYPES.U53);
    /**
     * Optional caption text.
     */
    public caption = this.optionalColumn('caption', 'string');
    /**
     * Optional Correlation ID.
     */
    public correlationId = this.optionalColumn('correlationId', 'string');
    /**
     * Optional duration (in seconds).
     */
    public duration = this.optionalColumn<f64>('durationSeconds', 'custom', CUSTOM_TYPES.F64);

    public constructor() {
        super('messageAudioData'); // Table name in the database
    }
})();

/**
 * Metadata about stored files.
 */
export const tFileData = new (class TFileData extends Table<DBConnection, 'TFileData'> {
    public uid = this.autogeneratedPrimaryKey<DbFileDataUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.FILE_DATA_UID,
    );

    /**
     * File ID assigned by the file storage.
     */
    public fileId = this.column<FileId>('fileId', 'custom', CUSTOM_TYPES.FILE_ID);

    /**
     * File encryption key (must be different for every file).
     */
    public encryptionKey = this.column<FileEncryptionKey>(
        'encryptionKey',
        'custom',
        CUSTOM_TYPES.FILE_ENCRYPTION_KEY,
    );

    /**
     * Unencrypted file size in bytes.
     */
    public unencryptedByteCount = this.column<u53>(
        'unencryptedByteCount',
        'custom',
        CUSTOM_TYPES.U53,
    );

    /**
     * Storage format version.
     */
    public storageFormatVersion = this.column<u53>(
        'storageFormatVersion',
        'custom',
        CUSTOM_TYPES.U53,
    );

    public constructor() {
        super('fileData'); // Table name in the database
    }
})();

/**
 * Reactions to a message
 *
 * The combination (messageUid, senderIdentity) must always be unique.
 */
export const tMessageReaction = new (class TMessageReaction extends Table<
    DBConnection,
    'TMessageReaction'
> {
    public uid = this.autogeneratedPrimaryKey<DbMessageReactionUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.MESSAGE_REACTION_UID,
    );

    /**
     * Time stamp of this reaction
     */
    public reactionAt = this.column('reactionAt', 'localDateTime');

    /**
     * The type of the reaction
     */
    public reaction = this.column<EmojiReaction>('reaction', 'custom', CUSTOM_TYPES.EMOJI_REACTION);

    /**
     * The sender of the reaction
     */
    public senderIdentity = this.column<IdentityString>(
        'senderIdentity',
        'custom',
        CUSTOM_TYPES.IDENTITY,
    );

    /**
     * The message that the reaction belongs to
     */
    public messageUid = this.column<DbMessageUid>('messageUid', 'custom', CUSTOM_TYPES.MESSAGE_UID);

    public constructor() {
        super('messageReactions');
    }
})();

/**
 * Key-value-based settings storage.
 */
export const tSettings = new (class TSettings extends Table<DBConnection, 'TSettings'> {
    public uid = this.autogeneratedPrimaryKey('uid', 'bigint');

    /**
     * The settings category.
     */
    public category = this.column('category', 'string');

    /**
     * The bytes of the category settings encoded with protobuf.
     */
    public settingsBytes = this.column<Uint8Array>(
        'settingsBytes',
        'custom',
        CUSTOM_TYPES.UINT8ARRAY,
    );

    public constructor() {
        super('settings'); // Table name in the database
    }
})();

export const tPersistentProtocolState = new (class TPersistentProtocolState extends Table<
    DBConnection,
    'TPersistentProtocolState'
> {
    public uid = this.autogeneratedPrimaryKey<DbPersistentProtocolStateUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.PERSISTENT_PROTOCOL_STATE_UID,
    );

    /**
     * Persistent protocol state type.
     */
    public type = this.column<PersistentProtocolStateType>(
        'type',
        'custom',
        CUSTOM_TYPES.PERSISTENT_PROTOCOL_STATE_TYPE,
    );

    /**
     * Timestamp for when the entry has been created on the local device.
     */
    public createdAt = this.column('createdAt', 'localDateTime');

    /**
     * Bytes of a protobuf-encoded protocol state.
     */
    public stateBytes = this.column<ReadonlyUint8Array>(
        'stateBytes',
        'custom',
        CUSTOM_TYPES.UINT8ARRAY,
    );

    public constructor() {
        super('persistentProtocolState');
    }
})();

/**
 * A status message in a conversation.
 */
export const tStatusMessage = new (class TStatusMessages extends Table<
    DBConnection,
    'TStatusMessages'
> {
    public uid = this.autogeneratedPrimaryKey<DbStatusMessageUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.STATUS_MESSAGE_UID,
    );

    /**
     * The conversation associated with this status message.
     */
    public conversationUid = this.column<DbConversationUid>(
        'conversationUid',
        'custom',
        CUSTOM_TYPES.CONVERSATION_UID,
    );

    /**
     * Status message type.
     */
    public type = this.column<StatusMessageType>(
        'type',
        'custom',
        CUSTOM_TYPES.STATUS_MESSAGE_TYPE,
    );

    /**
     * Bytes of a protobuf-encoded status message (e.g. {@link GroupMemberChange} or
     * {@link GroupNameChange}).
     */
    public statusBytes = this.column<ReadonlyUint8Array>(
        'statusBytes',
        'custom',
        CUSTOM_TYPES.UINT8ARRAY,
    );

    /**
     * Timestamp for when the message has been created on the local device.
     */
    public createdAt = this.column('createdAt', 'localDateTime');

    /**
     * Alias for `createdAt`, but returns numeric timestamp (unix timestamp in milliseconds) instead
     * of a `Date`.
     */
    public createdAtTimestamp = this.virtualColumnFromFragment<u53>(
        'customInt',
        CUSTOM_TYPES.U53,
        (fragment) => fragment.sql`${this.createdAt}`,
    );

    public constructor() {
        super('statusMessages');
    }
})();

export const tEmojiSkinTones = new (class EmojiSkinTones extends Table<
    DBConnection,
    'TEmojiSkinTones'
> {
    public uid = this.autogeneratedPrimaryKey<DbEmojiSkinToneUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.EMOJI_SKIN_TONE_UID,
    );

    public baseEmoji = this.column<SingleUnicodeEmoji>(
        'baseEmoji',
        'custom',
        CUSTOM_TYPES.UNICODE_EMOJI,
    );

    public preferredSkinToneEmoji = this.column<SingleUnicodeEmoji>(
        'preferredSkinToneEmoji',
        'custom',
        CUSTOM_TYPES.UNICODE_EMOJI,
    );

    public constructor() {
        super('emojiSkinTones'); // Table name in the database
    }
})();

export const tEmojiData = new (class EmojiData extends Table<DBConnection, 'TEmojiData'> {
    public uid = this.autogeneratedPrimaryKey<DbEmojiDataUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.EMOJI_DATA_UID,
    );

    public emoji = this.column<SingleUnicodeEmoji>('emoji', 'custom', CUSTOM_TYPES.UNICODE_EMOJI);

    public lastUsedAt = this.column('lastUsedAt', 'localDateTime');

    public nUsed = this.column<u53>('nUsed', 'custom', CUSTOM_TYPES.U53);

    public constructor() {
        super('emojiData');
    }
})();

/**
 * Key-value-based global properties storage.
 */
export const tGlobalProperty = new (class TGlobalProperty extends Table<
    DBConnection,
    'TGlobalProperty'
> {
    public uid = this.autogeneratedPrimaryKey<DbGlobalPropertyUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.GLOBAL_PROPERTY_UID,
    );

    /**
     * The property identifier key.
     */
    public key = this.column<GlobalPropertyKey>('key', 'custom', CUSTOM_TYPES.GLOBAL_PROPERTY_KEY);

    /**
     * The bytes of the key's value.
     */
    public value = this.column<Uint8Array>('value', 'custom', CUSTOM_TYPES.UINT8ARRAY);

    public constructor() {
        super('globalProperties'); // Table name in the database
    }
})();

/**
 * Nonces Database.
 */
export const tNonce = new (class TNonce extends Table<DBConnection, 'TNonce'> {
    public uid = this.autogeneratedPrimaryKey<DbNonceUid>('uid', 'custom', CUSTOM_TYPES.NONCE_UID);

    /**
     * The property identifier key.
     */
    public scope = this.column<NonceScope>('scope', 'custom', CUSTOM_TYPES.NONCE_SCOPE);

    /**
     * The bytes of the key's value.
     */
    public nonce = this.column<NonceHash>('nonce', 'custom', CUSTOM_TYPES.NONCE_HASH);

    public constructor() {
        super('nonces'); // Table name in the database
    }
})();

export const tRunningGroupCalls = new (class TRunningGroupCalls extends Table<
    DBConnection,
    'TRunningGroupCalls'
> {
    public uid = this.autogeneratedPrimaryKey<DbRunningGroupCallUid>(
        'uid',
        'custom',
        CUSTOM_TYPES.RUNNING_GROUP_CALL_UID,
    );

    /**
     * The group associated with this group call.
     */
    public groupUid = this.column<DbGroupUid>('groupUid', 'custom', CUSTOM_TYPES.GROUP_UID);

    /**
     * The number of failed peek attempts, if any.
     */
    public nFailed = this.column<u53>('nFailed', 'custom', CUSTOM_TYPES.U53);

    /**
     * Corresponds to {@link GroupCallBaseData.startedAt}.
     */
    public startedAt = this.column('startedAt', 'localDateTime');

    /**
     * Corresponds to {@link GroupCallBaseData.startedBy}.
     */
    public creatorIdentity = this.column<IdentityString>(
        'creatorIdentity',
        'custom',
        CUSTOM_TYPES.IDENTITY,
    );

    /**
     * Corresponds to {@link GroupCallBaseData.protocolVersion}.
     */
    public protocolVersion = this.column<u53>('protocolVersion', 'custom', CUSTOM_TYPES.U53);

    /**
     * Corresponds to {@link GroupCallBaseData.gck}.
     */
    public gck = this.column<RawGroupCallKey>('gck', 'custom', CUSTOM_TYPES.GROUP_CALL_KEY);

    /**
     * Corresponds to {@link GroupCallBaseData.sfuBaseUrl.raw}.
     */
    public baseUrl = this.column('baseUrl', 'string');

    public constructor() {
        super('runningGroupCalls');
    }
})();
