types/interactive.js

const Text = require("./text");
const { Image, Document, Video } = require("./media");

/**
 * Interactive API object
 * 
 * @property {(ActionList|ActionButtons)} action The action component of the interactive message
 * @property {Body} body The body component of the interactive message
 * @property {Header} [header] The header component of the interactive message
 * @property {Footer} [footer] The footer component of the interactive message
 * @property {String} _ The type of the interactive message, for internal use only
 */
class Interactive {
    /**
     * Create an Interactive object for the API
     * 
     * @param {(ActionList|ActionButtons)} action The action component of the interactive message
     * @param {Body} body The body component of the interactive message
     * @param {Header} [header] The header component of the interactive message
     * @param {Footer} [footer] The footer component of the interactive message
     * @throws {Error} If action is not provided
     * @throws {Error} If body is not provided
     */
    constructor(action, body, header, footer) {
        if (!action) throw new Error("Interactive must have an action component");
        if (!body) throw new Error("Interactive must have a body component");

        this.type = action._;
        delete action._;
        
        this.action = action;
        this.body = body;
        if (header) this.header = header;
        if (footer) this.footer = footer;

        this._ = "interactive";
    }
}

/**
 * Body API object
 * 
 * @property {String} text The text of the body
 */
class Body {
    /**
     * Builds a body component for an Interactive message
     * 
     * @param {String} text The text of the message. Maximum length: 1024 characters.
     * @throws {Error} If text is not provided
     * @throws {Error} If text is over 1024 characters
     */
    constructor(text) {
        if (!text) throw new Error("Body must have a text object");
        if (text.length > 1024) throw new Error("Body text must be less than 1024 characters");

        this.text = text;
    }
}

/**
 * Footer API object
 * 
 * @property {String} text The text of the body
 */
class Footer {
    /**
     * Builds a footer component for an Interactive message
     * 
     * @param {String} text Text of the footer. Maximum length: 60 characters.
     * @throws {Error} If text is not provided
     * @throws {Error} If text is over 60 characters
     */
    constructor(text) {
        if (!text) throw new Error("Footer must have a text object");
        if (text.length > 60) throw new Error("Footer text must be 60 characters or less");

        this.text = text;
    }
}

/**
 * Header API object
 * 
 * @property {String} type The type of the header
 * @property {String} [text] The text of the parameter
 * @property {Image} [image] The image of the parameter
 * @property {Document} [document] The document of the parameter
 * @property {Video} [video] The video of the parameter
 */
class Header {
    /**
     * Builds a header component for an Interactive message
     * 
     * @param {(Document|Image|Text|Video)} object The message object for the header
     * @throws {Error} If object is not provided
     * @throws {Error} If object is not a Document, Image, Text, or Video
     * @throws {Error} If object is a Text and is over 60 characters
     */
    constructor(object) {
        if (!object) throw new Error("Header must have an object");
        if (!["text", "video", "image", "document"].includes(object._)) throw new Error(`Header object must be either Text, Video, Image or Document.`);

        this.type = object._;
        delete object._;

        // Text type can go to hell
        if (this.type === "text") {
            if (object.body > 60) throw new Error("Header text must be 60 characters or less");
            this[this.type] = object.body;
        } else this[this.type] = object;
    }
}

/**
 * Action API object
 * 
 * @property {String} button The button text
 * @property {Array<Section>} sections The sections of the action
 * @property {String} _ The type of the action, for internal use only
 */
class ActionList {
    /**
     * Builds an action component for an Interactive message
     * Required if interactive type is "list"
     * 
     * @param {String} button Button content. It cannot be an empty string and must be unique within the message. Emojis are supported, markdown is not. Maximum length: 20 characters.
     * @param  {...Section} sections Sections of the list
     * @throws {Error} If button is not provided
     * @throws {Error} If button is over 20 characters
     * @throws {Error} If no sections are provided or are over 10
     * @throws {Error} If more than 1 section is provided and at least one doesn't have a title
     */
    constructor(button, ...sections) {
        if (!button) throw new Error("Action must have a button content");
        if (button.length > 20) throw new Error("Button content must be 20 characters or less");
        if (!sections.length || sections.length > 10) throw new Error("Action must have between 1 and 10 sections");
        if (sections.length > 1) sections.forEach(s => { if (!s.title) throw new Error("Sections must have a title if more than 1 section is provided") });

        this._ = "list";
        this.button = button;
        this.sections = sections;
    }
}

/**
 * Section API object
 * 
 * @property {String} title The title of the section
 * @property {Array<Row>} rows The rows of the section
 */
class Section {
    /**
     * Builds a section component for ActionList
     * 
     * @param {String} title Title of the section, only required if there are more than one section
     * @param {...Row} rows Rows of the section
     * @throws {Error} If title is over 24 characters if provided
     * @throws {Error} If no rows are provided or are over 10
     */
    constructor(title, ...rows) {
        if (title && title.length > 24) throw new Error("Section title must be 24 characters or less");
        if (!rows.length || rows.length > 10) throw new Error("Section must have between 1 and 10 rows");

        if (title) this.title = title;
        this.rows = rows;
    }
}

/**
 * Row API object
 * 
 * @property {String} id The id of the row
 * @property {String} title The title of the row
 * @property {String} [description] The description of the row
 */
class Row {
    /**
     * Builds a row component for a Section
     * 
     * @param {String} id The id of the row. Maximum length: 200 characters.
     * @param {String} title The title of the row. Maximum length: 24 characters.
     * @param {String} [description] The description of the row. Maximum length: 72 characters.
     * @throws {Error} If id is not provided
     * @throws {Error} If id is over 200 characters
     * @throws {Error} If title is not provided
     * @throws {Error} If title is over 24 characters
     * @throws {Error} If description is over 72 characters
     */
    constructor(id, title, description) {
        if (!id) throw new Error("Row must have an id");
        if (id.length > 200) throw new Error("Row id must be 200 characters or less");
        if (!title) throw new Error("Row must have a title");
        if (title.length > 24) throw new Error("Row title must be 24 characters or less");
        if (description.length > 72) throw new Error("Row description must be 72 characters or less");

        this.id = id;
        this.title = title;
        if (description) this.description = description;
    }
}

/**
 * Action API object
 * 
 * @property {Array<Button>} buttons The buttons of the action
 * @property {String} _ The type of the action, for internal use only
 */
class ActionButtons {
    /**
     * Builds a reply buttons component for an Interactive message
     * 
     * @param {...Button} button Buttons to be used in the reply buttons. Each button title must be unique within the message. Emojis are supported, markdown is not. Must be between 1 and 3 buttons.
     * @throws {Error} If no buttons are provided or are over 3
     * @throws {Error} If two or more buttons have the same id
     * @throws {Error} If two or more buttons have the same title
     */
    constructor(...button) {
        if (!button.length || button.length > 3) throw new Error("Reply buttons must have between 1 and 3 buttons");

        // Find if there are duplicates in button.id
        const ids = button.map(b => b[b.type].id);
        if (ids.length !== new Set(ids).size) throw new Error("Reply buttons must have unique ids");

        // Find if there are duplicates in button.title
        const titles = button.map(b => b[b.type].title);
        if (titles.length !== new Set(titles).size) throw new Error("Reply buttons must have unique titles");

        this.buttons = button;
        this._ = "button";
    }
}

/**
 * Button API object
 * 
 * @property {String} type The type of the button
 * @property {String} reply.id The id of the row
 * @property {String} reply.title The title of the row
 */
class Button {
    /**
     * Builds a button component for ActionButtons
     * 
     * @param {String} id Unique identifier for your button. It cannot have leading or trailing spaces. This ID is returned in the webhook when the button is clicked by the user. Maximum length: 256 characters.
     * @param {String} title Button title. It cannot be an empty string and must be unique within the message. Emojis are supported, markdown is not. Maximum length: 20 characters.
     * @throws {Error} If id is not provided
     * @throws {Error} If id is over 256 characters
     * @throws {Error} If title is not provided
     * @throws {Error} If title is over 20 characters
     */
    constructor(id, title) {
        if (!id) throw new Error("Button must have an id");
        if (id.length > 256) throw new Error("Button id must be 256 characters or less");
        if (/^ | $/.test(id)) throw new Error("Button id cannot have leading or trailing spaces");
        if (!title) throw new Error("Button must have a title");
        if (title.length > 20) throw new Error("Button title must be 20 characters or less");

        this.type = "reply";
        this[this.type] = {
            title,
            id
        };
    }
}

module.exports = {
    Interactive,
    Body,
    Footer,
    Header,
    ActionList,
    Section,
    Row,
    ActionButtons,
    Button
};