import { dataSourceWorker } from '../../workers/data-source';
import { loadInlineWorker } from '../../workers/worker.utils';
import { BaseDataSource } from './BaseDataSource';
export class LocalDataSource extends BaseDataSource {
    constructor(source) {
        super(Array.isArray(source) ? source : undefined);
        this.remoteOperations = {};
        if (!Array.isArray(source)) {
            Object.assign(this, source);
            this.load = source.load;
        }
    }
    /**
     * Entry point to getting data
     */
    async fetchData(options) {
        return this.paginate(options, this.sort(options, this.load(options)));
    }
    /**
     * Loads data locally by default. Can be overridden by user configuration to load data from a remote source
     */
    async load(options) {
        if (!options) {
            return;
        }
        return this.localLoad(options);
    }
    /**
     * Paginate the complete data set
     */
    async paginate(options, data) {
        if (!data && (this.remoteOperations.paging)) {
            return this.fetchData(options);
        }
        let loaded = await data || await this.localLoad();
        if (loaded.data) {
            this.data = loaded.data;
        }
        if (loaded.filtered) {
            this.filtered = loaded.filtered;
            loaded = { data: loaded.filtered, total: loaded.total };
        }
        if (!options.range || this.remoteOperations.paging) {
            return loaded;
        }
        return {
            data: loaded.data.slice(options.range[0], options.range[0] + options.size),
            total: loaded.total
        };
    }
    /**
     * Sorting and filtering of a master set
     */
    async sort(options, data) {
        if (!data && this.remoteOperations.sorting) {
            return this.fetchData(options);
        }
        if (data) {
            return (!this.remoteOperations.sorting && (options.sort || options.filter || options.group)) ? this.asyncSort(options, data) : data;
        }
        return this.paginate(options, this.asyncSort(options, this.localLoad(options)));
    }
    async asyncSort(options, data) {
        const loaded = await data;
        return new Promise(resolve => {
            this.worker = this.connect(resolve);
            // TODO diff old vs new options so we don't do things we don't have to
            return this.delayStart([options, loaded]);
        });
    }
    // Load may get overwritten by custom data sources, so keep this here
    async localLoad(options) {
        return {
            data: options ? this.data : undefined,
            filtered: !options && this.filtered ? this.filtered : this.data,
            total: !options && this.filtered ? this.filtered.length : this.data.length
        };
    }
    async delayStart(message) {
        return new Promise(resolve => {
            window.setTimeout(() => {
                this.worker.postMessage(message);
                resolve();
            });
        });
    }
    connect(resolve) {
        const worker = loadInlineWorker(dataSourceWorker);
        worker.onmessage = (event) => {
            resolve(event.data);
            this.disconnect();
        };
        return worker;
    }
    disconnect() {
        if (this.worker) {
            this.worker.terminate();
            this.worker = undefined;
        }
    }
}
