import { api } from "src/boot/axios";

const createDbConnection = (dbName = "apiCache", version = 1) => {
  let db = null;

  const ready = new Promise((resolve, reject) => {
    const request = indexedDB.open(dbName, version);

    request.onerror = () => reject(request.error);

    request.onsuccess = () => {
      db = request.result;

      resolve(db);
    };

    request.onupgradeneeded = (event) => {
      const db = event.target.result;

      if (!db.objectStoreNames.contains("apiCache")) {
        db.createObjectStore("apiCache", { keyPath: "key" });
      }
    };
  });

  return {
    getDb: () => ready,
  };
};

const { getDb } = createDbConnection();

const getFromCache = async (key) => {
  const db = await getDb();

  return new Promise((resolve) => {
    const transaction = db.transaction(["apiCache"], "readonly");
    const store = transaction.objectStore("apiCache");
    const request = store.get(key);

    request.onsuccess = () => {
      const entry = request.result;

      if (entry?.timestamp + entry?.ttl > Date.now()) {
        resolve(entry.data);
      } else {
        resolve(null);
      }
    };
  });
};

const setInCache = async (key, data, ttl) => {
  const db = await getDb();

  const entry = {
    key,
    data,
    timestamp: Date.now(),
    ttl: ttl * 1000,
  };

  return new Promise((resolve) => {
    const transaction = db.transaction(["apiCache"], "readwrite");

    const store = transaction.objectStore("apiCache");

    store.put(entry);

    transaction.oncomplete = () => resolve();
  });
};

const clearCache = async (cacheKey) => {
  const db = await getDb();

  const transaction = db.transaction(["apiCache"], "readwrite");

  const store = transaction.objectStore("apiCache");

  return new Promise((resolve) => {
    if (!cacheKey) {
      store.clear();

      transaction.oncomplete = () => resolve();

      return;
    }

    const request = store.getAllKeys();

    request.onsuccess = () => {
      const deletePromises = request.result
        .filter((key) => key.includes(cacheKey))
        .map(
          (key) =>
            new Promise((resolve) => {
              const deleteRequest = store.delete(key);

              deleteRequest.onsuccess = () => resolve();
            }),
        );

      Promise.all(deletePromises).then(() => resolve());
    };
  });
};

export const createCachedApi = (namespace, ttl = 3600) => {
  const getCacheKey = (path) => `${namespace}:${path}`;

  const getResourcePaths = (path) => {
    const segments = path.split("/").filter(Boolean);

    const paths = [];

    for (let i = 0; i <= segments.length; i++) {
      const currentPath = "/" + segments.slice(0, i).join("/");

      paths.push(currentPath);
    }

    return paths;
  };

  const invalidateAll = async (path) => {
    const paths = getResourcePaths(path);

    await Promise.all(paths.map((path) => clearCache(getCacheKey(path))));
  };

  //

  const get = async (path, options = {}) => {
    const key = getCacheKey(path);

    const cached = await getFromCache(key);

    if (cached) return cached;

    const { data } = await api.get(path);

    await setInCache(key, { data }, options.ttl ?? ttl);

    return { data };
  };

  const post = async (path, payload) => {
    const { data } = await api.post(path, payload);

    await invalidateAll(path);

    return { data };
  };

  const put = async (path, payload) => {
    const { data } = await api.put(path, payload);

    await invalidateAll(path);

    return { data };
  };

  const del = async (path) => {
    const { data } = await api.delete(path);

    await invalidateAll(path);

    return { data };
  };

  return { get, post, put, del, invalidateAll };
};
