import { assert } from "../../../shared/src/asserts.js";
import { initBgIntervalProcess } from "../bg-interval.js";
import { StoreImpl } from "../dag/store-impl.js";
import { addDeletedClients, mergeDeletedClients, normalizeDeletedClients, getDeletedClients } from "../deleted-clients.js";
import { Latest, DD31, V6, V7 } from "../format-version-enum.js";
import { getKVStoreProvider } from "../get-kv-store-provider.js";
import { newRandomHash, assertHash } from "../hash.js";
import { createLogContext } from "../log-options.js";
import { withWrite, withRead } from "../with-transactions.js";
import { getClientGroups, clientGroupHasPendingMutations } from "./client-groups.js";
import { getClients } from "./clients.js";
import { IDBDatabasesStore } from "./idb-databases-store.js";
const COLLECT_IDB_INTERVAL = 12 * 60 * 60 * 1e3;
const INITIAL_COLLECT_IDB_DELAY = 5 * 60 * 1e3;
function initCollectIDBDatabases(idbDatabasesStore, kvStoreProvider, collectInterval, initialCollectDelay, maxAge, enableMutationRecovery, onClientsDeleted, lc, signal) {
  let initial = true;
  initBgIntervalProcess(
    "CollectIDBDatabases",
    async () => {
      await collectIDBDatabases(
        idbDatabasesStore,
        Date.now(),
        maxAge,
        kvStoreProvider,
        enableMutationRecovery,
        onClientsDeleted
      );
    },
    () => {
      if (initial) {
        initial = false;
        return initialCollectDelay;
      }
      return collectInterval;
    },
    lc,
    signal
  );
}
async function collectIDBDatabases(idbDatabasesStore, now, maxAge, kvStoreProvider, enableMutationRecovery, onClientsDeleted, newDagStore = defaultNewDagStore) {
  const databases = await idbDatabasesStore.getDatabases();
  const dbs = Object.values(databases);
  const collectResults = await Promise.all(
    dbs.map(
      async (db) => [
        db.name,
        await gatherDatabaseInfoForCollect(
          db,
          now,
          maxAge,
          enableMutationRecovery,
          kvStoreProvider.create,
          newDagStore
        )
      ]
    )
  );
  const dbNamesToRemove = [];
  const dbNamesToKeep = [];
  const deletedClientsToRemove = [];
  for (const [dbName, [canCollect, deletedClients]] of collectResults) {
    if (canCollect) {
      dbNamesToRemove.push(dbName);
      deletedClientsToRemove.push(...deletedClients);
    } else {
      dbNamesToKeep.push(dbName);
    }
  }
  const { errors } = await dropDatabases(
    idbDatabasesStore,
    dbNamesToRemove,
    kvStoreProvider.drop
  );
  if (errors.length) {
    throw errors[0];
  }
  if (deletedClientsToRemove.length > 0) {
    let allDeletedClients = deletedClientsToRemove;
    for (const name of dbNamesToKeep) {
      await withWrite(
        newDagStore(name, kvStoreProvider.create),
        async (dagWrite) => {
          const newDeletedClients = await addDeletedClients(
            dagWrite,
            deletedClientsToRemove
          );
          allDeletedClients = mergeDeletedClients(
            allDeletedClients,
            newDeletedClients
          );
        }
      );
    }
    const normalizedDeletedClients = normalizeDeletedClients(allDeletedClients);
    await onClientsDeleted(normalizedDeletedClients);
  }
}
async function dropDatabaseInternal(name, idbDatabasesStore, kvDropStore) {
  await kvDropStore(name);
  await idbDatabasesStore.deleteDatabases([name]);
}
async function dropDatabases(idbDatabasesStore, namesToRemove, kvDropStore) {
  const dropStoreResults = await Promise.allSettled(
    namesToRemove.map(async (name) => {
      await dropDatabaseInternal(name, idbDatabasesStore, kvDropStore);
      return name;
    })
  );
  const dropped = [];
  const errors = [];
  for (const result of dropStoreResults) {
    if (result.status === "fulfilled") {
      dropped.push(result.value);
    } else {
      errors.push(result.reason);
    }
  }
  return { dropped, errors };
}
function defaultNewDagStore(name, kvCreateStore) {
  const perKvStore = kvCreateStore(name);
  return new StoreImpl(perKvStore, newRandomHash, assertHash);
}
function gatherDatabaseInfoForCollect(db, now, maxAge, enableMutationRecovery, kvCreateStore, newDagStore) {
  if (db.replicacheFormatVersion > Latest) {
    return [false];
  }
  assert(
    db.replicacheFormatVersion === DD31 || db.replicacheFormatVersion === V6 || db.replicacheFormatVersion === V7
  );
  return canDatabaseBeCollectedAndGetDeletedClientIDs(
    enableMutationRecovery,
    newDagStore(db.name, kvCreateStore),
    now,
    maxAge
  );
}
async function dropDatabase(dbName, opts) {
  const logContext = createLogContext(opts?.logLevel, opts?.logSinks, {
    dropDatabase: void 0
  });
  const kvStoreProvider = getKVStoreProvider(logContext, opts?.kvStore);
  await dropDatabaseInternal(
    dbName,
    new IDBDatabasesStore(kvStoreProvider.create),
    kvStoreProvider.drop
  );
}
async function dropAllDatabases(opts) {
  const logContext = createLogContext(opts?.logLevel, opts?.logSinks, {
    dropAllDatabases: void 0
  });
  const kvStoreProvider = getKVStoreProvider(logContext, opts?.kvStore);
  const store = new IDBDatabasesStore(kvStoreProvider.create);
  const databases = await store.getDatabases();
  const dbNames = Object.values(databases).map((db) => db.name);
  return dropDatabases(store, dbNames, kvStoreProvider.drop);
}
function canDatabaseBeCollectedAndGetDeletedClientIDs(enableMutationRecovery, perdag, now, maxAge) {
  return withRead(perdag, async (read) => {
    if (enableMutationRecovery) {
      const clientGroups = await getClientGroups(read);
      for (const clientGroup of clientGroups.values()) {
        if (clientGroupHasPendingMutations(clientGroup)) {
          return [false];
        }
      }
    }
    const clients = await getClients(read);
    for (const [, client] of clients) {
      if (now - client.heartbeatTimestampMs < maxAge) {
        return [false];
      }
    }
    const existingDeletedClients = await getDeletedClients(read);
    const deletedClients = [...existingDeletedClients];
    for (const [clientID, client] of clients) {
      deletedClients.push({
        clientID,
        clientGroupID: client.clientGroupID
      });
    }
    return [true, deletedClients];
  });
}
export {
  COLLECT_IDB_INTERVAL,
  INITIAL_COLLECT_IDB_DELAY,
  collectIDBDatabases,
  dropAllDatabases,
  dropDatabase,
  initCollectIDBDatabases
};
//# sourceMappingURL=collect-idb-databases.js.map
