/*
Copyright 2024 New Vector Ltd.
Copyright 2019-2021 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

/*
 * Object holding the global EventIndex object. Can only be initialized if the
 * platform supports event indexing.
 */

import { logger } from "matrix-js-sdk/src/logger";

import PlatformPeg from "../PlatformPeg";
import EventIndex from "../indexing/EventIndex";
import { MatrixClientPeg } from "../MatrixClientPeg";
import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel";

const INDEX_VERSION = 1;

/**
 * Holds the current instance of the `EventIndex` to use across the codebase.
 * Looking for an `EventIndex`? Just look for the `EventIndexPeg` on the peg
 * board. "Peg" is the literal meaning of something you hang something on. So
 * you'll find a `EventIndex` hanging on the `EventIndexPeg`.
 */
export class EventIndexPeg {
    public index: EventIndex | null = null;
    public error: unknown;

    private _supportIsInstalled = false;

    /**
     * Initialize the EventIndexPeg and if event indexing is enabled initialize
     * the event index.
     *
     * @return {Promise<boolean>} A promise that will resolve to true if an
     * EventIndex was successfully initialized, false otherwise.
     */
    public async init(): Promise<boolean> {
        const indexManager = PlatformPeg.get()?.getEventIndexingManager();
        if (!indexManager) {
            logger.log("EventIndex: Platform doesn't support event indexing, not initializing.");
            return false;
        }

        this._supportIsInstalled = await indexManager.supportsEventIndexing();

        if (!this.supportIsInstalled()) {
            logger.log("EventIndex: Event indexing isn't installed for the platform, not initializing.");
            return false;
        }

        if (!SettingsStore.getValueAt(SettingLevel.DEVICE, "enableEventIndexing")) {
            logger.log("EventIndex: Event indexing is disabled, not initializing");
            return false;
        }

        return this.initEventIndex();
    }

    /**
     * Initialize the event index.
     *
     * @returns {boolean} True if the event index was successfully initialized,
     * false otherwise.
     */
    public async initEventIndex(): Promise<boolean> {
        const index = new EventIndex();
        const indexManager = PlatformPeg.get()?.getEventIndexingManager();
        const client = MatrixClientPeg.get();
        if (!indexManager || !client) {
            throw new Error("Unable to init event index");
        }

        const userId = client.getUserId()!;
        const deviceId = client.getDeviceId()!;

        try {
            await indexManager.initEventIndex(userId, deviceId);

            const userVersion = await indexManager.getUserVersion();
            const eventIndexIsEmpty = await indexManager.isEventIndexEmpty();

            if (eventIndexIsEmpty) {
                await indexManager.setUserVersion(INDEX_VERSION);
            } else if (userVersion === 0 && !eventIndexIsEmpty) {
                await indexManager.closeEventIndex();
                await this.deleteEventIndex();

                await indexManager.initEventIndex(userId, deviceId);
                await indexManager.setUserVersion(INDEX_VERSION);
            }

            logger.log("EventIndex: Successfully initialized the event index");
            await index.init();
        } catch (e) {
            logger.log("EventIndex: Error initializing the event index", e);
            this.error = e;
            return false;
        }

        this.index = index;

        return true;
    }

    /**
     * Check if the current platform has support for event indexing.
     *
     * @return {boolean} True if it has support, false otherwise. Note that this
     * does not mean that support is installed.
     */
    public platformHasSupport(): boolean {
        return PlatformPeg.get()?.getEventIndexingManager() != null;
    }

    /**
     * Check if event indexing support is installed for the platform.
     *
     * Event indexing might require additional optional modules to be installed,
     * this tells us if those are installed. Note that this should only be
     * called after the init() method was called.
     *
     * @return {boolean} True if support is installed, false otherwise.
     */
    public supportIsInstalled(): boolean {
        return this._supportIsInstalled;
    }

    /**
     * Get the current event index.
     *
     * @return {EventIndex} The current event index.
     */
    public get(): EventIndex | null {
        return this.index;
    }

    public start(): void {
        if (this.index === null) return;
        this.index.startCrawler();
    }

    public stop(): void {
        if (this.index === null) return;
        this.index.stopCrawler();
    }

    /**
     * Unset our event store
     *
     * After a call to this the init() method will need to be called again.
     *
     * @return {Promise} A promise that will resolve once the event index is
     * closed.
     */
    public async unset(): Promise<void> {
        if (this.index === null) return;
        await this.index.close();
        this.index = null;
    }

    /**
     * Delete our event indexer.
     *
     * After a call to this the init() method will need to be called again.
     *
     * @return {Promise} A promise that will resolve once the event index is
     * deleted.
     */
    public async deleteEventIndex(): Promise<void> {
        const indexManager = PlatformPeg.get()?.getEventIndexingManager();

        if (indexManager) {
            await this.unset();
            logger.log("EventIndex: Deleting event index.");
            await indexManager.deleteEventIndex();
        }
    }
}

if (!window.mxEventIndexPeg) {
    window.mxEventIndexPeg = new EventIndexPeg();
}
export default window.mxEventIndexPeg;
