// Session cache for hash-addressed image bytes, deliberately not Vuex. See docs/implementation.md#filecache-design-rationale.

const MAX_BYTES = 150 * 1024 * 1024; // budget for decoded image bytes before evicting LRU entries

class FileCache {
    constructor() {
        this._entries = new Map(); // key -> {url, size}; Map iteration order doubles as LRU order
        this._inflight = new Map(); // key -> Promise<string>, de-duplicates concurrent callers
        this._totalBytes = 0;
    }

    _touch(key) {
        // Delete+re-insert moves this entry to the MRU end of the Map's iteration order.
        const entry = this._entries.get(key);
        this._entries.delete(key);
        this._entries.set(key, entry);
    }

    _evict(key) {
        const entry = this._entries.get(key);
        if (!entry) return;
        URL.revokeObjectURL(entry.url);
        this._totalBytes -= entry.size;
        this._entries.delete(key);
    }

    _evictUntilUnderBudget() {
        for (const key of this._entries.keys()) {
            if (this._totalBytes <= MAX_BYTES) break;
            this._evict(key); // oldest (least recently used) first - Map iterates in insertion order
        }
    }

    // fetcher: () => Promise<Blob>, called at most once per key even under concurrent callers.
    async get(key, fetcher) {
        if (this._entries.has(key)) {
            this._touch(key);
            return this._entries.get(key).url;
        }
        if (this._inflight.has(key)) {
            return this._inflight.get(key);
        }
        const promise = (async () => {
            const blob = await fetcher();
            const url = URL.createObjectURL(blob);
            this._entries.set(key, {url, size: blob.size});
            this._totalBytes += blob.size;
            this._evictUntilUnderBudget();
            return url;
        })();
        this._inflight.set(key, promise);
        try {
            return await promise;
        } finally {
            this._inflight.delete(key);
        }
    }

    invalidate(key) {
        this._evict(key);
    }

    clear() {
        for (const key of [...this._entries.keys()]) this._evict(key);
    }
}

// Singleton - every AuthenticatedImage instance shares the same cache.
export default new FileCache();
