/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-dupe-class-members */
import { ProjectKind, FolderKind, DataFileKind, Folder, HttpProject, Project, DataNamespace, DataFile, SpaceKind, Space, BackendCommandType, BackendCommandKind, RouteBuilder, } from '@api-client/core/build/browser.js';
import { PlatformBindings } from './PlatformBindings.js';
import { Events } from '../../events/Events.js';
import { EventTypes } from '../../events/EventTypes.js';
import { HttpStore } from '../../store/HttpStore.js';
import { StoreBroadcast } from '../../http-client/store/StoreBroadcast.js';
/**
 * The base class for API store bindings.
 */
export class StoreBindings extends PlatformBindings {
    constructor() {
        super(...arguments);
        /**
         * This is used by the client side (main thread or the renderer process)
         * to define the current space the app is using.
         * This space is used in some APIs instead of adding the space to the events system.
        */
        this.space = '';
        this.filesChannel = new BroadcastChannel(StoreBroadcast.files);
        this.mediaChannel = new BroadcastChannel(StoreBroadcast.media);
        this.spacesChannel = new BroadcastChannel(StoreBroadcast.spaces);
        this.trashChannel = new BroadcastChannel(StoreBroadcast.trash);
        this.historyChannel = new BroadcastChannel(StoreBroadcast.history);
        this.projectExeChannel = new BroadcastChannel(StoreBroadcast.projectRuns);
        this.initialized = false;
        this.auth = {
            isAuthenticated: async (env) => {
                let { store } = this;
                if (env) {
                    store = new HttpStore(env);
                }
                if (!store) {
                    throw new Error(`Environment is not set.`);
                }
                return store.isAuthenticated(env);
            },
            authenticate: async (options = {}) => {
                const { env, force, updateEnvironment } = options;
                let { store } = this;
                if (env) {
                    store = new HttpStore(env);
                }
                if (!store) {
                    throw new Error(`Environment is not set.`);
                }
                const result = await store.getStoreSessionToken(undefined, force);
                if (updateEnvironment && result.new) {
                    await Events.Config.Environment.update(env || this.globalEnvironment);
                }
                return result;
            },
            /**
             * @returns The current store token or undefined if no store is set.
             */
            getToken: async () => {
                const { store } = this;
                return store && store.sdk && store.sdk.token || undefined;
            },
        };
        this.global = {
            /**
             * Sets the store environment to be used globally by the application.
             * This initializes the store with the configuration.
             *
             * Note, this won't initialize the session with the store.
             *
             * @param env The environment to set. Note, it must have the `location` set.
             */
            set: async (env) => {
                if (!env.location) {
                    throw new Error(`The location is required for store environment configuration.`);
                }
                this.globalEnvironment = env;
                const store = new HttpStore(env);
                this.store = store;
            },
            get: async () => {
                const { globalEnvironment } = this;
                if (!globalEnvironment) {
                    throw new Error(`The current environment is not set.`);
                }
                return globalEnvironment;
            },
        };
        this.spaces = {
            list: (options) => this.getStore().sdk.spaces.list(options),
            add: (nameOrObject) => {
                let space;
                if (typeof nameOrObject === 'string') {
                    space = Space.fromName(nameOrObject).toJSON();
                }
                else {
                    space = nameOrObject;
                }
                return this.getStore().sdk.spaces.add(space);
            },
            read: (key) => this.getStore().sdk.spaces.read(key),
            delete: async (key) => {
                await this.getStore().sdk.spaces.delete(key);
                return {
                    key,
                    kind: SpaceKind,
                };
            },
            addUser: (space, otherUserKey, role, expires) => this.getStore().sdk.spaces.addUser(this.space || space, otherUserKey, role, expires),
            removeUser: (space, otherUserKey) => this.getStore().sdk.spaces.removeUser(this.space || space, otherUserKey),
            patchUsers: (space, value) => this.getStore().sdk.spaces.patchUsers(this.space || space, value),
        };
        this.trash = {
            add: (entry, space) => this.getStore().sdk.trash.add(entry, this.space || space),
            list: (options) => {
                const cp = { ...options };
                if (!cp.space) {
                    cp.space = this.space;
                }
                return this.getStore().sdk.trash.list(cp);
            },
            delete: async (key) => {
                await this.getStore().sdk.trash.delete(key);
                return { key };
            },
            batchDelete: (keys) => this.getStore().sdk.trash.batchDelete(keys),
            restore: (key) => this.getStore().sdk.trash.restore(key),
            batchRestore: (keys) => this.getStore().sdk.trash.batchDelete(keys),
            empty: (space) => this.getStore().sdk.trash.empty(this.space || space),
        };
        this.revisions = {
            list: (key, options) => this.getStore().sdk.revisions.list(key, options),
        };
        this.media = {
            add: (key, contents, mime) => this.getStore().sdk.file.createMedia(contents, this.space, key, { mime }),
            read: (key) => this.getStore().sdk.file.read(this.space, key, true),
            patch: (key, info) => this.getStore().sdk.file.patch(this.space, key, info, true),
        };
        this.files = {
            list: (options, kinds) => this.getStore().sdk.file.list(this.space, kinds, options),
            add: (input, opts) => this.getStore().sdk.file.createMeta(input, this.space, opts),
            read: (key) => this.getStore().sdk.file.read(this.space, key, false),
            readBulk: (keys) => this.getStore().sdk.file.readBulk(this.space, keys),
            delete: (key) => this.getStore().sdk.file.delete(this.space, key),
            patch: (key, info) => this.getStore().sdk.file.patch(this.space, key, info, false),
            breadcrumbs: (key) => this.getStore().sdk.file.breadcrumbs(this.space, key),
        };
        this.shared = {
            list: (options, kinds) => this.getStore().sdk.shared.list(this.space, kinds, options),
        };
        this.users = {
            list: (options) => this.getStore().sdk.user.list(options),
            me: () => this.getStore().sdk.user.me(),
            read: (key) => this.getStore().sdk.user.read(key),
        };
    }
    async initializeStore() {
        // ...
    }
    async initialize() {
        if (this.initialized) {
            return;
        }
        this.initialized = true;
        const target = document.body;
        window.addEventListener(EventTypes.Store.initEnvironment, this.handleInitializeEnvironment.bind(this));
        window.addEventListener(EventTypes.Store.info, this.handleStoreInfo.bind(this));
        window.addEventListener(EventTypes.Store.Global.setEnv, this.setGlobalEnvHandler.bind(this));
        window.addEventListener(EventTypes.Store.Global.getEnv, this.getGlobalEnvHandler.bind(this));
        // Auth
        window.addEventListener(EventTypes.Store.Auth.authenticate, this.storeAuthHandler.bind(this));
        window.addEventListener(EventTypes.Store.Auth.isAuthenticated, this.storeIsAuthenticatedHandler.bind(this));
        window.addEventListener(EventTypes.Store.Auth.getToken, this.getTokenHandler.bind(this));
        // Files
        window.addEventListener(EventTypes.Store.File.list, this.handleFileList.bind(this));
        window.addEventListener(EventTypes.Store.File.listShared, this.handleSharedFileList.bind(this));
        window.addEventListener(EventTypes.Store.File.create, this.handleFileCreate.bind(this));
        window.addEventListener(EventTypes.Store.File.createDefault, this.fileCreateDefaultHandler.bind(this));
        window.addEventListener(EventTypes.Store.File.read, this.handleFileRead.bind(this));
        window.addEventListener(EventTypes.Store.File.patch, this.handleFilePatch.bind(this));
        window.addEventListener(EventTypes.Store.File.delete, this.handleFileDelete.bind(this));
        window.addEventListener(EventTypes.Store.File.patchUsers, this.handleFilePatchUser.bind(this));
        window.addEventListener(EventTypes.Store.File.listUsers, this.handleListFileUsers.bind(this));
        window.addEventListener(EventTypes.Store.File.breadcrumbs, this.handleFileBreadcrumbs.bind(this));
        // User
        window.addEventListener(EventTypes.Store.User.me, this.handlerMe.bind(this));
        window.addEventListener(EventTypes.Store.User.list, this.handleUsersList.bind(this));
        window.addEventListener(EventTypes.Store.User.read, this.handleUserRead.bind(this));
        // history
        target.addEventListener(EventTypes.Store.History.create, this.handleHistoryCreate.bind(this));
        target.addEventListener(EventTypes.Store.History.createBulk, this.handleHistoryCreateBulk.bind(this));
        target.addEventListener(EventTypes.Store.History.delete, this.handleHistoryDelete.bind(this));
        target.addEventListener(EventTypes.Store.History.deleteBulk, this.handleHistoryDeleteBulk.bind(this));
        target.addEventListener(EventTypes.Store.History.list, this.handleHistoryList.bind(this));
        target.addEventListener(EventTypes.Store.History.read, this.handleHistoryRead.bind(this));
        target.addEventListener(EventTypes.Store.History.clearSpace, this.handleHistoryClearSpace.bind(this));
        target.addEventListener(EventTypes.Store.History.clearProject, this.handleHistoryClearProject.bind(this));
        target.addEventListener(EventTypes.Store.History.clearRequest, this.handleHistoryClearRequest.bind(this));
        // project runs
        target.addEventListener(EventTypes.Store.ProjectRuns.add, this.handleAddProjectExecution.bind(this));
        target.addEventListener(EventTypes.Store.ProjectRuns.list, this.handleProjectExecutionList.bind(this));
        target.addEventListener(EventTypes.Store.ProjectRuns.read, this.handleProjectExecutionRead.bind(this));
        target.addEventListener(EventTypes.Store.ProjectRuns.delete, this.handleProjectExecutionDelete.bind(this));
        target.addEventListener(EventTypes.Store.ProjectRuns.deleteBulk, this.handleProjectExecutionDeleteBulk.bind(this));
        target.addEventListener(EventTypes.Store.ProjectRuns.clearSpace, this.handleProjectExecutionClearSpace.bind(this));
        target.addEventListener(EventTypes.Store.ProjectRuns.clearProject, this.handleProjectExecutionClearProject.bind(this));
        target.addEventListener(EventTypes.Store.ProjectRuns.clearParent, this.handleProjectExecutionClearFolder.bind(this));
    }
    handleInitializeEnvironment(input) {
        const e = input;
        e.preventDefault();
        const copy = { ...e.detail };
        e.detail.result = this.environment.init(copy);
    }
    handleStoreInfo(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.environment.readStoreInfo(e.detail.baseUri);
    }
    storeAuthHandler(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.auth.authenticate(e.detail.options);
    }
    storeIsAuthenticatedHandler(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.auth.isAuthenticated(e.detail.env);
    }
    setGlobalEnvHandler(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.global.set(e.detail.env);
    }
    getGlobalEnvHandler(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.global.get();
    }
    getTokenHandler(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.auth.getToken();
    }
    handleFileList(event) {
        event.preventDefault();
        const { kinds, options } = event.detail;
        const { space } = this;
        event.detail.result = this.getStore().sdk.file.list(space, kinds, options);
    }
    handleSharedFileList(event) {
        event.preventDefault();
        const { kinds, options } = event.detail;
        const { space } = this;
        event.detail.result = this.getStore().sdk.shared.list(space, kinds, options);
    }
    handleFileCreate(event) {
        event.preventDefault();
        const { meta, media, opts } = event.detail;
        if (media) {
            event.detail.result = this.getStore().sdk.file.create(meta, media, this.space, opts);
        }
        else {
            event.detail.result = this.getStore().sdk.file.createMeta(meta, this.space, opts);
        }
    }
    fileCreateDefaultHandler(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.createDefaultFile(e.detail.name, e.detail.kind, e.detail.opts);
    }
    handleFileRead(event) {
        event.preventDefault();
        const { media, key } = event.detail;
        if (media) {
            event.detail.result = this.getStore().sdk.file.read(this.space, key, media);
        }
        else {
            event.detail.result = this.getStore().sdk.file.read(this.space, key, media);
        }
    }
    handleFilePatch(event) {
        event.preventDefault();
        const { key, id, value, app, media } = event.detail;
        const info = {
            app: app.code,
            appVersion: app.version,
            patch: value,
            id,
        };
        event.detail.result = this.getStore().sdk.file.patch(this.space, key, info, media);
    }
    handleFileDelete(event) {
        event.preventDefault();
        event.detail.result = this.getStore().sdk.file.delete(this.space, event.detail.key);
    }
    handleFilePatchUser(event) {
        event.preventDefault();
        const { key, id, value } = event.detail;
        const info = {
            patch: value,
            id,
        };
        event.detail.result = this.getStore().sdk.file.patchUsers(this.space, key, info);
    }
    handleListFileUsers(event) {
        event.preventDefault();
        event.detail.result = this.getStore().sdk.file.listUsers(this.space, event.detail.key);
    }
    handleFileBreadcrumbs(event) {
        event.preventDefault();
        event.detail.result = this.files.breadcrumbs(event.detail.key);
        // this.getStore().sdk.file.breadcrumbs(this.space, event.detail.key);
    }
    handlerMe(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.users.me();
    }
    handleUsersList(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.users.list(e.detail.options);
    }
    handleUserRead(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.users.read(e.detail.key);
    }
    handleHistoryCreate(input) {
        const e = input;
        e.preventDefault();
        const cp = { ...e.detail.history };
        if (!cp.space) {
            cp.space = this.space;
        }
        e.detail.result = this.getStore().sdk.history.add(cp);
    }
    handleHistoryCreateBulk(input) {
        const e = input;
        e.preventDefault();
        const cp = { ...e.detail.info };
        if (!cp.space) {
            cp.space = this.space;
        }
        e.detail.result = this.getStore().sdk.history.createBulk(cp);
    }
    handleHistoryDelete(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.deleteHistory(e.detail.key);
    }
    handleHistoryList(input) {
        const e = input;
        e.preventDefault();
        const cp = { ...e.detail.init };
        if (!cp.space) {
            cp.space = this.space;
        }
        e.detail.result = this.getStore().sdk.history.list(cp);
    }
    handleHistoryRead(input) {
        const e = input;
        e.preventDefault();
        e.detail.result = this.getStore().sdk.history.read(e.detail.key);
    }
    handleHistoryDeleteBulk(event) {
        event.preventDefault();
        event.detail.result = this.getStore().sdk.history.deleteBulk(event.detail.keys);
    }
    handleHistoryClearSpace(event) {
        event.preventDefault();
        event.detail.result = this.getStore().sdk.history.clearSpace(event.detail.space);
    }
    handleHistoryClearProject(event) {
        event.preventDefault();
        event.detail.result = this.getStore().sdk.history.clearProject(event.detail.space, event.detail.project);
    }
    handleHistoryClearRequest(event) {
        event.preventDefault();
        event.detail.result = this.getStore().sdk.history.clearRequest(event.detail.space, event.detail.project, event.detail.request);
    }
    async deleteHistory(key) {
        await this.getStore().sdk.history.delete(key);
        return {
            key,
        };
    }
    handleAddProjectExecution(event) {
        event.preventDefault();
        const { item } = event.detail;
        event.detail.result = this.getStore().sdk.projectExecution.add(this.space, item);
    }
    handleProjectExecutionRead(event) {
        event.preventDefault();
        const { key } = event.detail;
        event.detail.result = this.getStore().sdk.projectExecution.read(key);
    }
    handleProjectExecutionList(event) {
        event.preventDefault();
        const { options } = event.detail;
        const cp = { ...options };
        if (!cp.space) {
            cp.space = this.space;
        }
        event.detail.result = this.getStore().sdk.projectExecution.list(cp);
    }
    handleProjectExecutionDelete(event) {
        event.preventDefault();
        const { key } = event.detail;
        event.detail.result = this.getStore().sdk.projectExecution.delete(key);
    }
    handleProjectExecutionDeleteBulk(event) {
        event.preventDefault();
        const { keys } = event.detail;
        event.detail.result = this.getStore().sdk.projectExecution.deleteBulk(keys);
    }
    handleProjectExecutionClearSpace(event) {
        event.preventDefault();
        const { space } = event.detail;
        event.detail.result = this.getStore().sdk.projectExecution.clearSpace(space);
    }
    handleProjectExecutionClearProject(event) {
        event.preventDefault();
        const { space, project } = event.detail;
        event.detail.result = this.getStore().sdk.projectExecution.clearProject(space, project);
    }
    handleProjectExecutionClearFolder(event) {
        event.preventDefault();
        const { space, project, folder } = event.detail;
        event.detail.result = this.getStore().sdk.projectExecution.clearParent(space, project, folder);
    }
    /**
     * A logic that creates an empty (default) file from its name and the file kind.
     *
     * @param name The name of the file.
     * @param kind The kind of the file.
     * @param opts Optional create options
     * @returns The key of the created file.
     */
    async createDefaultFile(space, name, kind, opts) {
        switch (kind) {
            case ProjectKind: return this.createDefaultProject(space, name, opts);
            case FolderKind: return this.createDefaultFolder(space, name, opts);
            case DataFileKind: return this.createDefaultDataNamespace(space, name, opts);
            default:
                throw new Error(`Unrecognized file kind: ${kind}`);
        }
    }
    /**
     * Creates a default HttpProject from a name
     *
     * @param name The name of the project
     * @param opts Create options.
     * @returns The key of the created file.
     */
    async createDefaultProject(space, name, opts) {
        const { store } = this;
        if (!store) {
            throw new Error(`Environment is not set.`);
        }
        const media = HttpProject.fromName(name);
        const file = Project.fromProject(media, space);
        return store.sdk.file.create(file.toJSON(), media.toJSON(), space, opts);
    }
    /**
     * Creates a default DataNamespace from a name
     *
     * @param name The name of the DataNamespace
     * @param opts Create options.
     * @returns The key of the created file.
     */
    async createDefaultDataNamespace(space, name, opts) {
        const { store } = this;
        if (!store) {
            throw new Error(`Environment is not set.`);
        }
        const media = DataNamespace.fromName(name);
        const file = DataFile.fromDataNamespace(media, space);
        return store.sdk.file.create(file.toJSON(), media.toJSON(), space, opts);
    }
    /**
     * Creates a default Folder from a name
     *
     * @param name The name of the space
     * @param opts Create options.
     * @returns The key of the created file.
     */
    async createDefaultFolder(space, name, opts) {
        const { store } = this;
        if (!store) {
            throw new Error(`Environment is not set.`);
        }
        const folder = Folder.fromName(name, space);
        return store.sdk.file.createMeta(folder.toJSON(), space, opts);
    }
    getStore() {
        const { store } = this;
        if (!store) {
            throw new Error(`Environment is not set.`);
        }
        return store;
    }
    async observeSpaces() {
        await this.observePath(RouteBuilder.spaces());
    }
    async unobserveSpaces() {
        await this.unobservePath(RouteBuilder.spaces());
    }
    async observeFiles() {
        await this.observePath(RouteBuilder.files());
    }
    async unobserveFiles() {
        await this.unobservePath(RouteBuilder.files());
    }
    async observeFile(key, alt = 'meta') {
        const path = RouteBuilder.file(key);
        await this.observePath(`${path}?alt=${alt}`);
    }
    async unobserveFile(key, alt = 'meta') {
        const path = RouteBuilder.file(key);
        await this.unobservePath(`${path}?alt=${alt}`);
    }
    async observeHistory() {
        await this.observePath(RouteBuilder.history());
    }
    async unobserveHistory() {
        await this.unobservePath(RouteBuilder.history());
    }
    async observeProjectExecutions() {
        await this.observePath(RouteBuilder.projectExecutions());
    }
    async unobserveProjectExecutions() {
        await this.unobservePath(RouteBuilder.projectExecutions());
    }
    async observeTrash() {
        await this.observePath(RouteBuilder.trash());
    }
    async unobserveTrash() {
        await this.unobservePath(RouteBuilder.trash());
    }
    async sendSocket(ws, command) {
        ws.send(JSON.stringify(command));
    }
    async observePath(path) {
        const { space } = this;
        const url = new URL(path, 'http://localhost');
        url.searchParams.set('space', space);
        const fullPath = `${url.pathname}${url.search}`;
        const socket = await this.getEventsSocket();
        const cmd = {
            cmd: BackendCommandType.observe,
            kind: BackendCommandKind,
            path: fullPath,
        };
        await this.sendSocket(socket, cmd);
    }
    async unobservePath(path) {
        const socket = await this.getEventsSocket();
        const cmd = {
            cmd: BackendCommandType.unobserve,
            kind: BackendCommandKind,
            path,
        };
        await this.sendSocket(socket, cmd);
    }
    async getEventsSocket() {
        if (!this.eventsSocket) {
            const store = this.getStore();
            const { token } = store.sdk;
            const url = store.sdk.getUrl('/events');
            this.eventsSocket = await store.sdk.ws.createAndConnect(url.toString(), token);
            this.eventsSocket.addEventListener('message', this.storeEventHandler.bind(this));
        }
        return this.eventsSocket;
    }
    async closeEventsSocket() {
        const { eventsSocket } = this;
        if (!eventsSocket) {
            return;
        }
        this.eventsSocket = undefined;
        eventsSocket.close();
    }
    storeEventHandler(e) {
        const event = JSON.parse(e.data);
        if (event.type === 'error') {
            throw new Error(event.cause);
        }
        if (event.space && event.space !== this.space) {
            // we allow files here to support shared files.
            if (event.path === RouteBuilder.files()) {
                this.filesChannel.postMessage(event);
            }
            return;
        }
        if (event.path === RouteBuilder.spaces()) {
            this.spacesChannel.postMessage(event);
        }
        else if (event.path === RouteBuilder.files()) {
            this.filesChannel.postMessage(event);
        }
        else if (event.path.startsWith(RouteBuilder.files())) {
            const typed = event;
            if (typed.alt === 'media') {
                this.mediaChannel.postMessage(event);
            }
            else {
                this.filesChannel.postMessage(event);
            }
        }
        else if (event.path === RouteBuilder.trash()) {
            this.trashChannel.postMessage(event);
        }
        else if (event.path === RouteBuilder.history()) {
            this.historyChannel.postMessage(event);
        }
        else if (event.path === RouteBuilder.projectExecutions()) {
            this.projectExeChannel.postMessage(event);
        }
        else {
            // eslint-disable-next-line no-console
            console.warn(`Broadcast path not handled`, event.path, event);
        }
    }
}
//# sourceMappingURL=StoreBindings.js.map