/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */

import { deepCopy } from "#util/DeepCopy.js";
import { CloneableStorage, StorageDriver, StorageError } from "./StorageDriver.js";
import { SupportedStorageTypes } from "./StringifyTools.js";

export class MemoryStorageDriver extends StorageDriver implements CloneableStorage {
    static readonly id: string = "memory";

    protected isInitialized = false;

    constructor(protected store: Record<string, Record<string, SupportedStorageTypes>> = {}) {
        super();
    }

    get initialized() {
        return this.isInitialized;
    }

    static create(_namespace?: unknown, _descriptor?: StorageDriver.Descriptor) {
        const storage = new MemoryStorageDriver();
        storage.initialize();
        return storage;
    }

    private createContextKey(contexts: string[]) {
        for (const ctx of contexts) {
            if (!ctx.length || ctx.includes(".")) {
                throw new StorageError("Context must not contain empty segments or leading or trailing dots.");
            }
        }
        return contexts.join(".");
    }

    initialize() {
        if (this.initialized) throw new StorageError("Storage already initialized!");
        this.isInitialized = true;
        // nothing else to do
    }

    clone() {
        const clone = new MemoryStorageDriver(deepCopy(this.store));
        clone.initialize();
        return clone;
    }

    async close() {
        this.isInitialized = false;
        // nothing to do
    }

    get data() {
        return this.store;
    }

    get(contexts: string[], key: string): SupportedStorageTypes | undefined {
        this.#assertInitialized();
        if (!key.length) throw new StorageError("Key must not be empty.");
        return this.store[this.createContextKey(contexts)]?.[key];
    }

    #setKey(contexts: string[], key: string, value: SupportedStorageTypes) {
        if (!key.length) throw new StorageError("Key must not be empty.");
        const contextKey = this.createContextKey(contexts);
        if (this.store[contextKey] === undefined) {
            this.store[contextKey] = {};
        }
        this.store[contextKey][key] = value;
    }

    set(
        contexts: string[],
        keyOrValues: string | Record<string, SupportedStorageTypes>,
        value?: SupportedStorageTypes,
    ) {
        this.#assertInitialized();
        if (typeof keyOrValues === "string") {
            this.#setKey(contexts, keyOrValues, value);
        } else {
            Object.entries(keyOrValues).forEach(([key, value]) => {
                this.#setKey(contexts, key, value);
            });
        }
    }

    delete(contexts: string[], key: string) {
        this.#assertInitialized();
        if (!key.length) throw new StorageError("Key must not be empty.");
        delete this.store[this.createContextKey(contexts)]?.[key];
    }

    keys(contexts: string[]) {
        this.#assertInitialized();
        return Object.keys(this.store[this.createContextKey(contexts)] ?? {});
    }

    values(contexts: string[]) {
        // Initialize and context checks are done by keys method
        const values = {} as Record<string, SupportedStorageTypes>;
        for (const key of this.keys(contexts)) {
            values[key] = this.get(contexts, key);
        }
        return values;
    }

    contexts(contexts: string[]) {
        this.#assertInitialized();
        const contextKey = contexts.length ? this.createContextKey(contexts) : "";
        const startContextKey = contextKey.length ? `${contextKey}.` : "";
        const foundContexts = new Array<string>();
        Object.keys(this.store).forEach(key => {
            if (key.startsWith(startContextKey)) {
                const subKeys = key.substring(startContextKey.length).split(".");
                if (subKeys.length < 1) return; // should never happen
                const context = subKeys[0];
                if (context.length && !foundContexts.includes(context)) {
                    foundContexts.push(context);
                }
            }
        });
        return foundContexts;
    }

    clearAll(contexts: string[]) {
        this.#assertInitialized();
        const contextKey = this.createContextKey(contexts);
        if (contextKey.length) {
            delete this.store[contextKey];
        }
        const startContextKey = contextKey.length ? `${contextKey}.` : "";
        Object.keys(this.store).forEach(key => {
            if (key.startsWith(startContextKey)) {
                delete this.store[key];
            }
        });
    }

    #assertInitialized() {
        if (this.initialized) {
            return;
        }

        throw new StorageError("Storage is not initialized");
    }
}
