export const dataSourceWorker = () => {
    /* Start Worker */
    // only add the listener if we're in the worker context
    if (typeof window === 'undefined') {
        addEventListener('message', (event) => {
            if (!event)
                return;
            const [options, data] = event.data;
            // Funky Safari issue https://stackoverflow.com/questions/46913856/value-is-not-a-sequence-safari-exception
            self.postMessage(processData(data, options));
        }, false);
    }
    // Doubles as an exposed entry point to be used outside a web worker
    const processData = (data, options) => {
        let dataKey = 'data';
        if (options.filter) {
            dataKey = 'filtered';
            data.filtered = data.data.filter(d => filter(d, options.filter));
            data.total = data.filtered.length;
        }
        if (options.sort || options.group) {
            data[dataKey] = sortBy(data[dataKey], options.group ? options.group.concat(options.sort ? options.sort : []) : options.sort);
            if (dataKey === 'data') {
                data.total = data.data.length;
            }
        }
        if (options.group) {
            // TODO: someday we might need multi-level grouping
            data.filtered = group(data[dataKey], options.group[0], options.size);
            data.total = data.filtered.length;
        }
        return data;
    };
    const filter = (data, filters) => {
        let included = true;
        for (let i = 0; i < filters.length; i++) {
            // Global filter first which is just a basic string contains across the object
            if (!filters[i].key) {
                included = filterGlobal(data, filters[i].query.trim().toLowerCase());
            }
            else if (typeof filters[i].query === 'string') {
                included = filterByString(data[filters[i].key], filters[i].query.trim().toLowerCase(), filters[i].type);
            }
            else if (isNumberQuery(filters[i].query)) {
                included = filterByNumber(data[filters[i].key], filters[i].query, filters[i].type);
            }
            else {
                included = filterByDate(data[filters[i].key], filters[i].query, filters[i].type);
            }
            if (!included) {
                break;
            }
        }
        return included;
    };
    const filterGlobal = (data, query) => {
        let included = false;
        for (const prop in data) {
            if (data.hasOwnProperty(prop)) {
                included = filterByString(`${data[prop]}`, query);
                if (included) {
                    break;
                }
            }
        }
        return included;
    };
    const filterByString = (data, query, filterType) => {
        if (!data)
            return false;
        const value = data.toLowerCase();
        switch (filterType) {
            case 'equals':
                return value === query;
            case 'ends-with':
                return value.indexOf(query) === value.length - query.length;
            case 'starts-with':
                return value.indexOf(query) === 0;
            default:
                return value.includes(query);
        }
    };
    const filterByDate = (data, query, filterType) => {
        const value = coerceToDateValue(data);
        if (!value)
            return false;
        switch (filterType) {
            case 'contains':
                return value >= query[0].valueOf() && value <= query[1].valueOf();
            case 'ends-with':
                return value <= query.valueOf();
            case 'starts-with':
                return value >= query.valueOf();
            default:
                return value === query.valueOf();
        }
    };
    const filterByNumber = (value, query, filterType) => {
        if (!value)
            return false;
        switch (filterType) {
            case 'contains':
                return value >= query[0] && value <= query[1];
            case 'ends-with':
                return value <= query;
            case 'starts-with':
                return value >= query;
            default:
                return value === query;
        }
    };
    const coerceToDateValue = (value) => {
        // If there's no value or it is already a date, just kick it back
        if (!value)
            return undefined;
        if (typeof value.getMonth === 'function')
            return value.valueOf();
        const maybeDate = new Date(value);
        return maybeDate.valueOf();
    };
    const isNumberQuery = (query) => {
        const value = Array.isArray(query) ? query[0] : query;
        return typeof value === 'number';
    };
    const group = (data, groupOpts, size) => {
        const groups = [];
        const groupsByKey = {};
        let itemGroup;
        let isExpanded;
        data.forEach((d) => {
            if (!groupsByKey[d[groupOpts.key]]) {
                groupsByKey[d[groupOpts.key]] = [0];
                itemGroup = {};
                itemGroup[groupOpts.groupKey] = groupsByKey[d[groupOpts.key]];
                itemGroup[groupOpts.key] = d[groupOpts.key];
                isExpanded = groupExpanded(groupOpts, d);
                groups.push(itemGroup);
            }
            if (isExpanded && groups.length > 0 && groups.length % size === 0) {
                groups.push(itemGroup);
            }
            if (isExpanded) {
                groups.push(d);
                groupsByKey[d[groupOpts.key]][0] = groupsByKey[d[groupOpts.key]][0] + 1;
            }
        });
        return groups;
    };
    const groupExpanded = (groupOpts, data) => {
        if (!groupOpts.groupKeys)
            return groupOpts.autoExpand;
        return groupOpts.autoExpand ? !groupOpts.groupKeys.includes(data[groupOpts.key]) : groupOpts.groupKeys.includes(data[groupOpts.key]);
    };
    const sortBy = (data, sorts) => {
        return data.sort((a, b) => compare(a, b, sorts));
    };
    const compare = (a, b, sorts) => {
        let value = 0;
        for (let i = 0; i < sorts.length; i++) {
            const aVal = a[sorts[i].key];
            const bVal = b[sorts[i].key];
            value = typeof aVal === 'string' ? aVal.localeCompare(bVal) : aVal - bVal;
            if (value !== 0) {
                if (sorts[i].order === 'desc') {
                    value = value * -1;
                }
                break;
            }
        }
        return value;
    };
    /* End Worker */
    // Return the entry function to be used outside the worker
    return { 'publicApi': processData };
};
