/**
 * @typedef {import('strophe.js/src/builder.js').Builder} Strophe.Builder
 */
import _converse from '../_converse.js';
import log from '../../log.js';
import { Strophe, toStanza } from 'strophe.js';
import { TimeoutError } from '../errors.js';

export default {
    /**
     * Allows you to send XML stanzas.
     * @method _converse.api.send
     * @param {Element|Strophe.Builder} stanza
     * @return {void}
     * @example
     * const msg = converse.env.$msg({
     *     'from': 'juliet@example.com/balcony',
     *     'to': 'romeo@example.net',
     *     'type':'chat'
     * });
     * _converse.api.send(msg);
     */
    send (stanza) {
        const { api } = _converse;
        if (!api.connection.connected()) {
            log.warn("Not sending stanza because we're not connected!");
            log.warn(Strophe.serialize(stanza));
            return;
        }
        if (typeof stanza === 'string') {
            stanza = toStanza(stanza);
        } else if (stanza?.tree) {
            stanza = stanza.tree();
        }

        if (stanza.tagName === 'iq') {
            return api.sendIQ(stanza);
        } else {
            api.connection.get().send(stanza);
            api.trigger('send', stanza);
        }
    },

    /**
     * Send an IQ stanza
     * @method _converse.api.sendIQ
     * @param {Element|Strophe.Builder} stanza
     * @param {number} [timeout] - The default timeout value is taken from
     *  the `stanza_timeout` configuration setting.
     * @param {boolean} [reject=true] - Whether an error IQ should cause the promise
     *  to be rejected. If `false`, the promise will resolve instead of being rejected.
     * @returns {Promise} A promise which resolves (or potentially rejected) once we
     *  receive a `result` or `error` stanza or once a timeout is reached.
     *  If the IQ stanza being sent is of type `result` or `error`, there's
     *  nothing to wait for, so an already resolved promise is returned.
     */
    sendIQ (stanza, timeout, reject=true) {
        const { api } = _converse;

        if (!api.connection.connected()) {
            throw new Error("Not sending IQ stanza because we're not connected!");
        }

        const connection = api.connection.get();

        let promise;
        stanza = stanza.tree?.() ?? stanza;
        if (['get', 'set'].includes(stanza.getAttribute('type'))) {
            timeout = timeout || api.settings.get('stanza_timeout');
            if (reject) {
                promise = new Promise((resolve, reject) => connection.sendIQ(stanza, resolve, reject, timeout));
                promise.catch((e) => {
                    if (e === null) {
                        throw new TimeoutError(
                            `Timeout error after ${timeout}ms for the following IQ stanza: ${Strophe.serialize(stanza)}`
                        );
                    }
                });
            } else {
                promise = new Promise((resolve) => connection.sendIQ(stanza, resolve, resolve, timeout));
            }
        } else {
            connection.sendIQ(stanza);
            promise = Promise.resolve();
        }
        api.trigger('send', stanza);
        return promise;
    }
}
