import { chunkString } from './utils';
const maxSizeMessage = 4096;
/**
 * Перелік причин відмови.
 */
export var DeclineCause;
(function (DeclineCause) {
    DeclineCause["Timeout"] = "TIMEOUT";
    DeclineCause["Busy"] = "BUSY";
    DeclineCause["Cancel"] = "CANCEL";
    DeclineCause["Transfer"] = "TRANSFER";
})(DeclineCause || (DeclineCause = {}));
/**
 * Перелік дій чату.
 */
export var ChatActions;
(function (ChatActions) {
    ChatActions["Message"] = "message";
    ChatActions["UserInvite"] = "user_invite";
    ChatActions["Invite"] = "invite_conversation";
    ChatActions["Joined"] = "join_conversation";
    ChatActions["Close"] = "close_conversation";
    ChatActions["Leave"] = "leave_conversation";
    ChatActions["Decline"] = "decline_invite";
    ChatActions["Update"] = "update_channel";
    ChatActions["Destroy"] = "destroy";
    ChatActions["MessageDeleted"] = "message_deleted";
})(ChatActions || (ChatActions = {}));
/**
 * Перелік станів розмови.
 */
export var ConversationState;
(function (ConversationState) {
    ConversationState["Init"] = "init";
    ConversationState["Invite"] = "invite";
    ConversationState["Active"] = "active";
    ConversationState["Pending"] = "pending";
    ConversationState["Closed"] = "closed";
})(ConversationState || (ConversationState = {}));
/**
 * Клас Conversation: Описує чат, включаючи його стан, учасників, повідомлення та інші атрибути.
 *
 * Конструктор Conversation:
 *  client: Клієнт для виконання запитів, використовуваних у розмові.
 *  conversationId: Ідентифікатор розмови.
 *  title: Назва розмови.
 *  members: Список учасників розмови.
 *  messages: Список повідомлень у розмові.
 *  variables: Опційні змінні виклику, що можуть бути використані в розмові.
 * Конструктор ініціалізує всі властивості класу, задаючи значення за замовчуванням або використовує
 * передані параметри для налаштування об'єкта розмови.
 */
export class Conversation {
    /**
     * Конструктор для розмови.
     * @param {Client} client - Клієнт для виконання запитів.
     * @param {string} conversationId - Ідентифікатор розмови.
     * @param {string} title - Назва розмови.
     * @param {ChatChannel[]} members - Члени розмови.
     * @param {Message[]} messages - Повідомлення в розмові.
     * @param {CallVariables} [variables] - Змінні виклику.
     */
    constructor(client, conversationId, title, members, messages, variables) {
        this.client = client;
        this.conversationId = conversationId;
        this.title = title;
        this.channelId = null;
        this.createdAt = Date.now();
        this.answeredAt = 0;
        this.updatedAt = 0;
        this.invitedAt = 0;
        this.closedAt = 0;
        this.task = null;
        this.data = null;
        this.queue = null;
        this.members = (members || []).map((i) => wrapChannelMember(i));
        this._messages = messages || [];
        this.state = ConversationState.Invite;
        this.variables = {};
        this._hasReporting = !!(variables && variables.cc_reporting === 'true');
        this.autoAnswered = false;
        this._autoAnswerParam = false;
        this._autoAnswerTimerId = null;
        this._cause = null;
        this.lastAction = null;
        this.contact = {
            id: null,
            hide: false,
        };
        this.meetingId = null;
        for (const k in variables) {
            if (!k.startsWith('cc_') && variables.hasOwnProperty(k)) {
                if (k === 'wbt_auto_answer') {
                    this._autoAnswerParam = variables.wbt_auto_answer;
                }
                // TODO move contact to conversation
                switch (k) {
                    case 'wbt_contact_id':
                        this.contact.id = +variables[k];
                        break;
                    case 'wbt_hide_contact':
                        this.contact.hide = variables[k] === 'true';
                        break;
                    case 'wbt_meeting_id':
                        this.meetingId = variables[k];
                        break;
                    default:
                        this.variables[k] = variables[k];
                }
            }
        }
        if (variables &&
            variables.hasOwnProperty('cc_attempt_id') &&
            this.client.agent) {
            this.queue = {
                id: +variables.cc_queue_id || null,
                name: variables.cc_queue_name || '',
                manual_distribution: variables.cc_manual_distribution === 'true',
            };
            this.task = this.client.agent.task.get(+variables.cc_attempt_id) || null;
        }
    }
    /**
     * Встановлює інформацію про запрошення.
     * @param {string} inviteId - Ідентифікатор запрошення.
     * @param {number} timestamp - Час запрошення.
     */
    setInvite(inviteId, timestamp) {
        this.inviteId = inviteId;
        this.invitedAt = timestamp;
        if (this.autoAnswer) {
            this.joinDelay().catch((e) => {
                this.client.emit('error', e);
            });
        }
    }
    /**
     * Перевіряє, чи є ручний розподіл в черзі.
     * @type {boolean}
     */
    get manualDistribution() {
        return this.queue && this.queue.manual_distribution;
    }
    /**
     * Отримує ідентифікатор контакту.
     * @type {number | null}
     */
    get contactId() {
        return (this.contact && this.contact.id) || null;
    }
    /**
     * Перевіряє, чи слід приховувати контакт.
     * @type {boolean}
     */
    get hideContact() {
        return this.contact && this.contact.hide;
    }
    /**
     * Встановлює ідентифікатор контакту.
     * @param {number} id - Ідентифікатор контакту.
     */
    setContactId(id) {
        if (!this.contact) {
            this.contact = {
                hide: false,
                id,
            };
        }
        else {
            this.contact.id = id;
        }
    }
    /**
     * Перевіряє, чи потрібно автоматично відповісти.
     * @type {boolean}
     */
    get autoAnswer() {
        return this.autoAnswerDelay > 0;
    }
    /**
     * Отримує затримку для автоматичної відповіді.
     * @type {number}
     */
    get autoAnswerDelay() {
        if (!this._autoAnswerParam || `${this._autoAnswerParam}` === 'false') {
            return 0;
        }
        else if (isFinite(+this._autoAnswerParam)) {
            return +this._autoAnswerParam;
        }
        return this.client.autoAnswerDelayTime;
    }
    /**
     * Встановлює розмову як відповідану.
     * @param {string} channelId - Ідентифікатор каналу.
     * @param {number} timestamp - Час відповіді.
     * @param {ChatChannel} member - Член чату.
     */
    setAnswered(channelId, timestamp, member) {
        this.state = ConversationState.Active;
        this.answeredAt = timestamp;
        this.channelId = channelId;
        if (this._autoAnswerTimerId) {
            clearTimeout(this._autoAnswerTimerId);
            this._autoAnswerTimerId = null;
        }
        this.member = wrapChannelMember(member);
        if (!this.members.some((m) => m.id === this.member.id)) {
            this.members.push(this.member);
        }
        this.inviteId = null; // deleted in DB
    }
    /**
     * Встановлює розмову як закриту.
     * @param {number} timestamp - Час закриття.
     */
    setClosed(timestamp) {
        this.state = ConversationState.Closed;
        this.closedAt = timestamp;
    }
    /**
     * Встановлює причину відмови від запрошення.
     * @param {DeclineInviteEvent} e - Подія відмови.
     */
    setDecline(e) {
        this.setClosed(e.timestamp);
        this._cause = e.cause || null;
    }
    /**
     * Встановлює причину виходу з чату.
     * @param {LeavedEvent} e - Подія виходу.
     */
    setLeave(e) {
        if (e.cause) {
            this._cause = e.cause;
        }
        this.setClosed(e.timestamp);
    }
    /**
     * Оновлює видалене повідомлення.
     * @param {MessageDeleted} d - Видалене повідомлення.
     */
    setDeletedMessage(d) {
        const messages = this._messages;
        for (let i = 0; i < messages.length; i++) {
            if (messages[i].id === d.id) {
                d.created_at = messages[i].created_at;
                messages.splice(i, 1, d);
            }
        }
    }
    /**
     * Отримує ідентифікатор розмови.
     * @type {string}
     */
    get id() {
        return this.channelId || this.inviteId || this.conversationId;
    }
    /**
     * Отримує причину відмови або виходу.
     * @type {DeclineCause | null}
     */
    get cause() {
        return this._cause;
    }
    /**
     * Отримує всі повідомлення з каналу.
     * @type {MessageWithChannel[]}
     */
    get messages() {
        return this.getMessages();
    }
    get getConversationId() {
        return this.conversationId;
    }
    /**
     * Отримує всі повідомлення з каналу.
     * @returns {MessageWithChannel[]}
     */
    getMessages() {
        return this._messages.map((i) => {
            const msg = {
                id: i.id,
                type: i.type,
                member: this.messageMember(i),
                channelId: i.channel_id,
                createdAt: i.created_at,
                updatedAt: i.updated_at,
                contact: null,
            };
            if (i.hasOwnProperty('file')) {
                if (i.file && i.file.id > 0) {
                    i.file.url = this.client.fileUrlDownload(i.file.id, i.file.mime);
                    i.file.streamUrl = this.client.fileUrlStream(i.file.id, i.file.mime);
                }
                msg.file = i.file;
            }
            if (i.hasOwnProperty('text')) {
                msg.text = i.text;
            }
            if (i.hasOwnProperty('contact')) {
                msg.contact = i.contact;
            }
            return msg;
        });
    }
    /**
     * Обробляє наступний етап (потрібно реалізувати).
     * @returns {Promise<void>}
     */
    async next() {
        throw new Error('TODO');
    }
    /**
     * Обробляє пагінацію для повідомлень.
     * @param {number} page - Номер сторінки.
     * @param {number} perPage - Кількість повідомлень на сторінку.
     * @returns {Promise<void>}
     */
    async pagination(page, perPage) {
        throw new Error('TODO');
    }
    /**
     * Додає нове повідомлення до розмови.
     * @param {MessageEvent} e - Подія нового повідомлення.
     */
    newMessage(e) {
        this._messages.push(e);
    }
    /**
     * Перевіряє, чи дозволена відмова.
     * @type {boolean}
     */
    get allowDecline() {
        return !!this.inviteId;
    }
    /**
     * Перевіряє, чи дозволено приєднання.
     * @type {boolean}
     */
    get allowJoin() {
        return !!this.inviteId;
    }
    /**
     * Перевіряє, чи дозволено вихід.
     * @type {boolean}
     */
    get allowLeave() {
        return !!this.channelId && (this.closedAt === 0 || !this.hasReporting);
    }
    /**
     * Перевіряє, чи дозволена подача звітності.
     * @type {boolean}
     */
    get allowReporting() {
        const isAutoAnswer = this._autoAnswerParam === true || this._autoAnswerParam === 'true';
        return (this.answeredAt > 0 || isAutoAnswer) && this.hasReporting;
    }
    /**
     * Перевіряє, чи є звітність.
     * @type {boolean}
     */
    get hasReporting() {
        return this._hasReporting;
    }
    /**
     * Отримує ідентифікатори всіх учасників.
     * @type {string[]}
     */
    get membersId() {
        const res = [this.id];
        for (const m of this.members) {
            res.push(m.id);
        }
        return res;
    }
    /**
     * @deprecated
     * Отримує завдання, яке зараз виконується.
     * @type {Task | null}
     */
    get attempt() {
        return this.task;
    }
    /**
     * Перевіряє, чи була розмова передана.
     * @type {boolean}
     */
    get isTransferred() {
        return (this._cause && this._cause.toUpperCase()) === DeclineCause.Transfer;
    }
    /*
     * Дії
     */
    /**
     * Відмовляється від розмови.
     * @param {DeclineCause} [cause] - Причина відмови.
     * @returns {Promise<void>}
     */
    async decline(cause) {
        if (!this.inviteId)
            throw new Error('This conversation is joined');
        let _cause = cause;
        if (!cause && this.answeredAt === 0) {
            _cause = DeclineCause.Busy;
        }
        return this.client.request(`decline_chat`, {
            invite_id: this.inviteId,
            cause: _cause,
        });
    }
    /**
     * Затримує приєднання до розмови, якщо потрібно.
     * @returns {Promise<void>}
     */
    async joinDelay() {
        if (this.autoAnswered) {
            return;
        }
        this.autoAnswered = true;
        this._autoAnswerTimerId = setTimeout(async () => {
            if (!this.answeredAt) {
                await this.join();
            }
        }, this.autoAnswerDelay);
    }
    /**
     * Приєднується до розмови.
     * @returns {Promise<void>}
     */
    async join() {
        if (!this.inviteId)
            throw new Error('This conversation is joined');
        return this.client.request(`join_chat`, {
            invite_id: this.inviteId,
        });
    }
    /**
     * Закриває розмову.
     * @param {string} cause - Причина закриття.
     * @returns {Promise<void>}
     */
    async close(cause) {
        if (!this.channelId)
            throw new Error('This conversation not active');
        return this.client.request(`close_chat`, {
            channel_id: this.channelId,
            conversation_id: this.conversationId,
            cause,
        });
    }
    /**
     * Виходить з розмови.
     * @param {string} cause - Причина виходу.
     * @returns {Promise<void>}
     */
    async leave(cause) {
        if (!this.channelId)
            throw new Error('This conversation not active');
        if (this.closedAt !== 0 &&
            !this.hasReporting &&
            this.cause !== DeclineCause.Busy) {
            return this.client.destroyConversation(this);
        }
        return this.client.request(`leave_chat`, {
            channel_id: this.channelId,
            conversation_id: this.conversationId,
            cause,
        });
    }
    /**
     * Відправляє текстове повідомлення.
     * @param {string} text - Текст повідомлення.
     * @returns {Promise<void>}
     */
    async sendText(text) {
        if (!this.channelId) {
            throw new Error('conversation not active');
        }
        if (!text.length) {
            throw new Error('empty message');
        }
        const chunk = chunkString(text, maxSizeMessage);
        for (let i = 0; i < chunk.length; i++) {
            if (i + 1 >= chunk.length) {
                return this.sendMessageTextChunk(chunk[i]);
            }
            await this.sendMessageTextChunk(chunk[i]);
        }
    }
    /**
     * Відправляє файл.
     * @param {File} file - Файл для відправки.
     * @param {FileUploadProgress} [cb] - Колбек для відстеження прогресу завантаження.
     * @returns {Promise<void>}
     */
    async sendFile(file, cb) {
        const storedFiles = await this.client.storeFile(this.conversationId, [file], cb, 'chat');
        const f = storedFiles[0];
        // todo bug if chat response error
        return this.client.request(`send_file_chat`, {
            channel_id: this.channelId,
            conversation_id: this.conversationId,
            id: f.id,
            name: file.name,
            mime: f.mime,
            url: f.shared,
            size: f.size,
        });
    }
    /**
     * Відправляє дані (текст або файл).
     * @param {string | File} data - Дані для відправки.
     * @param {FileUploadProgress} [cb] - Колбек для відстеження прогресу завантаження.
     * @returns {Promise<void>}
     */
    async send(data, cb) {
        if (data instanceof File) {
            return this.sendFile(data, cb);
        }
        return this.sendText(data);
    }
    /**
     * Оновлює інформацію про членів розмови.
     * @param {ChatChannel[]} members - Нові члени розмови.
     */
    setMembers(members) {
        this.members = members.map((i) => wrapChannelMember(i));
    }
    /**
     * Перевіряє, чи належить повідомлення розмові.
     * @param {Message} m - Повідомлення.
     * @returns {boolean}
     */
    isMessageInConversation(m) {
        return this._messages.some((i) => i.id === m.id);
    }
    /**
     * Надсилає звіт для поточної розмови.
     *
     * Цей метод викликає метод reporting об'єкта Task, щоб надіслати звіт про розмову.
     * Якщо розмова не знаходиться в черзі, буде згенерована помилка.
     *
     * @param {Reporting} reporting - Об'єкт Reporting, який містить дані звіту.
     * @throws {Error} - Якщо розмова не знаходиться в черзі.
     * @returns {Promise<any>} - Результат виконання методу reporting об'єкта Task.
     */
    async reporting(reporting) {
        if (!this.task) {
            throw new Error(`this conversation not in queue`);
        }
        return this.task.reporting(reporting);
    }
    /**
     * Оновлює час для поточної розмови в черзі.
     *
     * Цей метод викликає метод renew об'єкта Task, щоб оновити час розмови.
     * Якщо розмова не знаходиться в черзі, буде згенерована помилка.
     *
     * @param {number} [sec] - Кількість секунд для продовження розмови. Якщо не зазначено, використовується значення за замовчуванням.
     * @throws {Error} - Якщо розмова не знаходиться в черзі.
     * @returns {Promise<any>} - Результат виконання методу renew об'єкта Task.
     */
    async renew(sec) {
        if (!this.task) {
            throw new Error(`this conversation not in queue`);
        }
        return this.task.renew(sec);
    }
    /**
     * Переносить розмову до плану.
     *
     * Цей метод відправляє запит на сервер для перенесення розмови до зазначеного плану.
     *
     * @param {number} planId - Ідентифікатор плану, до якого потрібно перенести розмову.
     * @param {CallVariables} variables - Змінні.
     * @returns {Promise<any>} - Результат запиту на сервер для перенесення розмови.
     */
    async transferToPlan(planId, variables) {
        return this.client.request(`blind_transfer_chat`, {
            conversation_id: this.conversationId,
            channel_id: this.channelId,
            plan_id: planId,
            variables,
        });
    }
    /**
     * Переносить розмову до іншого користувача.
     *
     * Цей метод відправляє запит на сервер для перенесення розмови до вказаного користувача.
     *
     * @param {number} userId - Ідентифікатор користувача, до якого потрібно перенести розмову.
     * @param {CallVariables} variables - Змінні.
     * @returns {Promise<any>} - Результат запиту на сервер для перенесення розмови.
     */
    async transferToUser(userId, variables) {
        return this.client.request(`transfer_user_chat`, {
            conversation_id: this.conversationId,
            channel_id: this.channelId,
            user_id: userId,
            variables,
        });
    }
    /**
     * Встановлює контакт для розмови.
     *
     * Цей метод відправляє запит на сервер для призначення контактного ідентифікатора до розмови.
     *
     * @param {number} contactId - Ідентифікатор контакту, який потрібно призначити для розмови.
     * @returns {Promise<any>} - Результат запиту на сервер для встановлення контакту.
     */
    async setContact(contactId) {
        return this.client.request(`chat_set_contact`, {
            id: this.conversationId,
            channel_id: this.channelId,
            contact_id: contactId,
        });
    }
    /**
     * Надсилає частину текстового повідомлення у розмову.
     *
     * Цей метод надсилає частину текстового повідомлення на сервер у рамках розмови.
     *
     * @param {string} text - Текст повідомлення, яке потрібно надіслати.
     * @returns {Promise<any>} - Результат запиту на сервер для надсилання частини текстового повідомлення.
     */
    sendMessageTextChunk(text) {
        return this.client.request(`send_text_chat`, {
            channel_id: this.channelId,
            conversation_id: this.conversationId,
            text,
        });
    }
    /**
     * Визначає учасника, який надіслав повідомлення.
     *
     * Цей метод перевіряє, чи є відправник повідомлення поточним учасником розмови або одним з членів розмови.
     *
     * @param {Message} msg - Повідомлення, для якого потрібно визначити відправника.
     * @returns {ChatChannel | null} - Об'єкт `ChatChannel`, що представляє учасника, який надіслав повідомлення, або `null`, якщо учасник не знайдено.
     */
    messageMember(msg) {
        if (this.member && msg.channel_id === this.channelId) {
            return {
                self: true,
            };
        }
        for (const m of this.members) {
            if (m.id === msg.channel_id) {
                return m;
            }
        }
        return null; // TODO
    }
}
/**
 * Оновлює або покращує об'єкт ChatChannel.
 * @param {ChatChannel} m - Об'єкт ChatChannel для оновлення.
 * @returns {ChatChannel} - Оновлений об'єкт ChatChannel.
 */
function wrapChannelMember(m) {
    // TODO: Реалізувати додаткову логіку обробки об'єкта ChatChannel
    // Встановлює значення властивості messenger як тип каналу
    m.messenger = m.type;
    return m;
}
//# sourceMappingURL=conversation.js.map