var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to set private field on non-instance");
    }
    privateMap.set(receiver, value);
    return value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return privateMap.get(receiver);
};
var _data, _observer, _queue, _serverCancel, _serverDelete, _serverRename, _serverUpload;
export class FileManager {
    constructor(handlers) {
        _data.set(this, new Map());
        _observer.set(this, new Map());
        _queue.set(this, new Map());
        _serverCancel.set(this, void 0);
        _serverDelete.set(this, void 0);
        _serverRename.set(this, void 0);
        _serverUpload.set(this, void 0);
        this.cancel = async (id) => {
            const queue = __classPrivateFieldGet(this, _queue).get(id);
            if (queue) {
                __classPrivateFieldGet(this, _queue).delete(id);
                this.executeObservers([id]);
            }
            return __classPrivateFieldGet(this, _serverCancel).call(this, id);
        };
        this.delete = async (id) => {
            const queue = __classPrivateFieldGet(this, _queue).get(id);
            const data = __classPrivateFieldGet(this, _data).get(id);
            if (queue)
                __classPrivateFieldGet(this, _queue).delete(id);
            if (data)
                __classPrivateFieldGet(this, _data).delete(id);
            if (queue || data)
                this.executeObservers([id]);
            return __classPrivateFieldGet(this, _serverDelete).call(this, id);
        };
        this.rename = async (id, name) => {
            const queue = __classPrivateFieldGet(this, _queue).get(id);
            const data = __classPrivateFieldGet(this, _data).get(id);
            if (queue)
                __classPrivateFieldGet(this, _queue).set(id, Object.assign(Object.assign({}, queue), { file: Object.assign(Object.assign({}, queue.file), { name }) }));
            if (data)
                __classPrivateFieldGet(this, _data).set(id, Object.assign(Object.assign({}, data), { name }));
            if (queue || data)
                this.executeObservers([id]);
            return __classPrivateFieldGet(this, _serverRename).call(this, id, name);
        };
        this.upload = async (file) => {
            // Create file ref for queue
            const { id, name, type } = file;
            const hash = await file.hash();
            const url = URL.createObjectURL(file.blob);
            const queued = { hash, id, name, path: '', type, url };
            __classPrivateFieldGet(this, _queue).set(file.id, { file: queued, progress: 0 });
            this.executeObservers([id]);
            // Perform upload task
            const partial = await __classPrivateFieldGet(this, _serverUpload).call(this, file);
            // Remove from queue
            const ref = Object.assign(Object.assign({}, queued), partial);
            if (url !== ref.url)
                URL.revokeObjectURL(queued.url);
            __classPrivateFieldGet(this, _queue).set(file.id, { file: ref, progress: 1 });
            this.executeObservers([id]);
            return ref;
        };
        __classPrivateFieldSet(this, _serverCancel, handlers.cancel);
        __classPrivateFieldSet(this, _serverDelete, handlers.delete);
        __classPrivateFieldSet(this, _serverRename, handlers.rename);
        __classPrivateFieldSet(this, _serverUpload, handlers.upload);
    }
    static inMemory(options = {}) {
        return createInMemoryFileManager(Object.assign({ delay: {} }, options));
    }
    get(id) {
        return __classPrivateFieldGet(this, _data).get(id);
    }
    getIds() {
        // const files = [...this.#data.keys(), ...this.#queue.keys()];
        const files = [];
        // Using simple loops for compatibility
        __classPrivateFieldGet(this, _data).forEach((_, key) => files.push(key));
        __classPrivateFieldGet(this, _queue).forEach((_, key) => files.push(key));
        return Array.from(new Set(files));
    }
    setFiles(files) {
        const data = new Map();
        for (const file of files) {
            __classPrivateFieldGet(this, _queue).delete(file.id);
            data.set(file.id, file);
        }
        const ids = this.getIds();
        const changes = ids.filter(id => {
            const prev = __classPrivateFieldGet(this, _data).get(id);
            const next = data.get(id);
            return !this.isEqual(prev, next);
        });
        __classPrivateFieldSet(this, _data, data);
        if (changes.length)
            this.executeObservers(changes);
    }
    setProgress(id, progress) {
        const queue = __classPrivateFieldGet(this, _queue).get(id);
        if (!queue)
            return;
        __classPrivateFieldGet(this, _queue).set(id, Object.assign(Object.assign({}, queue), { progress }));
        this.executeObservers([id]);
    }
    subscribe(observer, filter) {
        __classPrivateFieldGet(this, _observer).set(observer, filter);
        observer(this.getDataObject(filter));
        return () => this.unsubscribe(observer);
    }
    unsubscribe(observer) {
        __classPrivateFieldGet(this, _observer).delete(observer);
    }
    executeObservers(ids) {
        for (const [observer, filter] of __classPrivateFieldGet(this, _observer).entries()) {
            if (!filter || ids.some(id => filter.indexOf(id) >= 0))
                observer(this.getDataObject(filter));
        }
    }
    getDataObject(filter) {
        const files = this.getIds();
        return files.reduce((acc, id) => {
            var _a;
            if (filter && filter.indexOf(id) < 0)
                return acc;
            const data = __classPrivateFieldGet(this, _data).get(id);
            const queue = __classPrivateFieldGet(this, _queue).get(id);
            acc[id] = { file: data || (queue === null || queue === void 0 ? void 0 : queue.file), progress: (_a = queue === null || queue === void 0 ? void 0 : queue.progress) !== null && _a !== void 0 ? _a : 1 };
            return acc;
        }, {});
    }
    isEqual(ref1, ref2) {
        // Check existence
        if (ref1 == undefined || ref2 == undefined)
            return ref1 === ref2;
        // Check change in progress
        if (typeof ref1 === 'number' || typeof ref2 === 'number')
            return ref1 === ref2;
        // Check change in file structure
        for (const prop in ref1) {
            if (ref1[prop] !== ref2[prop])
                return false;
        }
        return true;
    }
}
_data = new WeakMap(), _observer = new WeakMap(), _queue = new WeakMap(), _serverCancel = new WeakMap(), _serverDelete = new WeakMap(), _serverRename = new WeakMap(), _serverUpload = new WeakMap();
const createInMemoryFileManager = (options) => {
    const tasks = new Map();
    const files = new Map();
    const cancel = async (id) => {
        const task = tasks.get(id);
        if (!task)
            return;
        window.clearTimeout(task);
        tasks.delete(id);
    };
    const doDelete = async (id) => {
        files.delete(id);
        update();
    };
    const rename = async (id, name) => {
        const file = files.get(id);
        if (!file)
            return;
        files.set(file.id, Object.assign(Object.assign({}, file), { name }));
        update();
    };
    const upload = file => {
        const { min = 2000, max = 5000 } = options.delay;
        return new Promise(res => {
            const delay = Math.random() * (max - min + 1) + min;
            const timer = window.setTimeout(async () => {
                const ref = {
                    hash: await file.hash(),
                    id: file.id,
                    name: file.name,
                    path: '<in-memory>',
                    type: file.type,
                    url: URL.createObjectURL(file.blob)
                };
                tasks.delete(file.id);
                files.set(file.id, ref);
                res(ref);
                update();
            }, delay);
            tasks.set(file.id, timer);
        });
    };
    const update = () => {
        manager.setFiles(Array.from(files.values()));
    };
    const manager = new FileManager({ cancel, delete: doDelete, rename, upload });
    return manager;
};
