import {marked} from 'marked';
import {getLocaleData} from "@wordpress/i18n";
import {handleError} from "../admin/helpers/notifications";
import {GetConfigs} from "../admin/rest/configs";

/**
 * Is image
 *
 * @param {string|File} data File or mimetype
 * @param {boolean} isFile Check type
 * @return {*|boolean}
 */
export const isImage = (data, isFile = false) => {
    if (isFile) {
        return !!data.type?.startsWith('image/');
    }
    // Mimetype
    return !!data?.startsWith('image/');
}

/**
 * Get file formated size
 *
 * @param {number} fileSize File size bytes
 * @return {string}
 */
export const getFileFormatedSize = (fileSize) => {
    try {
        // Bytes
        let size = +fileSize, symbol = 'bytes';
        // KB
        if (size > 999) {
            size /= 1024;
            symbol = 'kb';
        }
        // MB
        if (size > 999) {
            size /= 1024;
            symbol = 'mb';
        }
        // GB
        if (size > 999) {
            size /= 1024;
            symbol = 'gb';
        }
        return Math.round(size * 100) / 100 + symbol;
    } catch (e) {
        handleError(e);
        return '';
    }
}

/**
 * Get base64 file size in bytes
 *
 * @param {string} base64String Base64 string
 * @return {number}
 */
export const getBase64FileSize = (base64String) => {
    // Remove the data URL prefix if present
    const base64 = base64String.split(',')[1] || base64String;
    // Calculate padding count
    const padding = (base64.match(/=/g) || []).length;
    // Calculate size in bytes
    return (base64.length * 3) / 4 - padding;
}

/**
 * Get base64 formated size
 *
 * @param {string} base64String Base64 string
 * @return {string}
 */
export const getBase64FormatedSize = (base64String) => {
    return getFileFormatedSize(getBase64FileSize(base64String));
}

/**
 * URL search params
 *
 * @param {object} params Query params
 * @param {string} pre '?'
 * @return {string}
 */
export const urlSearchParams = (params, pre = LimbChatbot.rest.url.includes('?') ? '&' : '?') => {
    try {
        const urlSearchParams = new URLSearchParams();
        for (const key in params) {
            if (Array.isArray(params[key])) {
                params[key].forEach(value => urlSearchParams.append(`${key}[]`, value));
            } else {
                urlSearchParams.append(key, params[key]);
            }
        }
        // To string
        let queryString = urlSearchParams.toString();
        // Add pre part
        if (queryString) {
            queryString = pre + queryString
        }
        return queryString;
    } catch (e) {
        handleError(e);
        return '';
    }
}

/**
 * Is valid date
 *
 * @param {Date} date Date
 * @return {boolean}
 */
export const isValidDate = (date) => {
    try {
        return !isNaN(date.getTime());
    } catch (e) {
        return false;
    }
}

/**
 * Get formated date
 *
 * @param {Date} date Date
 * @param {string} format Format
 * @return {string}
 */
export const formatDate = (date, format = 'DD.MM.YYYY, HH:mm') => {
    try {
        const day = String(date.getDate()).padStart(2, '0');
        const dayNonPadded = String(date.getDate());
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const year = date.getFullYear();

        // Get browser locale for proper translation
        const locale = getLocale();
        // Get translated month names using Intl API with browser locale
        let monthNameShort, monthNameFull;
        try {
            monthNameShort = date.toLocaleString(locale, {month: 'short'});
            monthNameFull = date.toLocaleString(locale, {month: 'long'});
        } catch (e) {
            // Fallback to en-US if locale is invalid
            monthNameShort = date.toLocaleString('en-US', {month: 'short'});
            monthNameFull = date.toLocaleString('en-US', {month: 'long'});
        }

        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');

        // Replace format placeholders with actual values
        return format
            .replace('DD', day)
            .replace('D', dayNonPadded)
            .replace('MMMM', monthNameFull)
            .replace('MMM', monthNameShort)
            .replace('MM', month)
            .replace('YYYY', year)
            .replace('HH', hours)
            .replace('mm', minutes);
    } catch (e) {
        handleError(e);
        return '';
    }
}

/**
 * Get formated date from date string
 *
 * @param {string} dateString Datetime
 * @param {string} format Format
 * @param {boolean} utc Convert from UTC or not
 * @return {string|string}
 */
export const getFormatedDate = (dateString, format = 'DD.MM.YYYY, HH:mm', utc = true) => {
    try {
        return formatDate(parseDateString(dateString, utc), format);
    } catch (e) {
        handleError(e);
        return '';
    }
}

/**
 * Get smart formatted date that shows time always, day if different day, month if different month, year if different year
 *
 * @param {string} dateString Datetime
 * @param {boolean} utc Convert from UTC or not
 * @return {string}
 */
export const getSmartFormatedDate = (dateString, utc = true) => {
    try {
        const messageDate = parseDateString(dateString, utc);
        const now = new Date();

        if (!messageDate) {
            return '';
        }

        // Get browser locale for proper translation
        const locale = getLocale();

        // Always show time in 24-hour format
        let time;
        try {
            time = messageDate.toLocaleTimeString(locale, {
                hour: '2-digit',
                minute: '2-digit',
                hour12: false
            });
        } catch (e) {
            // Fallback to en-US if locale is invalid
            time = messageDate.toLocaleTimeString('en-US', {
                hour: '2-digit',
                minute: '2-digit',
                hour12: false
            });
        }

        // Check if same day
        const isSameDay = messageDate.getDate() === now.getDate() &&
            messageDate.getMonth() === now.getMonth() &&
            messageDate.getFullYear() === now.getFullYear();

        if (isSameDay) {
            return time;
        }

        // Check if same year
        const isSameYear = messageDate.getFullYear() === now.getFullYear();

        let monthName;
        try {
            monthName = messageDate.toLocaleString(locale, {month: 'short'});
        } catch (e) {
            // Fallback to en-US if locale is invalid
            monthName = messageDate.toLocaleString('en-US', {month: 'short'});
        }

        if (isSameYear) {
            const day = messageDate.getDate();
            return `${day} ${monthName}, ${time}`;
        }

        // Different year - show full date
        const day = messageDate.getDate();
        const year = messageDate.getFullYear();
        return `${day} ${monthName} ${year}, ${time}`;
    } catch (e) {
        handleError(e);
        return '';
    }
}

/**
 * Get formated date from timestamp
 *
 * @param {number} timestamp Timestamp
 * @param {string} format Format
 * @return {string}
 */
export const getFormatedTimestamp = (timestamp, format = 'DD.MM.YYYY, HH:mm') => {
    try {
        return formatDate(new Date(timestamp), format);
    } catch (e) {
        handleError(e);
        return '';
    }
}

/**
 * Parse date string
 *
 * @param {string} str Date string like "YYYY-MM-DD HH:mm:ss"
 * @param {boolean} utc Convert from UTC or not
 * @return {null|Date}
 */
export const parseDateString = (str, utc = true) => {
    try {
        const iso = str.replace(" ", "T") + (utc && !str.endsWith('Z') ? 'Z' : '');
        const date = new Date(iso);
        return isValidDate(date) ? date : null;
    } catch (e) {
        handleError(e);
        return null;
    }
}

/**
 * Converts a date string to UTC date string
 *
 * @param {string} str The date string to convert (e.g., '2010-10-10')
 * @returns {string|null} UTC date string in format 'YYYY-MM-DD HH:mm:ss' or null if invalid
 */
export const dateStringToUtc = (str) => {
    try {
        // Parse the date string as local time
        const date = new Date(str);

        // Check if the date is valid
        if (!isValidDate(date)) {
            return null;
        }

        // Format as UTC date string
        const year = date.getUTCFullYear();
        const month = String(date.getUTCMonth() + 1).padStart(2, '0');
        const day = String(date.getUTCDate()).padStart(2, '0');
        const hours = String(date.getUTCHours()).padStart(2, '0');
        const minutes = String(date.getUTCMinutes()).padStart(2, '0');
        const seconds = String(date.getUTCSeconds()).padStart(2, '0');

        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    } catch (e) {
        handleError(e);
        return null;
    }
}

/**
 * Get ISO dates diff in milliseconds
 *
 * @param {string} str1 Date ISO
 * @param {string} str2 Date ISO
 * @return {number|null}
 */
export const getDatesDiff = (str1, str2) => {
    try {
        const date1 = new Date(str1.replace(" ", "T"));
        const date2 = new Date(str2.replace(" ", "T"));

        return Math.abs(date2 - date1);
    } catch (e) {
        handleError(e);
        return null;
    }
}

/**
 * Save JSON parser
 *
 * @param {string} string JSON string
 * @param {any} defaultValue Default value
 * @return {any|null}
 */
export const safeJsonParse = (string, defaultValue = null) => {
    try {
        return JSON.parse(string);
    } catch (e) {
        return defaultValue;
    }
}

/**
 * Remove empty values from object
 *
 * @param {object} obj Object
 * @return {*}
 */
export const removeObjectEmptyValues = (obj) => {
    try {
        const cloneObj = JSON.parse(JSON.stringify(obj));
        for (const key in cloneObj) {
            if (!cloneObj[key]) {
                delete cloneObj[key];
            }
        }
        return cloneObj;
    } catch (e) {
        handleError(e);
    }
    return obj;
};

/**
 * Create a new object with specific keys
 *
 * @param {string[]} keys Keys
 * @param {object} obj Object
 * @param {object} excludeEmpty Object
 * @return {*|boolean}
 */
export const newObjectByKeys = (keys, obj, excludeEmpty = false) => {
    try {
        return keys.reduce((acc, key) => {
            if (key in obj) {
                if (excludeEmpty && !obj[key]) {
                    return acc;
                }
                acc[key] = obj[key];
            }
            return acc;
        }, {});
    } catch (e) {
        handleError(e);
    }
    return {};
}

/**
 * Convert WordPress locale format (en_GB) to BCP 47 format (en-GB)
 *
 * @param {string} locale WordPress locale string
 * @return {string} BCP 47 locale string
 */
const convertLocaleToBCP47 = (locale) => {
    if (!locale || typeof locale !== 'string') {
        return 'en-US';
    }

    // Trim whitespace and replace ALL underscores with hyphens for BCP 47 format
    // WordPress uses en_GB, JavaScript Intl APIs need en-GB
    // Use global replace to handle all underscores (e.g., en_GB -> en-GB)
    const trimmed = locale.trim();

    // If after trimming we have an empty string, return default
    if (!trimmed) {
        return 'en-US';
    }

    return trimmed.replace(/_/g, '-');
};

/**
 * Get locale
 *
 * @param {object} data Locale data
 * @return {string}
 */
export const getLocale = (data) => {
    try {
        if (!data) {
            data = getLocaleData();
        }

        const wpLocale = data?.['']?.lang || 'en-US';
        return convertLocaleToBCP47(wpLocale);
    } catch (e) {
        // If there's any error getting locale data, fallback to en-US
        handleError(e);
        return 'en-US';
    }
};

const numberFormatterCache = new Map();

/**
 * Number format
 *
 * @param {number} number Number
 * @param {number|false} fractions fractions count
 * @param {boolean} round Whether round the number or not
 * @return {string}
 */
export const numberFormat = (number, fractions = 2, round = false) => {
    try {
        // Locale
        const locale = getLocale();
        // Infinite case
        const num = !isFinite(number) ? 0 : number;

        let minFrac = 0;
        let maxFrac = 0;

        if (fractions !== false) {
            const [, decPart = ''] = num.toString().split('.');

            if (round) {
                maxFrac = fractions;
            } else {
                minFrac = fractions;
                maxFrac = Math.max(fractions, decPart.length);
            }
        }

        const cacheKey = `${locale}-${minFrac}-${maxFrac}`;

        if (!numberFormatterCache.has(cacheKey)) {
            try {
                numberFormatterCache.set(
                    cacheKey,
                    new Intl.NumberFormat(locale, {
                        minimumFractionDigits: minFrac,
                        maximumFractionDigits: maxFrac,
                    })
                );
            } catch (e) {
                // If locale is invalid, fallback to en-US
                handleError(e);
                const fallbackCacheKey = `en-US-${minFrac}-${maxFrac}`;
                if (!numberFormatterCache.has(fallbackCacheKey)) {
                    numberFormatterCache.set(
                        fallbackCacheKey,
                        new Intl.NumberFormat('en-US', {
                            minimumFractionDigits: minFrac,
                            maximumFractionDigits: maxFrac,
                        })
                    );
                }
                return numberFormatterCache.get(fallbackCacheKey).format(num);
            }
        }

        return numberFormatterCache.get(cacheKey).format(num);
    } catch (e) {
        handleError(e);
        // Fallback to basic number formatting
        return number.toString();
    }
}

/**
 * Compare 2 variables
 *
 * @param {any} a A
 * @param {any} b B
 * @param {string} o Operator
 * @return {boolean}
 */
export const compare = (a, b, o) => {
    switch (o) {
        case '==':
            return a == b;
        case '===':
            return a === b;
        case '!=':
            return a != b;
        case '!==':
            return a !== b;
        case '<':
            return a < b;
        case '>':
            return a > b;
        case '<=':
            return a <= b;
        case '>=':
            return a >= b;
        default:
            throw new Error("Invalid operator");
    }
}

/**
 * Show widget
 *
 * @param {array[]} locations Widget location
 * @param {object} params Current location params
 * @return {boolean}
 */
export const showWidget = (locations, params = {}) => {
    try {
        const ors = [];
        for (const locationsOrs of locations) {
            // Ors
            const ands = [];
            for (const locationAnd of locationsOrs) {
                // Ands
                let isConditionMet = false;
                if (locationAnd.param in params) {
                    isConditionMet = compare(params[locationAnd.param], locationAnd.value, locationAnd.operator);
                }
                ands.push(isConditionMet);
            }
            // Add ands group result in ors
            ors.push(ands.every(Boolean));
        }
        return ors.some(Boolean);
    } catch (e) {
        handleError(e);
        return false;
    }
}

/**
 * Prepare user message to send
 *
 * @param {string} text Text message
 * @param {object[]|null} images Images
 * @param {object[]|null} files Files
 * @param {object|null} voice Voice message
 * @return {object|boolean}
 */
export const prepareUserMessage = ({text = '', images = null, files = null, voice = null}) => {
    try {
        const content = [];
        // Check and add text
        if (text?.trim().length > 0) {
            content.push({
                type: 'text',
                text: {
                    value: text.trim(),
                },
            });
        }
        // Add images
        if (images?.length) {
            for (const item of images) {
                if (item.base64) {
                    content.push({
                        type: 'attachment',
                        attachment: {
                            mime_type: item.type,
                            value: item.base64,
                        }
                    });
                }
            }
        }
        // Add files
        if (files?.length) {
            for (const item of files) {
                if (item.base64) {
                    content.push({
                        type: 'attachment',
                        attachment: {
                            mime_type: item.type,
                            value: item.base64,
                        }
                    });
                }
            }
        }
        // Check and add audio
        if (voice) {
            content.push({
                type: 'audio',
                audio: {
                    mime_type: voice.type,
                    value: voice.base64
                },
            });
        }
        return {
            role: 'user',
            content,
        };
    } catch (e) {
        handleError(e);
        return false;
    }
}

export const localCacheKey = (key) => `lbaic.cache.${key}`;

export const removeLocalCache = (key) => {
    localStorage.removeItem(localCacheKey(key));
}

export const setLocalCache = (key, data, ttl = 60 * 1000) => {
    // Cache data
    const cacheData = {
        data,
        expiry: Date.now() + ttl
    };
    localStorage.setItem(localCacheKey(key), JSON.stringify(cacheData));
    // Delete the key automatically
    setTimeout(() => removeLocalCache(key), ttl);
}

export const getLocalCache = (key) => {
    // Get cache data
    const item = localStorage.getItem(localCacheKey(key));
    if (!item) {
        return null;
    }
    const cacheData = JSON.parse(item);
    // Check expiry
    if (Date.now() > cacheData.expiry) {
        removeLocalCache(key);
        return null;
    }

    return cacheData.data;
}

/**
 * Default config cached data
 *
 * @type {null|object}
 */
let sharedDefaultConfigCachedData = null;
/**
 * Fetching default config promise
 *
 * @type {null|object}
 */
let fetchingDefaultConfigPromise = null;
/**
 * Get shared default config
 *
 * @param {string} aiProviderId AI provider id
 * @return {Promise<*>}
 */
export const getSharedDefaultConfig = async (aiProviderId) => {
    if (sharedDefaultConfigCachedData) {
        return sharedDefaultConfigCachedData;
    }
    if (!fetchingDefaultConfigPromise) {
        fetchingDefaultConfigPromise = GetConfigs(LimbChatbot.rest.url, LimbChatbot.rest.nonce, {
            related_to: aiProviderId,
            default: 1,
            mask: false,
        }).then(res => {
            sharedDefaultConfigCachedData = res.items[0];
            return res.items[0];
        });
    }

    return fetchingDefaultConfigPromise;
}

/**
 * Are variables equal
 *
 * @param {any} a First variable
 * @param {any} b Second variable
 * @return {boolean}
 */
export const areDeepEqual = (a, b) => {
    // Handle strict equality and special cases like NaN
    if (Object.is(a, b)) return true;

    // Different types mean not equal
    if (typeof a !== typeof b) return false;

    // Handle null or undefined cases
    if (a == null || b == null) return false;

    // Handle arrays using your trusted function
    if (Array.isArray(a)) {
        if (!Array.isArray(b)) return false;
        return areArraysSame(a, b);
    }

    // Handle objects
    if (typeof a === 'object') {
        if (Array.isArray(b)) return false; // a is object, b is array

        const keysA = Object.keys(a);
        const keysB = Object.keys(b);

        if (keysA.length !== keysB.length) return false;

        for (let key of keysA) {
            if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
            if (!areDeepEqual(a[key], b[key])) return false;
        }

        return true;
    }

    // For all other primitives (boolean, string, number, symbol, etc.)
    return false;
}

/**
 * Check if arrays are the same
 *
 * @param {any[]} arr1 Array 1
 * @param {any[]} arr2 Array 2
 * @return {boolean}
 */
export const areArraysSame = (arr1, arr2) => {
    try {
        if (arr1.length !== arr2.length) return false;

        const sorted1 = [...arr1.map(item => {
            if (typeof item === 'object') {
                return JSON.stringify(item);
            }
            return item;
        })];
        const sorted2 = [...arr2.map(item => {
            if (typeof item === 'object') {
                return JSON.stringify(item);
            }
            return item;
        })];

        return sorted1.every((val, index) => val === sorted2[index]);
    } catch (e) {
        handleError(e);
        return false;
    }
}

/**
 * If the first array exists in the second one
 *
 * @param {{key: string, value: any}[]} arr1
 * @param {{key: string, value: any}[]} arr2
 * @param {string} key Array key of `key`
 * @param {string} value Array key of `value`
 * @return {*}
 */
export const ifArrayExists = (arr1, arr2, key = 'key', value = 'value') => {
    try {
        if (!arr2?.length && arr1?.length) {
            return false;
        }
        const map1 = new Map(arr2.map(item => [item[key], item[value]]));

        return arr1.every(item => areDeepEqual(map1.get(item[key]), item[value]));
    } catch (e) {
        handleError(e);
        return false;
    }
}

/**
 * Decode html
 *
 * @param {string} html HTML
 * @return {*}
 */
export const decodeHtml = (html) => {
    const txt = document.createElement('textarea');
    txt.innerHTML = html;
    return txt.value;
}

/**
 * Escape regex
 *
 * @param {string} str String
 * @return {*}
 */
export const escapeRegex = (str) => {
    try {
        return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
    } catch (e) {
        handleError(e);
    }
    return str;
}

/**
 * Field focus (atr the end)
 *
 * @param {Element} element Element
 */
export const fieldFocus = (element) => {
    if (!element) {
        return;
    }

    element.focus();

    const range = document.createRange();
    range.selectNodeContents(element);
    // Collapse to end
    range.collapse(false);

    const selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(range);
}

/**
 * Is empty object
 *
 * @param {object} obj Object
 * @return {boolean}
 */
export const isEmptyObject = (obj) => {
    try {
        for (const prop in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, prop)) {
                return false;
            }
        }

        return true;
    } catch (e) {
        handleError(e);
        return true;
    }
}

/**
 * Generate unique id
 *
 * @param {number} length Unique id length (from 1 to 10)
 * @return {string}
 */
export const generateUniqueId = (length = 10) => {
    // Length from 1 to 10
    if (length > 10) {
        length = 10;
    } else if (length < 1) {
        length = 1;
    }
    return Math.random().toString(36).substring(2, length + 2);
}

/**
 * Get unique array
 *
 * @param {array} arr Array
 * @return {*}
 */
export const getUniqueArray = (arr) => {
    try {
        return arr.filter((item, index) => {
            return arr.indexOf(item) === index;
        });
    } catch (e) {
        handleError(e);
    }
}

/**
 * Set object value by path
 *
 * @param {object} obj Object
 * @param {string|array} path Path to the property
 * @param {any} value Value
 * @return {object}
 */
export const setObjValue = (obj, path, value) => {
    if (typeof path === 'string') {
        path = path.split('.');
    }

    return path.reduce((acc, key, index) => {
        if (index === path.length - 1) {
            acc[key] = value;
        } else {
            if (typeof acc[key] !== 'object' || acc[key] === null) {
                acc[key] = {};
            }
        }
        return acc[key];
    }, obj);
}

/**
 * Get object value by path
 *
 * @param {object} obj Object
 * @param {string|array} path Path to the property
 * @param {any} defaultValue Default value
 * @return {string|undefined}
 */
export const getObjValue = (obj, path, defaultValue = undefined) => {
    try {
        if (typeof path === 'string') {
            path = path.split('.');
        }

        return path.reduce((acc, key) => {
            if (acc && Object.prototype.hasOwnProperty.call(acc, key)) {
                return acc[key];
            }
            return undefined;
        }, obj) ?? defaultValue;
    } catch (e) {
        handleError(e);
    }

    return defaultValue;
}

/**
 * Debounce
 *
 * @param {function} fn Function
 * @param {number} delay Delay in milliseconds
 * @return {(function(...[*]): void)|*}
 */
export const debounce = (fn, delay = 400) => {
    let timer;

    return function (...args) {
        if (timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

/**
 * Scroll messages container to the bottom
 *
 * @param {object} messagesContainerRef Messages container ref
 * @param {object} scrolledToTheBottomRef Scrolled to the bottom info ref
 */
export const scrollMCToTheBottom = (messagesContainerRef, scrolledToTheBottomRef) => {
    if (!messagesContainerRef) {
        return;
    }
    // Scroll to the bottom
    const {scrollHeight, clientHeight} = messagesContainerRef;
    if (!scrollHeight && !clientHeight) {
        return;
    }
    const scrollDifference = scrollHeight - clientHeight;
    const startScroll = messagesContainerRef.scrollTop;
    const startTime = performance.now();

    const duration = 180; // Duration in ms

    const animateScroll = (timestamp) => {
        const elapsed = timestamp - startTime;
        const progress = Math.min(elapsed / duration, 1);
        const easeInOutQuad = progress < 0.5
            ? 2 * progress * progress
            : -1 + (4 - 2 * progress) * progress;

        if (messagesContainerRef) {
            messagesContainerRef.scrollTop = startScroll + (scrollDifference - startScroll) * easeInOutQuad;
        }

        if (elapsed < duration) {
            window.requestAnimationFrame(animateScroll);
        }
    };

    if (scrolledToTheBottomRef) {
        if (scrolledToTheBottomRef.current) {
            // With animation
            window.requestAnimationFrame(animateScroll);
        } else {
            // No animation needs it here
            messagesContainerRef.scrollTop = scrollHeight;
        }
        // Set scrolled to the bottom info
        scrolledToTheBottomRef.current = true;
    } else {
        window.requestAnimationFrame(animateScroll);
    }
}

/**
 * Format number with decimals only if necessary
 *
 * @param {number} num Number to format
 * @param {number} decimals Maximum number of decimal places (default: 2)
 * @return {string}
 */
export const formatNumber = (num, decimals = 2) => {
    try {
        // Check if the number has meaningful decimal places
        const hasDecimals = num % 1 !== 0;

        if (hasDecimals) {
            // Format with specified decimal places and remove trailing zeros
            return parseFloat(num.toFixed(decimals)).toString();
        }

        // Return as integer if no decimals
        return Math.round(num).toString();
    } catch (e) {
        handleError(e);
        return num.toString();
    }
}

/**
 * Are variables equal
 *
 * @param {any} a First variable
 * @param {any} b Second variable
 * @param {boolean} strict Strict equality
 * @return {boolean}
 */
export const areEqual = (a, b, strict = false) => {
    if (strict) {
        return a === b;
    }
    if (!isNaN(a) || !isNaN(b)) {
        // Numeric
        return +a === +b;
    } else {
        return a === b;
    }
}

/**
 * Strip markdown formatting from text
 *
 * @param {string} markdownText Markdown formatted text
 * @return {string} Plain text without markdown
 */
export const stripMarkdown = (markdownText) => {
    const html = marked(markdownText);
    const text = html.replace(/<[^>]*>/g, '');
    const div = document.createElement('div');
    div.innerHTML = text;
    return div.textContent || div.innerText || '';
}

/**
 * Prepare a chatbot message
 *
 * @param {string} markdownText Markdown formatted text
 * @param {object} config Configs
 * @return {*}
 */
export const prepareChatbotMessage = (markdownText, config = {}) => {
    try {
        const renderer = new marked.Renderer();

        renderer.link = ({href, title, text}) => {
            let link = `<a href="${href}" target="_blank" rel="noopener noreferrer"`;
            if (title) {
                link += ` title="${title}"`;
            }
            link += `>${text}</a>`;

            return link;
        };

        renderer.table = function(token) {
            // Render header
            let headerHtml = '<thead><tr>';
            token.header.forEach(cell => {
                const align = cell.align ? ` align="${cell.align}"` : '';
                const content = this.parser.parseInline(cell.tokens);
                headerHtml += `<th${align}>${content}</th>`;
            });
            headerHtml += '</tr></thead>';

            // Render body rows
            let bodyHtml = '<tbody>';
            token.rows.forEach(row => {
                bodyHtml += '<tr>';
                row.forEach(cell => {
                    const align = cell.align ? ` align="${cell.align}"` : '';
                    const content = this.parser.parseInline(cell.tokens);
                    bodyHtml += `<td${align}>${content}</td>`;
                });
                bodyHtml += '</tr>';
            });
            bodyHtml += '</tbody>';

            return `<div class='lbaic-message-table-wrapper'><table>${headerHtml}${bodyHtml}</table></div>`;
        };

        marked.setOptions({
            renderer: renderer
        });

        return marked.parse(markdownText);
    } catch (e) {
        return markdownText;
    }
}

/**
 * Get cookie value by name
 *
 * @param {string} name Cookie name
 * @return {string|null} Cookie value or null if not found
 */
export const getCookie = (name) => {
    try {
        if (typeof document === 'undefined') {
            return null;
        }
        const nameEQ = name + '=';
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            let cookie = cookies[i];
            while (cookie.charAt(0) === ' ') {
                cookie = cookie.substring(1, cookie.length);
            }
            if (cookie.indexOf(nameEQ) === 0) {
                return decodeURIComponent(cookie.substring(nameEQ.length, cookie.length));
            }
        }
        return null;
    } catch (e) {
        handleError(e);
        return null;
    }
}

/**
 * Check is mobile
 *
 * @return {boolean|boolean}
 */
export const checkIsMobile = () => {
    try {
        const ua = navigator.userAgent;

        // Check user agent
        const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
        const isMobileUA = mobileRegex.test(ua);

        // Check screen width
        const isMobileWidth = window.innerWidth <= 768;

        // Check touch capability
        const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;

        // Combined check
        return isMobileUA || (isMobileWidth && hasTouch);
    } catch (e) {
        return false;
    }
}

/**
 * Chunk array
 *
 * @param {array} arr Array
 * @param {number} size Chunk size
 * @param {boolean} fill Fill chunk
 * @return {*[]}
 */
export const chunkArray = (arr, size, fill = false) => {
    try {
        const items = fill && arr.length % size !== 0
            ? [...arr, ...Array(size - (arr.length % size)).fill(null)]
            : [...arr];

        const chunks = [];
        for (let i = 0; i < items.length; i += size) {
            chunks.push(items.slice(i, i + size));
        }
        return chunks;
    } catch (e) {
        handleError(e);
        return [];
    }
}

/**
 * Get timezone offset
 *
 * @return {`${string}${number}`} Like +4
 */
export const getTimezoneOffset = () => {
    const offset = new Date().getTimezoneOffset();
    const hours = Math.abs(Math.floor(offset / 60));
    // const minutes = Math.abs(offset % 60);
    const sign = offset <= 0 ? '+' : '-';

    return `${sign}${hours}`;
}