
/*
 * ==========================================================
 * MAIN SCRIPT
 * ==========================================================
 *
 * Main Javascript file. This file is shared by frond-end and back-end. © 2021 board.support. All rights reserved.
 * 
 */

'use strict';

(function ($) {

    var version = '3.3.6';
    var main;
    var global;
    var upload_target;
    var admin = false;
    var tickets = false;
    var timeout = false;
    var interval = false;
    var timeout_debounce = [];
    var previous_search;
    var sb_current_user = false;
    var chat;
    var chat_editor;
    var chat_textarea;
    var chat_header;
    var chat_status;
    var chat_emoji;
    var chat_scroll_area;
    var document_title = document.title;
    var CHAT_SETTINGS = {};
    var mobile = $(window).width() < 429;
    var today = new Date();
    var bot_id;
    var force_action = '';
    var dialogflow_human_takeover;
    var agents_online = false;
    var ND = 'undefined';
    var cookies_supported = true;
    var utc_offset = (new Date()).getTimezoneOffset() * 60000;
    var cloud_data = false;
    var articles_page = false;

    /*
    * ----------------------------------------------------------
    * EXTERNAL PLUGINS
    * ----------------------------------------------------------
    */

    // Auto Expand Scroll Area | Schiocco
    $.fn.extend({ manualExpandTextarea: function () { var t = this[0]; t.style.height = "auto", t.style.maxHeight = "25px"; window.getComputedStyle(t); t.style.height = (t.scrollHeight > 350 ? 350 : t.scrollHeight) + "px", t.style.maxHeight = "", $(t).trigger("textareaChanged") }, autoExpandTextarea: function () { var t = this[0]; t.addEventListener("input", function (e) { $(t).manualExpandTextarea() }, !1) } });

    // autolink-js
    (function () { var t = [].slice; String.prototype.autoLink = function () { var n, a, r, i, c, e, l; return e = /(^|[\s\n]|<[A-Za-z]*\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi, 0 < (c = 1 <= arguments.length ? t.call(arguments, 0) : []).length ? (i = c[0], n = i.callback, r = function () { var t; for (a in t = [], i) l = i[a], "callback" !== a && t.push(" " + a + "='" + l + "'"); return t }().join(""), this.replace(e, function (t, a, i) { return "" + a + (("function" == typeof n ? n(i) : void 0) || "<a href='" + i + "'" + r + ">" + i + "</a>") })) : this.replace(e, "$1<a href='$2'>$2</a>") } }).call(this);

    /*
    * ----------------------------------------------------------
    * # FUNCTIONS
    * ----------------------------------------------------------
    */

    var SBF = {

        // Main Ajax function
        ajax: function (data, onSuccess = false) {
            data['login-cookie'] = this.loginCookie();
            if (!('user_id' in data) && activeUser()) {
                data['user_id'] = activeUser().id;
            }
            if (!('language' in data) && typeof SB_LANG != ND) {
                data['language'] = SB_LANG;
            }
            if (cloud_data) {
                data['cloud'] = cloud_data;
            }
            $.ajax({
                method: 'POST',
                url: SB_AJAX_URL,
                data: data
            }).done((response) => {
                let result;
                if (Array.isArray(response)) {
                    result = response;
                } else {
                    try {
                        result = JSON.parse(response);
                    } catch (e) {
                        SBChat.is_busy_update = false;
                        SBChat.busy(false);
                        console.log(response);
                        SBF.error(response.length > 500 ? response.substr(0, 500) + '... Check the console for more details.' : response, `SBF.ajax.${data['function']}`);
                        return;
                    }
                }
                if (result[0] == 'success') {
                    if (onSuccess != false) onSuccess(result[1]);
                } else if (SBF.errorValidation(result)) {
                    if (onSuccess != false) onSuccess(result);
                } else {
                    if (admin && result[1] == 'security-error') {
                        setTimeout(() => { SBF.reset() }, 1000);
                    }
                    SBChat.is_busy_update = false;
                    SBChat.busy(false);
                    SBF.error(result[1] + (SBF.null(result[2]) ? '' : '\nFunction name: ' + result[2]) + (SBF.null(result[3]) ? '' : '\nError message: ' + (typeof result[3] == 'string' ? result[3] : ('error' in result[3] && 'message' in result[3]['error'] ? `${result[3]['error']['message']} in function '${result[3]['error']['function']}'` : result[3]))), `SBF.ajax.${data['function']}`);
                }
            });
        },

        // Cors function
        cors: function (method = 'GET', url, onSuccess) {
            let xhr = new XMLHttpRequest();
            if ('withCredentials' in xhr) {
                xhr.open(method, url, true);
            } else if (typeof XDomainRequest != ND) {
                xhr = new XDomainRequest();
                xhr.open(method, url);
            } else {
                return false;
            }
            xhr.onload = function () {
                onSuccess(xhr.responseText);
            };
            xhr.onerror = function () {
                return false;
            };
            xhr.send();
        },

        // Uploads
        upload: function (form, onSuccess) {
            if (cloud_data) form.append('cloud', cloud_data);
            jQuery.ajax({
                url: SB_URL + '/include/upload.php',
                cache: false,
                contentType: false,
                processData: false,
                data: form,
                type: 'POST',
                success: function (response) {
                    onSuccess(response);
                }
            });
        },

        // UTC Time
        UTC: function (datetime) {
            return new Date(datetime).getTime() - utc_offset;
        },

        // Check if a variable is null or empty
        null: function (obj) { if (typeof (obj) !== ND && obj !== null && obj !== 'null' && obj !== false && (obj.length > 0 || typeof (obj) == 'number' || typeof (obj.length) == ND) && obj !== ND) return false; else return true; },

        // Deactivate and hide the elements
        deactivateAll: function () {
            global.find('.sb-popup, .sb-tooltip, .sb-list .sb-menu, .sb-select ul').sbActivate(false);
        },

        // Deselect the content of the target
        deselectAll: function () {
            if (window.getSelection) { window.getSelection().removeAllRanges(); }
            else if (document.selection) { document.selection.empty(); }
        },

        // Get URL parameters
        getURL: function (name = false, url = false) {
            if (url == false) {
                url = location.search;
            }
            if (name == false) {
                var c = url.split('?').pop().split('&');
                var p = {};
                for (var i = 0; i < c.length; i++) {
                    var d = c[i].split('=');
                    p[d[0]] = SBF.escape(d[1]);
                }
                return p;
            }
            if (url.indexOf('?') > 0) {
                url = url.substr(0, url.indexOf('?'));
            }
            return SBF.escape(decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(url) || [, ""])[1].replace(/\+/g, '%20') || ""));
        },

        // Convert a json encoded string to normal text
        restoreJson: function (value) {
            if (SBF.null(value)) return '';
            if (typeof value !== 'string') return '';
            return value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, "\t").replace(/\\f/g, "\f").replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
        },

        // Convert a string to slug and inverse
        stringToSlug: function (string) {
            string = string.trim();
            string = string.toLowerCase();
            var from = "åàáãäâèéëêìíïîòóöôùúüûñç·/_,:;";
            var to = "aaaaaaeeeeiiiioooouuuunc------";
            for (var i = 0, l = from.length; i < l; i++) {
                string = string.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
            }
            return string.replace(/[^a-z0-9 -]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-+/, '').replace(/-+$/, '').replace(/ /g, '');
        },

        slugToString: function (string) {
            string = string.replace(/_/g, ' ').replace(/-/g, ' ');
            return string.charAt(0).toUpperCase() + string.slice(1);
        },

        // Random string
        random: function () {
            let chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
            let result = '';
            for (var i = 5; i > 0; --i) result += chars[Math.floor(Math.random() * 62)];
            return result;
        },

        // Check if a user type is an agent
        isAgent: function (user_type) {
            return user_type == 'agent' || user_type == 'admin' || user_type == 'bot';
        },

        // Get cors elapsed of a given date from now
        beautifyTime: function (datetime, extended = false, future = false) {
            let date;
            if (datetime == '0000-00-00 00:00:00') return '';
            if (datetime.indexOf('-') > 0) {
                let arr = datetime.split(/[- :]/);
                date = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
            } else {
                let arr = datetime.split(/[. :]/);
                date = new Date(arr[2], arr[1] - 1, arr[0], arr[3], arr[4], arr[5]);
            }
            let date_string = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));
            let diff_days = Math.round(((new Date()) - date_string) / (1000 * 60 * 60 * 24)) * (future ? -1 : 1);
            let days = [sb_('Sunday'), sb_('Monday'), sb_('Tuesday'), sb_('Wednesday'), sb_('Thursday'), sb_('Friday'), sb_('Saturday')];
            let time = date_string.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
            if (diff_days < 1) {
                return (extended ? sb_('Today') + ' ' : '') + time;
            } else if (diff_days < 8) {
                return `<span>${days[date_string.getDay()]}</span>${extended ? ` <span>${time}</span>` : ''}`;
            } else {
                return `<span>${date_string.toLocaleDateString()}</span>${extended ? ` <span>${time}</span>` : ''}`;
            }
        },

        // Get the unix timestamp value of a data string with format yyyy-mm-dd hh:mm:ss
        unix: function (datetime) {
            let arr = datetime.split(/[- :]/);
            return Date.UTC(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
        },

        // Generate a string containing the agent location and time
        getLocationTimeString: function (details, onSuccess) {
            if ('timezone' in details) {
                let location = {};
                location['timezone'] = details['timezone']['value'];
                location['country'] = 'country' in details ? details['country']['value'] : location['timezone'].split('/')[0].replace(/_/g, ' ');
                location['city'] = 'city' in details ? details['city']['value'] : location['timezone'].split('/')[1].replace(/_/g, ' ');
                SBF.cors('GET', 'https://worldtimeapi.org/api/timezone/' + location['timezone'], function (response) {
                    response = JSON.parse(response);
                    if (SBF.null(response) || !SBF.null(response['error'])) {
                        SBF.error(response, 'SBF.getLocationTimeString()');
                        onSuccess(responseonSuccess);
                    } else {
                        let datetime = response['datetime'].replace('T', ' ');
                        onSuccess(`${new Date(datetime.substr(0, datetime.indexOf('.'))).toLocaleString([], { hour: '2-digit', minute: '2-digit' })} ${sb_('in')} ${location['city'] ? location['city'] : ''}${location['country'] ? ', ' + location['country'] : ''}`);
                    }
                });
            }
        },

        // Date string
        dateDB: function (date) {
            if (date == 'now') {
                date = (new Date).toISOString().replace('T', ' ');
                if (date.indexOf('.') > 0) {
                    date = date.substr(0, date.indexOf('.'));
                }
                return date;
            } else {
                return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
            }
        },

        // Set and get users last activity
        updateUsersActivity: function (user_id, return_user_id, onSuccess) {
            if (SBPusher.active) {
                onSuccess(SBPusher.online_ids.includes(return_user_id) ? 'online' : 'offline');
            } else {
                SBF.ajax({
                    function: 'update-users-last-activity',
                    user_id: user_id,
                    return_user_id: return_user_id,
                    check_slack: !admin && CHAT_SETTINGS['slack-active']
                }, (response) => {
                    if (response === 'online') {
                        onSuccess('online');
                    } else {
                        onSuccess('offline');
                    }
                });
            }
        },

        // Search functions
        search: function (search, searchFunction) {
            search = search.toLowerCase();
            if (search == previous_search) {
                global.find('.sb-search-btn i').sbLoading(false);
                return;
            }
            clearTimeout(timeout);
            timeout = setTimeout(function () {
                previous_search = search;
                searchFunction();
            }, 200);
        },

        searchClear: function (icon, onSuccess) {
            let search = $(icon).next().val();
            if (search) {
                $(icon).next().val('');
                onSuccess();
            }
        },

        // Support Board error js reporting
        error: function (message, function_name) {
            if (admin && SBAdmin.is_logout) return;
            if (message instanceof Error) message = message.message;
            if (message[message.length - 1] == '.') message = message.slice(0, -1);
            if (message.includes('Support Board Error')) message = message.replace('Support Board Error ', '').replace(']:', ']');
            if (admin && message && !function_name.includes('update-users-last-activity') && !function_name.startsWith('security-error')) SBAdmin.dialog(`[Error] [${function_name}] ${message}. Check the console for more details.`, 'info');
            global.find('.sb-loading').sbLoading(false);
            SBF.event('SBError', { message: message, function_name: function_name });
            throw new Error(`Support Board Error [${function_name}]: ${message}.`);
        },

        errorValidation: function (response, code = true) {
            return Array.isArray(response) && response[0] === 'validation-error' && (code === true || response[1] == code);
        },

        // Login
        loginForm: function (button, area = false, onSuccess = false) {
            button = $(button);
            if (!button.sbLoading()) {
                if (area === false) area = button.closest('.sb-rich-login'); else area = $(area);
                let email = $.trim(area.find('#email input').val());
                let password = $.trim(area.find('#password input').val());
                if (email == '' || password == '') {
                    area.find('.sb-info').html(sb_('Please insert an email and password.')).sbActivate();
                } else {
                    SBF.ajax({
                        function: 'login',
                        email: email,
                        password: password
                    }, (response) => {
                        if (response != false && Array.isArray(response)) {
                            if (!admin && this.isAgent(response[0]['user_type'])) {
                                SBForm.showErrorMessage(area, 'You cannot sign in as an agent.');
                                SBChat.scrollBottom();
                            } else {
                                SBF.loginCookie(response[1]);
                                SBF.event('SBLoginForm', new SBUser(response[0]));
                                if (onSuccess !== false) onSuccess(response);
                            }
                        } else {
                            area.find('.sb-info').html(sb_('Invalid email or password.')).sbActivate();
                            if (!admin) SBChat.scrollBottom();
                        }
                        button.sbLoading(false);
                    });
                    area.find('.sb-info').html('').sbActivate(false);
                    button.sbLoading(true);
                }
            }
        },

        // Set the login cookie
        loginCookie: function (value = false) {
            if (CHAT_SETTINGS.cloud) {
                if (value === false) return storage('login');
                storage('login', value);
            } else {
                if (value === false) return this.cookie('sb-login');
                this.cookie('sb-login', value, 3650, 'set');
            }
        },

        // Login
        login: function (email = '', password = '', user_id = '', token = '', onSuccess = false) {
            SBF.ajax({
                function: 'login',
                email: email,
                password: password,
                user_id: user_id,
                token: token
            }, (response) => {
                if (response != false && Array.isArray(response)) {
                    this.loginCookie(response[1]);
                    if (onSuccess) onSuccess(response);
                    return true;
                } else {
                    return false;
                }
            });
        },

        // Logout
        logout: function (reload = true) {
            SBChat.stopRealTime();
            this.cookie('sb-login', '', '', false);
            this.cookie('sb-cloud', '', '', false);
            storage('open-conversation', '');
            storage('login', '');
            SBChat.conversations = false;
            activeUser(false);
            if (typeof sb_beams_client !== ND) {
                sb_beams_client.stop();
            }
            SBF.ajax({
                function: 'logout'
            }, () => {
                SBF.event('SBLogout');
                if (reload) {
                    setTimeout(() => { location.reload() }, 500);
                }
            });
        },

        // Return the active user
        activeUser: function () {
            return activeUser();
        },

        // Get the active user
        getActiveUser: function (database = false, onSuccess) {
            let app_login = SBApps.login();
            if (!app_login && (storage('wp-login') || storage('whmcs-login') || storage('perfex-login') || storage('aecommerce-login'))) {
                this.cookie('sb-login', '', '', 'delete');
                activeUser(false);
                storage('login', '');
                storage('wp-login', '');
                storage('whmcs-login', '');
                storage('perfex-login', '');
                storage('aecommerce-login', '');
            }
            SBF.ajax({
                function: 'get-active-user',
                db: database,
                login_app: app_login,
                user_token: SBF.getURL('token')
            }, (response) => {
                if (!response) {
                    onSuccess();
                    return false;
                } else {
                    if ('cookie' in response) SBF.loginCookie(response['cookie']);
                    if ('user_type' in response) {
                        if (!admin && SBF.isAgent(response['user_type'])) {
                            let message = 'You are logged in as both agent and user. Logout or use another browser, Incognito or Private mode, to login as user. Force a logout by running the function SBF.reset() in the console.';
                            if (!storage('double-login-alert')) {
                                storage('double-login-alert', true);
                                alert(message);
                            }
                            console.warn('Support Board: ' + message);
                            SBF.event('SBDoubleLoginError');
                        } else {
                            activeUser(new SBUser(response, 'phone' in response ? { phone: response.phone } : {}));
                            SBPusher.start();
                            if (app_login) {
                                storage(app_login[1] + '-login', true);
                            }
                            onSuccess();
                        }
                    }
                }
            });
        },

        // Clean
        reset: function () {
            let cookies = ['sb-login', 'sb-cloud', 'sb-dialogflow-disabled'];
            for (var i = 0; i < cookies.length; i++) {
                this.cookie(cookies[i], '', 0, false);
            }
            try { localStorage.removeItem('support-board') } catch (e) { }
            this.logout();
        },

        // Lightbox
        lightbox: function (content) {
            let lightbox = $(admin ? global : main).find('.sb-lightbox-media');
            lightbox.sbActivate().find(' > div').html(content);
            if (admin) SBAdmin.open_popup = lightbox;
        },

        // Manage the local storage
        storage: function (key, value = ND) {
            try { if (typeof localStorage == ND) return false } catch (e) { return false }
            let settings = localStorage.getItem('support-board');
            if (settings === null) {
                settings = {};
            } else {
                settings = JSON.parse(settings);
            }
            if (value === ND) {
                if (key in settings) {
                    return settings[key];
                } else {
                    return false;
                }
            } else {
                if (value == '') {
                    delete settings[key];
                } else {
                    settings[key] = value;
                }
                localStorage.setItem('support-board', JSON.stringify(settings));
            }
        },

        // Save the current time or check if the saved time is older than now plus the given hours
        storageTime: function (key, hours = false) {
            if (hours === false) {
                storage(key, today.getTime());
            } else {
                return storage(key) == false || (today.getTime() - storage(key)) > (3600000 * hours);
            }
        },

        // Set or get a cookie
        cookie: function (name, value = false, expiration_days = false, action = 'get', seconds = false) {
            let cookie_https = location.protocol == 'https:' ? 'SameSite=None;Secure;' : '';
            let domain = CHAT_SETTINGS && CHAT_SETTINGS['cookie-domain'] ? 'domain=' + CHAT_SETTINGS['cookie-domain'] + ';' : '';
            if (action == 'get') {
                if (!cookies_supported) {
                    return this.storage(name);
                }
                let cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    var cookie = cookies[i];
                    while (cookie.charAt(0) == ' ') {
                        cookie = cookie.substring(1);
                    }
                    if (cookie.indexOf(name) == 0) {
                        let value = cookie.substring(name.length + 1, cookie.length);
                        return this.null(value) ? false : value;
                    }
                }
                return false;
            } else if (action == 'set') {
                if (!cookies_supported) {
                    this.storage(name, value);
                } else {
                    let date = new Date();
                    date.setTime(date.getTime() + (expiration_days * (seconds ? 1 : 86400) * 1000));
                    document.cookie = name + "=" + value + ";expires=" + date.toUTCString() + ";path=/;" + cookie_https + domain;
                }
            } else if (this.cookie(name)) {
                if (!cookies_supported) {
                    this.storage(name, '');
                } else {
                    document.cookie = name + "=" + value + ";expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;" + cookie_https + domain;
                }
            }
        },

        // Return a front setting
        setting: function (key) {
            return typeof CHAT_SETTINGS !== ND && key in CHAT_SETTINGS ? CHAT_SETTINGS[key] : -1;
        },

        // Return the shortcode array
        shortcode: function (shortcode) {
            return SBRichMessages.shortcode(shortcode);
        },

        // Events and webhooks
        event: function (name, parameters) {
            $(document).trigger(name, parameters);
            if (this.setting('webhooks') && ['SBSMSSent', 'SBLoginForm', 'SBUserDeleted', 'SBMessageSent', 'SBBotMessage', 'SBEmailSent', 'SBNewMessagesReceived', 'SBNewConversationReceived', 'SBNewConversationCreated', 'SBActiveConversationStatusUpdated', 'SBSlackMessageSent', 'SBMessageDeleted', 'SBRegistrationForm', 'SBRichMessageSubmit', 'SBNewEmailAddress'].includes(name)) {
                SBF.ajax({
                    function: 'webhooks',
                    function_name: name,
                    parameters: parameters
                });
            }
        },

        // Translate a string
        translate: function (string) {
            if ((!admin && SBF.null(CHAT_SETTINGS)) || (admin && typeof SB_TRANSLATIONS === ND)) return string;
            let translations = admin ? SB_TRANSLATIONS : CHAT_SETTINGS['translations'];
            if (translations && string in translations) {
                return translations[string] == '' ? string : translations[string];
            } else {
                return string;
            }
        },

        // Escape a string
        escape: function (string) {
            if (!string) return string;
            return string.replaceAll('<script', '&lt;script').replaceAll('</script', '&lt;/script').replaceAll('javascript:', '').replaceAll('onclick=', '').replaceAll('onerror=', '');
        },

        // Visibility change function
        visibilityChange: function (visibility = '') {
            if (visibility == 'hidden') {
                if (!admin) {
                    SBChat.stopRealTime();
                }
                SBChat.tab_active = false;
            } else {
                if (activeUser() && !admin) {
                    SBChat.startRealTime();
                }
                SBChat.tab_active = true;
                clearInterval(interval);
                document.title = document_title;
            }
        },

        // Convert a settings string to an Array
        settingsStringToArray: function (string) {
            if (this.null(string)) return [];
            let result = [];
            string = string.split(',');
            for (var i = 0; i < string.length; i++) {
                let values = string[i].split(':');
                result[values[0]] = values[1] == 'false' ? false : values[1] == 'true' ? true : values[1];
            }
            return result;
        },

        // Open a browser window
        openWindow: function (link, width = 550, height = 350) {
            let left = (screen.width / 2) - (width / 2);
            let top = (screen.height / 2) - (height / 2);
            window.open(link, 'targetWindow', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=' + width + ',height=' + height + ', top=' + top + ', left=' + left);
            return false;
        },

        // Convert a date to local time
        convertUTCDateToLocalDate: function (date, UTCoffset = 0) {
            date = new Date(date); // Y/m/d H:i:s
            date = new Date(date.getTime() + UTCoffset * 3600000);
            return new Date(date.getTime() + utc_offset * -1);
        },

        // Load a js or css file
        loadResource: function (url, script = false) {
            let head = document.getElementsByTagName('head')[0];
            let resource = document.createElement(script ? 'script' : 'link');
            if (script) {
                resource.src = url + '?v=' + version;
            } else {
                resource.id = 'support-board';
                resource.rel = 'stylesheet';
                resource.type = 'text/css';
                resource.href = url + '?v=' + version;
                resource.media = 'all';
            }
            head.appendChild(resource);
        },

        // Debounce
        debounce: function (bounceFunction, id, interval = 500) {
            if (!(id in timeout_debounce)) {
                timeout_debounce[id] = true;
                bounceFunction();
                setTimeout(() => {
                    delete timeout_debounce[id];
                }, interval);
            }
        }
    }

    /*
    * ----------------------------------------------------------
    * # PUSHER
    * ----------------------------------------------------------
    */

    var SBPusher = {
        channels: {},
        channels_presence: [],
        active: false,
        pusher: false,
        started: false,
        pusher_beams: false,
        initialized: false,
        online_ids: [],
        init_push_notifications: false,

        // Initialize Pusher
        init: function (onSuccess = false) {
            if (SBPusher.active) {
                if (this.pusher) {
                    return onSuccess ? onSuccess() : true;
                } else if (onSuccess) {
                    $(window).one('SBPusherInit', () => {
                        onSuccess();
                    });
                } else return;
                this.initialized = true;
                $.getScript('https://js.pusher.com/7.0/pusher.min.js', () => {
                    this.pusher = new Pusher(admin ? SB_ADMIN_SETTINGS['pusher-key'] : CHAT_SETTINGS['pusher-key'], { cluster: admin ? SB_ADMIN_SETTINGS['pusher-cluster'] : CHAT_SETTINGS['pusher-cluster'], authEndpoint: SB_URL + '/include/pusher.php', auth: { params: { login: SBF.loginCookie() } } });
                    SBF.event('SBPusherInit');
                });
            }
        },

        // Initialize Push notifications
        initPushNotifications: function () {
            if (activeUser() || admin) {
                $.getScript('https://js.pusher.com/beams/1.0/push-notifications-cdn.js', () => {
                    window.navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
                        this.pusher_beams = new PusherPushNotifications.Client({
                            instanceId: admin ? SB_ADMIN_SETTINGS['push-notifications-id'] : CHAT_SETTINGS['push-notifications-id'],
                            serviceWorkerRegistration: serviceWorkerRegistration,
                        });
                        this.pusher_beams.start().then(() => this.pusher_beams.setDeviceInterests(admin ? [SB_ACTIVE_AGENT['id'], 'agents'] : [activeUser().id, 'users'])).catch(console.error);
                        this.init_push_notifications = false;
                    });
                });
            }
        },

        // Initialize service worker
        initServiceWorker: function () {
            if ('serviceWorker' in navigator) {
                navigator.serviceWorker.register(admin ? SB_URL + '/sw.js?v=' + version : CHAT_SETTINGS['push-notifications-url'] + '?v=' + version).then(function (registration) {
                    registration.update();
                }).catch(function (error) {
                    console.warn(error);
                });
                navigator.serviceWorker.onmessage = function (e) {
                    if (admin && e.data.conversation_id) {
                        SBAdmin.conversations.openConversation(e.data.conversation_id, e.data.user_id);
                    }
                    if (!admin && e.data == 'sb-open-chat') {
                        SBChat.open();
                    }
                };
            }
        },

        // Start Pusher and Push notifications
        start: function () {
            if (!admin && !this.started && activeUser()) {
                if (this.active) {
                    this.init(() => {
                        this.event('client-typing', (response) => {
                            if (response.user_id == SBChat.agent_id && SBChat.conversation && response.conversation_id == SBChat.conversation.id) {
                                SBChat.typing(-1, 'start');
                                clearTimeout(timeout);
                                timeout = setTimeout(() => { SBChat.typing(-1, 'stop') }, 1000);
                            }
                        });
                        this.event('new-message', () => {
                            SBChat.update();
                        });
                        this.presence();
                        this.started = true;
                        SBChat.automations.run_all();
                    });
                }
                if (CHAT_SETTINGS['push-notifications']) {
                    if (typeof Notification != ND && Notification.permission == 'granted') this.initPushNotifications();
                    else this.init_push_notifications = true;
                }
            }
        },

        // Subscribe to a channel
        subscribe: function (channel_name, onSuccess = false) {
            if (!this.pusher) return this.init(() => { this.subscribe(channel_name, onSuccess) });
            channel_name = this.cloudChannelRename(channel_name);
            let channel = this.pusher.subscribe(channel_name);
            channel.bind('pusher:subscription_error', (error) => {
                return console.log(error);
            });
            channel.bind('pusher:subscription_succeeded', () => {
                this.channels[channel_name] = channel;
                if (onSuccess) onSuccess();
            })
        },

        // Add event listener for a channel
        event: function (event, callback, channel = 'private-user-' + activeUser().id) {
            if (!this.pusher) return this.init(() => { this.event(event, callback, channel) });
            let channel_original = channel;
            channel = this.cloudChannelRename(channel);
            if (channel in this.channels) {
                this.channels[channel].unbind(event);
                this.channels[channel].bind(event, (data) => {
                    callback(data);
                });
            } else this.subscribe(channel_original, () => { this.event(event, callback, channel_original) });
        },

        // Trigger an event
        trigger: function (event, data = {}, channel = 'private-user-' + activeUser().id) {
            if (event.indexOf('client-') == 0) {
                return this.channels[this.cloudChannelRename(channel)].trigger(event, data);
            } else {
                SBF.ajax({
                    function: 'pusher-trigger',
                    channel: channel,
                    event: event,
                    data: data
                }, (response) => { return response });
            }
        },

        // Presence  
        presence: function (index = 1, onSuccess) {
            if (!this.pusher) return this.init(() => { this.presence() });
            let channel = this.pusher.subscribe('presence-' + index);
            channel.bind('pusher:subscription_succeeded', (members) => {
                if (members.count > 98) return this.subscribe(index + 1);
                members.each((member) => {
                    if (this.presenceCheck(member)) {
                        this.online_ids.push(member.id);
                    }
                });
                SBChat.updateUsersActivity();
                if (onSuccess) onSuccess();
            })
            channel.bind('pusher:subscription_error', (error) => {
                return console.log(error);
            });
            channel.bind('pusher:member_added', (member) => {
                if (this.presenceCheck(member)) {
                    this.presenceAdd(member.id);
                }
            });
            channel.bind('pusher:member_removed', (member) => {
                this.presenceRemove(member.id);
            });
            this.channels_presence.push(channel);
            if (!admin && CHAT_SETTINGS['slack-active']) {
                this.event('add-user-presence', (response) => {
                    this.presenceAdd(response.agent_id)
                });
                SBF.ajax({
                    function: 'slack-presence',
                    list: true
                }, (response) => {
                    for (var i = 0; i < response.length; i++) {
                        this.presenceAdd(response[i]);
                    }
                    SBChat.updateUsersActivity();
                });
            }
        },

        presenceCheck: function (member) {
            let agent = SBF.isAgent(member.info.user_type);
            return ((admin && !agent) || (!admin && agent)) && !this.online_ids.includes(member.id);
        },

        presenceAdd: function (user_id) {
            if (typeof user_id != ND && !this.online_ids.includes(user_id)) {
                this.online_ids.push(user_id);
                this.presenceUpdateAdmin(user_id);
                SBChat.updateUsersActivity();
            }
        },

        presenceRemove: function (user_id) {
            if (typeof user_id == ND) return;
            let index = this.online_ids.indexOf(user_id);
            if (index !== -1) {
                this.online_ids.splice(index, 1);
                this.presenceUpdateAdmin(user_id);
                SBChat.updateUsersActivity();
            } else if (admin) {
                global.find(`.sb-conversation-busy[data-agent="${user_id}"]`).remove();
            }
        },

        presenceUnsubscribe: function () {
            for (var i = 0; i < this.channels_presence.length; i++) {
                this.channels_presence[i].unsubscribe('presence-' + (i + 1));
            }
        },

        presenceUpdateAdmin: function (user_id) {
            if (admin) {
                if (global.find('.sb-area-users.sb-active').length) SBAdmin.users.update();
                if (activeUser() && activeUser().id == user_id) SBAdmin.users.updateUsersActivity();
            }
        },

        pushNotification: function (message) {
            let image = admin ? SB_ACTIVE_AGENT['profile_image'] : activeUser().image;
            SBF.ajax({
                function: 'push-notification',
                title: admin ? SB_ACTIVE_AGENT['full_name'] : activeUser().name,
                message: (new SBMessage()).strip(message),
                icon: image.indexOf('user.svg') > 0 ? CHAT_SETTINGS['notifications-icon'] : image,
                interests: SBChat.getRecipientUserID(),
                conversation_id: SBChat.conversation == false ? false : SBChat.conversation.id
            }, (response) => { return response });
        },

        cloudChannelRename: function (channel) {
            return (CHAT_SETTINGS.cloud || (admin && SB_ADMIN_SETTINGS.cloud)) ? channel + '-' + (admin ? SB_ADMIN_SETTINGS.cloud.cloud_user_id : CHAT_SETTINGS.cloud.cloud_user_id) : channel;
        }
    }

    /*
    * ----------------------------------------------------------
    * GLOBAL FUNCTIONS
    * ----------------------------------------------------------
    */

    window.SBF = SBF;
    window.SBPusher = SBPusher;
    window.sb_current_user = sb_current_user;

    /*
    * ----------------------------------------------------------
    * JQUERY FUNCTIONS
    * ----------------------------------------------------------
    */

    // Activate or deactivate the element
    $.fn.sbActivate = function (show = true) {
        $(this).setClass('sb-active', show);
        return this;
    };

    // Check if active 
    $.fn.sbActive = function () {
        return $(this).hasClass('sb-active');
    };

    // Loading check, set and unset
    $.fn.sbLoading = function (value = 'check') {
        if (value == 'check') {
            return $(this).hasClass('sb-loading');
        } else {
            $(this).setClass('sb-loading', value);
        }
        return this;
    }

    // Popup
    $.fn.sbTogglePopup = function (button = false) {
        let showed = true;
        if (admin) SBAdmin.open_popup = false;
        if ($(this).sbActive()) {
            $(this).sbActivate(false);
            global.removeClass('sb-popup-active');
            showed = false;
        } else {
            global.addClass('sb-popup-active');
            global.find('.sb-popup').sbActivate(false);
            if (button) $(this).css('left', $(button).offset().left + 15).sbActivate();
            if (admin) setTimeout(() => { SBAdmin.open_popup = this }, 500);
            SBF.deselectAll();
        }
        return showed;
    };

    // Uploader
    $.fn.sbUploadFiles = function (onSuccess) {
        let files = $(this).prop('files');
        for (var i = 0; i < files.length; i++) {
            let file = files[i];
            let form = new FormData();
            form.append('file', file);
            SBF.upload(form, onSuccess);
        }
        $(this).value = '';
    }

    // Set profile box
    $.fn.setProfile = function (name = false, profile_image = false) {
        if (SBF.null(name)) name = activeUser() ? activeUser().name : '';
        if (SBF.null(profile_image)) profile_image = activeUser() ? activeUser().image : SB_URL + '/media/user.svg';
        $(this).find('img').attr('src', profile_image);
        $(this).find('.sb-name').html(name);
        return this;
    }

    // Add or remove a class
    $.fn.setClass = function (class_name, add = true) {
        if (add) {
            $(this).addClass(class_name);
        } else {
            $(this).removeClass(class_name);
        }
    }

    /*
    * ----------------------------------------------------------
    * CLASSIC FUNCTIONS
    * ----------------------------------------------------------
    */

    // Delta value
    function sbDelta(e) {
        let delta = e.originalEvent.wheelDelta;
        if (typeof delta == ND) {
            delta = e.originalEvent.deltaY;
        }
        if (typeof delta == ND) {
            delta = e.originalEvent.detail * -1;
        }
        return delta;
    }

    // Check if an elemen is loading and set it status to loading
    function loading(element) {
        if ($(element).sbLoading()) return true;
        else $(element).sbLoading(true);
        return false;
    }

    // Shortcut for local storage function
    function storage(key, value = ND) {
        return SBF.storage(key, value);
    }

    // Support Board js translations
    function sb_(string) {
        return SBF.translate(string);
    }

    // Access the global user variable
    function activeUser(value = -1) {
        if (value === -1) {
            return window.sb_current_user;
        } else {
            window.sb_current_user = value;
        }
    }

    /* 
    * ----------------------------------------------------------
    * # USER
    * ----------------------------------------------------------
    */

    class SBUser {
        constructor(details = {}, extra = {}) {
            this.details = details;
            this.extra = extra;
            this.conversations = [];
            this.processArray(details);
        }

        get id() {
            return this.get('id') == '' ? this.get('user_id') : this.get('id');
        }

        get type() {
            return this.get('user_type');
        }

        get name() {
            return 'first_name' in this.details ? this.details['first_name'] + (this.details['last_name'] ? ' ' + this.details['last_name'] : '') : '';
        }

        get nameBeautified() {
            return 'last_name' in this.details && this.details['last_name'].charAt(0) != '#' ? this.name : CHAT_SETTINGS['visitor-default-name'];
        }

        get image() {
            return this.get('profile_image');
        }

        get language() {
            let language = this.getExtra('language');
            if (language == '') language = this.getExtra('browser_language');
            return language == '' ? '' : language['value'].toLowerCase();
        }

        get(id) {
            if (id in this.details && !SBF.null(this.details[id])) return this.details[id];
            else return '';
        }

        getExtra(id) {
            if (id in this.extra && !SBF.null(this.extra[id])) return this.extra[id];
            else return '';
        }

        set(id, value) {
            this.details[id] = value;
        }

        setExtra(id, value) {
            this.extra[id] = value;
        }

        // Initialization
        processArray(details) {
            if (details && 'details' in details) {
                for (var i = 0; i < details['details'].length; i++) {
                    this.setExtra(details['details'][i]['slug'], details['details'][i]);
                }
                delete details['details'];
                this.details = details;
            }
        }

        // Update the user details and extra details
        update(onSuccess) {
            if (this.id) {
                SBF.ajax({
                    function: 'get-user',
                    user_id: this.id,
                    extra: true
                }, (response) => {
                    this.processArray(response);
                    onSuccess();
                });
            } else {
                SBF.error('Missing user ID', 'SBUser.update');
            }
        }

        // Get user conversations
        getConversations(onSuccess = false, exclude_id) {
            if (this.id) {
                SBF.ajax({
                    function: 'get-user-conversations',
                    user_id: this.id,
                    exclude_id: exclude_id
                }, (response) => {
                    let conversations = [];
                    for (var i = 0; i < response.length; i++) {
                        let conversation = new SBConversation([new SBMessage(response[i])], { id: response[i]['conversation_id'], conversation_status_code: response[i]['conversation_status_code'], department: response[i]['department'], title: response[i]['title'] });
                        conversations.push(conversation);
                    }
                    this.conversations = conversations;
                    if (onSuccess) onSuccess(conversations);
                });
            } else {
                SBF.error('Missing user ID', 'SBUser.getConversations');
            }
        }

        // Get conversations code
        getConversationsCode(conversations = false) {
            let code = '';
            let active_conversation_id = SBChat.conversation ? SBChat.conversation.id : -1;
            if (!conversations) conversations = this.conversations;
            for (var i = 0; i < conversations.length; i++) {
                if (conversations[i] instanceof SBConversation) {
                    code += `<li ${active_conversation_id == conversations[i].id ? 'class="sb-active" ' : ''}data-conversation-status="${conversations[i].get('conversation_status_code')}" data-conversation-id="${conversations[i].id}" data-department="${conversations[i].get('department')}">${conversations[i].getCode()}</li>`;
                } else {
                    SBF.error('Conversation not of type SBConversation', 'SBUser.getConversationsCode');
                }
            }
            if (!code) code = `<p class="sb-no-results">${sb_('No conversations found.')}</p>`;
            return code;
        }

        // Get single conversation
        getFullConversation(conversation_id = false, onSuccess = false) {
            if (conversation_id !== false) {
                SBF.ajax({
                    function: 'get-conversation',
                    conversation_id: conversation_id
                }, (response) => {
                    let messages = [];
                    if (response) {
                        for (var i = 0; i < response['messages'].length; i++) {
                            messages.push(new SBMessage(response['messages'][i]));
                        }
                    }
                    if (onSuccess) onSuccess(new SBConversation(messages, response ? response['details'] : false));
                });
            } else {
                SBF.error('Missing conversation ID', 'SBUser.getFullConversation');
            }
        }

        getConversationByID(conversation_id) {
            for (var i = 0; i < this.conversations.length; i++) {
                if (this.conversations[i].id == conversation_id) {
                    return this.conversations[i];
                }
            }
            return false;
        }

        // Add a new conversation
        addConversation(conversation) {
            if (conversation instanceof SBConversation) {
                let conversation_id = conversation.id;
                let is_new = true;
                for (var i = 0; i < this.conversations.length; i++) {
                    if (this.conversations[i].id == conversation_id) {
                        this.conversations[i] = conversation;
                        is_new = false;
                        break;
                    }
                }
                if (is_new) {
                    this.conversations.unshift(conversation);
                }
                return is_new;
            } else {
                SBF.error('Conversation not of type SBConversation', 'SBUser.addConversation');
            }
        }

        // Get the last conversation
        getLastConversation() {
            return this.isConversationsEmpty() ? false : this.conversations[this.conversations.length - 1];
        }

        // Check if the conversation array is empty
        isConversationsEmpty() {
            return this.conversations.length == 0;
        }

        // Check if the extra array is empty
        isExtraEmpty() {
            return Object.keys(this.extra).length === 0 && this.extra.constructor === Object;
        }

        // Delete the user
        delete(onSuccess) {
            if (this.id) {
                SBF.ajax({
                    function: 'delete-user',
                    user_id: this.id
                }, () => {
                    SBF.event('SBUserDeleted', this.id);
                    onSuccess();
                    return true;
                });
            } else {
                SBF.error('Missing user ID', 'SBUser.delete');
            }
        }
    }
    window.SBUser = SBUser;

    /* 
    * ----------------------------------------------------------
    * # MESSAGE
    * ----------------------------------------------------------
    */

    class SBMessage {
        constructor(details = {}) {
            this.details = details;
            if ('first_name' in this.details) {
                this.details['full_name'] = this.details['first_name'] + ' ' + this.details['last_name'];
            }
            this.set('payload', this.get('payload') ? JSON.parse(this.get('payload')) : {});
        }

        get id() {
            return this.get('id');
        }

        get attachments() {
            return !SBF.null(this.details['attachments']) ? JSON.parse(this.details['attachments']) : [];
        }

        get message() {
            return this.get('message');
        }

        get(id) {
            if (id in this.details && !SBF.null(this.details[id])) return this.details[id];
            else return '';
        }

        set(id, value) {
            this.details[id] = value;
        }

        payload(key = false, value = false) {
            let payload = this.get('payload');
            if (key !== false && value !== false) {
                payload[key] = value;
                this.set('payload', payload);
            } else if (key !== false) {
                return key in payload ? payload.key : ('id' in payload && payload.id == key ? payload : false);
            }
            return payload;
        }

        getCode() {
            let agent = SBF.isAgent(this.details['user_type']);
            let admin_menu = admin ? SBAdmin.conversations.messageMenu(agent) : '';
            let message = this.render(this.message == '' && 'preview' in this.payload ? this.payload.preview : this.message);
            let attachments = this.attachments;
            let attachments_code = '';
            let media_code = '';
            let thumb = admin || (agent && !CHAT_SETTINGS['hide-agents-thumb']) || (!agent && CHAT_SETTINGS['display-users-thumb']) ? `<div class="sb-thumb"><img src="${this.details['profile_image']}"><div>${this.details['full_name']}</div></div>` : '';
            let css = (!agent ? 'sb-right' : '') + (thumb == '' ? '' : ' sb-thumb-active');
            let type = '';
            if (message == '' && attachments.length == 0) {
                return '';
            }

            // Rich Messages
            if (agent) {
                let shortcodes = message.match(/\[.+?\]/g) || [];
                let rich = false;
                for (var i = 0; i < shortcodes.length; i++) {
                    let settings = SBRichMessages.shortcode(shortcodes[i]);
                    let rich_message = SBRichMessages.generate(settings[1], settings[0]);
                    if (rich_message) {
                        if (message.charAt(0) != '[') {
                            rich_message = rich_message.replace('sb-rich-message', 'sb-rich-message sb-margin-top');
                        }
                        if (message.charAt(message.length - 1) != ']') {
                            rich_message = rich_message.replace('sb-rich-message', 'sb-rich-message sb-margin-bottom');
                        }
                        message = message.replace(shortcodes[i], rich_message);
                        rich = true;
                        type = `data-type="${settings[0]}"`;
                    }
                }
                if (rich) {
                    css += ' sb-rich-cnt';
                    if (shortcodes.length > 1) {
                        type = 'data-type="multiple"';
                    }
                }
            }

            // Attachments
            if (attachments.length) {
                attachments_code = '<div class="sb-message-attachments">';
                for (var i = 0; i < attachments.length; i++) {
                    let url = attachments[i][1];
                    if (/.jpg|.jpeg|.png|.gif/.test(url)) {
                        media_code += `<div class="sb-image${url.includes('.png') ? ' sb-image-png' : ''}"><img src="${url}" /></div>`;
                    } else {
                        attachments_code += `<a rel="noopener" target="_blank" href="${url}">${attachments[i][0]}</a>`;
                    }
                }
                attachments_code += '</div>';
            }

            // Message creation
            return `<div data-id="${this.details['id']}" class="${css}" ${type}>${thumb}<div class="sb-cnt"><div class="sb-message${media_code && message == '' ? ' sb-message-media' : ''}">${message}${media_code}</div>${attachments_code}<div class="sb-time">${SBF.beautifyTime(this.details['creation_time'], true)}</div></div>${admin_menu}</div>`;
        }

        render(message = false) {
            if (message === false) message = '' + this.details['message'];
            let len = message.length;

            // Code block
            let codes = message.match(/```([\s\S]*)```/) || [];
            for (var i = 0; i < codes.length; i++) {
                message = message.replace(codes[i], '[code-' + i + ']');
            }

            // Breakline
            message = message.replace(/(?:\r\n|\r|\n)/g, '<br>');

            // Bold
            message = message.replace(/\*([^\**]+)\*/g, "<b>$1</b>");

            // Italic
            message = message.replace(/\__([^\____]+)\__/g, "<i>$1</i>");

            // Strikethrough
            message = message.replace(/\~([^\~~]+)\~/g, "<del>$1</del>");

            // Code
            message = message.replace(/\`([^\``]+)\`/g, "<code>$1</code>");

            // Unicode
            let false_positives = ['cede', 'ubbed'];
            for (var i = 0; i < false_positives.length; i++) {
                message = message.replaceAll(false_positives[i], `[U${i}]`);
            }
            message = message.replaceAll('uccede', '[U1]');
            message = message.replace(/u([0-9a-f]{4,5})/mi, '&#x$1;');
            for (var i = 0; i < false_positives.length; i++) {
                message = message.replaceAll(`[U${i}]`, false_positives[i]);
            }

            // Single emoji
            if (((len == 6 || len == 5) && message.startsWith('&#x')) || len < 3 && message.match(/(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/)) {
                message = `<span class="emoji-large">${message}</span>`;
            }

            // Links
            if (message.includes('http')) {
                message = message.autoLink({ target: '_blank' });
            }

            // Code block restore
            for (var i = 0; i < codes.length; i++) {
                message = message.replace('[code-' + i + ']', '<pre>' + $.trim($.trim(codes[i]).substr(3, codes[i].length - 6).replace(/(?:\r\n|\r|\n)/g, '<br>'))) + '</pre>';
            }

            return message;
        }

        strip(message = false) {
            if (message === false) message = '' + this.details['message'];
            let replaces = [message.match(/\*([^\**]+)\*/g), message.match(/\__([^\____]+)\__/g), message.match(/\~([^\~~]+)\~/g), message.match(/\`([^\``]+)\`/g)];
            let chars = ['*', '__', '~', '`'];
            for (var i = 0; i < replaces.length; i++) {
                let items = replaces[i];
                if (!SBF.null(items)) {
                    for (var y = 0; y < items.length; y++) {
                        message = message.replace(items[y], items[y].replaceAll(chars[i], ''));
                    }
                }
            }
            return message;
        }
    }
    window.SBMessage = SBMessage;

    /* 
    * ----------------------------------------------------------
    * # CONVERSATION
    * ----------------------------------------------------------
    */

    class SBConversation {
        constructor(messages, details) {
            this.details = SBF.null(details) ? {} : details;
            if (Array.isArray(messages)) {
                this.messages = [];
                if (messages.length) {
                    if (messages[0] instanceof SBMessage) {
                        this.messages = messages;
                    } else {
                        SBF.error('Messages not of type SBMessage', 'SBConversation.constructor');
                    }
                }
            } else {
                SBF.error('Message array not of type Array', 'SBConversation.constructor');
            }
        }

        get id() {
            return this.get('id') == '' ? this.get('conversation_id') : this.get('id');
        }

        get(id) {
            if (id in this.details && !SBF.null(this.details[id])) {
                return this.details[id];
            }
            if (id == 'title') {
                if (!SBF.null(this.details['first_name'])) {
                    return this.details['first_name'] + ' ' + this.details['last_name'];
                } else if (this.messages.length) {
                    return this.messages[0].get('full_name');
                }
            }
            return '';
        }

        set(id, value) {
            this.details[id] = value;
        }

        getMessage(id) {
            for (var i = 0; i < this.messages.length; i++) {
                if (this.messages[i].id == id) {
                    this.messages[i].set('index', i);
                    return this.messages[i];
                }
            }
            return false;
        }

        getLastMessage() {
            let index = this.messages.length - 1;
            for (var i = index; i > -1; i--) {
                if (this.messages[i].message || this.messages[i].attachments.length) return this.messages[i];
            }
            return false;
        }

        getLastUserMessage(index = false, agent = false) {
            if (index === false) index = this.messages.length - 1;
            for (var i = index; i > -1; i--) {
                let message = this.messages[i];
                let user_type = message.get('user_type');
                if (!message.message && !message.attachments.length) continue;
                if ((!agent && !SBF.isAgent(user_type)) || (agent === true && (user_type == 'agent' || user_type == 'admin')) || (agent == 'bot' && user_type == 'bot') || (agent == 'no-bot' && user_type != 'bot') || (agent == 'all' && SBF.isAgent(user_type))) {
                    this.messages[i].set('index', i - 1);
                    return this.messages[i];
                }
            }
            return false;
        }

        getUserMessages(user_type = 'user') {
            let results = [];
            let checks = user_type == 'user' ? ['visitor', 'lead', 'user'] : (user_type == 'agents' ? ['agent', 'admin'] : ['bot']);
            for (var i = 0; i < this.messages.length; i++) {
                if (checks.includes(this.messages[i].get('user_type'))) {
                    results.push(this.messages[i]);
                }
            }
            return results;
        }

        updateMessage(id, message) {
            if (message instanceof SBMessage) {
                for (var i = 0; i < this.messages.length; i++) {
                    if (this.messages[i].id == id) {
                        this.messages[i] = message;
                        return true;
                    }
                }
            } else {
                SBF.error('Message not of type SBMessage', 'SBConversation.updateMessage');
            }
            return false;
        }

        addMessages(messages) {
            if (Array.isArray(messages)) {
                for (var i = 0; i < messages.length; i++) {
                    if (messages[i] instanceof SBMessage) {
                        this.messages.push(messages[i]);
                    }
                }
            } else {
                if (messages instanceof SBMessage) {
                    this.messages.push(messages);
                } else {
                    SBF.error('Messages not of type SBMessage', 'SBConversation.addMessages()');
                }
            }
            return this;
        }

        getCode() {
            let count = this.messages.length;
            if (count) {
                let message = this.messages[count - 1];
                let text = message.message;
                if (text.indexOf('[') !== false) {
                    let shortcodes = text.match(/\[.+?\]/g) || [];
                    if (shortcodes.length) {
                        let shortcode = SBRichMessages.shortcode(shortcodes[0]);
                        text = text.replace(shortcodes[0], sb_(SBF.null(shortcode[1]['message']) ? (SBF.null(shortcode[1]['title']) ? '' : shortcode[1]['title']) : shortcode[1]['message']));
                    }
                }
                let title = tickets && !CHAT_SETTINGS['tickets-conversations-title-user'] ? this.get('title') : sb_(!admin && message.get('last_name').charAt(0) == '#' ? 'You' : message.get('full_name'));
                return `<div class="sb-conversation-item" data-user-id="${message.get('user_id')}"><img src="${message.get('profile_image')}"><div><span class="sb-name">${title}</span><span class="sb-time">${SBF.beautifyTime(message.get('creation_time'))}</span></div><div class="sb-message">${text.length > 114 ? text.substr(0, 114) + ' ...' : text}</div></div>`;
            }
            return '';
        }

        deleteMessage(id) {
            for (var i = 0; i < this.messages.length; i++) {
                if (this.messages[i].id == id) {
                    this.messages.splice(i, 1);
                    return true;
                }
            }
            return false;
        }

        searchMessages(search, exact_match = false) {
            let results = [];
            for (var i = 0; i < this.messages.length; i++) {
                let message = this.messages[i].message;
                if ((exact_match && message == search) || (!exact_match && message.includes(search))) {
                    results.push[messages[i]];
                }
            }
            return results;
        }

        getAttachments() {
            let list = [];
            for (var i = 0; i < this.messages.length; i++) {
                let attachments = this.messages[i].attachments;
                for (var j = 0; j < attachments.length; j++) {
                    let link = attachments[j][1];
                    list.push([attachments[j][0], link, link.substr(link.lastIndexOf('.') + 1), this.messages[i].id]);
                }
            }
            return list;
        }
    }
    window.SBConversation = SBConversation;

    /* 
    * ----------------------------------------------------------
    * # CHAT
    * ----------------------------------------------------------
    */

    var SBChat = {

        // Variables
        rich_messages_list: ['chips', 'buttons', 'select', 'inputs', 'table', 'list'],
        emoji_options: { range: 0, range_limit: 47, list: [], list_now: [], touch: false },
        initialized: false,
        editor_listening: false,
        conversation: false,
        is_busy: false,
        is_busy_update: false,
        is_busy_populate: false,
        chat_open: false,
        real_time: false,
        agent_id: -1,
        agent_online: false,
        user_online: false,
        expanded: false,
        main_header: true,
        start_header: false,
        desktop_notifications: false,
        flash_notifications: false,
        datetime_last_message: '2000-01-01 00:00:00',
        datetime_last_message_conversation: '2000-01-01 00:00:00',
        audio: false,
        audio_out: false,
        tab_active: true,
        notifications: [],
        typing_settings: { typing: false, sent: false, timeout: false },
        email_sent: false,
        dashboard: false,
        articles: false,
        articles_categories: false,
        slack_channel: [-1, -1],
        skip: false,
        queue_interval: false,
        departments: false,
        default_department: null,
        default_agent: null,
        offline_message_set: false,

        // Send a message
        sendMessage: function (user_id = -1, message = '', attachments = [], onSuccess = false, payload = false, conversation_status_code = false) {
            let is_dialogflow_human_takeover = dialogflow_human_takeover && SBApps.dialogflow.active();
            let is_return = false;

            // Check settings and contents
            if (!this.conversation) {
                let last_conversation = admin ? false : activeUser().getLastConversation();
                if (last_conversation && force_action != 'new-conversation') {
                    this.openConversation(last_conversation.id);
                    this.setConversation(last_conversation);
                    force_action = false;
                } else {
                    this.newConversation(conversation_status_code, user_id, '', [], (admin && SB_ACTIVE_AGENT['department'] ? SB_ACTIVE_AGENT['department'] : null), null, () => {
                        return this.sendMessage(user_id, message, attachments, onSuccess, payload);
                    });
                    return;
                }
            }
            if (user_id == -1) user_id = admin ? SB_ACTIVE_AGENT['id'] : activeUser().id;
            let is_user = user_id != bot_id;
            if (!message && !attachments.length) {
                message = chat_textarea.val().trim();
                chat_editor.find('.sb-attachments > div').each(function () {
                    attachments.push([$(this).attr('data-name'), $(this).attr('data-value')]);
                });
                if (admin && SBAdmin.must_translate) {
                    SBApps.dialogflow.translate([message], activeUser().language, (response) => {
                        if (response) {
                            if (payload) {
                                payload['original-message'] = message;
                            } else {
                                payload = { 'original-message': message };
                            }
                            if (response[0].translatedText) message = response[0].translatedText;
                        }
                        this.sendMessage(user_id, message, attachments, onSuccess, payload, conversation_status_code);
                    });
                    is_return = true;
                }
            }

            this.busy(true);
            if (is_user) {
                chat_textarea.val('').css('height', '');
                chat_editor.find('.sb-attachments').html('');
            }
            this.activateBar(false);

            if (is_return) return;
            if (conversation_status_code === false && user_id == bot_id) {
                conversation_status_code = 'skip';
            }
            if (!admin && is_user && !is_dialogflow_human_takeover) {
                conversation_status_code = 2;
            }

            // Send message
            if (message || attachments.length || payload) {
                SBF.ajax({
                    function: 'send-message',
                    user_id: user_id,
                    conversation_id: this.conversation.id,
                    message: message,
                    attachments: attachments,
                    conversation_status_code: conversation_status_code,
                    queue: !admin && CHAT_SETTINGS['queue'] && is_user,
                    payload: payload,
                    recipient_id: admin ? activeUser().id : false
                }, (response) => {
                    let send_slack = admin || !is_dialogflow_human_takeover || response.human_takeover_active

                    // Update the dashboard conversations
                    if (!admin && this.dashboard && user_id == bot_id) {
                        this.updateConversations();
                    }

                    // Update the chat current conversation
                    if ((admin && !this.user_online) || (!admin && !this.agent_online)) {
                        this.update();
                    }

                    // Follow up and offline messages
                    if (!admin && is_user && !dialogflow_human_takeover) {
                        this.followUp();
                        this.offlineMessage();
                    }

                    // Dialogflow
                    if (is_user && (!payload || (payload['id'] != 'sb-human-takeover' && SBF.null(payload['skip-dialogflow'])))) {
                        SBApps.dialogflow.message(message, attachments);
                    }

                    // Slack and visitor to lead
                    if (!admin && is_user && activeUser().type == 'visitor') {
                        SBF.ajax({ function: 'update-user-to-lead', user_id: user_id }, () => {
                            activeUser().set('user_type', 'lead');
                            if (CHAT_SETTINGS['slack-active'] && send_slack) {
                                this.slackMessage(user_id, activeUser().name, activeUser().image, message, attachments);
                            }
                        });
                    } else if (send_slack && !this.skip) {
                        if (admin && SB_ADMIN_SETTINGS['slack-active']) {
                            this.slackMessage(activeUser().id, SB_ACTIVE_AGENT['full_name'], SB_ACTIVE_AGENT['profile_image'], message, attachments);
                        } else if (CHAT_SETTINGS['slack-active']) {
                            this.slackMessage(activeUser().id, (is_user ? activeUser().name : CHAT_SETTINGS['bot-name']), (is_user ? activeUser().image : CHAT_SETTINGS['bot-image']), message, attachments);
                        }
                    }

                    // Language detection
                    if (is_user && CHAT_SETTINGS['language-detection'] && this.conversation && message.split(' ').length > 1 && !SBF.storage('language-detection-completed')) {
                        SBF.ajax({ function: 'google-language-detection-update-user', user_id: user_id, string: message, token: SBApps.dialogflow.token });
                        SBF.storage('language-detection-completed', true);
                    }

                    // Articles
                    if (this.articles && !admin && CHAT_SETTINGS['articles'] && !CHAT_SETTINGS['office-hours'] && !this.isInitDashboard()) {
                        setTimeout(() => {
                            if (this.conversation) {
                                this.sendMessage(bot_id, '[articles]');
                                this.scrollBottom();
                                this.articles = false;
                            }
                        }, 5000);
                    }

                    // Queue
                    if (response['queue']) {
                        this.queue(this.conversation.id);
                    }

                    // Events
                    let message_response = { user_id: user_id, conversation_id: this.conversation.id, conversation_status_code: conversation_status_code, message_id: response.id, message: message, attachments: attachments };
                    SBF.event('SBMessageSent', message_response);
                    if (tickets) SBTickets.onMessageSent();
                    if (onSuccess) onSuccess(message_response);
                    if (response.notifications.length) SBF.event('SBNotificationsSent', response.notifications);

                    // Miscellaneous
                    if (this.skip) this.skip = false;
                    this.busy(false);
                });

                // Display the message as sending in progress
                if (is_user) {
                    message = SBF.escape(message);
                    chat.append((new SBMessage({ id: 'sending', profile_image: (admin ? SB_ACTIVE_AGENT['profile_image'] : activeUser().image), full_name: activeUser().name, creation_time: '0000-00-00 00:00:00', message: message, user_type: (admin ? 'agent' : 'user') })).getCode().replace('<div class="sb-time"></div>', `<div class="sb-time">${sb_('Sending')}<i></i></div>`));
                    if (!this.dashboard) this.scrollBottom();
                }

                // Sounds
                if (this.audio && (!admin && this.chat_open && is_user && ['a', 'aa'].includes(CHAT_SETTINGS['chat-sound'])) || (admin && SB_ADMIN_SETTINGS['sounds'] == 'a')) {
                    this.audio_out.play();
                }
            } else this.busy(false);
        },

        // [Deprecated] This function will be removed soon
        sendBotMessage: function (message = '', attachments = []) {
            return SBApps.dialogflow.message(message, attachments);
        },

        // Update message
        updateMessage: function (message_id, message = '') {
            SBF.ajax({
                function: 'update-message',
                message_id: message_id,
                message: message
            });
        },

        // Email notifications
        sendEmail: function (message, attachments, send_to_active_user = false) {
            let recipient_id = send_to_active_user ? activeUser().id : this.getRecipientUserID();
            if (!admin && !isNaN(recipient_id) && this.agent_online) {
                return false;
            }
            SBF.ajax({
                function: 'create-email',
                recipient_id: recipient_id,
                sender_name: send_to_active_user ? CHAT_SETTINGS['bot-name'] : (admin ? SB_ACTIVE_AGENT['full_name'] : activeUser().name),
                sender_profile_image: send_to_active_user ? CHAT_SETTINGS['bot-image'] : (admin ? SB_ACTIVE_AGENT['profile_image'] : activeUser().image),
                message: message,
                attachments: attachments,
                department: this.conversation ? this.conversation.get('department') : false,
                conversation_id: this.conversation ? this.conversation.id : false
            }, () => {
                SBF.event('SBEmailSent', { recipient_id: recipient_id, message: message, attachments: attachments });
            });
        },

        // SMS notifications
        sendSMS: function (message) {
            let recipient_id = this.getRecipientUserID();
            if (!admin && !isNaN(recipient_id) && this.agent_online) return false;
            SBF.ajax({
                function: 'send-sms',
                to: recipient_id,
                message: message,
                conversation_id: this.conversation ? this.conversation.id : false
            }, (response) => {
                if (response['status'] == 'sent' || response['status'] == 'queued') {
                    SBF.event('SBSMSSent', { recipient_id: this.getRecipientUserID(), message: message, response: response });
                } else if (response.message) {
                    SBF.error(response.message, 'SBChat.sendSMS');
                }
            });
        },

        // Push notifications. [Deprecated. This function will be removed soon.]
        pushNotification: function (message) {
            SBPusher.pushNotification(message);
        },

        // Desktop notifications
        desktopNotification: function (title, message, icon, conversation_id = false, user_id = false) {
            if (Notification.permission !== 'granted') {
                Notification.requestPermission();
            } else {
                let notify = new Notification(title, {
                    body: (new SBMessage()).strip(message),
                    icon: icon.indexOf('user.svg') > 0 ? CHAT_SETTINGS['notifications-icon'] : icon
                });
                notify.onclick = () => {
                    if (admin) {
                        if (conversation_id) {
                            SBAdmin.conversations.openConversation(conversation_id, user_id == false ? activeUser().id : user_id);
                        }
                    } else {
                        this.start();
                    }
                    window.focus();
                }
            }
        },

        // Returns the recipient user ID 
        getRecipientUserID: function () {
            return admin ? activeUser().id : (this.lastAgent(false) ? this.lastAgent(false)['user_id'] : (SBF.null(this.conversation.get('agent_id')) ? (SBF.null(this.conversation.get('department')) ? 'agents' : 'department-' + this.conversation.get('department')) : this.conversation.get('agent_id')));
        },

        // Editor submit message
        submit: function () {
            if (!this.is_busy) {
                if (activeUser() == false && !admin) {
                    this.addUserAndLogin(() => {
                        this.newConversation(2, -1, '', [], null, null, () => {
                            this.sendMessage();
                        });
                    });
                    this.busy(true);
                    return;
                } else {
                    this.sendMessage();
                }
                if (CHAT_SETTINGS['cron-email-piping-active']) {
                    setInterval(function () {
                        SBF.ajax({ function: 'email-piping' });
                    }, 60000);
                    CHAT_SETTINGS['cron-email-piping-active'] = false;
                }
                if (SBPusher.init_push_notifications) {
                    SBPusher.initPushNotifications();
                }
            }
        },

        // Initialize the chat
        initChat: function () {
            if (admin) return;
            SBF.getActiveUser(true, () => {
                let active = activeUser() !== false;
                let user_type = active ? activeUser().type : false;
                if (!tickets && CHAT_SETTINGS['popup']['active'] && !storage('popup') && (!mobile || !CHAT_SETTINGS['popup-mobile-hidden'])) {
                    this.popup();
                }
                if (!tickets && CHAT_SETTINGS['privacy'] && !CHAT_SETTINGS['registration-required'] && !storage('privacy-approved')) {
                    this.privacy();
                    return;
                }
                if (typeof Notification !== ND && !CHAT_SETTINGS['push-notifications-users'] && (CHAT_SETTINGS['desktop-notifications'] == 'all' || (CHAT_SETTINGS['desktop-notifications'] == 'users') || (admin && CHAT_SETTINGS['desktop-notifications'] == 'agents'))) {
                    this.desktop_notifications = true;
                }
                if (CHAT_SETTINGS['flash-notifications'] == 'all' || (CHAT_SETTINGS['flash-notifications'] == 'users') || (admin && CHAT_SETTINGS['flash-notifications'] == 'agents')) {
                    this.flash_notifications = true;
                }
                if (this.registration(true) && !tickets) {
                    this.registration();
                    return;
                }
                if (!active && (typeof SB_WP_WAITING_LIST !== ND || CHAT_SETTINGS['visitors-registration'] || CHAT_SETTINGS['welcome'] || CHAT_SETTINGS['subscribe'] || tickets) && (!tickets || !CHAT_SETTINGS['tickets-registration-required'])) {
                    this.addUserAndLogin(() => {
                        this.welcome();
                        this.subscribe();
                        SBApps.woocommerce.waitingList();
                        this.finalizeInit();
                    });
                } else if (!this.conversation && active) {
                    this.populateConversations();
                } else {
                    this.finalizeInit();
                }
                if (CHAT_SETTINGS['header-name'] && active && user_type == 'user' && !tickets) {
                    chat_header.find('.sb-title').html(`${sb_('Hello')} ${activeUser().nameBeautified}!`);
                }
                this.welcome();
                this.subscribe();
                if (!SBPusher.active) {
                    setInterval(() => {
                        this.updateConversations();
                        this.updateUsersActivity();
                    }, 10200);
                    SBChat.automations.run_all();
                }
                SBApps.woocommerce.waitingList();
                this.scrollBottom(true);
            });
        },

        finalizeInit: function () {
            if (!this.initialized) {
                main.attr('style', '');
                if (!admin && !tickets) {
                    if (this.isInitDashboard()) {
                        this.showDashboard();
                    }
                    if (!mobile && window.innerHeight < 760) {
                        main.find(' > .sb-body').css('max-height', (window.innerHeight - 130) + 'px');
                    }
                }
                this.initialized = true;
                if (!admin) {
                    if (activeUser() && !this.registration(true)) {
                        if (storage('open-conversation')) this.openConversation(storage('open-conversation'));
                        if (SBF.getURL('conversation')) this.openConversation(SBF.getURL('conversation'));
                    }
                    if (!this.chat_open && ((!mobile && storage('chat-open')) || SBF.getURL('chat') == 'open') || SBF.getURL('conversation')) {
                        setTimeout(() => { this.start(); }, 500);
                    }
                    if (CHAT_SETTINGS['woocommerce-returning-visitor']) {
                        if (storage('returning-visitor') === false) {
                            SBF.storageTime('returning-visitor');
                        } else if (SBF.storageTime('returning-visitor', 24) && !storage('returning-visitor-processed')) {
                            setTimeout(() => {
                                SBF.ajax({
                                    function: 'woocommerce-returning-visitor'
                                }, () => {
                                    storage('returning-visitor-processed', true);
                                });
                            }, 15000);
                        }
                    }
                    if (CHAT_SETTINGS['timetable-type']) SBChat.offlineMessage();
                }
                if (tickets) SBTickets.init();
                SBF.event('SBInit');
            }
        },

        // Initialize the chat settings and open the chat
        start: function () {
            if (this.initialized) {
                this.populate();
                this.headerAgent();
                this.updateUsersActivity();
                this.startRealTime();
                this.chat_open = true;
                this.popup(true);
                if (this.conversation) this.updateNotifications('remove', this.conversation.id);
                main.sbActivate();
                $('body').addClass('sb-chat-open');
                if (CHAT_SETTINGS['welcome-trigger'] == 'open') {
                    this.welcome();
                }
            }
        },

        // Open or close the chat
        open: function (open = true) {
            if (open && !this.chat_open) {
                this.start();
                this.chat_open = true;
                this.startRealTime();
                main.sbActivate();
                $('body').addClass('sb-chat-open');
                storage('chat-open', true);
                SBF.event('SBChatOpen');
            } else if (!open && this.chat_open) {
                main.sbActivate(false);
                this.stopRealTime();
                this.chat_open = false;
                storage('chat-open', false);
                $('body').removeClass('sb-chat-open');
                SBF.event('SBChatClose');
            }
        },

        // Get a full conversation and display it in the chat
        openConversation: function (conversation_id) {
            activeUser().getFullConversation(conversation_id, (response) => {
                if (response['id'] == '') {
                    storage('open-conversation', '');
                    return false;
                }
                this.setConversation(response);
                this.hideDashboard();
                this.populate();
                this.main_header = false;
                if (storage('queue') == conversation_id) {
                    this.queue(conversation_id);
                }
                if ((this.chat_open || tickets)) {
                    this.updateNotifications('remove', conversation_id);
                }
                if (tickets) SBTickets.activateConversation(response);
                storage('open-conversation', conversation_id);
                SBF.event('SBConversationOpen', response);
            });
        },

        // Update the active conversation with the latest messages
        update: function () {
            if (this.conversation && !this.is_busy_update) {
                let last_message = this.conversation.getLastMessage();
                let is_update = false;
                SBF.ajax({
                    function: 'get-new-messages',
                    conversation_id: this.conversation.id,
                    datetime: this.datetime_last_message_conversation
                }, (response) => {
                    let count = response.length;
                    this.is_busy_update = false;
                    if (this.conversation) {
                        if (Array.isArray(response) && count > 0 && (!last_message || last_message.id != response[count - 1]['id'] || last_message.message != response[count - 1]['message'] || last_message.payload != response[count - 1]['payload'] || last_message.attachments != response[count - 1]['attachments'])) {
                            let code = '';
                            let messages = [];
                            let id_check = [];
                            let dialogflow_activation = false;

                            // Generate and add the new messages
                            for (var i = 0; i < count; i++) {
                                if (!id_check.includes(response[i]['id'])) {
                                    let message = new SBMessage(response[i]);
                                    let payload = message.payload();

                                    this.datetime_last_message_conversation = message.get('creation_time');

                                    // Payload
                                    if (typeof payload !== 'boolean' && 'event' in payload) {
                                        let event = payload['event'];
                                        if ((event == 'delete-message' && this.conversation.getMessage(message.id) !== false) || (!admin && message.message == '' && !message.attachments.length)) {
                                            this.deleteMessage(message.id);
                                        }
                                        if (event == 'woocommerce-update-cart' && !admin) {
                                            SBApps.woocommerce.updateCart(payload['action'], payload['id']);
                                        }
                                        if (!SBApps.dialogflow.active() && (event == 'conversation-status-update-3' || event == 'conversation-status-update-4' || event == 'activate-bot')) {
                                            SBApps.dialogflow.active('activate');
                                            dialogflow_activation = true;
                                        }
                                    }

                                    // Message
                                    if (this.conversation.getMessage(response[i]['id']) == false) {
                                        this.conversation.addMessages(message);
                                        code += message.getCode();
                                    } else {
                                        this.conversation.updateMessage(message.id, message);
                                        chat.find(`[data-id="${message.id}"]`).replaceWith(message.getCode());
                                        is_update = true;
                                    }
                                    messages.push(message);
                                    id_check.push(message.id);
                                }
                            }
                            chat.append(code);

                            // Update status code
                            let last_message = this.conversation.getLastMessage();
                            let user_type = last_message.get('user_type');
                            let is_agent = SBF.isAgent(user_type);
                            if (!admin && is_agent && user_type != 'bot') {
                                if (this.chat_open) {
                                    if (last_message.message.indexOf('sb-rich-success') == -1) this.setConversationStatus(0);
                                    if (CHAT_SETTINGS['follow']['active']) clearTimeout(timeout);
                                }
                                if (!dialogflow_activation) SBApps.dialogflow.active(false);
                            }

                            // Queue
                            if (storage('queue') == this.conversation.id && is_agent && user_type != 'bot') {
                                this.queue('clear');
                            }

                            // Miscellaneous
                            chat.find('[data-id="sending"]').remove();
                            this.headerAgent();
                            if (!this.dashboard && !is_update) {
                                this.scrollBottom();
                                setTimeout(() => { this.scrollBottom() }, 300);
                            }
                            this.typing(-1, 'stop');
                            this.busy(false);
                            if (this.audio && (count != 1 || !(SBF.null(messages[0].message) && SBF.null(messages[0].attachments))) && ((!admin && this.chat_open && (is_agent || user_type == 'bot') && ['aa', 'ia'].includes(CHAT_SETTINGS['chat-sound'])) || (admin && !SBF.isAgent(user_type) && ['a', 'i', 'ic'].includes(SB_ADMIN_SETTINGS['sounds'])))) {
                                this.audio.play();
                            }
                            SBF.event('SBNewMessagesReceived', messages);
                            if (tickets) SBTickets.onNewMessageReceived(messages[0]);
                        }
                    }
                });
                this.is_busy_update = true;
                setTimeout(() => { this.is_busy_update = false }, 5000);
            }
        },

        // Update the user conversations list with the latest conversations and messages
        updateConversations: function () {
            if (activeUser()) {
                SBF.ajax({
                    function: 'get-new-user-conversations',
                    datetime: this.datetime_last_message
                }, (response) => {
                    if (response.length) {
                        let count = response.length;
                        this.datetime_last_message = response[0]['creation_time'];
                        for (var i = 0; i < count; i++) {
                            let conversation_id = response[i]['conversation_id'];
                            let message = new SBMessage(response[i]);
                            let status_code = response[i]['conversation_status_code'];
                            let conversation = new SBConversation([message], { id: conversation_id, conversation_status_code: status_code, department: response[i]['department'], title: response[i]['title'] });
                            let is_new = activeUser().addConversation(conversation);

                            // Red notifications
                            if (response[i]['user_id'] != activeUser().id && (this.conversation.id != conversation_id || !this.chat_open)) {
                                this.updateNotifications('add', conversation_id);
                                if (CHAT_SETTINGS['auto-open']) {
                                    this.start();
                                }
                            }

                            // Payload
                            let payload = message.payload();
                            if (typeof payload !== 'boolean' && 'event' in payload) {
                                let event = payload['event'];
                                if (event == 'open-chat') {
                                    if (this.conversation.id != conversation_id || this.dashboard) {
                                        this.openConversation(conversation_id);
                                    }
                                    setTimeout(() => { this.open() }, 500);
                                }
                                if (message.message == '' && !message.attachments.length) {
                                    continue;
                                }
                            }

                            if (!this.tab_active) {

                                // Desktop notifications
                                if (this.desktop_notifications) {
                                    SBChat.desktopNotification(message.get('full_name'), message.message, message.get('profile_image'));
                                }

                                // Flash notificaitons
                                if (this.flash_notifications) {
                                    this.flashNotification();
                                }

                                // Sound notifications
                                if (this.audio && ['a', 'aa', 'i'].includes(CHAT_SETTINGS['chat-sound']) && (!this.chat_open || !this.tab_active || this.dashboard) && !(SBF.null(message.message) && SBF.null(message.attachments))) {
                                    this.audio.play();
                                }
                            }
                            if (is_new) {
                                SBF.event('SBNewConversationReceived', conversation);
                                if (tickets) SBTickets.onNewConversationReceived(conversation);
                            }
                        }
                        main.find('.sb-user-conversations').html(activeUser().getConversationsCode());
                        main.find('.sb-dashboard-conversations').setClass('sb-conversations-hidden', main.find('.sb-user-conversations > li').length > 3);
                    }
                });
            }
        },

        // Generate the conversation code and display it
        populate: function () {
            if (this.conversation) {
                let code = '';
                let notify = chat.find(' > .sb-notify-message');
                for (var i = 0; i < this.conversation.messages.length; i++) {
                    code += this.conversation.messages[i].getCode();
                }
                chat.html((notify.length ? notify[0].outerHTML : '') + code);
                if (!this.dashboard) {
                    this.scrollBottom();
                }
            } else if (activeUser() && !activeUser().isConversationsEmpty()) {
                this.showDashboard();
            }
        },

        // Populate the dashboard with all conversations
        populateConversations: function (onSuccess = false) {
            if (!this.is_busy_populate && activeUser()) {
                this.is_busy_populate = true;
                setTimeout(() => { this.is_busy_populate = false }, 5000); 
                activeUser().getConversations((response) => {
                    let count = response.length;
                    if (count) {
                        let now = Date.now();
                        this.datetime_last_message = response[0]['messages'][0].get('creation_time');
                        for (var i = 0; i < count; i++) {
                            if (response[i].get('conversation_status_code') == 1 && (!this.conversation || this.conversation.id != response[i].id)) {
                                this.updateNotifications('add', response[i].id);
                            }
                            if ((now - SBF.UTC(response[i]['messages'][0].get('creation_time'))) < 6000) this.open();
                        }
                        main.removeClass('sb-no-conversations');
                        main.find('.sb-user-conversations').html(activeUser().getConversationsCode());
                        main.find('.sb-dashboard-conversations').setClass('sb-conversations-hidden', main.find('.sb-user-conversations > li').length > 3);
                    }
                    if ((!this.initialized || force_action == 'open-conversation') && count == 1 && !this.isInitDashboard() && !storage('open-conversation')) {
                        this.openConversation(activeUser().conversations[0].id);
                        if (force_action == 'open-conversation') force_action = '';
                    }
                    if (onSuccess !== false) {
                        onSuccess(response);
                    }
                    this.finalizeInit();
                });
            }
        },

        // Create a new conversation and optionally send the first message
        newConversation: function (status_code, user_id = -1, message = '', attachments = [], department = null, agent_id = null, onSuccess = false) {
            if (activeUser()) {
                SBF.ajax({
                    function: 'new-conversation',
                    status_code: status_code,
                    title: tickets ? main.find('.sb-ticket-title input').val() : null,
                    department: SBF.null(department) ? this.default_department : department,
                    agent_id: SBF.null(agent_id) ? this.default_agent : agent_id
                }, (response) => {
                    if (SBF.errorValidation(response, 'user-not-found')) {
                        this.addUserAndLogin(() => {
                            this.newConversation(status_code, user_id, message, attachments, department, agent_id, onSuccess);
                        });
                        return;
                    }
                    let conversation = new SBConversation([], response['details']);
                    this.setConversation(conversation);
                    if (message || attachments.length) {
                        this.sendMessage(user_id, message, attachments, false, (tickets ? { 'skip-dialogflow': true } : false));
                    }
                    if (user_id != bot_id) {
                        this.queue(conversation.id);
                    }
                    if (onSuccess) onSuccess(conversation);
                    SBF.event('SBNewConversationCreated', conversation);
                });
            } else {
                SBF.error('activeUser() not setted', 'SBChat.newConversation');
            }
        },

        // Set an existing conversation as active conversation
        setConversation: function (conversation) {
            if (conversation instanceof SBConversation) {
                this.conversation = conversation;
                this.datetime_last_message_conversation = this.conversation.getLastMessage() == false ? '2000-01-01 00:00:00' : this.conversation.getLastMessage().get('creation_time');
                if (conversation.id != this.conversation.id) {
                    this.queue(conversation.id);
                }
                storage('open-conversation', conversation.id);
                SBF.event('SBActiveConversationChanged', conversation);
            } else {
                SBF.error('Value not of type SBConversation', 'SBChat.setConversation');
            }
        },

        // Manage all the queue functionalities
        queue: function (conversation_id) {
            if (conversation_id == 'clear') {
                main.removeClass('sb-notify-active sb-queue-active');
                chat.find(' > .sb-notify-message').remove();
                clearInterval(this.queue_interval);
                this.queue_interval = false;
                storage('queue', '');
                return;
            }
            if (!admin && CHAT_SETTINGS['queue']) {
                SBF.ajax({
                    function: 'queue',
                    conversation_id: conversation_id,
                    department: this.conversation.get('department')
                }, (response) => {
                    chat.find(' > .sb-notify-message').remove();
                    if (response == 0) {
                        let last_message = this.conversation.getLastUserMessage().message;
                        if (CHAT_SETTINGS['push-notifications']) {
                            SBPusher.pushNotification(last_message);
                        }
                        this.sendMessage(bot_id, sb_(CHAT_SETTINGS['queue-message-success'] == '' ? 'It\'s your turn! An agent will reply to you shortly.' : CHAT_SETTINGS['queue-message-success']), [], false, { preview: last_message });
                        this.queue('clear');
                    } else {
                        let time = (CHAT_SETTINGS['queue-response-time'] == '' ? 5 : parseInt(CHAT_SETTINGS['queue-response-time'])) * response;
                        let text = sb_(CHAT_SETTINGS['queue-message'] == '' ? 'Please wait for an agent. You are number {position} in the queue. Your waiting time is approximately {minutes} minutes.' : CHAT_SETTINGS['queue-message']).replace('{position}', '<b>' + response + '</b>').replace('{minutes}', '<b>' + time + '</b>');
                        chat.prepend(`<div class="sb-notify-message sb-rich-cnt"><div class="sb-cnt"><div class="sb-message">${text}</div></div></div>`);
                        if (this.queue_interval === false) {
                            this.queue_interval = setInterval(() => { this.queue(conversation_id) }, 10100);
                            main.addClass('sb-notify-active sb-queue-active');
                            storage('queue', conversation_id);
                        }
                    }
                    SBF.event('SBQueueUpdate', response);
                });
            }
        },

        // Get the departments details and generate the department code
        getDepartmentCode(department_id, onSuccess) {
            if (this.departments) {
                if (department_id == 'all') {
                    let code = '';
                    for (var key in this.departments) {
                        this.getDepartmentCode(this.departments[key]['id'], (response) => { code += response; });
                    };
                    onSuccess(code);
                } else {
                    onSuccess(`<div data-color="${this.departments[department_id]['color']}">${this.departments[department_id]['image'] == '' ? `<span></span>` : `<img src="${this.departments[department_id]['image']}" />`}<div>${this.departments[department_id]['name']}<div></div>`);
                }
            } else {
                SBF.ajax({
                    function: 'get-departments'
                }, (response) => {
                    if (response) {
                        this.departments = response;
                        this.getDepartmentCode(department_id, onSuccess);
                    }
                });
            }
        },

        // Start and stop the real time check of new messages
        startRealTime: function () {
            if (SBPusher.active) return;
            this.stopRealTime();
            this.real_time = setInterval(() => {
                this.update();
                this.typing(admin ? (activeUser() ? activeUser().id : -1) : this.agent_id, 'check');
            }, 1000);
        },

        stopRealTime: function () {
            clearInterval(this.real_time);
        },

        // Check if the agent is online and set the online status of the active user
        updateUsersActivity: function () {
            if (activeUser()) {
                SBF.updateUsersActivity(activeUser().id, this.agent_id, (response) => {
                    if (!this.typing_settings['typing']) {
                        if (response == 'online' || this.agent_id == bot_id) {
                            $(chat_status).addClass('sb-status-online').html(sb_('Online'));
                            this.agent_online = this.agent_id != bot_id;
                        } else {
                            $(chat_status).removeClass('sb-status-online').html(sb_('Away'));
                            this.agent_online = false;
                        }
                    }
                });
            }
        },

        // Show the loading icon and put the chat in busy mode
        busy: function (value) {
            chat_editor.find('.sb-loader').sbActivate(value);
            this.is_busy = value;
            SBF.event('SBBusy', value);
        },

        // Display the top header of the agent and hide the initial one
        headerAgent: function () {
            if (!admin && !tickets && !this.dashboard && this.conversation != false && (this.agent_id == -1 || (this.conversation.getLastMessage() != false && SBF.isAgent(this.conversation.getLastMessage().get('user_type')) && this.conversation.getLastMessage().get('user_id') != this.agent_id))) {
                let agent = this.lastAgent();
                if (agent) {
                    this.agent_id = agent['user_id'];
                    this.headerReset();
                    chat_header.addClass('sb-header-agent').attr('data-agent-id', this.agent_id).html(`<div class="sb-dashboard-btn sb-icon-arrow-left"></div><div class="sb-profile"><img src="${agent['profile_image']}" /><div><span class="sb-name">${agent['full_name']}</span><span class="sb-status">${sb_('Away')}</span></div><i class="sb-icon sb-icon-close sb-responsive-close-btn"></i></div>`);
                    chat_status = chat_header.find('.sb-status');
                    this.updateUsersActivity();
                    if (SBF.storageTime('header-animation', 1)) {
                        chat_header.addClass('sb-header-animation');
                        setTimeout(() => { chat_header.removeClass('sb-header-animation') }, 8000);
                        SBF.storageTime('header-animation');
                    }
                }
            }
        },

        headerReset: function () {
            if (this.start_header == false) {
                this.start_header = [chat_header.html(), chat_header.attr('class')];
            }
            chat_header.removeClass('sb-header-main sb-header-brand sb-header-agent sb-header-minimal');
            this.main_header = false;
        },

        // Return the last agent of the active conversation
        lastAgent: function (bot = true) {
            let agent = false;
            if (this.conversation) {
                let message = this.conversation.getLastUserMessage(false, bot ? 'all' : true);
                if (message) {
                    agent = { user_id: message.get('user_id'), full_name: message.get('full_name'), profile_image: message.get('profile_image') };
                }
            }
            return agent;
        },

        // Scroll the chat to the bottom
        scrollBottom: function (top = false) {
            setTimeout(() => {
                chat_scroll_area.scrollTop(top ? 0 : chat_scroll_area[0].scrollHeight);
                this.scrollHeader();
            }, 20);
        },

        // Dashboard header animation
        scrollHeader: function () {
            if (this.main_header && this.dashboard) {
                let scroll = chat_scroll_area.scrollTop();
                if (scroll > -1 && scroll < 1000) {
                    chat_header.find('.sb-content').css({ 'opacity': (1 - (scroll / 500)), 'top': (scroll / 10 * -1) + 'px' });
                };
            }
        },

        // Display the dashboard area 
        showDashboard: function () {
            if (!admin && !tickets) {
                main.addClass('sb-dashboard-active');
                chat_header.removeClass('sb-header-agent');
                this.hidePanel()
                if (this.start_header) chat_header.html(this.start_header[0]).addClass(this.start_header[1]);
                chat_scroll_area.find(' > div').sbActivate(false);
                main.find('.sb-dashboard').sbActivate();
                this.populateConversations();
                this.conversation = false;
                this.agent_id = -1;
                this.stopRealTime();
                this.dashboard = true;
                this.main_header = true;
                this.scrollBottom(true);
                SBF.event('SBDashboard');
            }
        },

        // Hide the dashboard area
        hideDashboard: function () {
            if (!admin && !tickets) {
                chat.sbActivate();
                main.removeClass('sb-dashboard-active').find('.sb-dashboard').sbActivate(false);
                this.dashboard = false;
                this.headerAgent();
                this.scrollHeader(0);
                if (this.chat_open) {
                    this.startRealTime();
                }
                SBF.event('SBDashboardClosed'); 
            }
        },

        // Show a chat panel
        showPanel: function (name, title) {
            if (tickets) return SBTickets.showPanel(name, title);
            let panel = chat_scroll_area.find(' > .sb-panel-' + name);
            if (panel.length) {
                chat_scroll_area.find(' > div').sbActivate(false);
                panel.sbActivate();
                if (!this.start_header) this.start_header = [chat_header.html(), chat_header.attr('class')];
                chat_header.attr('class', 'sb-header sb-header-panel').html(`${sb_(title)}<div class="sb-dashboard-btn sb-icon-close"></div>`);
                main.addClass('sb-panel-active');
                this.dashboard = true;
            }
            SBF.event('SBPanelActive', name);
        },

        hidePanel: function () {
            main.removeClass('sb-panel-active');
            chat_header.removeClass('sb-header-panel');
        },

        // Clear the conversation area and the active conversation
        clear: function () {
            this.conversation = false;
            chat.html('');
        },

        // Update the red notification counter of the chat
        updateNotifications: function (action = 'add', conversation_id) {
            if (action == 'add' && !this.notifications.includes(conversation_id)) {
                this.notifications.push(conversation_id);
            }
            if (action == 'remove') {
                for (var i = 0; i < this.notifications.length; i++) {
                    if (this.notifications[i] == conversation_id) {
                        this.notifications.splice(i, 1);
                        if (this.conversation.get('conversation_status_code') != 0 && ['1', '2', 1, 2].includes(this.conversation.get('conversation_status_code'))) {
                            this.setConversationStatus(0);
                        }
                        break;
                    }
                }
            }
            let count = this.notifications.length;
            main.find('.sb-chat-btn span').attr('data-count', count).html(count > -1 ? count : 0);
            SBF.event('SBNotificationsUpdate', { action: action, conversation_id: conversation_id });
        },

        // Set the active conversation status
        setConversationStatus: function (status_code) {
            if (this.conversation) {
                SBF.ajax({
                    function: 'update-conversation-status',
                    conversation_id: this.conversation.id,
                    status_code: status_code
                }, () => {
                    this.conversation.set('conversation_status_code', status_code);
                    SBF.event('SBActiveConversationStatusUpdated', { conversation_id: this.conversation.id, status_code: status_code });
                });
                return true;
            }
            return false;
        },

        // Typing status
        typing: function (user_id = -1, action = 'check') {
            if (this.conversation) {
                let valid = this.agent_online || (admin && this.user_online);
                if (action == 'check' && !SBPusher.active && user_id != -1 && user_id != bot_id && valid) {
                    SBF.ajax({
                        function: 'is-typing',
                        user_id: user_id,
                        conversation_id: this.conversation.id
                    }, (response) => {
                        if (response && !this.typing_settings['typing']) {
                            this.typing(-1, 'start');
                        } else if (!response && this.typing_settings['typing']) {
                            this.typing(-1, 'stop');
                        }
                    });
                } else if (action == 'set' && valid) {
                    if (SBPusher.active) {
                        SBF.debounce(() => {
                            SBPusher.trigger('client-typing', { user_id: admin ? SB_ACTIVE_AGENT['id'] : activeUser().id, conversation_id: this.conversation.id });
                        }, '#2');
                    } else {
                        if (!this.typing_settings['sent']) {
                            this.typing_settings['sent'] = true;
                            SBF.ajax({
                                function: 'set-typing',
                                user_id: user_id,
                                conversation_id: this.conversation.id
                            });
                            this.typing(user_id, 'set');
                        } else {
                            clearTimeout(this.typing_settings['timeout']);
                            this.typing_settings['timeout'] = setTimeout(() => {
                                SBF.ajax({
                                    function: 'set-typing',
                                    user_id: user_id,
                                    conversation_id: -1
                                }, () => {
                                    this.typing_settings['sent'] = false;
                                });
                            }, 2000);
                        }
                    }
                } else if (action == 'start' || action == 'stop') {
                    let start = action == 'start';
                    if (!admin && chat_status) {
                        if (start) {
                            $(chat_status).addClass('sb-status-typing').html(sb_('Typing'));
                        } else {
                            let online = this.agent_online || this.agent_id == bot_id;
                            $(chat_status).removeClass('sb-status-typing').html(sb_(online ? 'Online' : 'Away'));
                            if (online) $(chat_status).addClass('sb-status-online');
                        }
                    }
                    this.typing_settings['typing'] = start;
                    SBF.event('SBTyping', start);
                }
            }
        },

        // Articles
        showArticles: function (id = -1) {
            let panel = tickets ? main.find('.sb-panel-main .sb-panel') : chat_scroll_area.find(' > .sb-panel-articles');
            let code = '';
            panel.html('').sbLoading(true);
            this.showPanel('articles', CHAT_SETTINGS['articles-title'] ? CHAT_SETTINGS['articles-title'] : 'Help Center');
            this.getArticles(id, (articles) => {
                if (id == -1) {
                    let ids = [];
                    for (var y = 0; y < this.articles_categories.length; y++) {
                        let category = this.articles_categories[y];
                        let code_articles_category = '';
                        for (var i = 0; i < articles.length; i++) {
                            let article = articles[i];
                            if (!ids.includes(article.id) && (!category || SBF.null(article.parent_category) || article.parent_category.includes(category.id))) {
                                code_articles_category += `<div data-id="${article.id}"><div>${article.title}</div><span>${article.content}</span></div>`;
                                ids.push(article.id);
                            }
                        }
                        if (code_articles_category) code += (category ? `<div data-id="${category.id}" class="sb-title">${sb_(category.title)}</div>` : '') + code_articles_category;
                    }
                    panel.html(`<div class="sb-articles">${code}</div>`);
                } else if (articles) {
                    panel.html(this.getArticleCode(articles));
                }
                panel.sbLoading(false);
                SBF.event('SBArticles', { id: id, articles: articles });
            });
        },

        getArticles: function (id = -1, onSuccess = false, category = true) {
            if (this.articles === false || id != -1) {
                SBF.ajax({
                    function: 'get-articles',
                    categories: category,
                    articles_language: typeof SB_LANG != ND ? SB_LANG[0] : false,
                    id: id
                }, (response) => {
                    if (id == -1) {
                        this.articles = response[0];
                        this.articles_categories = response[1].length ? response[1] : [''];
                    }
                    onSuccess(id == -1 ? response[0] : response);
                });
            } else {
                if (onSuccess) {
                    if (category && category !== true && category != 'true') {
                        let articles = [];
                        for (var i = 0; i < this.articles.length; i++) {
                            if (('categories' in this.articles[i] && this.articles[i].categories.includes(category)) || ('parent_category' in this.articles[i] && category == this.articles[i].parent_category)) articles.push(this.articles[i]);
                        }
                        onSuccess(articles);
                    } else onSuccess(this.articles);
                } else {
                    return this.articles;
                }
            }
        },

        searchArticles: function (search, button, target) {
            if (search) {
                $(button).sbLoading(true);
                SBF.ajax({
                    function: 'search-articles',
                    articles_language: typeof SB_LANG != ND ? SB_LANG[0] : false,
                    search: search
                }, (articles) => {
                    let code = '';
                    let count = articles.length;
                    if (count == 0) {
                        code += `<p class="sb-no-results">${sb_('No articles found.')}</p>`;
                    } else {
                        for (var i = 0; i < articles.length; i++) {
                            code += `<div data-id="${articles[i]['id']}"><div>${articles[i]['title']}</div><span>${articles[i]['content']}</span></div>`;
                        }
                    }
                    $(target).html(code);
                    $(button).sbLoading(false);
                });
            }
        },

        setArticleRating: function (article_id, rating, onSuccess = false) {
            SBF.ajax({
                function: 'article-ratings',
                article_id: article_id,
                rating: rating
            }, (response) => {
                if (onSuccess) onSuccess(response);
            });
        },

        articleRatingOnClick: function (button) {
            let article = $(button).closest('.sb-article');
            if (!article[0].hasAttribute('data-user-rating')) {
                $(button).parent().sbLoading();
                let rating = $(button).attr('data-rating') == 'positive' ? 1 : -1;
                let article_id = $(button).closest('.sb-article').attr('data-id');
                SBChat.setArticleRating(article_id, rating, () => {
                    SBF.storage('article-rating-' + article_id, rating);
                    article.attr('data-user-rating', rating);
                    $(button).parent().sbLoading(false);
                });
            }
        },

        getArticleCode: function (article) {
            let user_rating = SBF.storage('article-rating-' + article['id']);
            let code = '';
            if (this.articles_categories && !SBF.null(article.categories)) {
                for (var i = 0; i < this.articles_categories.length; i++) {
                    let category_id = this.articles_categories[i].id;
                    if (article.categories.includes(category_id) || article.parent_category == category_id) {
                        code += `<span data-id="${category_id}">${sb_(this.articles_categories[i].title)}</span>`;
                    }
                }
            }
            return `<div data-id="${article.id}"${user_rating ? ` data-user-rating="${user_rating}"` : ''} class="sb-article"><div class="sb-title">${article.title}<div class="sb-close sb-icon-close"></div></div><div class="sb-content">${article.content.replace(/(?:\r\n|\r|\n)/g, '<br>')}</div>${SBF.null(article.link) ? '' : `<a href="${article.link}" target="_blank" class="sb-btn-text"><i class="sb-icon-plane"></i>${sb_('Read more')}</a>`}${code ? `<div class="sb-article-category-links">${code}</div>` : ''}<div class="sb-rating"><span>${sb_('Rate and review')}</span><div><i data-rating="positive" class="sb-submit sb-icon-like"><span>${sb_('Helpful')}</span></i><i data-rating="negative" class="sb-submit sb-icon-dislike"><span>${sb_('Not helpful')}</span></i></div></div></div>`;
        },

        initArticlesPage: function () {
            let code_articles = '';
            let code_categories = '';
            let cache;
            let panel_main;
            let categories = {};
            let category_names = {};
            articles_page = $('body').find('#sb-articles');
            if (!articles_page.length) articles_page = $('body');
            articles_page.sbLoading(true);
            this.getArticles(-1, () => {
                for (var i = 0; i < this.articles.length; i++) {
                    let article = this.articles[i];
                    code_articles += `<div data-id="${article.id}"><div>${article.title}</div><span>${article.content}</span></div>`;
                    if (!SBF.null(article.parent_category)) {
                        if (!(article.parent_category in categories)) categories[article.parent_category] = [];
                        if (!SBF.null(article.categories)) {
                            for (var j = 0; j < article.categories.length; j++) {
                                if (!categories[article.parent_category].includes(article.categories[j])) categories[article.parent_category].push(article.categories[j]);
                            }
                        }
                    }
                }
                for (var i = 0; i < this.articles_categories.length; i++) {
                    category_names[this.articles_categories[i].id] = this.articles_categories[i].title;
                }
                for (var key in categories) {
                    code_categories += `<div class="sb-parent-category"><span data-id="${key}">${sb_(category_names[key])}</span><div class="sb-ul">`;
                    for (var i = 0; i < categories[key].length; i++) {
                        code_categories += `<span data-id="${categories[key][i]}">${sb_(category_names[categories[key][i]])}</span>`;
                    }
                    code_categories += '</div></div>';
                }
                articles_page.html(`<div class="sb-articles-page${CHAT_SETTINGS.rtl ? ' sb-rtl' : ''}"><div class="sb-panel-main sb-articles">${code_articles}</div><div class="sb-panel-side"><div class="sb-title">${CHAT_SETTINGS['articles-title'] ? CHAT_SETTINGS['articles-title'] : 'Help Center'}</div><div class="sb-input sb-input-btn"><input placeholder="${sb_('Search for articles...')}" autocomplete="off"><div class="sb-submit-articles sb-icon-arrow-right"></div></div><div class="sb-title">${sb_('Categories')}</div><div class="sb-article-categories"><div><span data-id="true" class="sb-active">${sb_('All articles')}</span></div>${code_categories}</div></div></div>`);
                articles_page.sbLoading(false);
                panel_main = articles_page.find('.sb-panel-main');
            });

            articles_page.on('click', '.sb-articles > [data-id]', function () {
                cache = panel_main.html();
                panel_main.sbLoading(true);
                SBChat.getArticles($(this).attr('data-id'), (article) => {
                    panel_main.removeClass('sb-articles').html(SBChat.getArticleCode(article));
                    panel_main.sbLoading(false);
                });
            });

            articles_page.on('click', '.sb-article [data-rating]', function () {
                SBChat.articleRatingOnClick(this);
            });

            articles_page.on('click', '.sb-article .sb-title .sb-close', function () {
                articles_page.find('.sb-panel-main').addClass('sb-articles').html(cache);
            });

            articles_page.on('click', '.sb-submit-articles', function () {
                let search = $(this).parent().find('input').val();
                if (search) {
                    cache = panel_main.html();
                    panel_main.html('');
                    SBChat.searchArticles(search, this, panel_main);
                } else {
                    panel_main.html(cache);
                }
                panel_main.addClass('sb-articles');
            });

            articles_page.on('click', '.sb-article-categories [data-id]', function () {
                if (loading(panel_main)) return;
                let code = '';
                cache = panel_main.html();
                articles_page.find('.sb-article-categories [data-id]').sbActivate(false);
                $(this).sbActivate(true);
                SBChat.getArticles(-1, (articles) => {
                    for (var i = 0; i < articles.length; i++) {
                        code += `<div data-id="${articles[i].id}"><div>${articles[i]['title']}</div><span>${articles[i].content}</span></div>`;
                    }
                    panel_main.addClass('sb-articles').html(code ? code : `<p class="sb-no-results">${sb_('No articles found.')}</p>`);
                    panel_main.sbLoading(false);
                }, $(this).attr('data-id'));
            });
        },

        // Emoji
        categoryEmoji: function (category) {
            let list = this.emoji_options['list'];
            if (category == 'all') {
                this.emoji_options['list_now'] = list;
            } else {
                this.emoji_options['list_now'] = [];
                for (var i = 0; i < list.length; i++) {
                    if (list[i]['category'].startsWith(category)) {
                        this.emoji_options['list_now'].push(list[i]);
                    }
                }
            }
            this.emoji_options['range'] = 0;
            this.populateEmoji(0);
            this.populateEmojiBar();
        },

        mouseWheelEmoji: function (e) {
            let range = this.emoji_options['range'];
            if (sbDelta(e) > 0 || (mobile && typeof e.originalEvent.changedTouches !== ND && this.emoji_options['touch'] < e.originalEvent.changedTouches[0].clientY)) {
                range -= (range < 1 ? 0 : 1);
            } else {
                range += (range > this.emoji_options['range_limit'] ? 0 : 1);
            }
            chat_emoji.find('.sb-emoji-bar > div').sbActivate(false).eq(range).sbActivate();
            this.emoji_options['range'] = range;
            this.populateEmoji(range);
            e.preventDefault();
        },

        insertEmoji: function (emoji) {
            if (emoji.indexOf('.svg') > 0) {
                emoji = $.parseHTML(emoji)[0]['alt'];
            }
            this.insertText(emoji);
            chat_emoji.sbTogglePopup();
        },

        showEmoji: function (button) {
            if (chat_emoji.sbTogglePopup(button)) {
                if (!admin) {
                    chat_emoji.css({ left: chat_editor.offset().left + (tickets ? 68 : 20), top: chat_editor.offset().top - window.scrollY - (tickets ? chat_editor.height() - 330 : 304) });
                }
                if (chat_emoji.find('.sb-emoji-list > ul').html() == '') {
                    jQuery.ajax({
                        method: 'POST',
                        url: SB_AJAX_URL,
                        data: {
                            function: 'emoji'
                        }
                    }).done((response) => {
                        this.emoji_options['list'] = JSON.parse(response);
                        this.emoji_options['list_now'] = this.emoji_options['list'];
                        this.populateEmoji(0);
                        this.populateEmojiBar();
                    });
                }
                SBF.deselectAll();
            }
        },

        populateEmoji: function (range) {
            let code = '';
            let per_page = mobile ? 42 : 48;
            let limit = range * per_page + per_page;
            let list_now = this.emoji_options['list_now'];
            if (limit > list_now.length) limit = list_now.length;
            this.emoji_options['range_limit'] = list_now.length / per_page - 1;
            this.emoji_options['range'] = range;
            for (var i = (range * per_page); i < limit; i++) {
                code += `<li>${list_now[i]['char']}</li>`;
            }
            chat_emoji.find('.sb-emoji-list').html(`<ul>${code}</ul>`);
        },

        populateEmojiBar: function () {
            let code = '<div class="sb-active"></div>';
            let per_page = mobile ? 42 : 49;
            for (var i = 0; i < this.emoji_options['list_now'].length / per_page - 1; i++) {
                code += '<div></div>';
            }
            this.emoji_options['range'] = 0;
            chat_emoji.find('.sb-emoji-bar').html(code);
        },

        clickEmojiBar: function (item) {
            let range = $(item).index();
            this.populateEmoji(range);
            this.emoji_options['range'] = range;
            chat_emoji.find('.sb-emoji-bar > div').sbActivate(false).eq(range).sbActivate();
        },

        searchEmoji: function (search) {
            SBF.search(search, () => {
                if (search.length > 1) {
                    let list = this.emoji_options['list'];
                    let list_now = [];
                    for (var i = 0; i < list.length; i++) {
                        if (list[i]['category'].toLowerCase().includes(search) || list[i]['name'].toLowerCase().includes(search)) {
                            list_now.push(list[i]);
                        }
                    }
                    this.emoji_options['list_now'] = list_now;
                } else {
                    this.emoji_options['list_now'] = this.emoji_options['list'];
                }
                this.emoji_options['range'] = 0;
                this.populateEmoji(0);
                this.populateEmojiBar();
            });
        },

        // Editor methods
        textareaChange: function (textarea) {
            let value = $(textarea).val();

            // Saved replies
            if (admin) {
                SBAdmin.conversations.savedReplies(textarea, value);
            }

            // Typing
            if (value) {
                this.typing((admin && !SBPusher.active ? SB_ACTIVE_AGENT['id'] : activeUser().id), 'set');
                this.activateBar();
            } else {
                this.activateBar(false);
            }
        },

        insertText: function (text) {
            let textarea = $(chat_textarea.get(0));
            let index = 0;
            if (this.dashboard) return false;
            if ('selectionStart' in textarea.get(0)) {
                index = textarea.get(0).selectionStart;
            } else if ('selection' in document) {
                textarea.focus();
                let selection = document.selection.createRange();
                var selection_length = document.selection.createRange().text.length;
                selection.moveStart('character', -textarea.value.length);
                index = selection.text.length - selection_length;
            }
            textarea.val(textarea.val().substr(0, index) + text + textarea.val().substr(index));
            textarea.focus();
            textarea.manualExpandTextarea();
            this.activateBar();
        },

        enabledAutoExpand: function () {
            if (chat_textarea.length) chat_textarea.autoExpandTextarea();
        },

        // Privacy message
        privacy: function () {
            SBF.ajax({
                function: 'get-block-setting',
                value: 'privacy'
            }, (response) => {
                chat_scroll_area.append(`<div class="sb-privacy sb-init-form" data-decline="${response['decline']}"><div class="sb-title">${response['title']}</div><div class="sb-text">${response['message']}</div>` + (response['link'] ? `<a target="_blank" href="${response['link']}">${response['link-name']}</a>` : '') + `<div class="sb-buttons"><a class="sb-btn sb-approve">${response['btn-approve']}</a><a class="sb-btn sb-decline">${response['btn-decline']}</a></div></div>`);
                this.finalizeInit();
                SBF.event('SBPrivacy');
            });
            if (!this.dashboard) this.showDashboard();
            this.dashboard = true;
            main.addClass('sb-init-form-active');
        },

        // Popup message 
        popup: function (close = false, content = false) {
            if (close) {
                let popup = main.find('.sb-popup-message');
                let id = popup.attr('data-id');
                storage('popup' + (SBF.null(id) ? '' : id), true);
                popup.remove();
                return;
            }
            setTimeout(() => {
                if (!this.chat_open) {
                    if (content == false) content = CHAT_SETTINGS['popup'];
                    main.find('.sb-popup-message').remove();
                    main.append(`<div data-id="${'id' in content ? content['id'] : ''}" class="sb-popup-message">` + ('image' in content && content['image'] ? `<img src="${content['image']}" />` : '') + ('title' in content && content['title'] ? `<div class="sb-top">${content['title']}</div>` : '') + `<div class="sb-text">${content['message']}</div><div class="sb-icon-close"></div></div>`);
                    SBF.event('SBPopup', content);
                }
            }, 1000);
        },

        // Follow up message
        followUp: function () {
            if (this.followUpCheck()) {
                if (timeout) clearTimeout(timeout);
                timeout = setTimeout(() => {
                    if (this.followUpCheck()) {
                        let settings = CHAT_SETTINGS['follow'];
                        this.sendMessage(bot_id, `[email id="sb-follow-up-form" title="${settings['title']}" message="${settings['message']}" placeholder="${settings['placeholder']}" name="${settings['name']}" last-name="${settings['last-name']}" phone="${settings['phone']}" phone-required="${settings['phone-required']}" success="${settings['success']}"]`);
                        this.scrollBottom();
                        SBF.storageTime('email');
                        SBF.event('SBFollowUp');
                    }
                }, SBF.null(CHAT_SETTINGS['follow']['delay']) ? (CHAT_SETTINGS['office-hours'] || agents_online ? 15000 : (SBApps.dialogflow.active() ? 8000 : 5000)) : parseInt(CHAT_SETTINGS['follow']['delay']));
            }
        },

        followUpCheck: function () {
            return !admin && this.conversation && CHAT_SETTINGS['follow']['active'] && activeUser() && activeUser().get('email') == '' && SBF.storageTime('email', 24) && (CHAT_SETTINGS['office-hours'] || !CHAT_SETTINGS['follow']['disable-office-hours']);
        },

        // Welcome message
        welcome: function () {
            if ((CHAT_SETTINGS['welcome-trigger'] != 'open' || this.chat_open) && (CHAT_SETTINGS['office-hours'] || !CHAT_SETTINGS['welcome-disable-office-hours']) && CHAT_SETTINGS['welcome'] && !storage('welcome') && activeUser()) {
                SBF.ajax({
                    function: 'get-block-setting',
                    value: 'welcome'
                }, (response) => {
                    setTimeout(() => {
                        if (CHAT_SETTINGS['dialogflow-welcome']) {
                            if (this.conversation === false) {
                                this.newConversation(3, -1, '', [], null, null, function () { SBApps.dialogflow.welcome() });
                            } else {
                                SBApps.dialogflow.welcome();
                            }
                        } else {
                            this.sendMessage(bot_id, response['message'], [], false, false, 3);
                        }
                        if (response['open']) {
                            this.start();
                        }
                        if (response['sound']) {
                            this.audio.play();
                        }
                        this.skip = true;
                        SBF.event('SBWelcomeMessage');
                    }, parseInt(tickets ? 0 : CHAT_SETTINGS['welcome-delay']));
                    storage('welcome', true);
                });
            }
        },

        // Subscribe message
        subscribe: function () {
            if (CHAT_SETTINGS['subscribe'] && !storage('subscribe') && activeUser() && SBF.null(activeUser().get('email')) && activeUser()) {
                setTimeout(() => {
                    SBF.ajax({
                        function: 'get-block-setting',
                        value: 'subscribe'
                    }, (response) => {
                        this.sendMessage(bot_id, response['message'], [], false, { event: 'open-chat' }, 3);
                        if (response['sound'] && this.audio) {
                            this.audio.play();
                        }
                        this.skip = true;
                        storage('subscribe', true);
                    });
                }, parseInt(CHAT_SETTINGS['subscribe-delay']));
            }
        },

        // Offline timetable message
        offlineMessage: function () {
            if (!admin && !this.offline_message_set && CHAT_SETTINGS['timetable'] && (!CHAT_SETTINGS['office-hours'] || (!agents_online && !CHAT_SETTINGS['timetable-disable-agents'])) && SBF.storageTime('timetable', 1)) {
                let message = CHAT_SETTINGS['timetable-message'];
                switch (CHAT_SETTINGS['timetable-type']) {
                    case 'header':
                        if (message[0]) chat_header.find('.sb-title').html(message[0]);
                        chat_header.find('.sb-text').html(message[1]);
                        this.offline_message_set = true;
                        break;
                    case 'info':
                        chat.prepend(`<div class="sb-notify-message sb-rich-cnt"><div class="sb-cnt"><div class="sb-message">${message[0] ? `<b>${message[0]}</b> ` : ''}${message[1]}</div></div></div>`);
                        main.addClass('sb-notify-active');
                        this.offline_message_set = true;
                        break;
                    default:
                        setTimeout(() => {
                            if (this.conversation) {
                                this.sendMessage(bot_id, CHAT_SETTINGS['timetable-hide'] ? `${message[0] ? `*message[0]*\n` : ''}${message[1]}` : '[timetable]');
                                this.scrollBottom();
                                this.offline_message_set = true;
                                SBF.storageTime('timetable');
                            }
                        }, 5000);
                }
            }
        },

        // Send Slack message
        slackMessage: function (user_id, full_name, profile_image, message, attachments = []) {
            if (!this.conversation || (!message && !attachments.length)) return false;
            let conversation_id = this.conversation.id;
            SBF.ajax({
                function: 'send-slack-message',
                user_id: user_id,
                full_name: full_name,
                profile_image: profile_image,
                conversation_id: conversation_id,
                message: message,
                attachments: attachments,
                channel: this.slack_channel[0] == activeUser().id ? this.slack_channel[1] : -1
            }, (response) => {
                this.slack_channel = [activeUser().id, response[1]];
                SBF.event('SBSlackMessageSent', { message: message, conversation_id: conversation_id, slack_channel: response[1] });
            });
        },

        // Delete message
        deleteMessage: function (message_id) {
            SBF.ajax({
                function: 'delete-message',
                message_id: message_id
            }, () => {
                this.conversation.deleteMessage(message_id);
                chat.find(`[data-id="${message_id}"]`).remove();
                SBF.event('SBMessageDeleted', message_id);
            });
        },

        // Registration form
        registration: function (check = false, type = CHAT_SETTINGS['registration-required']) {
            if (check) return CHAT_SETTINGS['registration-required'] && (!CHAT_SETTINGS['registration-offline'] || !agents_online) && (!CHAT_SETTINGS['registration-timetable'] || !CHAT_SETTINGS['office-hours']) && (activeUser() === false || ['visitor', 'lead'].includes(activeUser().type));
            chat_scroll_area.append(SBRichMessages.generate({}, CHAT_SETTINGS['registration-link'] ? 'login' : type, 'sb-init-form'));
            if (!this.dashboard) this.showDashboard();
            this.dashboard = true;
            this.finalizeInit();
            main.addClass('sb-init-form-active');
        },

        // Display the send button
        activateBar: function (show = true) {
            chat_editor.find('.sb-bar').sbActivate(show);
        },

        // Shortcut for add user and login function
        addUserAndLogin: function (onSuccess = false) {
            SBF.ajax({
                function: 'add-user-and-login',
                login_app: SBApps.login()
            }, (response) => {
                SBF.loginCookie(response[1]);
                activeUser(new SBUser(response[0]));
                SBPusher.start();
                if (!SBPusher.active) SBChat.automations.run_all();
                if (onSuccess !== false) {
                    onSuccess(response);
                }
            });
        },

        // Check if the dashboard must be showed
        isInitDashboard: function () {
            return CHAT_SETTINGS['init-dashboard'] || (activeUser() && activeUser().conversations.length > 1);
        },

        // Upload response
        uploadResponse: function (response) {
            response = JSON.parse(response);
            if (response[0] == 'success') {
                if (response[1] == 'extension_error') {
                    let message = 'The file you are trying to upload has an extension that is not allowed.';
                    if (admin) SBAdmin.dialog(message, 'info'); else alert(message);
                } else if ($(upload_target).hasClass('sb-input-image')) {
                    $(upload_target).find('.image').attr('data-value', '').css('background-image', '');
                    setTimeout(() => {
                        $(upload_target).find('.image').attr('data-value', response[1]).css('background-image', `url("${response[1]}?v=${SBF.random()}")`).append('<i class="sb-icon-close"></i>');
                        upload_target = false;
                    }, 500);
                } else {
                    let name = response[1].substr(response[1].lastIndexOf('/') + 1);
                    chat_editor.find('.sb-attachments').append(`<div data-name="${name}" data-value="${response[1]}">${name}<i class="sb-icon-close"></i></div>`);
                    SBChat.activateBar();
                }
            } else {
                SBF.error(response[1], 'sb-upload-files.change');
            }
            this.busy(false);
        },

        // Automations
        automations: {
            history: [],
            busy: [],
            scroll_position_intervals: {},

            run_all: function () {
                let automations = CHAT_SETTINGS['automations'];
                for (var i = 0; i < automations.length; i++) {
                    let automation = automations[i];
                    let conditions = automation.conditions;
                    let count = conditions.length;
                    let valid = count == 0;
                    let browsing_time = false;
                    let scroll_position = false;
                    let server_conditions = false;
                    for (var j = 0; j < conditions.length; j++) {
                        let criteria = conditions[j][1];
                        valid = false;
                        switch (conditions[j][0]) {
                            case 'browsing_time':
                                valid = true;
                                browsing_time = criteria;
                                break;
                            case 'scroll_position':
                                valid = true;
                                scroll_position = criteria;
                                break;
                            case 'include_urls':
                            case 'exclude_urls':
                            case 'referring':
                                let url = conditions[j][0] == 'referring' ? document.referrer : window.location.href;
                                let checks = conditions[j][2].split(',');
                                let include = conditions[j][0] != 'exclude_urls';
                                if (!include) valid = true;
                                url = url.replace('https://', '').replace('http://', '').replace('www.', '');
                                for (var y = 0; y < checks.length; y++) {
                                    checks[y] = $.trim(checks[y].replace('https://', '').replace('http://', '').replace('www.', ''));
                                    if ((criteria == 'contains' && url.indexOf(checks[y]) != -1) || (criteria == 'does-not-contain' && url.indexOf(checks[y]) == -1) || (criteria == 'is-exactly' && checks[y] == url) || (criteria == 'is-not' && checks[y] != url)) {
                                        valid = include;
                                        break;
                                    }
                                }
                                break;
                            case 'custom_variable':
                                let variable = criteria.split('=');
                                if (variable[0] in window && window[variable[0]] == variable[1]) {
                                    valid = true;
                                }
                                break;
                            case 'returning_visitor':
                            case 'user_type':
                            case 'cities':
                            case 'languages':
                            case 'countries':
                                valid = activeUser();
                                server_conditions = true;
                                break;
                            case 'user_phone':
                                valid = activeUser() && !SBF.null(activeUser().getExtra('phone'));
                                break;
                            case 'user_email':
                                valid = activeUser() && !SBF.null(activeUser().get('email'));
                                break;
                            default:
                                valid = true;
                        }
                        if (!valid) break;
                    }
                    if (['messages', 'emails', 'sms'].includes(automation.type) && !activeUser()) valid = false;
                    if (valid) {
                        if (server_conditions) {
                            if (!(automation.id in this.busy)) {
                                SBF.ajax({
                                    function: 'automations-validate',
                                    automation: automation
                                }, (response) => {
                                    if (response !== false) {
                                        this.run_all_final(automation, scroll_position, browsing_time);
                                    }
                                    delete this.busy[automation.id];
                                });
                                this.busy[automation.id] = true;
                            }
                        } else {
                            this.run_all_final(automation, scroll_position, browsing_time);
                        }
                    }
                }
            },

            run_all_final: function (automation, scroll_position, browsing_time) {
                if (scroll_position) {
                    this.scroll_position_intervals[automation.id] = setInterval(() => {
                        if ($(window).scrollTop() > parseInt(scroll_position)) {
                            if (browsing_time) setTimeout(() => { this.run(automation) }, parseInt(browsing_time) * 1000);
                            else this.run(automation);
                            clearInterval(this.scroll_position_intervals[automation.id]);
                        }
                    }, 1000);
                } else if (browsing_time) {
                    setTimeout(() => { this.run(automation) }, parseInt(browsing_time) * 1000);
                } else this.run(automation);
            },

            run: function (automation) {
                if (this.history.includes(automation.id)) return;
                switch (automation.type) {
                    case 'messages':
                    case 'emails':
                    case 'sms':
                        if (!(automation.id in this.busy)) {
                            if (automation.type == 'messages' && SBChat.chat_open) {
                                let last_message = SBChat.conversation ? SBChat.conversation.getLastUserMessage(false, 'no-bot') : false;
                                if (last_message && ((Date.now() - 600000) < SBF.unix(last_message.get('creation_time')))) return;
                            }
                            SBF.ajax({
                                function: 'automations-run',
                                automation: automation
                            }, (response) => {
                                if (response !== false) {
                                    this.history.push(automation.id);
                                    if (automation.type == 'messages' && !SBPusher.active) SBChat.updateConversations();
                                }
                                delete this.busy[automation.id];
                            });
                            this.busy[automation.id] = true;
                        }
                        break;
                    case 'popups':
                        if (!storage('popup' + automation.id)) {
                            setTimeout(() => {
                                if (!SBChat.chat_open) {
                                    SBChat.popup(false, { id: automation.id, image: automation.profile_image, title: automation.title, message: automation.message });
                                    this.history.push(automation.id);
                                } else if (automation.fallback) {
                                    let last_message = SBChat.conversation ? SBChat.conversation.getLastUserMessage(false, 'no-bot') : false;
                                    if (!last_message || ((Date.now() - 600000) > SBF.unix(last_message.get('creation_time')))) {
                                        SBChat.sendMessage(bot_id, (SBF.null(automation.title) ? '' : `*${automation.title}*\n`) + automation.message, [], false, false, 0);
                                        storage('popup' + automation.id, true);
                                        this.history.push(automation.id);
                                    }
                                }
                            }, 1000);
                        }
                        break;
                    case 'design':
                        if (automation.background) {
                            chat_header.css('background-image', `url("${automation.background}")`);
                        }
                        if (automation.brand) {
                            chat_header.find('.sb-brand img').attr('src', automation.brand);
                        }
                        if (automation.title) {
                            chat_header.find('.sb-title').html(automation.title);
                        }
                        if (automation.message) {
                            chat_header.find('.sb-text').html(automation.message);
                        }
                        if (automation.icon) {
                            main.find('.sb-chat-btn .sb-icon').attr('src', automation.icon);
                        }
                        if (automation.color_1 || automation.color_2 || automation.color_3) {
                            SBF.ajax({ function: 'chat-css', color_1: automation.color_1, color_2: automation.color_2, color_3: automation.color_3 }, (response) => {
                                $('head').append(`<style>${response}</style>`);
                            });
                        }
                        this.history.push(automation.id);
                        break;
                }
            }
        },

        flashNotification: function () {
            clearInterval(interval);
            interval = setInterval(function () {
                document.title = document.title == document_title ? sb_('New message...') : document_title;
            }, 2000);
        }
    }
    window.SBChat = SBChat;

    /* 
    * ----------------------------------------------------------
    * # RICH MESSAGES
    * ----------------------------------------------------------
    */

    var SBRichMessages = {
        rich_messsages: {
            'email': '',
            'button': '',
            'video': '',
            'image': '',
            'woocommerce-button': '',
            'chips': '<div class="sb-buttons">[options]</div>',
            'buttons': '<div class="sb-buttons">[options]</div>',
            'select': '<div class="sb-select"><p></p><ul>[options]</ul></div>',
            'list': '<div class="sb-text-list">[values]</div>',
            'list-image': '<div class="sb-image-list">[values]</div>',
            'table': '<table><tbody>[header][values]</tbody></table>',
            'inputs': '<div class="sb-form">[values]</div>',
            'rating': `<div class="sb-rating"><span>[label]</span><div><i data-rating="positive" class="sb-submit sb-icon-like"><span>[positive]</span></i><i data-rating="negative" class="sb-submit sb-icon-dislike"><span>[negative]</span></i></div></div>`,
            'card': '<div class="sb-card">[settings]</div>',
            'share': '<div class="sb-social-buttons">[settings]</div>',
            'slider': '<div class="sb-slider"><div>[items]</div></div><div class="sb-slider-arrow sb-icon-arrow-left[class]"></div><div class="sb-slider-arrow sb-icon-arrow-right sb-active[class]"></div>',
            'slider-images': '<div class="sb-slider sb-slider-images"><div>[items]</div></div><div class="sb-slider-arrow sb-icon-arrow-left[class]"></div><div class="sb-slider-arrow sb-icon-arrow-right sb-active[class]"></div>',
            'phone': ''
        },
        cache: {},

        // Generate a rich message
        generate: function (settings, name, css = '') {
            let content;
            let next = true;
            let id = 'id' in settings ? settings['id'] : SBF.random();

            // Check if the rich message exist
            if (name in this.rich_messsages) {
                content = this.rich_messsages[name];
            } else if (name in this.cache) {
                content = this.cache[name];
            } else if (admin || (!SBF.null(CHAT_SETTINGS) && 'rich-messages' in CHAT_SETTINGS && CHAT_SETTINGS['rich-messages'].includes(name))) {
                if (!('id' in settings)) id = name;
                content = '<div class="sb-rich-loading sb-loading"></div>';
                SBF.ajax({
                    function: 'get-rich-message',
                    name: name
                }, (response) => {
                    response = this.initInputs(response);
                    response = (new SBMessage({})).render(response);
                    if (name == 'timetable') response = this.timetable(response);
                    main.find(`.sb-rich-message[id="${id}"]`).html(`<div class="sb-content">${response}</div>`);
                    this.cache[name] = response;
                    SBChat.scrollBottom(SBChat.dashboard);
                });
                next = false;
            } else {
                return false;
            }

            // Generate the rich message
            let disabled = 'disabled' in settings;
            if (next) {
                let options;
                let code = '';
                switch (name) {
                    case 'rating':
                        content = content.replace('[label]', sb_(SBF.null(settings['label']) ? 'Rate and review' : settings['label'])).replace('[positive]', sb_(SBF.null(settings['label-positive']) ? 'Helpful' : settings['label-positive'])).replace('[negative]', sb_(SBF.null(settings['label-negative']) ? 'Not helpful' : settings['label-negative']));
                        break;
                    case 'email':
                        let inputs = [];
                        let email = activeUser().get('email');
                        let default_name = activeUser().get('last_name').charAt(0) == '#';
                        if (settings['name'] == 'true') inputs.push(['first_name', settings['last-name'] == 'true' ? 'First name' : 'Name', default_name ? '' : (settings['last-name'] == 'true' ? activeUser().get('first_name') : activeUser().name), 'text', true]);
                        if (settings['last-name'] == 'true') inputs.push(['last_name', 'Last name', default_name ? '' : activeUser().get('last_name'), 'text', true]);
                        for (var i = 0; i < inputs.length; i++) {
                            content += `<div id="${inputs[i][0]}" data-type="text" class="sb-input sb-input-text"><span class="${inputs[i][2] == '' ? '' : 'sb-active sb-filled'}">${sb_(inputs[i][1])}</span><input value="${inputs[i][2]}" autocomplete="false" type="${inputs[i][3]}" ${inputs[i][4] ? 'required' : ''}></div>`;
                        }
                        if (settings['phone'] == 'true') {
                            let phone = activeUser().getExtra('phone');
                            let phone_codes = admin ? [] : CHAT_SETTINGS['phone-codes'];
                            let select = '';
                            for (var i = 0; i < phone_codes.length; i++) {
                                select += `<option value="+${phone_codes[i]}">+${phone_codes[i]}</option>`;
                            }
                            content += `<div id="phone" data-type="select-input" class="sb-input sb-input-select-input"><span class="${phone == '' ? '' : 'sb-active sb-filled'}">${sb_('Phone')}</span><div><select><option value=""></option>${select}</select></div><input value="${admin ? '' : phone}" pattern="[0-9]+" autocomplete="false" type="tel" ${settings['phone-required'] != 'false' ? 'required' : ''}></div>`;
                        }
                        content += `<div id="email" data-type="email" class="sb-input sb-input-btn"><span class="${email == '' ? '' : 'sb-active sb-filled'}">${sb_(SBF.null(settings.placeholder) ? 'Email' : settings['placeholder'])}</span><input value="${email}" autocomplete="off" type="email" required><div class="sb-submit sb-icon-arrow-right"></div></div>`;
                        break;
                    case 'image':
                        content = `<div class="sb-image"><img src="${settings['url']}"></div>`;
                        break;
                    case 'video':
                        content = `<iframe${'height' in settings ? ` height="${settings['height']}"` : ''} src="${settings['type'] == 'youtube' ? '//www.youtube.com/embed/' : '//player.vimeo.com/video/'}${settings['id']}" allowfullscreen></iframe>`;
                        break;
                    case 'select':
                        options = settings['options'].split(',');
                        for (var i = 0; i < options.length; i++) {
                            code += `<li data-value="${SBF.stringToSlug(options[i])}">${sb_(options[i])}</li>`;
                        }
                        content = content.replace('[options]', code);
                        break;
                    case 'chips':
                    case 'buttons':
                        options = settings['options'].split(',');
                        for (var i = 0; i < options.length; i++) {
                            code += `<div class="sb-btn sb-submit">${sb_(options[i])}</div>`;
                        }
                        content = content.replace('[options]', code);
                        break;
                    case 'button':
                        content = `<a href="${settings['link'].replace(/<i>/g, '_').replace(/<\/i>/g, '_')}"${'target' in settings ? ' target="_blank"' : ''} class="sb-rich-btn sb-btn${settings['style'] == 'link' ? '-text' : ''}">${sb_(settings['name'])}</a>`;
                        break;
                    case 'list':
                        options = settings['values'].split(',');
                        let list = name == 'list';
                        let list_double = list && options.length && options[0].indexOf(':') > 0;
                        if (list && !list_double) {
                            content = content.replace('sb-text-list', 'sb-text-list sb-text-list-single');
                        }
                        for (var i = 0; i < options.length; i++) {
                            code += list_double ? `<div><div>${sb_(options[i].split(':')[0])}</div><div>${sb_(options[i].split(':')[1])}</div></div>` : `<div>${$.trim(sb_(options[i]))}</div>`;
                        }
                        content = content.replace('[values]', code);
                        break;
                    case 'list-image':
                        options = settings['values'].split(',');
                        for (var i = 0; i < options.length; i++) {
                            let item = options[i].replace('://', '///').split(':');
                            code += `<div><div class="sb-thumb" style="background-image:url('${item[0].replace('///', '://')}')"></div><div class="sb-list-title">${item[1]}</div><div>${item[2]}</div></div>`;
                        }
                        content = content.replace('[values]', code);
                        break;
                    case 'table':
                        options = settings['header'].split(',');
                        code += '<tr>';
                        for (var i = 0; i < options.length; i++) {
                            code += `<th>${options[i]}</th>`;
                        }
                        code += '</tr>';
                        content = content.replace('[header]', code);
                        code = '';
                        options = settings['values'].split(',');
                        for (var i = 0; i < options.length; i++) {
                            let tds = options[i].split(':');
                            code += '<tr>';
                            for (var i = 0; i < tds.length; i++) {
                                code += `<td>${tds[i]}</td>`;
                            }
                            code += '</tr>';
                        }
                        content = content.replace('[values]', code);
                        break;
                    case 'inputs':
                        options = settings['values'].split(',');
                        for (var i = 0; i < options.length; i++) {
                            if (disabled && options[i] == '') continue;
                            code += `<div id="${SBF.stringToSlug(options[i])}" data-type="text" class="sb-input sb-input-text"><span>${sb_(options[i])}</span><input autocomplete="false" type="text" required></div>`;
                        }
                        code += '<div class="sb-btn sb-submit">' + sb_('button' in settings ? settings['button'] : 'Send now') + '</div>';
                        content = content.replace('[values]', code);
                        break;
                    case 'card':
                        code = `${'image' in settings ? `<div class="sb-card-img" style="background-image:url('${settings['image']}')"></div>` : ''}<div class="sb-card-header">${settings['header']}</div>${'extra' in settings ? `<div class="sb-card-extra">${settings['extra']}</div>` : ''}${'description' in settings ? `<div class="sb-card-description">${settings['description']}</div>` : ''}${'link' in settings ? `<a class="sb-card-btn" href="${settings['link']}"${'target' in settings ? ' target="_blank"' : ''}>${sb_(settings['link-text'])}</a>` : ''}`;
                        content = content.replace('[settings]', code);
                        break;
                    case 'share':
                        let channels = 'channels' in settings ? settings['channels'].replace(/ /g, '').split(',') : ['fb', 'tw', 'li', 'wa', 'pi'];
                        let link = '';
                        for (var i = 0; i < channels.length; i++) {
                            switch (channels[i]) {
                                case 'fb':
                                    link = 'www.facebook.com/sharer.php?u=';
                                    break;
                                case 'tw':
                                    link = 'twitter.com/intent/tweet?url=';
                                    break;
                                case 'li':
                                    link = 'www.linkedin.com/sharing/share-offsite/?url=';
                                    break;
                                case 'wa':
                                    link = 'web.whatsapp.com/send?text=';
                                    break;
                                case 'pi':
                                    link = 'www.pinterest.com/pin/create/button/?url=';
                                    break;
                            }
                            code += `<div class="sb-${channels[i]} sb-icon-social-${channels[i]}" data-link="https://${link}${settings['link']}"></div>`;
                        }
                        content = content.replace('[settings]', code);
                        break;
                    case 'slider':
                        let count = 0;
                        for (var i = 1; i < 16; i++) {
                            if (('header-' + i) in settings) {
                                code += `<div>${('image-' + i) in settings ? `<div class="sb-card-img" style="background-image:url('${settings['image-' + i]}')"></div>` : ''}<div class="sb-card-header">${settings['header-' + i]}</div>${('extra-' + i) in settings ? `<div class="sb-card-extra">${settings['extra-' + i]}</div>` : ''}${('description-' + i) in settings ? `<div class="sb-card-description">${settings['description-' + i]}</div>` : ''}${('link-' + i) in settings ? `<a class="sb-card-btn" href="${settings['link-' + i]}"${'target' in settings ? ' target="_blank"' : ''}>${sb_(settings['link-text-' + i])}</a>` : ''}</div>`;
                                count++;
                            } else {
                                break;
                            }
                        }
                        content = content.replace('[items]', code).replace(/\[class\]/g, count == 1 ? ' sb-hide' : '');
                        break;
                    case 'slider-images':
                        if ('images' in settings) {
                            let images = settings['images'].split(',');
                            for (var i = 0; i < images.length; i++) {
                                code += `<div class="sb-card-img" data-value="${images[i]}" style="background-image:url('${images[i]}')"></div>`;
                            }
                            content = content.replace(/\[class\]/g, images.length == 1 ? ' sb-hide' : '');
                        }
                        content = content.replace('[items]', code);
                        break;
                    case 'woocommerce-button':
                        settings['settings'] = `checkout:${settings['checkout']},coupon:${settings['coupon']}`;
                        content = `<a href="#" data-ids="${settings['ids']}" class="sb-rich-btn sb-btn">${settings['name']}</a>`;
                        break;
                }
            }
            return `<div id="${id}" data-type="${name}"${disabled ? 'disabled="true"' : ''}${'settings' in settings ? ` data-settings="${settings['settings']}"` : ''}class="sb-rich-message sb-rich-${name} ${css}">` + ('title' in settings ? `<div class="sb-top">${sb_(settings['title'])}</div>` : '') + ('message' in settings ? `<div class="sb-text">${sb_(settings['message'])}</div>` : '') + `<div class="sb-content">${content}</div><div data-success="${'success' in settings ? settings['success'].replace(/"/g, '') : ''}" class="sb-info"></div></div>`;
        },

        // Function of built-in rich messages
        submit: function (area, type, element) {
            if (!admin && !loading(element) && !this.is_busy) {
                let error = '';
                let shortcode = '';
                let parameters = {};
                let success = $(area).find('[data-success]').length ? $(area).find('[data-success]').attr('data-success') : '';
                let rich_message_id = $(area).closest('.sb-rich-message').attr('id');
                let message_id = $(area).closest('[data-id]').attr('data-id');
                let message = '';
                let payload = { 'rich-messages': {} };
                let user_settings = activeUser() == false ? { 'profile_image': ['', ''], 'first_name': ['', ''], 'last_name': ['', ''], 'email': ['', ''], 'password': ['', ''], 'user_type': ['', ''] } : { 'profile_image': [activeUser().image, ''], 'first_name': [activeUser().get('first_name'), ''], 'last_name': [activeUser().get('last_name'), ''], 'email': [activeUser().get('email'), ''], 'password': ['', ''], 'user_type': ['', ''] };
                let settings = {};
                let input = $(element);
                let dialogflow_response = '';
                let active_conversation = SBChat.conversation !== false;
                let settings_extra = {};
                let payload_settings = {};

                if (SBF.null(message_id)) {
                    message_id = -1;
                } else {
                    let item = SBChat.conversation.getMessage(message_id);
                    message = item.message;
                    payload = item.payload();
                    if (!('rich-messages' in payload)) {
                        payload['rich-messages'] = {};
                    }
                }
                if (!$(element).hasClass('sb-btn') && !$(element).hasClass('sb-select') && !$(element).hasClass('sb-submit')) {
                    input = $(element).closest('.sb-btn,.sb-select');
                }
                $(area).find('.sb-info').html('').sbActivate(false);

                switch (type) {
                    case 'email':
                        settings = SBForm.getAll(area);
                        if ('first_name' in settings) {
                            user_settings['user_type'] = ['user', ''];
                            if (!('last_name' in settings)) {
                                user_settings['last_name'] = ['', ''];
                            }
                        }
                        if ('phone' in settings) {
                            settings_extra = { 'phone': [settings['phone'][0], 'Phone'] };
                        }
                        $.extend(user_settings, settings);
                        error = 'Please fill in all required fields and make sure the email is valid.';
                        success = success == '' ? success : sb_(success) + ` *${user_settings['email'][0]}*`;
                        payload['rich-messages'][rich_message_id] = { type: type, result: settings };
                        payload['event'] = 'update-user';
                        parameters = { function: 'update-user-and-message', settings: user_settings, settings_extra: settings_extra, payload: payload };
                        dialogflow_response = 'email-complete';
                        break;
                    case 'registration':
                        settings_extra = SBForm.getAll(area.find('.sb-form-extra'));
                        $.extend(user_settings, SBForm.getAll(area.find('.sb-form-main')));
                        payload_settings = $.extend({}, user_settings);
                        if (success) {
                            success = sb_(success);
                        }
                        if (CHAT_SETTINGS['registration-details']) {
                            success += '[list values="';
                            for (var key in user_settings) {
                                let value = user_settings[key][0].replace(/:|,/g, '');
                                if (value) {
                                    if (key == 'profile_image') {
                                        value = value.substr(value.lastIndexOf('/') + 1);
                                    }
                                    if (key == 'password' || key == 'password-check') {
                                        value = '********';
                                        payload_settings[key] = '********';
                                    }
                                    success += user_settings[key][1] == '' ? '' : `${sb_(user_settings[key][1].replace(/:|,/g, ''))}:${value},`;
                                }
                            }
                            for (var key in settings_extra) {
                                if (settings_extra[key][0]) {
                                    success += `${sb_(settings_extra[key][1].replace(/:|,/g, ''))}:${settings_extra[key][0].replace(/:|,/g, '')},`;
                                }
                            }
                            success = success.slice(0, -1) + '"]';
                        }
                        user_settings['user_type'] = ['user', ''];
                        payload['rich-messages'][rich_message_id] = { type: type, result: { user: payload_settings, extra: settings_extra } };
                        payload['event'] = 'update-user';
                        parameters = { function: activeUser() ? 'update-user-and-message' : 'add-user-and-login', settings: user_settings, settings_extra: settings_extra, payload: payload };
                        error = SBForm.getRegistrationErrorMessage(area);
                        dialogflow_response = JSON.stringify(user_settings);
                        break;
                    case 'rating':
                        let rating = $(element).attr('data-rating');
                        success = `${sb_('Your rating is')} *${sb_(rating)}*. ${sb_(success == '' ? 'Thank you for your feedback!' : success)}`;
                        settings = { conversation_id: SBChat.conversation.id, agent_id: SBChat.agent_id, user_id: activeUser().id, message: area.find('textarea').val(), rating: (rating == 'positive' ? 1 : -1) };
                        payload['rich-messages'][rich_message_id] = { type: type, result: settings };
                        parameters = { function: 'set-rating', payload: payload, settings: settings };
                        dialogflow_response = rating;
                        break;
                    case 'chips':
                    case 'select':
                    case 'buttons':
                        settings = SBF.escape($(element).html());
                        success = success == '' ? success : sb_(success) + ` *${settings}*`;
                        payload['rich-messages'][rich_message_id] = { type: type, result: settings };
                        parameters = { function: 'update-message', payload: payload };
                        dialogflow_response = settings;
                        if (type == 'chips') {
                            SBChat.sendMessage(activeUser().id, settings, [], false, { id: rich_message_id, event: 'chips-click', result: settings }, rich_message_id == 'sb-human-takeover' && input.index() == 0 ? 2 : false);
                            if (rich_message_id == 'sb-human-takeover' && $(element).index() == 0) {
                                SBApps.dialogflow.humanTakeover();
                            }
                            $(element).closest('.sb-content').remove();
                        }
                        break;
                    case 'inputs':
                        settings = SBForm.getAll(area);
                        error = 'All fields are required.';
                        if (success) {
                            success = sb_(success) + ' [list values="';
                            for (var key in settings) {
                                success += `${sb_(settings[key][1].replace(/:|,/g, ''))}:${settings[key][0].replace(/:|,/g, '')},`;
                            }
                            success = success.slice(0, -1) + '"]';
                        }
                        payload['rich-messages'][rich_message_id] = { type: type, result: settings };
                        parameters = { function: 'update-message', payload: payload };
                        dialogflow_response = JSON.stringify(settings);
                        break;
                }

                shortcode = message.substr(message.indexOf('[' + type))
                shortcode = shortcode.substr(0, shortcode.indexOf(']') + 1);

                if (error && SBForm.errors(area)) {
                    SBForm.showErrorMessage(area, error);
                    input.sbLoading(false);
                    if (SBChat.dashboard || (active_conversation && SBChat.conversation.getLastMessage().id == message_id)) {
                        SBChat.scrollBottom();
                    }
                    return false;
                }
                if (success == '' && type != 'registration') {
                    let shortcode_settings = this.shortcode(shortcode);
                    let title = 'title' in shortcode_settings[1] ? `title="${shortcode_settings[1]['title']}"` : '';
                    let message = 'message' in shortcode_settings[1] ? `message="${shortcode_settings[1]['message']}"` : '';
                    let value = '';
                    if (['inputs', 'email'].includes(type)) {
                        for (var key in settings) {
                            value += settings[key][0] + ',';
                        }
                        value = `values="${value.slice(0, -1)}"`
                    } else {
                        value = `options="${settings}"`;
                    }
                    success = `[${type == 'email' ? 'inputs' : type} ${title} ${message} ${value} disabled="true"]`;
                }
                if (message_id != -1) {
                    $.extend(parameters, {
                        message_id: message_id,
                        message: message ? message.replace(shortcode, success) : success,
                        payload: payload
                    });
                }
                SBF.ajax(parameters, (response) => {
                    if (response != false && !SBF.errorValidation(response)) {
                        switch (type) {
                            case 'email':
                                for (var key in user_settings) {
                                    activeUser().set(key, user_settings[key][0]);
                                }
                                for (var key in settings_extra) {
                                    activeUser().setExtra(key, settings_extra[key][0]);
                                }
                                SBF.loginCookie(response[1]);
                                if (rich_message_id == 'sb-subscribe-form') {
                                    SBF.ajax({
                                        function: 'subscribe-email',
                                        email: activeUser().get('email')
                                    });
                                }
                                SBChat.automations.run_all();
                                SBF.event('SBNewEmailAddress', { id: rich_message_id, name: activeUser().name, email: activeUser().get('email') });
                                break;
                            case 'registration':
                                SBF.loginCookie(response[1]);
                                if (activeUser() == false) {
                                    activeUser(new SBUser(response[0]));
                                    for (var key in settings_extra) {
                                        activeUser().setExtra(key, settings_extra[key][0]);
                                    }
                                    SBPusher.start();
                                    SBChat.initChat();
                                    if ((!CHAT_SETTINGS['init-dashboard'] || !main.find('.sb-departments-list').length) && success) SBChat.sendMessage(bot_id, success, [], false, false, 3);
                                } else {
                                    for (var key in user_settings) {
                                        activeUser().set(key, user_settings[key][0]);
                                    }
                                    for (var key in settings_extra) {
                                        activeUser().setExtra(key, settings_extra[key][0]);
                                    }
                                    SBChat.automations.run_all();
                                }
                                if (SBChat.dashboard) {
                                    main.removeClass('sb-init-form-active');
                                    $(area).remove();
                                    if (!SBChat.isInitDashboard()) {
                                        SBChat.hideDashboard();
                                    }
                                }
                                delete this.cache['registration'];
                                SBF.event('SBRegistrationForm', { id: rich_message_id, user: user_settings, extra: payload['rich-messages'][rich_message_id]['result']['extra'] });
                                SBF.event('SBNewEmailAddress', { id: rich_message_id, name: activeUser().name, email: activeUser().get('email') });
                                break;
                        }
                        if (message_id == -1) {
                            $(element).closest('.sb-rich-message').html(success);
                        } else if ((!('type' in payload) || payload['type'] != 'close-message') && !dialogflow_human_takeover) {
                            SBChat.setConversationStatus(2);
                        }
                        if (!['email', 'registration', 'login', 'chips', 'rating'].includes(type)) {
                            SBApps.dialogflow.message(`${rich_message_id}|${type}|${dialogflow_response}`);
                        }
                        if (CHAT_SETTINGS['slack-active'] && (!dialogflow_human_takeover || !SBF.storageTime('human-takeover-' + SBChat.conversation.id, 240))) {
                            SBChat.slackMessage(activeUser().id, activeUser().name, activeUser().image, success);
                        }
                        if (SBPusher.active) SBChat.update();
                        SBF.event('SBRichMessageSubmit', { result: response, data: payload['rich-messages'][rich_message_id], id: rich_message_id });
                    } else {
                        SBForm.showErrorMessage(area, SBForm.getRegistrationErrorMessage(response, 'response'));
                        if (SBChat.dashboard) {
                            SBChat.scrollBottom();
                        }
                        input.sbLoading(false);
                    }
                });
            }
        },

        // Return the shortcode name and the shortcode settings
        shortcode: function (shortcode) {
            if (shortcode.indexOf(' ') < 0) {
                return [shortcode.slice(1, -1), {}];
            }
            let result = {};
            let shortcode_name = shortcode.substr(1, shortcode.indexOf(' ') - 1);
            shortcode = shortcode.slice(1, -1).substr(shortcode_name.length + 1);
            let settings = shortcode.split('" ');
            for (var i = 0; i < settings.length; i++) {
                if (settings[i].includes('=')) {
                    let item = [settings[i].substr(0, settings[i].indexOf('=')), settings[i].substr(settings[i].indexOf('=') + 2)];
                    result[$.trim(item[0])] = item[1].replace(/"/g, '');
                }
            }
            return [shortcode_name, result];
        },

        // Init the rich message inputs
        initInputs: function (code) {
            code = $($.parseHTML('<div>' + code + '</div>'));
            code.find('.sb-input input').each(function () {
                if ($(this).val()) {
                    $(this).siblings().addClass('sb-active sb-filled');
                }
            });
            return code.html();
        },

        // Timetable shortcode
        timetable: function (code) {
            let table = $($.parseHTML(`<div>${code}</div>`));
            let offset = table.find('[data-offset]').attr('data-offset');
            offset = SBF.null(offset) ? 0 : parseInt(offset);
            table.find('[data-time]').each(function () {
                let times = $(this).attr('data-time').split('|');
                code = ''
                for (var i = 0; i < times.length; i++) {
                    if (times[i] == 'closed') {
                        code += sb_('Closed');
                        break;
                    } else if (times[i]) {
                        let hm = times[i].split(':');
                        let time = SBF.convertUTCDateToLocalDate(`01/01/2000 ${hm[0]}:${hm[1]}`, offset);
                        code += time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + (i == 0 || i == 2 ? `<span>${sb_('to')}</span>` : i == 1 && times[i + 1] ? `<br />` : '');
                    }
                }
                table.find(' > div > span').html(`<i class="sb-icon-clock"></i> ${sb_('Time zone')} ${Intl.DateTimeFormat().resolvedOptions().timeZone}`);
                $(this).html(code);
            });
            return table.html();
        },

        // Slider
        sliderChange: function (id, direction = 'left') {
            let slider = chat.find(`#${id}`);
            if (slider.length && !slider.hasClass('sb-moving')) {
                let items = slider.find('.sb-slider > div > div');
                let item = items.eq(0);
                let width = Math.ceil(item.closest('.sb-slider').width());
                let negative = (direction == 'right' ? -1 : 1);
                let margin = parseFloat(parseFloat(parseFloat(item.css('margin-left')) + (width * negative)).toFixed(2)) + (-0.5 * negative);
                let check = width * (items.length - 1) * -1;
                if (margin < 1 && margin >= check) {
                    item.css('margin-left', margin + 'px');
                    slider.addClass('sb-moving');
                    setTimeout(() => { slider.removeClass('sb-moving'); }, 1200);
                }
                slider.find('.sb-icon-arrow-right').sbActivate(!(check > (margin - 15) && check < (margin + 15)));
                slider.find('.sb-icon-arrow-left').sbActivate(margin < -10);
            }
        }
    }

    /* 
    * ----------------------------------------------------------
    * FORM METHODS
    * ----------------------------------------------------------
    */

    var SBForm = {

        // Get all settings
        getAll: function (area) {
            let settings = {};
            $(area).find('.sb-input[id]').each((i, element) => {
                settings[$(element).attr('id')] = this.get(element);
            });
            return settings;
        },

        // Get a single setting
        get: function (input) {
            input = $(input);
            let type = input.data('type');
            let name = sb_(SBF.escape(input.find(' > span').html()));
            switch (type) {
                case 'image':
                    let url = input.find('.image').attr('data-value');
                    return [SBF.null(url) ? '' : url, name];
                case 'select':
                    return [SBF.escape(input.find('select').val()), name];
                case 'select-input':
                    return [SBF.escape(input.find('select').val() + input.find('input').val()), name];
                default:
                    let target = input.find('input');
                    return [SBF.escape(target.length ? target.val() : input.find('[data-value]').attr('data-value')), name];
            }
        },

        // Set a single setting
        set: function (item, value) {
            item = $(item);
            if (item.length) {
                let type = item.data('type');
                switch (type) {
                    case 'image':
                        if (value == '') {
                            item.find('.image').removeAttr('data-value').removeAttr('style');
                        } else {
                            item.find('.image').attr('data-value', value).css('background-image', `url("${value}")`);
                        }
                        break;
                    case 'select':
                        item.find('select').val(value);
                        break;
                    default:
                        item.find('input,textarea').val(value);
                        break;
                }
                return true;
            }
            return false;
        },

        // Clear all the input values
        clear: function (area) {
            $(area).find('.sb-input,.sb-input-setting').each((i, element) => {
                this.set(element, '');
                $(element).find('input, select, textarea').removeClass('sb-error');
            });
            this.set($(area).find('#user_type'), 'user');
        },

        // Check for errors on user input
        errors: function (area) {
            let errors = false;
            let items = $(area).find('input, select, textarea').removeClass('sb-error');
            items.each(function (i) {
                let value = $(this).val();
                let type = $(this).attr('type');
                let required = $(this).prop('required');
                if ((required && value == '') || ((required || value) && ((type == 'password' && (value.length < 8 || (items.length > (i + 1) && items.eq(i + 1).attr('type') == 'password' && items.eq(i + 1).val() != value))) || (type == 'email' && (value.indexOf('@') < 0 || value.indexOf('.') < 0 || /;|:|\/|\\|,|#|"|!|=|\+|\*|{|}|[|]|£|\$|€|~|'|>|<|\^|&/.test(value))) || (type == 'tel' && value && ($(this).parent().find('select').val() == '' || isNaN(value) || value.includes('+') || value.length < 5))))) {
                    errors = true;
                    $(this).addClass('sb-error');
                }
            });
            items = $(area).find('[data-required]').removeClass('sb-error');
            items.each(function () {
                if (SBF.null($(this).attr('data-value'))) {
                    $(this).addClass('sb-error');
                    errors = true;
                }
            });
            return errors;
        },

        // Display a error message
        showErrorMessage: function (area, message) {
            $(area).find('.sb-info').html(sb_(message)).sbActivate();
            clearTimeout(timeout);
            timeout = setTimeout(function () {
                $(area).find('.sb-info').sbActivate(false);
            }, 7000);
        },

        // Display a success message
        showSuccessMessage: function (area, message) {
            $(area).find('.sb-info').remove();
            $(area).addClass('sb-success').find('.sb-content').html(`<div class="sb-text">${message}</div>`);
        },

        // Return the registration error message
        getRegistrationErrorMessage(area_or_response, type = 'validation') {
            if (type == 'response') return SBF.errorValidation(area_or_response, 'duplicate-email') ? 'This email is already in use. Please use another email.' : (SBF.errorValidation(area_or_response, 'duplicate-phone') ? 'This phone number is already in use. Please use another number.' : 'Error. Please check your information and try again.')
            let name = $(area_or_response).find('#last_name').length ? 'First name, last name' : 'Name';
            let phone = $(area_or_response).find('#phone[required]').length ? ', phone number' : '';
            let email = $(area_or_response).find('#email').length ? ', email' : '';
            let password = $(area_or_response).find('#password').length ? ', and a password (8 character minimum)' : '';
            return `${name}${email}${phone}${password} ${(email + phone + password) == '' ? 'is' : 'are'} required.`;
        }
    }
    window.SBForm = SBForm;

    /* 
    * ----------------------------------------------------------
    * # APPS
    * ----------------------------------------------------------
    */

    var SBApps = {

        // Get the login data 
        login: function () {
            if (this.is('wp') && typeof SB_WP_ACTIVE_USER != ND && CHAT_SETTINGS['wp-users-system'] == 'wp') {
                return [[SB_WP_ACTIVE_USER, typeof SB_WP_AVATAR != ND ? SB_WP_AVATAR : ''], 'wp'];
            }
            if (typeof SB_PERFEX_ACTIVE_USER != ND) {
                return [[SB_PERFEX_ACTIVE_USER, SB_PERFEX_CONTACT_ID], 'perfex'];
            }
            if (typeof SB_WHMCS_ACTIVE_USER != ND) {
                return [SB_WHMCS_ACTIVE_USER, 'whmcs'];
            }
            if (typeof SB_AECOMMERCE_ACTIVE_USER != ND) {
                return [SB_AECOMMERCE_ACTIVE_USER, 'aecommerce'];
            }
            return false;
        },

        // Check if an app is installed and active
        is: function (name) {
            if (admin) return SBAdmin.apps.is(name);
            if (name == 'wordpress' || name == 'wp') return CHAT_SETTINGS['wp'];
            return name in CHAT_SETTINGS ? CHAT_SETTINGS[name] : false;
        },

        // WordPress
        wordpress: {

            // Ajax
            ajax: function (action, data, onSuccess) {
                $.ajax({
                    method: 'POST',
                    url: SB_WP_AJAX_URL,
                    data: $.extend({ action: 'sb_wp_ajax', type: action }, data)
                }).done((response) => {
                    if (onSuccess !== false) {
                        onSuccess(response);
                    }
                });
            }
        },

        // WooCoommerce
        woocommerce: {

            // Update the cart
            updateCart: function (action, product_id, onSuccess = false) {
                SBApps.wordpress.ajax(action, { product_id: product_id }, onSuccess);
            },

            // Waiting list
            waitingList: function (action = 'request', product_id = false) {
                if (typeof SB_WP_WAITING_LIST !== ND && (action != 'request' || SB_WP_WAITING_LIST && SBF.storageTime('waiting-list-' + SB_WP_PAGE_ID, 24)) && activeUser()) {
                    SBF.ajax({
                        function: 'woocommerce-waiting-list',
                        product_id: product_id === false ? SB_WP_PAGE_ID : product_id,
                        conversation_id: SBChat.conversation.id,
                        action: action,
                        token: this.token
                    }, (response) => {
                        if (response) {
                            SBF.storageTime('waiting-list-' + SB_WP_PAGE_ID);
                            if (action == 'request' && (!SBChat.chat_open || SBChat.dashboard)) {
                                SBChat.updateConversations();
                            }
                        }
                    });
                }
            }
        },

        // Dialogflow
        dialogflow: {
            token: storage('dialogflow-token'),
            typing_enabled: false,

            // Send a message to the bot and get the reply
            message: function (message = '', attachments = [], delay = false) {
                if (this.active()) {
                    let human_takeover_active = !SBF.storageTime('human-takeover-' + SBChat.conversation.id, 240);
                    if (!human_takeover_active || this.typing_enabled) {
                        timeout = setTimeout(() => {
                            SBChat.typing(-1, 'start');
                            setTimeout(() => {
                                SBChat.typing(-1, 'stop');
                            }, 10000);
                        }, 1000);
                    }
                    setTimeout(() => {
                        if (!SBChat.conversation) return;
                        SBF.ajax({
                            function: 'dialogflow-message',
                            conversation_id: SBChat.conversation.id,
                            message: message,
                            attachments: attachments,
                            token: this.token,
                            dialogflow_language: storage('dialogflow-language') ? storage('dialogflow-language') : SB_LANG
                        }, (response) => {
                            if (response === false) return;
                            if ('human_takeover' in response) {
                                SBChat.offlineMessage();
                                SBChat.followUp();
                                return this.active(false);
                            }
                            if ('language_detection' in response && !storage('dialogflow-language')) {
                                storage('dialogflow-language', [response.language_detection]);
                            }
                            if (!SBF.errorValidation(response)) {
                                let query_result = 'queryResult' in response['response'] ? response['response']['queryResult'] : false;
                                let is_unknow = query_result && (query_result['action'] == 'input.unknown' || ('match' in query_result && query_result['match']['matchType'] == 'NO_MATCH'));
                                let messages = 'messages' in response && Array.isArray(response['messages']) ? response['messages'] : [];
                                SBChat.typing(-1, 'stop');
                                clearTimeout(timeout);
                                if (this.token != response['token']) {
                                    this.token = response['token'];
                                    storage('dialogflow-token', response['token']);
                                }
                                if (is_unknow) {
                                    if (human_takeover_active) this.typing_enabled = false;
                                } else this.typing_enabled = true;
                                if (query_result) {

                                    // Actions
                                    if ('action' in query_result) {
                                        let action = query_result['action'];
                                        if (action == 'end') {
                                            this.active(false);
                                        }
                                        SBF.event('SBBotAction', action);
                                    }

                                    // Payload
                                    for (var i = 0; i < messages.length; i++) {
                                        if ('payload' in messages[i]) {
                                            let payloads = ['human-takeover', 'redirect', 'woocommerce-update-cart', 'woocommerce-checkout', 'open-article', 'transcript', 'department', 'agent', 'send-email', 'disable-bot'];
                                            let payload = messages[i]['payload'];
                                            if (SBF.null(payload)) payload = [];
                                            if (payloads[0] in payload && payload[payloads[0]] === true) {
                                                this.humanTakeover();
                                            }
                                            if (payloads[1] in payload) {
                                                setTimeout(function () {
                                                    'new-window' in payload && payload['new-window'] ? window.open(payload[payloads[1]]) : document.location = payload[payloads[1]];
                                                }, 500);
                                            }
                                            if (payloads[2] in payload) {
                                                let payload_value = payload[payloads[2]];
                                                SBApps.woocommerce.updateCart(payload_value[0], payload_value[1], (response) => {
                                                    if (response) {
                                                        SBF.event('SBWoocommerceCartUpdated', { 'action': payload_value[0], 'product': payload_value[1] });
                                                    }
                                                });
                                            }
                                            if (payloads[3] in payload && payload[payloads[3]] === true) {
                                                SBApps.wordpress.ajax('url', { url_name: 'checkout' }, (response) => {
                                                    setTimeout(function () {
                                                        document.location = response;
                                                    }, 500);
                                                });
                                            }
                                            if (payloads[4] in payload) {
                                                SBChat.showArticles(payload[payloads[4]]);
                                            }
                                            if (payloads[5] in payload && payload[payloads[5]] === true) {
                                                SBF.ajax({
                                                    function: 'transcript',
                                                    conversation_id: SBChat.conversation.id,
                                                    type: 'txt'
                                                }, (response) => { window.open(response) });
                                            }
                                            if (payloads[6] in payload) {
                                                SBF.ajax({
                                                    function: 'update-conversation-department',
                                                    conversation_id: SBChat.conversation.id,
                                                    department: payload[payloads[6]],
                                                    message: SBChat.conversation.getLastUserMessage().message
                                                });
                                            }
                                            if (payloads[7] in payload) {
                                                SBF.ajax({
                                                    function: 'update-conversation-agent',
                                                    conversation_id: SBChat.conversation.id,
                                                    agent_id: payload[payloads[7]],
                                                    message: SBChat.conversation.getLastUserMessage().message
                                                });
                                            }
                                            if (payloads[8] in payload) {
                                                let email = payload[payloads[8]];
                                                SBChat.sendEmail(email.message, email.attachments, email.recipient == 'active_user');
                                            }
                                            if (payloads[9] in payload) {
                                                this.active(false);
                                                SBF.cookie('sb-dialogflow-disabled', true, 300, 'set', true);
                                            }
                                            SBF.event('SBBotPayload', payload);
                                        }
                                    }

                                    // Diagnostic info
                                    if ('diagnosticInfo' in query_result) {
                                        let info = query_result['diagnosticInfo'];

                                        // End conversation
                                        if ('end_conversation' in info && info['end_conversation']) {
                                            this.active(false);
                                        }
                                    }
                                }

                                // Slack
                                if (CHAT_SETTINGS['slack-active'] && messages && (!dialogflow_human_takeover || human_takeover_active)) {
                                    for (var i = 0; i < messages.length; i++) {
                                        SBChat.slackMessage(activeUser().id, CHAT_SETTINGS['bot-name'], CHAT_SETTINGS['bot-image'], messages[i]['message'], messages[i]['attachments']);
                                    }
                                }

                                SBF.event('SBBotMessage', { response: response, message: message });
                            }
                        });
                    }, delay !== false ? delay : (CHAT_SETTINGS['bot-delay'] == 0 ? 2000 : parseInt(CHAT_SETTINGS['bot-delay'])));
                }
            },

            // Check if Dialogflow is active or deactivate it
            active: function (active = true) {
                let name = 'human-takeover-' + SBChat.conversation.id;
                if (active === false) {
                    SBF.storageTime(name);
                    return false;
                }
                if (active == 'activate') {
                    SBF.cookie('sb-dialogflow-disabled', 0, 0, 'delete');
                    SBF.storage(name, '');
                }
                return CHAT_SETTINGS['dialogflow-active'] && !admin && !SBF.cookie('sb-dialogflow-disabled') && (SBF.storageTime(name, 240) || !SBChat.agent_online) && (!CHAT_SETTINGS['bot-office-hours'] || !CHAT_SETTINGS['office-hours']);
            },

            // Trigger the welcome Intent
            welcome: function () {
                if (CHAT_SETTINGS['dialogflow-welcome']) {
                    SBF.ajax({
                        function: 'dialogflow-message',
                        message: '',
                        conversation_id: SBChat.conversation.id,
                        token: this.token,
                        event: 'Welcome',
                        dialogflow_language: storage('dialogflow-language') ? storage('dialogflow-language') : SB_LANG
                    });
                }
            },

            // Start the human takeover process
            humanTakeover: function () {
                SBF.ajax({
                    function: 'dialogflow-human-takeover',
                    conversation_id: SBChat.conversation.id
                }, () => {
                    SBChat.offlineMessage();
                    SBChat.followUp();
                    this.active(false);
                });
            },

            // Translate a string
            translate: function (strings, language_code, onSuccess) {
                SBF.ajax({
                    function: 'google-translate',
                    strings: strings,
                    language_code: language_code,
                    token: this.token
                }, (response) => {
                    this.token = response[1]
                    onSuccess('data' in response[0] && 'translations' in response[0].data ? response[0].data.translations : false);
                });
            }
        },

        aecommerce: {

            // Update the user cart
            cart: function () {
                if (SBApps.is('aecommerce') && typeof SB_AECOMMERCE_CART != ND && typeof SB_AECOMMERCE_ACTIVE_USER != ND && storage('aecommerce') != JSON.stringify(SB_AECOMMERCE_CART)) {
                    SBF.ajax({
                        function: 'aecommerce-cart',
                        cart: SB_AECOMMERCE_CART
                    }, () => {
                        storage('aecommerce', JSON.stringify(SB_AECOMMERCE_CART));
                    });
                }
            }
        }
    }
    window.SBApps = SBApps;

    /*
    * ----------------------------------------------------------
    * # INIT
    * ----------------------------------------------------------
    */

    $(document).ready(function () {
        main = $('.sb-admin, .sb-admin-start');
        if (main.length) {
            admin = true;
            initialize();
            return;
        }
        let url_full;
        let url;
        let init = false;
        if (typeof SB_INIT_URL != ND) {
            if (SB_INIT_URL.indexOf('.js') < 0) {
                SB_INIT_URL += '/js/main.js?v=' + version;
            }
            url_full = SB_INIT_URL;
        } else {
            let scripts = document.getElementsByTagName('script');
            let checks = ['init.js', 'main.js', 'min/init.min.js', 'min/main.min.js'];
            for (var i = 0; i < scripts.length; i++) {
                let source = scripts[i].src;
                if (scripts[i].id == 'sbinit') {
                    url_full = source;
                    init = init ? init : url_full.includes('init.');
                    break;
                } else {
                    for (var j = 0; j < checks.length; j++) {
                        if (source.includes('/supportboard/js/' + checks[j])) {
                            url_full = source;
                            init = init ? init : url_full.includes('init.');
                            break;
                        }
                    }
                }
            }
        }
        let parameters = SBF.getURL(false, url_full);
        if ('url' in parameters) {
            url_full = parameters['url'];
        }
        if (typeof SB_DISABLED != ND && SB_DISABLED) {
            return;
        }
        if (init) {
            initialize();
            return;
        }
        if (typeof SB_TICKETS != ND || ('mode' in parameters && parameters['mode'] == 'tickets')) {
            tickets = true;
            parameters['mode'] = 'tickets';
        }
        if ('cloud' in parameters) {
            cloud_data = parameters['cloud'];
        }

        url = url_full.substr(0, url_full.lastIndexOf('main.js') > 0 ? (url_full.lastIndexOf('main.js') - 4) : (url_full.lastIndexOf('main.min.js') - 8));
        let url_chat = url + '/include/init.php' + ('lang' in parameters ? '?lang=' + parameters['lang'] : '') + ('mode' in parameters ? '&mode=' + parameters['mode'] : '') + (cloud_data ? '&cloud=' + cloud_data : '');
        SBF.cors('GET', url_chat.replace('.php&', '.php?'), (response) => {
            let target = 'body';
            if (tickets && $('#sb-tickets').length) {
                target = '#sb-tickets';
            }
            $(target).append(response);
            SBF.loadResource(url + '/css/min/' + (tickets ? 'tickets' : 'main') + '.min.css');
            if (tickets) {
                SBF.loadResource(url + '/apps/tickets/tickets.js', true);
            }
            initialize();
        });
    });

    function initialize() {
        main = $('.sb-admin, .sb-chat, .sb-tickets');

        // Initalize the chat and the user
        if (main.length && typeof SB_AJAX_URL != ND) {
            chat = main.find('.sb-list');
            chat_editor = main.find('.sb-editor');
            chat_textarea = chat_editor.find('textarea');
            chat_scroll_area = main.find(admin || tickets ? '.sb-list' : '> div > .sb-scroll-area');
            chat_header = chat_scroll_area.find('.sb-header');
            chat_emoji = chat_editor.find('.sb-emoji');
            chat_status = tickets ? main.find('.sb-profile-agent .sb-status') : null;
            SBChat.enabledAutoExpand();
            SBChat.audio = main.find('#sb-audio').get(0);
            SBChat.audio_out = main.find('#sb-audio-out').get(0);

            // Check if cookies works
            SBF.cookie('sb-check', 'ok', 1, 'set');
            if (SBF.cookie('sb-check') != 'ok') {
                cookies_supported = false;
                console.warn('Support Board: cookies not available.');
            } else {
                SBF.cookie('sb-check', false, false, false);
            }

            if (!admin) {
                SBF.ajax({
                    function: 'get-front-settings',
                    current_url: window.location.href
                }, (response) => {
                    CHAT_SETTINGS = response;
                    bot_id = CHAT_SETTINGS['bot-id'];
                    dialogflow_human_takeover = CHAT_SETTINGS['dialogflow-human-takeover'];
                    agents_online = CHAT_SETTINGS['agents-online'];
                    SBPusher.active = CHAT_SETTINGS['pusher'];
                    if (typeof SB_REGISTRATION_REQUIRED != ND) {
                        CHAT_SETTINGS['registration-required'] = SB_REGISTRATION_REQUIRED;
                        CHAT_SETTINGS['tickets-registration-required'] = SB_REGISTRATION_REQUIRED;
                    }
                    if (typeof SB_ARTICLES_PAGE != ND && SB_ARTICLES_PAGE) {
                        SBChat.initArticlesPage();
                    }
                    if ((!tickets || !CHAT_SETTINGS['tickets-manual-init']) && ((tickets && !CHAT_SETTINGS['tickets-manual-init']) || (!CHAT_SETTINGS['chat-manual-init'] && (!CHAT_SETTINGS['disable-offline'] || agents_online) && (!CHAT_SETTINGS['disable-office-hours'] || CHAT_SETTINGS['office-hours']) && (!CHAT_SETTINGS['chat-login-init'] || SBApps.login())))) {
                        SBChat.initChat();
                    }
                    if (CHAT_SETTINGS['cron']) {
                        setTimeout(function () {
                            SBF.ajax({ function: 'cron-jobs' });
                        }, 10000);
                    }
                    if (CHAT_SETTINGS['cron-email-piping']) {
                        setTimeout(function () {
                            SBF.ajax({ function: 'email-piping' });
                        }, 8000);
                    }
                    if (CHAT_SETTINGS['push-notifications-users']) {
                        SBPusher.initServiceWorker();
                    }
                    if (tickets && CHAT_SETTINGS['tickets-default-department']) {
                        SBChat.default_department = CHAT_SETTINGS['tickets-default-department'];
                    }
                    SBApps.aecommerce.cart();
                    SBF.event('SBReady');
                });
                if (SBChat.audio) SBChat.audio.volume = 0.6;
                if (SBChat.audio_out) SBChat.audio_out.volume = 0.6;
            } else {
                SBF.event('SBReady');
            }
            $(chat_editor).on('keydown', 'textarea', function (e) {
                if (e.which == 13 && (!tickets || CHAT_SETTINGS['tickets-enter-button']) && (!admin || !e.ctrlKey)) {
                    SBChat.submit();
                    e.preventDefault;
                    return false;
                }
                if (admin && e.which == 13 && e.ctrlKey) {
                    SBChat.insertText('\n');
                }
            });
            if (typeof SB_DEFAULT_DEPARTMENT !== ND) {
                SBChat.default_department = SB_DEFAULT_DEPARTMENT;
            }
            if (typeof SB_DEFAULT_AGENT !== ND) {
                SBChat.default_agent = SB_DEFAULT_AGENT;
            }
        } else {
            SBF.event('SBReady');
        }

        // Disable real-time if browser tab not active
        document.addEventListener('visibilitychange', function () {
            SBF.visibilityChange(document.visibilityState);
        }, false);

        $(main).on('click', function () {
            if (!SBChat.tab_active) {
                SBF.visibilityChange();
            }
        });

        // Set the global container for both admin and front
        global = main;
        if (admin) {
            main = main.find('.sb-conversation');
        }

        // Scroll detection
        $(chat_scroll_area).on('scroll', function (e) {
            SBChat.scrollHeader();
        });

        // Show the message menu
        $(chat).on('click', '.sb-menu-btn', function () {
            let menu = $(this).parent().find('.sb-menu');
            let active = $(menu).sbActive();
            SBF.deactivateAll();
            if (!active) {
                $(menu).sbActivate();
                SBF.deselectAll();
                if (admin) SBAdmin.open_popup = menu;
            }
        });

        // Mobile
        if (mobile) {
            $(chat_editor).on('click', '.sb-textarea', function () {
                main.addClass('sb-header-hidden');
                $(this).find('textarea').focus();
                if (chat_scroll_area[0].scrollTop === (chat_scroll_area[0].scrollHeight - chat_scroll_area[0].offsetHeight)) {
                    SBChat.scrollBottom();
                    setTimeout(() => {
                        SBChat.scrollBottom();
                    }, 200);
                }
            });

            $(chat_editor).on('focusout', '.sb-textarea', function () {
                setTimeout(() => {
                    main.removeClass('sb-header-hidden');
                }, 300);
            });

            $(chat_editor).on('click', '.sb-submit', function () {
                chat_textarea.blur();
            });
        }

        // Hide the message menu
        $(chat).on('click', '.sb-menu li', function () {
            $(this).parent().sbActivate(false);
        });

        // Send a message
        $(chat_editor).on('click', '.sb-submit', function () {
            SBChat.submit();
        });

        // Open the chat
        $('body').on('click', '.sb-chat-btn,.sb-responsive-close-btn,#sb-open-chat,.sb-open-chat', function () {
            SBChat.open(!SBChat.chat_open);
        });

        // Show the dashboard
        $(main).on('click', '.sb-dashboard-btn', function () {
            SBChat.showDashboard();
            if (chat_scroll_area.find(' > .sb-panel-articles > .sb-article').length) SBChat.showArticles();
            storage('open-conversation', 0);
            force_action = false;
        });

        // Open a conversation from the dashboard
        $(main).on('click', '.sb-user-conversations li', function () {
            SBChat.openConversation($(this).attr('data-conversation-id'));
        });

        // Start a new conversation from the dashboard
        $(main).on('click', '.sb-btn-new-conversation,.sb-departments-list > div', function () {
            if (!SBF.null($(this).data('id'))) {
                SBChat.default_department = parseInt($(this).data('id'));
            }
            force_action = 'new-conversation';
            SBChat.clear();
            SBChat.hideDashboard();
        });

        // Displays all conversations in the dashboard
        $(main).on('click', '.sb-btn-all-conversations', function () {
            main.find('.sb-dashboard-conversations').removeClass('sb-conversations-hidden');
        });

        // Events uploader
        $(chat_editor).on('click', '.sb-btn-attachment', function () {
            if (!SBChat.is_busy) {
                chat_editor.find('.sb-upload-files').val('').click();
            }
        });

        $(chat_editor).on('click', '.sb-attachments > div > i', function (e) {
            $(this).parent().remove();
            if (chat_textarea.val() == '' && chat_editor.find('.sb-attachments > div').length == 0) {
                SBChat.activateBar(false);
            }
            e.preventDefault();
            return false;
        });

        $(chat_editor).on('change', '.sb-upload-files', function (data) {
            SBChat.busy(true);
            $(this).sbUploadFiles(function (response) {
                SBChat.uploadResponse(response);
            });
            SBF.event('SBAttachments');
        });

        $(chat_editor).on('dragover', function (e) {
            $(this).addClass('sb-drag');
            clearTimeout(timeout);
            e.preventDefault();
            e.stopPropagation();
        });

        $(chat_editor).on('dragleave', function (e) {
            timeout = setTimeout(() => {
                $(this).removeClass('sb-drag');
            }, 200);
            e.preventDefault();
            e.stopPropagation();
        });

        $(chat_editor).on('drop', function (e) {
            let files = e.originalEvent.dataTransfer.files;
            e.preventDefault();
            e.stopPropagation();
            if (files.length > 0) {
                for (var i = 0; i < files.length; ++i) {
                    let form = new FormData();
                    form.append('file', files[i]);
                    SBF.upload(form, function (response) { SBChat.uploadResponse(response) });
                }
            }
            $(this).removeClass('sb-drag');
            return false;
        });

        // Articles
        $(main).on('click', '.sb-btn-all-articles', function () {
            SBChat.showArticles();
        });

        $(main).on('click', '.sb-articles > div:not(.sb-title)', function () {
            SBChat.showArticles($(this).attr('data-id'));
        });

        $(main).on('click', '.sb-dashboard-articles .sb-input-btn .sb-submit-articles', function () {
            SBChat.searchArticles($(this).parent().find('input').val(), this, $(this).parent().next());
        });

        $(main).on('click', '.sb-article [data-rating]', function () {
            SBChat.articleRatingOnClick(this);
        });

        $(chat).on('click', '.sb-rich-button a', function (e) {
            let link = $(this).attr('href');
            if (link.indexOf('#') === 0) {
                if (link.indexOf('#article-') === 0) {
                    SBChat.showArticles(link.replace('#article-', ''));
                    e.preventDefault();
                    return false;
                }
            }
        });

        // Lightbox
        $(global).on('click', '.sb-lightbox-media > i', function () {
            global.find('.sb-lightbox-media').sbActivate(false);
            if (admin) SBAdmin.open_popup = false;
            return false;
        });

        $(main).on('click', '.sb-image', function () {
            SBF.lightbox($(this).html());
        });

        $(main).on('click', '.sb-slider-images .sb-card-img', function () {
            SBF.lightbox(`<img src="${$(this).attr('data-value')}" />`);
        });

        // Event: on conversation loaded
        $(document).on('SBConversationLoaded', function () {
            if (storage('queue')) {
                SBChat.queue(storage('queue'));
            }
        });

        // Events emoji
        $(chat_editor).on('click', '.sb-btn-emoji', function () {
            SBChat.showEmoji(this);
        });

        $(chat_emoji).on('click', '.sb-emoji-list li', function (e) {
            SBChat.insertEmoji($(this).html());
            if (mobile) {
                clearTimeout(timeout);
            }
        });

        $(chat_emoji).find('.sb-emoji-list').on('touchend', function (e) {
            timeout = setTimeout(() => {
                SBChat.mouseWheelEmoji(e);
            }, 50);
        });

        $(chat_emoji).find('.sb-emoji-list').on('mousewheel DOMMouseScroll', function (e) {
            SBChat.mouseWheelEmoji(e);
        });

        $(chat_emoji).find('.sb-emoji-list').on('touchstart', function (e) {
            SBChat.emoji_options['touch'] = e.originalEvent.touches[0].clientY;
        });

        $(chat_emoji).on('click', '.sb-emoji-bar > div', function () {
            SBChat.clickEmojiBar(this);
        });

        $(chat_emoji).on('click', '.sb-select li', function () {
            SBChat.categoryEmoji($(this).data('value'));
        });

        $(chat_emoji).find('.sb-search-btn input').on('change keyup paste', function () {
            SBChat.searchEmoji($(this).val());
        });

        // Textarea
        $(chat_textarea).on('keyup', function () {
            SBChat.textareaChange(this);
        });

        // Privacy message
        $(main).on('click', '.sb-privacy .sb-approve', function () {
            storage('privacy-approved', true);
            $(this).closest('.sb-privacy').remove();
            main.removeClass('sb-init-form-active');
            chat_header.find(' > div').css({ 'opacity': 1, 'top': 0 });
            SBChat.initChat();
            if (tickets) {
                SBTickets.showPanel('new-ticket');
            } else if (!SBChat.isInitDashboard()) {
                SBChat.hideDashboard();
            }
        });

        $(main).on('click', '.sb-privacy .sb-decline', function () {
            let privacy = $(this).closest('.sb-privacy');
            $(privacy).find('.sb-text').html($(privacy).attr('data-decline'));
            $(privacy).find('.sb-decline').remove();
            SBChat.scrollBottom(true);
        });

        // Popup message
        $(main).on('click', '.sb-popup-message .sb-icon-close', function () {
            SBChat.popup(true);
        });

        // Rich messages and inputs
        $(main).on('click', '.sb-rich-message .sb-submit,.sb-rich-message .sb-select ul li', function () {
            let message = $(this).closest('.sb-rich-message');
            if (!message[0].hasAttribute('disabled')) {
                SBRichMessages.submit(message, message.attr('data-type'), this);
            };
        });

        $(main).on('click', '.sb-rich-message .sb-input > span', function () {
            $(this).sbActivate();
            $(this).siblings().focus();
        });

        $(main).on('focus focusout click', '.sb-rich-message .sb-input input,.sb-rich-message .sb-input select', function (e) {
            switch (e.type) {
                case 'focusin':
                case 'focus':
                    $(this).siblings().sbActivate();
                    break;
                case 'focusout':
                    if ($(this).val() == '') {
                        $(this).siblings().sbActivate(false);
                    } else {
                        $(this).siblings().addClass('sb-filled sb-active');
                    }
                    break;
                case 'click':
                    $(this).siblings().removeClass('sb-filled');
                    break;
            }
        });

        $(main).on('click', '.sb-slider-arrow', function () {
            SBRichMessages.sliderChange($(this).closest('[id]').attr('id'), $(this).hasClass('sb-icon-arrow-right') ? 'right' : 'left');
        });

        $(main).on('change', '.sb-rich-message [data-type="select"] select', function () {
            $(this).siblings().sbActivate();
        });

        $(main).on('click', '[data-type="select-input"] > div', function () {
            $(this).prev().sbActivate();
            $(this).next().addClass('sb-focus');
        });

        $(main).on('focusout', '[data-type="select-input"] input,[data-type="select-input"] select', function () {
            let cnt = $(this).closest('.sb-input');
            if (cnt.find('input').val() + cnt.find('select').val() == '') {
                cnt.find('span').sbActivate(false);
            }
            cnt.find('.sb-focus').removeClass('sb-focus');
        });

        // Registration and Login
        $(main).on('click', '.sb-rich-registration .sb-login-area', function () {
            $(this).closest('.sb-rich-registration').replaceWith(SBRichMessages.generate({}, 'login', 'sb-init-form'));
            SBChat.scrollBottom(true);
        });

        $(main).on('click', '.sb-rich-login .sb-registration-area', function () {
            if (CHAT_SETTINGS['registration-link']) {
                document.location = CHAT_SETTINGS['registration-link'];
            } else {
                $(this).closest('.sb-rich-login').replaceWith(SBRichMessages.generate({}, 'registration', 'sb-init-form'));
                SBChat.scrollBottom(true);
            }
        });

        $(main).on('click', '.sb-rich-login .sb-submit-login', function () {
            SBF.loginForm(this, false, (response) => {
                activeUser(new SBUser(response[0]));
                main.removeClass('sb-init-form-active');
                $(this).closest('.sb-rich-login').remove();
                force_action = 'open-conversation';
                SBChat.initChat();
                SBPusher.start();
                if (!SBChat.isInitDashboard()) {
                    SBChat.hideDashboard();
                }
            });
        });

        // Social share buttons
        $(chat).on('click', '.sb-social-buttons div', function () {
            SBF.openWindow($(this).attr('data-link'));
        });

        // WooCommerce
        $(chat).on('click', '.sb-rich-woocommerce-button a, [href="#"].sb-card-btn', function (e) {
            let settings = SBF.settingsStringToArray($(this).closest('.sb-rich-message').attr('data-settings'));
            let checkout = settings['link-type'] == 'checkout' || settings['checkout'];
            let product_ids = $(this)[0].hasAttribute('data-ids') ? $(this).attr('data-ids').split(',') : [settings['id'].split('|')[$(this).parent().index()]];
            if (product_ids.length) {
                if (loading(this)) return;
                SBApps.wordpress.ajax('button-purchase', { product_ids: product_ids, checkout: checkout, coupon: settings['coupon'] }, (response) => {
                    if (checkout) {
                        document.location = response;
                    } else {
                        $(this).addClass('sb-icon-check').sbLoading(false);
                    }
                });
            }
            e.preventDefault();
            return false;
        });

        $(chat).on('click', '#sb-waiting-list .sb-submit', function () {
            if ($(this).index() == 0) {
                setTimeout(() => { SBApps.woocommerce.waitingList('submit') }, 1000);
            }
        });

        $(document).on('SBNewEmailAddress', function (e, response) {
            if (response['id'] == 'sb-waiting-list-email') {
                SBApps.woocommerce.waitingList('submit');
            }
        });

        /*
        * ----------------------------------------------------------
        * COMPONENTS
        * ----------------------------------------------------------
        */

        // Search
        $(global).on('click', '.sb-search-btn > i', function () {
            let parent = $(this).parent();
            let active = $(parent).sbActive();
            if (active) {
                $(parent).find('input').val('');
                setTimeout(function () {
                    $(parent).find('input').trigger('change');
                }, 550);
            };
            $(parent).sbActivate(!active);
            $(parent).find('input').focus();
            global.find('.sb-select ul').sbActivate(false);
        });

        // Select
        $(global).on('click', '.sb-select', function () {
            let ul = $(this).find('ul');
            ul.toggleClass('sb-active');
            if (admin) SBAdmin.open_popup = ul.hasClass('sb-active') ? this : false;
        });

        $(global).on('click', '.sb-select li', function () {
            let select = $(this).closest('.sb-select');
            let value = $(this).data('value');
            var item = $(select).find(`[data-value="${value}"]`);
            $(select).find('li').sbActivate(false);
            $(select).find('p').attr('data-value', value).html($(item).html());
            $(item).sbActivate();
        });

        // Image uploader
        $(global).on('click', '.sb-input-image .image', function () {
            upload_target = $(this).parent();
            chat_editor.find('.sb-upload-files').click();
        });

        $(global).on('click', '.sb-input-image .image > .sb-icon-close', function (e) {
            $(this).parent().removeAttr('data-value').css('background-image', '');
            e.preventDefault();
            return false;
        });
    }

}(jQuery));