import { debug } from '@sentry/core';
import { promises } from 'fs';
import { join, dirname } from 'path';
import { Mutex } from './mutex.js';

const dateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.*\d{0,10}Z$/;
/** JSON revive function to enable de-serialization of Date objects */
function dateReviver(_, value) {
    if (typeof value === 'string' && dateFormat.test(value)) {
        return new Date(value);
    }
    return value;
}
/**
 * Stores data serialized to a JSON file.
 */
class Store {
    /**
     * Creates a new store.
     *
     * @param path A unique filename to store this data.
     * @param id A unique filename to store this data.
     * @param initial An initial value to initialize data with.
     */
    constructor(path, id, initial) {
        this._lock = new Mutex();
        this._path = join(path, `${id}.json`);
        this._initial = initial;
    }
    /**
     * Updates data by replacing it with the given value.
     * @param data New data to replace the previous one.
     */
    async set(data) {
        await this._lock.runExclusive(async () => {
            this._data = data;
            try {
                if (data === undefined) {
                    try {
                        await promises.unlink(this._path);
                    }
                    catch {
                        //
                    }
                }
                else {
                    await promises.mkdir(dirname(this._path), { recursive: true });
                    await promises.writeFile(this._path, JSON.stringify(data));
                }
            }
            catch (e) {
                debug.warn('Failed to write to store', e);
                // This usually fails due to anti virus scanners, issues in the file
                // system, or problems with network drives. We cannot fix or handle this
                // issue and must resume gracefully. Thus, we have to ignore this error.
            }
        });
    }
    /**
     * Returns the current data.
     *
     * When invoked for the first time, it will try to load previously stored data
     * from disk. If the file does not exist, the initial value provided to the
     * constructor is used.
     */
    async get() {
        return this._lock.runExclusive(async () => {
            if (this._data === undefined) {
                try {
                    this._data = JSON.parse(await promises.readFile(this._path, 'utf8'), dateReviver);
                }
                catch {
                    this._data = this._initial;
                }
            }
            return this._data;
        });
    }
    /**
     * Updates data by passing it through the given function.
     * @param fn A function receiving the current data and returning new one.
     */
    async update(fn) {
        await this.set(fn(await this.get()));
    }
    /** Returns store to its initial state */
    async clear() {
        await this.set(this._initial);
    }
    /** Gets the Date that the file was last modified */
    async getModifiedDate() {
        try {
            return (await promises.stat(this._path))?.mtime;
        }
        catch {
            return undefined;
        }
    }
}
/**
 * Extends Store to throttle writes.
 */
class BufferedWriteStore extends Store {
    /**
     * Creates a new ThrottledStore.
     *
     * @param path A unique filename to store this data.
     * @param id A unique filename to store this data.
     * @param initial An initial value to initialize data with.
     * @param throttleTime The minimum time between writes
     */
    constructor(path, id, initial, _throttleTime = 500) {
        super(path, id, initial);
        this._throttleTime = _throttleTime;
    }
    /** @inheritdoc */
    async set(data) {
        this._data = data;
        this._pendingWrite = {
            // We overwrite the data for the pending write so that the latest data is written in the next flush
            data,
            // If there is already a pending timeout, we keep that rather than starting the timeout again
            timeout: this._pendingWrite?.timeout || setTimeout(() => this._writePending(), this._throttleTime),
        };
    }
    /** Writes the pending write to disk */
    _writePending() {
        if (this._pendingWrite) {
            const data = this._pendingWrite.data;
            // Clear the pending write immediately so that subsequent writes can be queued
            this._pendingWrite = undefined;
            super.set(data).catch(() => {
                // ignore
            });
        }
    }
}

export { BufferedWriteStore, Store };
//# sourceMappingURL=store.js.map
