import { DeleteFailedError, NotFoundError, PutFailedError } from 'interface-store';
import filter from 'it-filter';
import merge from 'it-merge';
import sort from 'it-sort';
import take from 'it-take';
import { BaseDatastore } from './base.js';
/**
 * A datastore that can combine multiple stores inside various
 * key prefixes
 */
export class MountDatastore extends BaseDatastore {
    mounts;
    constructor(mounts) {
        super();
        this.mounts = mounts.slice();
    }
    /**
     * Lookup the matching datastore for the given key
     */
    _lookup(key) {
        for (const mount of this.mounts) {
            if (mount.prefix.toString() === key.toString() || mount.prefix.isAncestorOf(key)) {
                return {
                    datastore: mount.datastore,
                    mountpoint: mount.prefix
                };
            }
        }
    }
    async put(key, value, options) {
        const match = this._lookup(key);
        if (match == null) {
            throw new PutFailedError('No datastore mounted for this key');
        }
        await match.datastore.put(key, value, options);
        return key;
    }
    /**
     * @param {Key} key
     * @param {Options} [options]
     */
    async get(key, options = {}) {
        const match = this._lookup(key);
        if (match == null) {
            throw new NotFoundError('No datastore mounted for this key');
        }
        return match.datastore.get(key, options);
    }
    async has(key, options) {
        const match = this._lookup(key);
        if (match == null) {
            return Promise.resolve(false);
        }
        return match.datastore.has(key, options);
    }
    async delete(key, options) {
        const match = this._lookup(key);
        if (match == null) {
            throw new DeleteFailedError('No datastore mounted for this key');
        }
        await match.datastore.delete(key, options);
    }
    batch() {
        const batchMounts = {};
        const lookup = (key) => {
            const match = this._lookup(key);
            if (match == null) {
                throw new Error('No datastore mounted for this key');
            }
            const m = match.mountpoint.toString();
            if (batchMounts[m] == null) {
                batchMounts[m] = match.datastore.batch();
            }
            return {
                batch: batchMounts[m]
            };
        };
        return {
            put: (key, value) => {
                const match = lookup(key);
                match.batch.put(key, value);
            },
            delete: (key) => {
                const match = lookup(key);
                match.batch.delete(key);
            },
            commit: async (options) => {
                await Promise.all(Object.keys(batchMounts).map(async (p) => { await batchMounts[p].commit(options); }));
            }
        };
    }
    query(q, options) {
        const qs = this.mounts.map(m => {
            return m.datastore.query({
                prefix: q.prefix,
                filters: q.filters
            }, options);
        });
        let it = merge(...qs);
        if (q.filters != null) {
            q.filters.forEach(f => { it = filter(it, f); });
        }
        if (q.orders != null) {
            q.orders.forEach(o => { it = sort(it, o); });
        }
        if (q.offset != null) {
            let i = 0;
            const offset = q.offset;
            it = filter(it, () => i++ >= offset);
        }
        if (q.limit != null) {
            it = take(it, q.limit);
        }
        return it;
    }
    queryKeys(q, options) {
        const qs = this.mounts.map(m => {
            return m.datastore.queryKeys({
                prefix: q.prefix,
                filters: q.filters
            }, options);
        });
        /** @type AsyncIterable<Key> */
        let it = merge(...qs);
        if (q.filters != null) {
            q.filters.forEach(f => { it = filter(it, f); });
        }
        if (q.orders != null) {
            q.orders.forEach(o => { it = sort(it, o); });
        }
        if (q.offset != null) {
            let i = 0;
            const offset = q.offset;
            it = filter(it, () => i++ >= offset);
        }
        if (q.limit != null) {
            it = take(it, q.limit);
        }
        return it;
    }
}
//# sourceMappingURL=mount.js.map