import {ActionGroup} from 'app/common/ActionGroup';
import {DocAction} from 'app/common/DocActions';
import {FilteredDocUsageSummary} from 'app/common/DocUsage';
import {Product} from 'app/common/Features';
import {UserProfile} from 'app/common/LoginSessionAPI';
import {StringUnion} from 'app/common/StringUnion';

export const ValidEvent = StringUnion(
  'docListAction', 'docUserAction', 'docShutdown', 'docError',
  'docUsage', 'docChatter', 'clientConnect');
export type ValidEvent = typeof ValidEvent.type;


/**
 * A request in the appropriate form for sending to the server.
 */
export interface CommRequest {
  reqId: number;
  method: string;
  args: any[];
}

/**
 * A regular, successful response from the server.
 */
export interface CommResponse {
  reqId: number;
  data: any;
  error?: null;  // TODO: keep until sure server never sets this on regular responses.
}

/**
 * An exceptional response from the server when there is an error.
 */
export interface CommResponseError {
  reqId: number;
  error: string;
  errorCode?: string;
  shouldFork?: boolean;  // if set, the server suggests forking the document.
  details?: any;  // if set, error has extra details available. TODO - the treatment of
                  // details could do with some harmonisation between rest API and ws API,
                  // and between front-end and back-end types.
}

/**
 * A message pushed from the server, not in response to a request.
 */
export interface CommMessageBase {
  type: ValidEvent;
  docFD?: number;
  data?: unknown;
}

export type CommDocMessage = CommDocUserAction | CommDocUsage | CommDocShutdown | CommDocError | CommDocChatter;
export type CommMessage = CommDocMessage | CommDocListAction | CommClientConnect;

export type CommResponseBase = CommResponse | CommResponseError | CommMessage;

export type CommDocEventType = CommDocMessage['type'];

/**
 * Event for a change to the document list.
 * These are sent to all connected clients, regardless of which documents they have open.
 * TODO: This is entirely unused at the moment.
 */
export interface CommDocListAction extends CommMessageBase {
  type: 'docListAction';
  addDocs?: string[];        //  names of documents to add to the docList.
  removeDocs?: string[];     //  names of documents that got removed.
  renameDocs?: string[];     //  [oldName, newName] pairs for renamed docs.
  addInvites?: string[];     //  document invite names to add.
  removeInvites?: string[];  //  documents invite names to remove.
}

/**
 * Event for a user action on a document, or part of one. Sent to all clients that have this
 * document open.
 */
export interface CommDocUserAction extends CommMessageBase {
  type: 'docUserAction';
  docFD: number;           // The file descriptor of the open document, specific to each client.
  fromSelf?: boolean;      // Flag to indicate whether the action originated from this client.

  // ActionGroup object containing user action, and doc actions.
  data: {
    docActions: DocAction[];
    actionGroup: ActionGroup;
    docUsage: FilteredDocUsageSummary;
    error?: string;
  };
}


export enum WebhookMessageType {
  Update = 'webhookUpdate',
  Overflow = 'webhookOverflowError'
}
export interface CommDocChatter extends CommMessageBase {
  type: 'docChatter';
  docFD: number;
  data: {
    webhooks?: {
      type: WebhookMessageType,
      // If present, something happened related to webhooks.
      // Currently, we give no details, leaving it to client
      // to call back for details if it cares.
    },
    // This could also be a fine place to send updated info
    // about other users of the document.
    timing?: {
      status: 'active'|'disabled';
    }
  };
}

/**
 * Event for a change to document usage. Sent to all clients that have this document open.
 */
export interface CommDocUsage extends CommMessageBase {
  type: 'docUsage';
  docFD: number;           // The file descriptor of the open document, specific to each client.
  data: {
    docUsage: FilteredDocUsageSummary;  // Document usage summary.
    product?: Product;                  //Product that was used to compute `data.docUsage`
  };
}

/**
 * Event for when a document is forcibly shutdown, and requires the client to re-open it.
 */
export interface CommDocShutdown extends CommMessageBase {
  type: 'docShutdown';
  docFD: number;
  data: null;
}

/**
 * Event that signals an error while opening a doc.
 */
export interface CommDocError extends CommMessageBase {
  type: 'docError';
  docFD: number;
  data: {
    when: string;
    message: string;
  }
}

/**
 * Event sent by server received when a client first connects.
 */
export interface CommClientConnect extends CommMessageBase {
  type: 'clientConnect';

  // ID for the client, which may be reused if a client reconnects to reattach to its state on
  // the server.
  clientId: string;

  // If set, the reconnecting client cannot be sent all missed messages, and needs to reload.
  needReload?: boolean;

  // Array of serialized messages missed from the server while disconnected.
  missedMessages?: string[];

  // Which version the server reports for itself.
  serverVersion?: string;

  // Object containing server settings and features which should be used to initialize the client.
  settings?: {[key: string]: unknown};

  // Object containing session profile information if the user is signed in, or null otherwise.
  profile: UserProfile|null;

  dup?: boolean;  // Flag that's set to true when it's a duplicate clientConnect message.
}
