import { DataTable } from "./table";
import { mcerror, mclog } from "../log";
import { getObjectFromArray } from "../array";
import { afterEvents, waitForNamespace } from "../events";
export class DataBase {
    static initializeHelperEvents() {
        if (this.databaseRegistry.size)
            return;
        afterEvents.scriptEventRecieve.subscribe("clear_database", event => {
            var _a, _b;
            const id = (_a = event.data) !== null && _a !== void 0 ? _a : (_b = event.sourceEntity) === null || _b === void 0 ? void 0 : _b.id;
            if (id) {
                this.clearDatabase(id);
            }
            else {
                mcerror(`No id provided to clear_database, options are ${Array.from(this.databaseRegistry.keys()).join(", ")}`, DataBase.logTag);
            }
        });
        afterEvents.scriptEventRecieve.subscribe("print_database", event => {
            var _a, _b, _c, _d;
            const id = (_a = event.data) !== null && _a !== void 0 ? _a : (_b = event.sourceEntity) === null || _b === void 0 ? void 0 : _b.id;
            if (id) {
                const db = { [id]: {} };
                Object.entries((_d = (_c = this.getDatabase(id)) === null || _c === void 0 ? void 0 : _c.Tables) !== null && _d !== void 0 ? _d : {}).forEach(([key, data]) => {
                    db[id][key] = getObjectFromArray(data.getEntries());
                });
                mclog(JSON.stringify(db, null, "\t"), DataBase.logTag);
            }
            else {
                mcerror(`No id provided to print_database, options are ${Array.from(this.databaseRegistry.keys()).join(", ")}`, DataBase.logTag);
            }
        });
        afterEvents.scriptEventRecieve.subscribe("list_database", event => {
            mclog(`Databases: ${Array.from(this.databaseRegistry.keys()).join(", ")}`, DataBase.logTag);
        });
    }
    /**
     * @remarks Gets a database registered with the given id.
     * @param id The id of the database.
     * @returns The database registered with that id, or undefined if it does not exist.
     */
    static getDatabase(id) {
        return this.databaseRegistry.get(id);
    }
    /**
     * @remarks Clears all tables in the database with the given id.
     * @param id The id of the database.
     */
    static clearDatabase(id) {
        const database = this.getDatabase(id);
        if (database) {
            database.clearAll();
        }
    }
    /**
     * @remarks The tables in the database.
     */
    get Tables() {
        return this.tables;
    }
    /**
     * @remarks The size of the database.
     */
    get Size() {
        let size = 0;
        Object.values(this.tables).forEach(table => size += table.Size);
        return size;
    }
    /**
     * @remarks Creates a new database with the given options.
     * @param registryKey The key to register the database with. This should be unique (like an entity id).
     * @param options The type of data and tables to create.
     */
    constructor(registryKey, options) {
        this.registryKey = registryKey;
        this.tables = {};
        waitForNamespace().then(() => {
            DataBase.initializeHelperEvents();
            DataBase.databaseRegistry.set(registryKey, this);
            for (const key in options.tables) {
                this.tables[key] = new DataTable(registryKey + key, options.tables[key], this);
            }
            mclog(`${registryKey} initialized with ${Object.keys(this.tables).length} tables: [${Object.keys(options.tables)}]`, DataBase.logTag);
        });
    }
    /**
     * @remarks Gets the table with the given key.
     * @param tableKey The key of the table to get.
     * @returns The table with the given key.
     * @throws If the table does not exist.
     */
    getTable(tableKey) {
        if (!this.tables[tableKey])
            throw new Error(`Table ${String(tableKey)} does not exist`);
        return this.tables[tableKey];
    }
    /**
     * @remarks Whether the entry with the given key exists in any table.
     * @param key The key of the entry to search for.
     * @returns True if the entry exists in any table, false otherwise.
     */
    hasEntry(key) {
        return Object.values(this.tables).some(table => table.hasEntry(key));
    }
    /**
     * @remarks Gets the table containing the entry with the given key.
     * @param entryKey The key of the entry to search for.
     * @returns The table containing the entry with the given key, or undefined if it does not exist.
     */
    getTableFromEntryKey(entryKey) {
        return Object.values(this.tables).find(table => table.hasEntry(entryKey));
    }
    /**
     * @remarks Gets the entry and table with the given key.
     * @param entryKey The key of the entry to search for.
     * @returns An object containing the data and table of the entry with the given key, or undefined if it does not exist.
     */
    getEntryFromKey(entryKey) {
        const table = this.getTableFromEntryKey(entryKey);
        const data = table === null || table === void 0 ? void 0 : table.getEntry(entryKey);
        if (table && data) {
            return { data, table };
        }
    }
    /**
     * @remarks Adds an entry to the first table in the database.
     * @param key The key of the entry to add.
     * @param data The data to add at that key.
     * @returns True if the entry was added to any table, false otherwise.
     */
    addEntryToFirstTable(key, data) {
        var _a, _b;
        return (_b = (_a = Object.values(this.tables)[0]) === null || _a === void 0 ? void 0 : _a.setEntry(key, data)) !== null && _b !== void 0 ? _b : false;
    }
    /**
     * @remarks Clears the entry with the given key from all tables in the database.
     * @param key The key of the entry to delete.
     */
    clearEntryFromAllTables(key) {
        Object.values(this.tables).forEach(table => {
            table.deleteEntry(key);
        });
    }
    /**
     * @remarks Clears all entries from all tables in the database.
     */
    clearAll() {
        return new Promise(async (resolve) => {
            for (const table of Object.values(this.tables)) {
                await table.clear();
            }
            mclog(`${this.registryKey} cleared`, DataBase.logTag);
            resolve();
        });
    }
    /**
     * @remarks Calls the given callback on each entry in the database.
     * @param callback The callback to call on each entry.
     */
    forEach(callback) {
        Object.values(this.tables).forEach(table => table.forEach(callback));
    }
}
DataBase.databaseRegistry = new Map();
DataBase.logTag = "DataBase";
