Source: inline_keyboard_markup.js

/**
 * @module inline_keyboard_markup
 */
'use strict';

import { ValidationError } from 'vvlad1973-error-definitions';

/**
 * @typedef {Object} module:inline_keyboard_markup.InlineKeyboardButton
 * @property {string} text - Text of the button. If none of the optional fields
 *     are used, it will be sent as a message when the button is pressed
 * @property {string} [url] - HTTP or tg:// url to be opened when the button is 
 *     pressed. Links tg://user?id=<user_id> can be used to mention a user by 
 *     their ID without using a username, if this is allowed by their privacy 
 *     settings
 * @property {string} [callback_data] - Data to be sent in a callback query to 
 *     the bot when button is pressed, 1-64 bytes
 * @property {module:inline_keyboard_markup.LoginUrl} [login_url] - An HTTP URL 
 *     used to automatically authorize the user. Can be used as a replacement 
 *     for the Telegram Login Widget
 * @property {string} [switch_inline_query] - If set, pressing the button will 
 *     prompt the user to select one of their chats, open that chat and insert 
 *     the bot's username and the specified inline query in the input field. 
 *     Can be empty, in which case just the bot's username will be inserted
 * @property {string} [switch_inline_query_current_chat] - If set, pressing the 
 *     button will insert the bot's username and the specified inline query in 
 *     the current chat's input field. Can be empty, in which case only the 
 *     bot's username will be inserted
 * @property {CallbackGame} [callback_game] - Description of the game that will
 *     be launched when the user presses the button.
 *     NOTE: This type of button must always be the first button in the first 
 *     row
 * @property {boolean} [pay] - Specify True, to send a Pay button.
 *     NOTE: This type of button must always be the first button in the first 
 *     row and can only be used in invoice messages
 */

/**
 * @typedef {Object} module:inline_keyboard_markup.LoginUrl
 * @property {string} url - An HTTP URL to be opened with user authorization 
 *     data added to the query string when the button is pressed. If the user
 *     refuses to provide authorization data, the original URL without 
 *     information about the user will be opened. The data added is the same 
 *     as described in Receiving authorization data.
 *     NOTE: You must always check the hash of the received data to verify the
 *     authentication and the integrity of the data as described in Checking 
 *     authorization
 * @property {string} [forward_text] - New text of the button in forwarded 
 *     messages
 * @property {string} [bot_username] - Username of a bot, which will be used
 *     for user authorization. See Setting up a bot for more details. If not 
 *     specified, the current bot's username will be assumed. The url's 
 *     domain must be the same as the domain linked with the bot. See Linking
 *     your domain to the bot for more details
 * @property {boolean} [request_write_access] - Pass True to request the 
 *     permission for your bot to send messages to the user
 */

/**
 * @class Implements structure for operations with Telegram bot's commands.
 *
 * @description This object represents an inline keyboard that appears right
 * next to the message it belongs to
 */
export class InlineKeyboardMarkup {

  /**
   * @property {Array.<module:inline_keyboard_markup.InlineKeyboardButton[]>} - Array of
   *     button rows, each represented by an Array of InlineKeyboardButton
   *     objects
   */
  inline_keyboard = [];

  /**
   * @returns {module:inline_keyboard_markup.InlineKeyboardMarkup} Instance of the InlineKeyboardMarkup class
   */

  /**
   * Append one or more rows to the keyboard
   *
   * @param {number} [number] - Number of rows to append. If not specified,
   *     wlll append one row
   */
  appendRows(number = 1) {
    for (let i = 0; i < number; i++) {
      this.inline_keyboard.push([]);
    }
  };

  /**
   * Insert one or more rows to the keyboard
   *
   * @param {number} [index] - Number of position to insert rows (0-based).
   *     If not specified, rows will insert at the first position
   * @param {number} [number] - Number of rows to append. If not specified,
   *     wlll insert one row
   */
  insertRows(index = 0, number = 1) {
    for (let i = 0; i < number; i++) {
      this.inline_keyboard.splice(index, 0, []);
    }
  };

  /**
   * Delete a row from the keyboard
   *
   * @param {number} [index] - Index of row to delete (0-based). If not
   *     specified, will delete the last row
   */
  deleteRow(index = -1) {
    if (index == -1) {
      this.inline_keyboard.splice(this.inline_keyboard.length - 1);
    } else if (index < this.inline_keyboard.length) {
      this.inline_keyboard.splice(index, 1);
    }
    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Remove a button from a specific row
   *
   * @param {number} [row] - Number of row of button to delete (0-based). 
   *     If not specified, will delete a button from the last row
   * @param {number} [column] - Number of column of button to delete
   *     (0-based). If not specified, will delete the last button from the
   *     row
   */
  deleteButton(row = -1, column = -1) {

    if (row === -1) row = this.inline_keyboard.length - 1;

    if (column === -1)
      column = this.inline_keyboard[row].length - 1;

    if (row < this.inline_keyboard.length) {
      if (column < this.inline_keyboard[row].length)
        this.keyboard[row].splice(column, 1)
      else
        throw new ValidationError('The button does not exist');
    } else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Insert a text button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} callbackData - Data to be sent in a callback query to
   *     the bot when button is pressed, 1-64 bytes
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the last row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertTextButton(text, callbackData, row = 0, column = 0) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row === -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length) {
      this.inline_keyboard[row].splice(
        column, 0, { text: text, callback_data: callbackData }
      );
    }
    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Append a text button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} callbackData - Data to be sent in a callback query to
   *     the bot when button is pressed, 1-64 bytes
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendTextButton(text, callbackData, row = -1) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row === -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length)
      this.inline_keyboard[row].push({ text: text, callback_data: callbackData });

    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Insert an url button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} url - HTTP or tg:// url to be opened when the button is
   *     pressed. Links tg://user?id=<user_id> can be used to mention a user
   *     by their ID without using a username, if this is allowed by their
   *     privacy settings
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the last row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertUrlButton(text, url, row = 0, column = 0) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row === -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length) {
      this.inline_keyboard[row].splice(
        column, 0, { text: text, url: url }
      );
    }
    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Append an url button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} url - HTTP or tg:// url to be opened when the button is
   *     pressed. Links tg://user?id=<user_id> can be used to mention a user
   *     by their ID without using a username, if this is allowed by their
   *     privacy settings
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendUrlButton(text, url, row = -1) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row === -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length)
      this.inline_keyboard[row].push({ text: text, url: url });

    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Add a pay button to inline keyboard
   *
   * @param {string} text - Text label for the button
   */
  addPayButton(text) {
    if (typeof this.inline_keyboard[0][0] === 'object' &&
      typeof this.inline_keyboard[0][0].pay) {
      this.inline_keyboard[0][0].text = text;
    } else if (typeof this.inline_keyboard[0][0] === 'object' &&
      typeof this.inline_keyboard[0][0].callback_game === 'object') {
      throw new ValidationError('There is the CallbackGame button added');
    } else {
      this.inline_keyboard[0].splice(
        0, 0, { text: text, pay: true }
      );
    }
  };

  /**
   * Add a callback game button to inline keyboard
   *
   * @param {string} text - Text label for the button
   */
  addCallbackGameButton(text) {
    if (typeof this.inline_keyboard[0][0] === 'object' &&
      typeof this.inline_keyboard[0][0].callback_game === 'object') {
      this.inline_keyboard[0][0].text = text;
    } else if (typeof this.inline_keyboard[0][0] === 'object' &&
      typeof this.inline_keyboard[0][0].pay === 'boolean') {
      throw new ValidationError('There is the Pay button added');
    } else {
      this.inline_keyboard[0].splice(
        0, 0, { text: text, callback_game: {} }
      );
    }
  };

  /**
   * Insert a SwitchInlineQuery button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} [switchInlineQuery = ""] - If set, pressing the button will
   *     prompt the user to select one of their chats, open that chat and
   *     insert the bot's username and the specified inline query in the
   *     input field. Can be empty, in which case just the bot's username
   *     will be inserted
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the last row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertSwitchInlineQueryButton(
    text, switchInlineQuery = '', row = 0, column = 0
  ) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row == -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length) {
      this.inline_keyboard[row].splice(column, 0, {
        text: text,
        switch_inline_query: switchInlineQuery
      }
      );
    }
    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Append a SwitchInlineQuery button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} [switchInlineQuery = ""] - If set, pressing the button will
   *     prompt the user to select one of their chats, open that chat and
   *     insert the bot's username and the specified inline query in the
   *     input field. Can be empty, in which case just the bot's username
   *     will be inserted
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendSwitchInlineQueryButton(
    text, switchInlineQuery = '', row = -1
  ) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row == -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length)
      this.inline_keyboard[row].push(
        {
          text: text,
          switch_inline_query: switchInlineQuery
        }
      );

    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Insert a SwitchInlineQueryCurrentChat button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} [switchInlineQueryCurrentChat = ""] - If set, pressing
   *     the button will insert the bot's username and the specified inline
   *     query in the current chat's input field. Can be empty, in which
   *     case only the bot's username will be inserted.
   *     This offers a quick way for the user to open your bot in inline
   *     mode in the same chat – good for selecting something from multiple
   *     options
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the last row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertSwitchInlineQueryCurrentChatButton(
    text, switchInlineQueryCurrentChat = '', row = 0, column = 0
  ) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row == -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length) {
      this.inline_keyboard[row].splice(column, 0, {
        text: text,
        switch_inline_query_current_chat: switchInlineQueryCurrentChat
      }
      );
    }
    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Append a SwitchInlineQueryCurrentChat button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} [switchInlineQueryCurrentChat = ""] - If set, pressing
   *     the button will insert the bot's username and the specified inline
   *     query in the current chat's input field. Can be empty, in which
   *     case only the bot's username will be inserted.
   *     This offers a quick way for the user to open your bot in inline
   *     mode in the same chat – good for selecting something from multiple
   *     options
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendSwitchInlineQueryCurrentChatButton(
    text, switchInlineQueryCurrentChat = '', row = -1
  ) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row == -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length)
      this.inline_keyboard[row].push(
        {
          text: text,
          switch_inline_query_current_chat: switchInlineQueryCurrentChat
        }
      );

    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Insert a LoginUrl button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} url - An HTTP URL to be opened with user authorization
   *     data added to the query string when the button is pressed. If the user
   *     refuses to provide authorization data, the original URL without
   *     information about the user will be opened. The data added is the same
   *     as described in Receiving authorization data.
   *     NOTE: You must always check the hash of the received data to verify the
   *     authentication and the integrity of the data as described in Checking
   *     authorization
   * @param {string} [forwardText] - New text of the button in forwarded
   *     messages
   * @param {string} [botUsername] - Username of a bot, which will be used
   *     for user authorization. See Setting up a bot for more details. If not
   *     specified, the current bot's username will be assumed. The url's
   *     domain must be the same as the domain linked with the bot. See Linking
   *     your domain to the bot for more details
   * @param {boolean} [requestWriteAccess] - Pass True to request the
   *     permission for your bot to send messages to the user
   * @param {number} [row] - Row number to insert button (0-based). If
   *     not specified, the button will insert to the last row
   * @param {number} [column] - Number of column of button to insert
   *     (0-based). If not specified, the button will insert to the first
   *     position of the row
   */
  insertLoginUrlButton(
    text, url, forwardText = '', botUsername = '', requestWriteAccess = false,
    row = 0, column = 0
  ) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row == -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length) {
      let data = { url: url };

      if (forwardText)
        data['forward_text'] = forwardText;
      if (botUsername)
        data['bot_user_name'] = botUsername;
      if (requestWriteAccess)
        data['request_write_access'] = requestWriteAccess;

      this.inline_keyboard[row].splice(column, 0, {
        text: text, login_url: data
      });
    }
    else
      throw new ValidationError('Keyboard row does not exist');
  };

  /**
   * Append a LoginUrl button to inline keyboard
   *
   * @param {string} text - Text label for the button
   * @param {string} url - An HTTP URL to be opened with user authorization
   *     data added to the query string when the button is pressed. If the user
   *     refuses to provide authorization data, the original URL without
   *     information about the user will be opened. The data added is the same
   *     as described in Receiving authorization data.
   *     NOTE: You must always check the hash of the received data to verify the
   *     authentication and the integrity of the data as described in Checking
   *     authorization
   * @param {string} [forwardText] - New text of the button in forwarded
   *     messages
   * @param {string} [botUsername] - Username of a bot, which will be used
   *     for user authorization. See Setting up a bot for more details. If not
   *     specified, the current bot's username will be assumed. The url's
   *     domain must be the same as the domain linked with the bot. See Linking
   *     your domain to the bot for more details
   * @param {boolean} [requestWriteAccess] - Pass True to request the
   *     permission for your bot to send messages to the user
   * @param {number} [row] - Row number to append button (0-based). If
   *     not specified, the button will append to the last row
   */
  appendLoginUrlButton(
    text, url, forwardText = '', botUsername = '', requestWriteAccess = false,
    row = -1
  ) {
    if (this.inline_keyboard.lenght === 0)
      this.inline_keyboard.push([]);

    if (row == -1)
      row = this.inline_keyboard.length - 1;

    if (row < this.inline_keyboard.length) {
      let data = { url: url };

      if (forwardText)
        data['forward_text'] = forwardText;
      if (botUsername)
        data['bot_user_name'] = botUsername;
      if (requestWriteAccess)
        data['request_write_access'] = requestWriteAccess;

      this.inline_keyboard[row].push(
        {
          text: text,
          login_url: data
        }
      );
    }
    else
      throw new ValidationError('Keyboard row does not exist');
  };
}

export default InlineKeyboardMarkup;