// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import type {DebugId, SourceMapV3} from './SourceMap.js';

/** A thin wrapper around the Cache API to store source map JSONs keyed on Debug IDs */
export class SourceMapCache {
  static readonly #INSTANCE = new SourceMapCache('devtools-source-map-cache');

  static instance(): SourceMapCache {
    if (typeof window === 'undefined') {
      // TODO(crbug.com/451502260): Move this behind a `HostRuntime` interface.
      return IN_MEMORY_INSTANCE as unknown as
          SourceMapCache;  // TS doesn't like that our in-memory class doesn't have the same private fields.
    }
    return this.#INSTANCE;
  }

  static createForTest(name: string): SourceMapCache {
    return new SourceMapCache(name);
  }

  readonly #name: string;
  #cachePromise?: Promise<Cache>;

  private constructor(name: string) {
    this.#name = name;
  }

  async set(debugId: DebugId, sourceMap: SourceMapV3): Promise<void> {
    const cache = await this.#cache();
    await cache.put(SourceMapCache.#urlForDebugId(debugId), new Response(JSON.stringify(sourceMap)));
  }

  async get(debugId: DebugId): Promise<SourceMapV3|null> {
    const cache = await this.#cache();
    const response = await cache.match(SourceMapCache.#urlForDebugId(debugId));
    return await response?.json() ?? null;
  }

  async #cache(): Promise<Cache> {
    if (this.#cachePromise) {
      return await this.#cachePromise;
    }

    this.#cachePromise = window.caches.open(this.#name);
    return await this.#cachePromise;
  }

  /** The Cache API only allows URL as keys, so we construct a simple one. Given that we have our own cache, we have no risk of conflicting URLs */
  static #urlForDebugId(debugId: DebugId): string {
    return 'http://debug.id/' + encodeURIComponent(debugId);
  }

  async disposeForTest(): Promise<void> {
    await window.caches.delete(this.#name);
  }
}

const IN_MEMORY_INSTANCE = new (class implements Pick<SourceMapCache, 'get'|'set'|'disposeForTest'> {
  readonly #cache = new Map<DebugId, SourceMapV3>();

  async set(debugId: DebugId, sourceMap: SourceMapV3): Promise<void> {
    this.#cache.set(debugId, sourceMap);
  }

  async get(debugId: DebugId): Promise<SourceMapV3|null> {
    return this.#cache.get(debugId) ?? null;
  }

  async disposeForTest(): Promise<void> {
    // Do nothing.
  }
})();
