import {IndexedDBManager} from './network/IndexedDBManager.mjs';
import {EnvUtils} from './utils/EnvUtils.mjs';

const BrowserCanAutoOffloadBlobs = EnvUtils.isChrome();
const UseCache = !BrowserCanAutoOffloadBlobs && window.caches;
const UseIndexedDB = !BrowserCanAutoOffloadBlobs && !UseCache && IndexedDBManager.isSupported();

export class FSBlob {
  constructor() {
    this.blobStore = new Map();
    this.blobStorePromises = new Map();

    if (UseCache) {
      this.cache = true;
      this.setupPromise = this.setupOrphanedCache();
    } else if (UseIndexedDB) {
      this.indexedDBManager = new IndexedDBManager();
      this.setupPromise = this.indexedDBManager.setup();
    }

    this.blobIndex = 0;
  }

  async setupOrphanedCache() {
    const cacheName = 'blob-cache-' + Date.now() + '-' + Math.random();
    const cache = await window.caches.open(cacheName);
    // Orphan it!
    await window.caches.delete(cacheName);
    // Store the cache reference for later use
    this.cache = cache;
  }

  async saveBlobInIndexedDBAsync(identifier, blob) {
    try {
      await this.setupPromise;
    } catch (e) {
      // IndexedDB is not supported
      console.warn('IndexedDB is not supported, falling back to memory storage');
      this.indexedDBManager = null;
      this.setupPromise = false;
      this.blobStorePromises.clear();
      return false;
    }
    // Store file
    await this.indexedDBManager.setFile(identifier, blob);
    // Get file
    const file = await this.indexedDBManager.getFile(identifier);

    if (EnvUtils.isFirefox()) {
      // Delete file to orphan it
      await this.indexedDBManager.deleteFile(identifier);
    }

    this.blobStore.set(identifier, file);
    return true;
  }

  async saveBlobUsingCache(identifier, blob) {
    try {
      await this.setupPromise;
    } catch (e) {
      console.warn('Cache API is not supported, falling back to memory storage');
      this.cache = null;
      this.setupPromise = false;
      this.blobStorePromises.clear();
      return false;
    }

    const response = new Response(blob);
    const identifierURL = this.getIdentifierURL(identifier);

    await this.cache.put(identifierURL, response);

    const match = await this.cache.match(identifierURL);

    const blobResponse = await match?.blob();

    this.blobStore.set(identifier, blobResponse);
  }

  getIdentifierURL(identifier) {
    return 'https://faststream.online/blob-cache?identifier=' + encodeURIComponent(identifier);
  }

  nextIdentifier() {
    return `blob${this.blobIndex++}`;
  }

  async saveBlobAsync(blob, identifier) {
    if (!identifier) {
      identifier = this.nextIdentifier();
    }
    this.blobStore.set(identifier, blob);
    let promise;
    if (this.cache) {
      promise = this.saveBlobUsingCache(identifier, blob);
    } else if (this.indexedDBManager) {
      promise = this.saveBlobInIndexedDBAsync(identifier, blob);
    }
    this.blobStorePromises.set(identifier, promise);

    await promise;

    return identifier;
  }

  saveBlob(blob) {
    const identifier = this.nextIdentifier();
    this.saveBlobAsync(blob, identifier);
    return identifier;
  }

  createBlob(data) {
    const blob = new Blob([data], {type: 'application/octet-stream'});
    return this.saveBlob(blob);
  }

  async deleteBlob(identifier) {
    this.blobStore.delete(identifier);

    if (this.blobStorePromises.has(identifier)) {
      await this.blobStorePromises.get(identifier);
    }

    this.blobStore.delete(identifier);
    this.blobStorePromises.delete(identifier);
    if (this.cache) {
      const identifierURL = this.getIdentifierURL(identifier);
      await this.cache.delete(identifierURL);
    } else if (this.indexedDBManager) {
      await this.indexedDBManager.deleteFile(identifier);
    }

    return true;
  }

  getBlob(identifier) {
    return this.blobStore.get(identifier);
  }

  async clear() {
    this.blobStore.clear();
    this.blobStorePromises.clear();
    if (this.cache) {
      // Setup a new cache entirely
      this.cache = true;
      this.setupPromise = this.setupOrphanedCache();
      await this.setupPromise;
    } else if (this.indexedDBManager) {
      await this.indexedDBManager.clearStorage();
    }
  }

  close() {
    return this.indexedDBManager?.close();
  }
}
