import { ThrottledDelayer } from "../../../common/async.mjs";
import { Emitter, Event } from "../../../common/event.mjs";
import { Disposable } from "../../../common/lifecycle.mjs";
import { isUndefinedOrNull } from "../../../common/types.mjs";
var __awaiter = globalThis && globalThis.__awaiter || function(thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function(resolve) {
      resolve(value);
    });
  }
  return new (P || (P = Promise))(function(resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }
    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }
    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }
    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
var StorageState;
(function(StorageState2) {
  StorageState2[StorageState2["None"] = 0] = "None";
  StorageState2[StorageState2["Initialized"] = 1] = "Initialized";
  StorageState2[StorageState2["Closed"] = 2] = "Closed";
})(StorageState || (StorageState = {}));
class Storage extends Disposable {
  constructor(database, options = /* @__PURE__ */ Object.create(null)) {
    super();
    this.database = database;
    this.options = options;
    this._onDidChangeStorage = this._register(new Emitter());
    this.onDidChangeStorage = this._onDidChangeStorage.event;
    this.state = StorageState.None;
    this.cache = /* @__PURE__ */ new Map();
    this.flushDelayer = new ThrottledDelayer(Storage.DEFAULT_FLUSH_DELAY);
    this.pendingDeletes = /* @__PURE__ */ new Set();
    this.pendingInserts = /* @__PURE__ */ new Map();
    this.whenFlushedCallbacks = [];
    this.registerListeners();
  }
  registerListeners() {
    this._register(this.database.onDidChangeItemsExternal((e) => this.onDidChangeItemsExternal(e)));
  }
  onDidChangeItemsExternal(e) {
    var _a, _b;
    (_a = e.changed) === null || _a === void 0 ? void 0 : _a.forEach((value, key) => this.accept(key, value));
    (_b = e.deleted) === null || _b === void 0 ? void 0 : _b.forEach((key) => this.accept(key, void 0));
  }
  accept(key, value) {
    if (this.state === StorageState.Closed) {
      return;
    }
    let changed = false;
    if (isUndefinedOrNull(value)) {
      changed = this.cache.delete(key);
    } else {
      const currentValue = this.cache.get(key);
      if (currentValue !== value) {
        this.cache.set(key, value);
        changed = true;
      }
    }
    if (changed) {
      this._onDidChangeStorage.fire(key);
    }
  }
  get(key, fallbackValue) {
    const value = this.cache.get(key);
    if (isUndefinedOrNull(value)) {
      return fallbackValue;
    }
    return value;
  }
  getBoolean(key, fallbackValue) {
    const value = this.get(key);
    if (isUndefinedOrNull(value)) {
      return fallbackValue;
    }
    return value === "true";
  }
  getNumber(key, fallbackValue) {
    const value = this.get(key);
    if (isUndefinedOrNull(value)) {
      return fallbackValue;
    }
    return parseInt(value, 10);
  }
  set(key, value) {
    return __awaiter(this, void 0, void 0, function* () {
      if (this.state === StorageState.Closed) {
        return;
      }
      if (isUndefinedOrNull(value)) {
        return this.delete(key);
      }
      const valueStr = String(value);
      const currentValue = this.cache.get(key);
      if (currentValue === valueStr) {
        return;
      }
      this.cache.set(key, valueStr);
      this.pendingInserts.set(key, valueStr);
      this.pendingDeletes.delete(key);
      this._onDidChangeStorage.fire(key);
      return this.doFlush();
    });
  }
  delete(key) {
    return __awaiter(this, void 0, void 0, function* () {
      if (this.state === StorageState.Closed) {
        return;
      }
      const wasDeleted = this.cache.delete(key);
      if (!wasDeleted) {
        return;
      }
      if (!this.pendingDeletes.has(key)) {
        this.pendingDeletes.add(key);
      }
      this.pendingInserts.delete(key);
      this._onDidChangeStorage.fire(key);
      return this.doFlush();
    });
  }
  get hasPending() {
    return this.pendingInserts.size > 0 || this.pendingDeletes.size > 0;
  }
  flushPending() {
    return __awaiter(this, void 0, void 0, function* () {
      if (!this.hasPending) {
        return;
      }
      const updateRequest = { insert: this.pendingInserts, delete: this.pendingDeletes };
      this.pendingDeletes = /* @__PURE__ */ new Set();
      this.pendingInserts = /* @__PURE__ */ new Map();
      return this.database.updateItems(updateRequest).finally(() => {
        var _a;
        if (!this.hasPending) {
          while (this.whenFlushedCallbacks.length) {
            (_a = this.whenFlushedCallbacks.pop()) === null || _a === void 0 ? void 0 : _a();
          }
        }
      });
    });
  }
  doFlush(delay) {
    return __awaiter(this, void 0, void 0, function* () {
      return this.flushDelayer.trigger(() => this.flushPending(), delay);
    });
  }
  dispose() {
    this.flushDelayer.dispose();
    super.dispose();
  }
}
Storage.DEFAULT_FLUSH_DELAY = 100;
class InMemoryStorageDatabase {
  constructor() {
    this.onDidChangeItemsExternal = Event.None;
    this.items = /* @__PURE__ */ new Map();
  }
  updateItems(request) {
    return __awaiter(this, void 0, void 0, function* () {
      if (request.insert) {
        request.insert.forEach((value, key) => this.items.set(key, value));
      }
      if (request.delete) {
        request.delete.forEach((key) => this.items.delete(key));
      }
    });
  }
}
export {
  InMemoryStorageDatabase,
  Storage
};
