@namespace("chat.1")
protocol common {

  import idl "github.com/keybase/client/go/protocol/gregor1" as gregor1;

  @typedef("bytes")  record ThreadID {}
  @typedef("uint") @lint("ignore") record MessageID {}
  @typedef("bytes")  record TopicID {}
  @typedef("bytes")  record ConversationID {}
  @typedef("bytes")  record TLFID {}
  @typedef("bytes")  record Hash {}
  @typedef("uint64") @lint("ignore") record InboxVers {}
  @typedef("bytes")  record OutboxID {}

  @go("nostring")
  enum MessageType {
    NONE_0,
    TEXT_1,
    ATTACHMENT_2,
    EDIT_3,
    DELETE_4,
    METADATA_5,
    TLFNAME_6, // Only used as the very first message in conversations whose topic name is not set when created
    HEADLINE_7,
    ATTACHMENTUPLOADED_8  // sent after upload completes to modify ATTACHMENT message
  }

  @go("nostring")
  enum TopicType {
    NONE_0,
    CHAT_1,
    DEV_2
  }

  enum ConversationStatus {
    // Default status of the conversation
    UNFILED_0,

    // Not used yet.
    FAVORITE_1,

    // This status is useful for temporarily muting a conversation. Unless told
    // otherwise in GetInboxQuery, gregor doesn't include conversation with
    // this status in results from GetInboxRemote. Whenever some post activity
    // (post, edit, delete, etc.) happens for the conversation with this
    // status, it's automatically changed back to UNFILED.
    IGNORED_2,

    // The conversation is blocked (i.e. not included in GetInboxRemote results
    // by default), until SetConversationStatus is called with a different
    // status.
    BLOCKED_3,

    // The conversation appears in the inbox with no snippet,
    // and does not emit notifications nor badges.
    MUTED_4
  }

  record Pagination {
    bytes next;
    bytes previous;
    int num; // Number of items requested when argument, and number returned when result
    boolean last; // Will be true if the number of results is less than requested
  }

  record RateLimit {
    string name;
    int callsRemaining; // Number of calls remaining for the given RPC in the current window
    int windowReset; // Amount of time (in seconds) until the window resets for this rate limit bucket
    int maxCalls; // Max amount of calls allowed in a window for the given RPC
  }

  enum TLFVisibility {
    ANY_0,
    PUBLIC_1,
    PRIVATE_2
  }

  record GetInboxQuery {
    union { null, ConversationID } convID;
    union { null, TopicType } topicType;
    union { null, TLFID } tlfID;
    union { null, TLFVisibility } tlfVisibility;
    union { null, gregor1.Time } before;
    union { null, gregor1.Time } after;
    union { null, boolean } oneChatTypePerTLF;

    // If left empty, default is to show unfiled and favorite
    array<ConversationStatus> status;

    // Extended list of conversation IDs to fetch (don't need to set convID, if convID is set then 
    // this it will be like appending it to this list)
    array<ConversationID> convIDs;

    boolean unreadOnly;
    boolean readOnly;
    boolean computeActiveList;
    boolean summarizeMaxMsgs; // if true, resulting conversation will only have summaries of max msgs
  }

  record ConversationIDTriple {
    @lint("ignore")
    TLFID tlfid;
    TopicType topicType;
    TopicID topicID;
  }

  record ConversationFinalizeInfo {
    string resetUser;
    string resetDate;
    string resetFull;
    gregor1.Time resetTimestamp;
  }

  record ConversationResolveInfo {
    string newTLFName;
  }

  record ConversationMetadata  {
    ConversationIDTriple idTriple;
    ConversationID conversationID;
    TLFVisibility visibility;
    ConversationStatus status;

    // Finalize info for underlying TLF
    union { null, ConversationFinalizeInfo } finalizeInfo;

    array<ConversationMetadata> supersedes; // metadata about the conversations this supersedes from a TLF finalize (if any).
    array<ConversationMetadata> supersededBy; // metadata about the conversations that superseded this conversation from a TLF finalize.

    // List of users sorted by recency of last [intentional] post.
    // Most recent first. May be incomplete or empty.
    array<gregor1.UID> activeList;
  }

  record ConversationReaderInfo {
    gregor1.Time mtime; // The last time the convo was modified from the user perspective
    MessageID readMsgid; // The message ID the user has read up to in the convo
    MessageID maxMsgid; // The max message ID in the convo
  }

  record Conversation {
    ConversationMetadata metadata;
    union { null, ConversationReaderInfo } readerInfo; // information about the convo from a user perspective 

    // maxMsgs is the maximum message for each messageType in the conversation
    array<MessageBoxed> maxMsgs;

    // maxMsgSummaries contains a subset of the full MessageBoxed for the maximum message for
    // each messageType in the conversation
    array<MessageSummary> maxMsgSummaries;
  }

  record MessageSummary {
    MessageID msgID;
    MessageType messageType;
    string tlfName;
    boolean tlfPublic;
    gregor1.Time ctime;
  }

  record MessageServerHeader {
    MessageID messageID;
    MessageID supersededBy;
    gregor1.Time ctime;
  }

  record MessagePreviousPointer {
    MessageID id;
    Hash hash;
  }

  record OutboxInfo {
    MessageID prev; // This is the message ID the sending client device saw as the previous
    gregor1.Time composeTime;
  }

  // The Boxer's compareClientHeaders function checks each of these fields. If
  // we add a field here, it needs to be updated.
  record MessageClientHeader {
    // This type is attached to MessageBoxed.
    // When on a received message these fields are server-set and have not been verified.
    // If adding fields, consider whether they should be signed,
    // and if so add them to MessageClientHeaderVerified as well.

    ConversationIDTriple conv;
    string tlfName;
    boolean tlfPublic;
    MessageType messageType;
    MessageID supersedes;
    array<MessageID> deletes;
    array<MessagePreviousPointer> prev;
    gregor1.UID sender;
    gregor1.DeviceID senderDevice;
    // Latest merkle root when sent. Can be nil.
    union { null, MerkleRoot } merkleRoot;
    union { null, OutboxID } outboxID;
    union { null, OutboxInfo } outboxInfo;
  }

  record MessageClientHeaderVerified {
    // This type is the result of unboxing.
    // And to be used locally to the client only.
    // All fields have been verified signed by the sender.
    // If adding fields, consider updating Boxer's compareHeaders methods
    // to check invariants early.

    ConversationIDTriple conv;
    string tlfName;
    boolean tlfPublic;
    MessageType messageType;
    array<MessagePreviousPointer> prev;
    gregor1.UID sender;
    gregor1.DeviceID senderDevice;
    union { null, OutboxID } outboxID;
    union { null, OutboxInfo } outboxInfo;
  }

  // The same format as in KBFS (see libkbfs/data_types.go)
  record EncryptedData {
    int   v;  // version = 1
    bytes e;  // encryptedData (output of secret box)
    bytes n;  // nonce
  }

  record SignEncryptedData {
    int   v; // version = 1
    bytes e; // signEncryptedData (output of signencrypt.SealWhole)
    bytes n; // nonce
  }

  // Encrypted or SignEncrypted. Must know which from context.
  record SealedData {
    int   v;  // version = 1
    // Encrypted: output of secret box
    // SignEncrypted: output of signencrypt.SealWhole
    bytes e;
    bytes n;  // nonce
  }

  record SignatureInfo {
    int   v; // version = 1
    bytes s; // signature; output of EdDSA
    bytes k; // Verifying key
  }

  record MerkleRoot {
    long seqno;
    bytes hash;
  }

  enum InboxResType {
    VERSIONHIT_0,
    FULL_1
  }

  record InboxViewFull {
    InboxVers vers;
    array<Conversation> conversations;
    union { null, Pagination } pagination;
  }

  variant InboxView switch (InboxResType rtype) {
    case VERSIONHIT: void;
    case FULL: InboxViewFull;
  }
}
