(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined'
    ? factory(exports, require('fs'), require('fs/promises'))
    : typeof define === 'function' && define.amd
      ? define(['exports', 'fs', 'fs/promises'], factory)
      : ((global =
          typeof globalThis !== 'undefined' ? globalThis : global || self),
        factory(
          (global.TinyBasePersisterFile = {}),
          global.fs,
          global['fs/promises'],
        ));
})(this, function (exports, fs, promises) {
  'use strict';

  const EMPTY_STRING = '';
  const UTF8 = 'utf8';
  const UNDEFINED = '\uFFFC';

  const isUndefined = (thing) => thing == void 0;
  const ifNotUndefined = (value, then, otherwise) =>
    isUndefined(value)
      ? otherwise == null
        ? void 0
        : otherwise()
      : then(value);
  const isArray = (thing) => Array.isArray(thing);
  const size = (arrayOrString) => arrayOrString.length;
  const errorNew = (message) => {
    throw new Error(message);
  };

  const arrayClear = (array, to) => array.splice(0, to);
  const arrayPush = (array, ...values) => array.push(...values);
  const arrayShift = (array) => array.shift();

  const object = Object;
  const getPrototypeOf = (obj) => object.getPrototypeOf(obj);
  const isObject = (obj) =>
    !isUndefined(obj) &&
    ifNotUndefined(
      getPrototypeOf(obj),
      (objPrototype) =>
        objPrototype == object.prototype ||
        isUndefined(getPrototypeOf(objPrototype)),

      /* istanbul ignore next */
      () => true,
    );
  const objIds = object.keys;
  const objFreeze = object.freeze;
  const objSize = (obj) => size(objIds(obj));
  const objIsEmpty = (obj) => isObject(obj) && objSize(obj) == 0;

  const jsonString = JSON.stringify;
  const jsonParse = JSON.parse;
  const jsonStringWithUndefined = (obj) =>
    jsonString(obj, (_key, value) => (value === void 0 ? UNDEFINED : value));
  const jsonParseWithUndefined = (str) =>
    jsonParse(str, (_key, value) => (value === UNDEFINED ? void 0 : value));

  const collHas = (coll, keyOrValue) => {
    var _a;
    return (_a = coll == null ? void 0 : coll.has(keyOrValue)) != null
      ? _a
      : false;
  };
  const collDel = (coll, keyOrValue) =>
    coll == null ? void 0 : coll.delete(keyOrValue);

  const mapNew = (entries) => new Map(entries);
  const mapGet = (map, key) => (map == null ? void 0 : map.get(key));
  const mapSet = (map, key, value) =>
    isUndefined(value)
      ? (collDel(map, key), map)
      : map == null
        ? void 0
        : map.set(key, value);
  const mapEnsure = (map, key, getDefaultValue, hadExistingValue) => {
    if (!collHas(map, key)) {
      mapSet(map, key, getDefaultValue());
    }
    return mapGet(map, key);
  };

  var __defProp = Object.defineProperty;
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
  var __defNormalProp = (obj, key, value) =>
    key in obj
      ? __defProp(obj, key, {
          enumerable: true,
          configurable: true,
          writable: true,
          value,
        })
      : (obj[key] = value);
  var __spreadValues = (a, b) => {
    for (var prop in b || (b = {}))
      if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]);
    if (__getOwnPropSymbols)
      for (var prop of __getOwnPropSymbols(b)) {
        if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]);
      }
    return a;
  };
  var __async$1 = (__this, __arguments, generator) => {
    return new Promise((resolve, reject) => {
      var fulfilled = (value) => {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      };
      var rejected = (value) => {
        try {
          step(generator.throw(value));
        } catch (e) {
          reject(e);
        }
      };
      var step = (x) =>
        x.done
          ? resolve(x.value)
          : Promise.resolve(x.value).then(fulfilled, rejected);
      step((generator = generator.apply(__this, __arguments)).next());
    });
  };
  const scheduleRunning = mapNew();
  const scheduleActions = mapNew();
  const getStoreFunctions = (persist = 1 /* StoreOnly */, store) =>
    persist != 1 /* StoreOnly */ && store.isMergeable()
      ? [
          1,
          store.getMergeableContent,
          store.getTransactionMergeableChanges,
          ([[changedTables], [changedValues]]) =>
            !objIsEmpty(changedTables) || !objIsEmpty(changedValues),
          store.setDefaultContent,
        ]
      : persist != 2 /* MergeableStoreOnly */
        ? [
            0,
            store.getContent,
            store.getTransactionChanges,
            ([changedTables, changedValues]) =>
              !objIsEmpty(changedTables) || !objIsEmpty(changedValues),
            store.setContent,
          ]
        : errorNew('Store type not supported by this Persister');
  const createCustomPersister = (
    store,
    getPersisted,
    setPersisted,
    addPersisterListener,
    delPersisterListener,
    onIgnoredError,
    persist,
    extra = {},
    scheduleId = [],
  ) => {
    let loadSave = 0;
    let loads = 0;
    let saves = 0;
    let action;
    let autoLoadHandle;
    let autoSaveListenerId;
    mapEnsure(scheduleRunning, scheduleId, () => 0);
    mapEnsure(scheduleActions, scheduleId, () => []);
    const [
      isMergeableStore,
      getContent,
      getChanges,
      hasChanges,
      setDefaultContent,
    ] = getStoreFunctions(persist, store);
    const run = () =>
      __async$1(void 0, null, function* () {
        /* istanbul ignore else */
        if (!mapGet(scheduleRunning, scheduleId)) {
          mapSet(scheduleRunning, scheduleId, 1);
          while (
            !isUndefined(
              (action = arrayShift(mapGet(scheduleActions, scheduleId))),
            )
          ) {
            try {
              yield action();
            } catch (error) {
              /* istanbul ignore next */
              onIgnoredError == null ? void 0 : onIgnoredError(error);
            }
          }
          mapSet(scheduleRunning, scheduleId, 0);
        }
      });
    const setContentOrChanges = (contentOrChanges) => {
      (isMergeableStore &&
        isArray(contentOrChanges == null ? void 0 : contentOrChanges[0])
        ? (contentOrChanges == null ? void 0 : contentOrChanges[2]) === 1
          ? store.applyMergeableChanges
          : store.setMergeableContent
        : (contentOrChanges == null ? void 0 : contentOrChanges[2]) === 1
          ? store.applyChanges
          : store.setContent)(contentOrChanges);
    };
    const load = (initialContent) =>
      __async$1(void 0, null, function* () {
        /* istanbul ignore else */
        if (loadSave != 2) {
          loadSave = 1;
          loads++;
          yield schedule(() =>
            __async$1(void 0, null, function* () {
              try {
                const content = yield getPersisted();
                if (isArray(content)) {
                  setContentOrChanges(content);
                } else {
                  errorNew(`Content is not an array ${content}`);
                }
              } catch (error) {
                onIgnoredError == null ? void 0 : onIgnoredError(error);
                if (initialContent) {
                  setDefaultContent(initialContent);
                }
              }
              loadSave = 0;
            }),
          );
        }
        return persister;
      });
    const startAutoLoad = (initialContent) =>
      __async$1(void 0, null, function* () {
        yield stopAutoLoad().load(initialContent);
        try {
          autoLoadHandle = yield addPersisterListener((content, changes) =>
            __async$1(void 0, null, function* () {
              if (changes || content) {
                /* istanbul ignore else */
                if (loadSave != 2) {
                  loadSave = 1;
                  loads++;
                  setContentOrChanges(changes != null ? changes : content);
                  loadSave = 0;
                }
              } else {
                yield load();
              }
            }),
          );
        } catch (error) {
          /* istanbul ignore next */
          onIgnoredError == null ? void 0 : onIgnoredError(error);
        }
        return persister;
      });
    const stopAutoLoad = () => {
      if (autoLoadHandle) {
        delPersisterListener(autoLoadHandle);
        autoLoadHandle = void 0;
      }
      return persister;
    };
    const isAutoLoading = () => !isUndefined(autoLoadHandle);
    const save = (changes) =>
      __async$1(void 0, null, function* () {
        /* istanbul ignore else */
        if (loadSave != 1) {
          loadSave = 2;
          saves++;
          yield schedule(() =>
            __async$1(void 0, null, function* () {
              try {
                yield setPersisted(getContent, changes);
              } catch (error) {
                /* istanbul ignore next */
                onIgnoredError == null ? void 0 : onIgnoredError(error);
              }
              loadSave = 0;
            }),
          );
        }
        return persister;
      });
    const startAutoSave = () =>
      __async$1(void 0, null, function* () {
        yield stopAutoSave().save();
        autoSaveListenerId = store.addDidFinishTransactionListener(() => {
          const changes = getChanges();
          if (hasChanges(changes)) {
            save(changes);
          }
        });
        return persister;
      });
    const stopAutoSave = () => {
      ifNotUndefined(autoSaveListenerId, store.delListener);
      autoSaveListenerId = void 0;
      return persister;
    };
    const isAutoSaving = () => !isUndefined(autoSaveListenerId);
    const schedule = (...actions) =>
      __async$1(void 0, null, function* () {
        arrayPush(mapGet(scheduleActions, scheduleId), ...actions);
        yield run();
        return persister;
      });
    const getStore = () => store;
    const destroy = () => {
      arrayClear(mapGet(scheduleActions, scheduleId));
      return stopAutoLoad().stopAutoSave();
    };
    const getStats = () => ({loads, saves});
    const persister = __spreadValues(
      {
        load,
        startAutoLoad,
        stopAutoLoad,
        isAutoLoading,
        save,
        startAutoSave,
        stopAutoSave,
        isAutoSaving,
        schedule,
        getStore,
        destroy,
        getStats,
      },
      extra,
    );
    return objFreeze(persister);
  };

  var __async = (__this, __arguments, generator) => {
    return new Promise((resolve, reject) => {
      var fulfilled = (value) => {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      };
      var rejected = (value) => {
        try {
          step(generator.throw(value));
        } catch (e) {
          reject(e);
        }
      };
      var step = (x) =>
        x.done
          ? resolve(x.value)
          : Promise.resolve(x.value).then(fulfilled, rejected);
      step((generator = generator.apply(__this, __arguments)).next());
    });
  };
  const createFilePersister = (store, filePath, onIgnoredError) => {
    const getPersisted = () =>
      __async(void 0, null, function* () {
        return jsonParseWithUndefined(yield promises.readFile(filePath, UTF8));
      });
    const setPersisted = (getContent) =>
      __async(void 0, null, function* () {
        return yield promises.writeFile(
          filePath,
          jsonStringWithUndefined(getContent()),
          UTF8,
        );
      });
    const addPersisterListener = (listener) => {
      if (!fs.existsSync(filePath)) {
        fs.writeFileSync(filePath, EMPTY_STRING, UTF8);
      }
      return fs.watch(filePath, () => listener());
    };
    const delPersisterListener = (watcher) =>
      watcher == null ? void 0 : watcher.close();
    return createCustomPersister(
      store,
      getPersisted,
      setPersisted,
      addPersisterListener,
      delPersisterListener,
      onIgnoredError,
      3,
      // StoreOrMergeableStore,
      {getFilePath: () => filePath},
    );
  };

  exports.createFilePersister = createFilePersister;
});
