import { Patch } from "@api-client/json";
import { filesSortFunction } from "../elements/store/FilesLib.js";
import { Events } from "../events/Events.js";
import { StoreBroadcast } from "../http-client/store/StoreBroadcast.js";
/**
 * Contains a logic to read files from the store.
 *
 * It uses app events to query for the data.
 */
export class FileSystem extends EventTarget {
    constructor() {
        super();
        this.source = 'own';
        /**
         * The list of files to render.
         * Do not overwrite this property or when you must then update the DataTable's `items` as well.
         */
        this.files = [];
        /**
         * The timeout for the query debouncer.
         * When any property change this is the time the element will wait
         * until the actual query is made.
         */
        this.debounceTimeout = 100;
        this.filesChannel = new BroadcastChannel(StoreBroadcast.files);
        this.eventsTarget = document.body;
        this.fileMetaMessageHandler = this.fileMetaMessageHandler.bind(this);
    }
    observe() {
        this.filesChannel.addEventListener('message', this.fileMetaMessageHandler);
    }
    unobserve() {
        this.filesChannel.removeEventListener('message', this.fileMetaMessageHandler);
    }
    fileMetaMessageHandler(e) {
        const event = e.data;
        if (event.parent !== this.parent) {
            return;
        }
        if (event.alt !== 'meta') {
            return;
        }
        switch (event.operation) {
            case 'created':
                this.handleFileCreated(event.data);
                break;
            case 'deleted':
                this.handleFileDeleted(event);
                break;
            case 'patch':
                this.handleFilePatch(event);
                break;
            case 'access-granted':
                this.handleFileAccessGranted(event);
                break;
            case 'access-removed':
                this.handleFileAccessRemoved(event);
                break;
            default:
        }
    }
    handleFileCreated(file) {
        this.files.push(file);
        // We don't sort here so files that appear at the end which makes sense because the list is scrolling.
        // this.files.sort(filesSortFunction);
        this.notifyUpdate();
    }
    handleFileDeleted(event) {
        this.removeFileFromList(event.key);
    }
    async handleFileAccessGranted(event) {
        if (this.source !== 'shared') {
            return;
        }
        const { key } = event;
        try {
            const file = await Events.Store.File.read(key, false, this.eventsTarget);
            const index = this.files.findIndex(i => i.key === key);
            if (index >= 0) {
                this.files.splice(index, 1);
            }
            this.handleFileCreated(file);
        }
        catch (e) {
            // eslint-disable-next-line no-console
            console.warn(e);
        }
    }
    handleFileAccessRemoved(event) {
        if (this.source !== 'shared') {
            return;
        }
        const { key } = event;
        this.removeFileFromList(key);
    }
    removeFileFromList(key) {
        const index = this.files.findIndex(i => i.key === key);
        if (index === -1) {
            return;
        }
        this.files.splice(index, 1);
        this.notifyUpdate();
        this.dispatchEvent(new CustomEvent('delete', { detail: key }));
    }
    handleFilePatch(event) {
        const { data, key } = event;
        const index = this.files.findIndex(i => i.key === key);
        if (index === -1) {
            return;
        }
        const file = this.files[index];
        const result = Patch.apply(file, data.patch);
        this.files[index] = result.doc;
        this.notifyUpdate();
    }
    debounceQuery() {
        if (this.debouncerValue) {
            clearTimeout(this.debouncerValue);
        }
        this.debouncerValue = window.setTimeout(() => {
            this.queryPage();
            this.queryBreadcrumbs();
        }, this.debounceTimeout);
    }
    resetList() {
        this.files = [];
        this.cursor = undefined;
        this.breadcrumbs = undefined;
        this.queryEnded = false;
    }
    async queryPage() {
        if (this.reading || this.queryEnded) {
            return;
        }
        this.reading = true;
        this.notifyUpdate();
        try {
            const result = await this.listFiles();
            if (!result) {
                throw new Error(`Files event not handled.`);
            }
            const { items, cursor } = result;
            if (!items.length) {
                this.queryEnded = true;
            }
            else {
                if (!this.files.length) {
                    // we only sort the first page as later the user scrolls and sorting would change the order.
                    items.sort(filesSortFunction);
                }
                this.files = this.files.concat(items);
            }
            if (cursor) {
                this.cursor = cursor;
            }
        }
        catch (e) {
            const err = e;
            this.notifyError(`Unable to load files list. ${err.message}`);
            // eslint-disable-next-line no-console
            console.error(e);
        }
        finally {
            this.reading = false;
        }
        this.dispatchEvent(new Event('querycomplete'));
        this.notifyUpdate();
    }
    async queryBreadcrumbs() {
        if (this.parent) {
            const br = await this.listBreadcrumbs(this.parent);
            this.breadcrumbs = br.items;
        }
        else {
            this.breadcrumbs = undefined;
        }
    }
    getPageQueryOptions() {
        const { cursor: filesCursor, parent, limit } = this;
        const opts = {
            space: '',
            descending: true,
        };
        if (filesCursor) {
            opts.cursor = filesCursor;
        }
        if (parent) {
            opts.parent = parent;
        }
        if (typeof limit === 'number' && limit) {
            opts.limit = limit;
        }
        return opts;
    }
    async listFiles() {
        const { kinds } = this;
        const opts = this.getPageQueryOptions();
        if (this.source === 'shared') {
            return Events.Store.File.listShared(kinds, opts, this.eventsTarget);
        }
        return Events.Store.File.list(kinds, opts, this.eventsTarget);
    }
    async listBreadcrumbs(folder) {
        return Events.Store.File.breadcrumbs(folder, this.eventsTarget);
    }
    notifyError(error) {
        this.dispatchEvent(new CustomEvent('error', {
            detail: error,
        }));
    }
    notifyUpdate() {
        this.dispatchEvent(new Event('change'));
    }
    /**
     * Sets a parent and makes the query.
     * @param key The key of the parent to query. Empty for root folder.
     */
    selectFolder(key) {
        this.parent = key;
        this.resetList();
        this.debounceQuery();
        // this.queryPage();
        this.queryBreadcrumbs();
    }
    /**
     * Moves to a parent folder, if any.
     */
    async parentUp() {
        const { breadcrumbs = [] } = this;
        const [, parent] = breadcrumbs;
        if (!parent) {
            if (!this.parent) {
                return;
            }
            this.parent = undefined;
        }
        else {
            if (this.parent === parent.key) {
                return;
            }
            this.parent = parent.key;
        }
        this.resetList();
        await Promise.all([
            this.queryPage(),
            this.queryBreadcrumbs(),
        ]);
        this.notifyUpdate();
    }
    /**
     * @param target The HTML element that is a scrollable list if files.
     * @returns True when the scroll position indicates that the user scrolled near the end of the list.
     */
    isListEnd(target) {
        const { scrollTop, offsetHeight, scrollHeight } = target;
        // 20 is the offset which qualifies as the end. An arbitrary number.
        return scrollTop + offsetHeight >= scrollHeight - 20;
    }
}
//# sourceMappingURL=FileSystem.js.map