import * as mc from "@minecraft/server";
import { mcerror, mclog } from "../log";
import { clearDynamicProperty, readDynamicProperty, writeDynamicProperty } from "../properties";
export class DataTable {
    constructor(registryKey, options, database) {
        this.registryKey = registryKey;
        this.options = options;
        this.database = database;
        this.entries = new Map();
        const keys = mc.world.getDynamicPropertyIds().filter(id => id.startsWith(this.registryKey));
        for (const key of keys) {
            const data = readDynamicProperty(mc.world, key);
            if (data)
                this.entries.set(key.replace(registryKey + ".", ""), data);
        }
        mclog(`${this.PublicKey} initialized with ${this.entries.size} entries`, "DataTable");
    }
    /**
     * @remarks The public key of the table, a more human-readable version of the registry key.
     */
    get PublicKey() {
        return this.registryKey.replace(this.database.registryKey, "");
    }
    get RegistryKey() {
        return this.registryKey;
    }
    /**
     * @remarks Whether the table is full.
     */
    get IsFull() {
        return this.entries.size >= this.options.maxSize;
    }
    /**
     * @remarks The number of entries in the table.
     */
    get Size() {
        return this.entries.size;
    }
    /**
     * @remarks Whether the table has an entry with the given key.
     * @param key The key to search for.
     * @returns True if the entry exists, false otherwise.
     */
    hasEntry(key) {
        return this.entries.has(key);
    }
    /**
     * @remarks Adds an entry to the table.
     * @param key The key of the entry to add.
     * @param data The data to ad at that key.
     * @returns True if the entry was added, false if the table is full and the overflow event failed.
     */
    setEntry(key, data) {
        if (!this.hasEntry(key) && this.entries.size >= this.options.maxSize) {
            if (this.options.onOverflow) {
                return this.options.onOverflow(key, data, this.database);
            }
            return false;
        }
        this.entries.set(key, data);
        try {
            writeDynamicProperty(mc.world, `${this.registryKey}.${key}`, data);
        }
        catch (error) {
            mcerror(`Failed to write property ${this.registryKey}.${key}, ${error}`, "DataTable");
        }
        return true;
    }
    /**
     * @remarks Gets an array of entries in the table as key-value pairs.
     * @returns An array of entries in the table as key-value pairs.
     */
    getEntries() {
        return Array.from(this.entries);
    }
    /**
     * @remarks Gets the entry with the given key.
     * @param key The key of the entry to get.
     * @returns The entry with the given key, or undefined if it does not exist.
     */
    getEntry(key) {
        return this.entries.get(key);
    }
    /**
     * @remarks Deletes the entry with the given key.
     * @param key The key of the entry to delete.
     * @returns True if the entry was deleted, false if it did not exist.
     */
    deleteEntry(key) {
        clearDynamicProperty(mc.world, `${this.registryKey}.${key}`);
        const result = this.entries.delete(key);
        return result;
    }
    /**
     * @remarks Moves an entry to another table.
     * @param key The key of the entry to move
     * @param table The table to move to. Can be a DataTable or a string key of a table in the database.
     * @param tradeIfFull Should the entry be traded with the first entry in the table if it is full?
     * @returns True if the entry was moved, false otherwise.
     */
    moveEntry(key, table, tradeIfFull = false) {
        const data = this.getEntry(key);
        if (!data)
            return false;
        if (typeof table === "string") {
            try {
                table = this.database.getTable(table);
            }
            catch (error) {
                return false;
            }
        }
        // Attempt to trade by creating space and moving entries. If it failes, revert changes
        if (table.IsFull && tradeIfFull) {
            const [tradeId, tradeData] = table.entries.entries().next().value;
            this.deleteEntry(key);
            table.deleteEntry(tradeId);
            if (this.setEntry(tradeId, tradeData) && table.setEntry(key, data)) {
                return true;
            }
            else {
                this.deleteEntry(tradeId);
                table.deleteEntry(key);
                this.setEntry(key, data);
                table.setEntry(tradeId, tradeData);
                return false;
            }
        }
        if (table.setEntry(key, data)) {
            return this.deleteEntry(key);
        }
        return false;
    }
    /**
     * @remarks Swaps two entries between two tables.
     * @param key1 The key of the entry in this table.
     * @param key2 The key of the entry in the other table.
     * @param table The table to swap with. Can be a DataTable or a string key of a table in the database.
     * @returns True if the entries were swapped, false otherwise.
     */
    swapEntries(key1, key2, table) {
        if (typeof table === "string") {
            try {
                table = this.database.getTable(table);
            }
            catch (error) {
                return false;
            }
        }
        const data1 = this.getEntry(key1);
        if (!data1)
            return false;
        const data2 = table.getEntry(key2);
        if (!data2)
            return false;
        this.deleteEntry(key1);
        table.deleteEntry(key2);
        if (this.setEntry(key2, data2) && table.setEntry(key1, data1)) {
            return true;
        }
        else {
            this.deleteEntry(key2);
            table.deleteEntry(key1);
            this.setEntry(key1, data1);
            table.setEntry(key2, data2);
            return false;
        }
    }
    /**
     * @remarks Clears all entries from the table.
     */
    clear() {
        const table = this;
        return new Promise(resolve => {
            mc.system.runJob(function* () {
                for (const key of table.entries.keys()) {
                    table.deleteEntry(key);
                    yield;
                }
                table.entries.clear();
                resolve();
            }());
        });
    }
    /**
     * @remarks Calls the given callback on each entry in the table.
     * @param callback The callback to call on each entry.
     */
    forEach(callback) {
        this.entries.forEach(callback);
    }
}
