/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Construction, ImplementationError, InternalError, Lifecycle, Logger, asyncNew } from "#general";
import { EndpointStore } from "../../endpoint/storage/EndpointStore.js";
import { IdentityConflictError } from "../server/IdentityService.js";
const NEXT_NUMBER_KEY = "__nextNumber__";
const logger = Logger.get("EndpointStoreService");
class EndpointStoreService {
}
class EndpointStoreFactory extends EndpointStoreService {
  #storage;
  #allocatedNumbers = /* @__PURE__ */ new Set();
  #preAllocatedNumbers = /* @__PURE__ */ new Set();
  #construction;
  #persistedNextNumber;
  #numbersPersisted;
  #numbersToPersist;
  #nextNumber;
  #defaultNextNumber;
  #root;
  get construction() {
    return this.#construction;
  }
  constructor({ storage, nextNumber }) {
    super();
    this.#storage = storage;
    this.#defaultNextNumber = nextNumber ?? 1;
    this.#construction = Construction(this);
    this.#construction.start();
  }
  async [Construction.construct]() {
    this.#nextNumber = await this.#storage.get(NEXT_NUMBER_KEY, this.#defaultNextNumber) % 65535;
    if (!this.#nextNumber) {
      this.#nextNumber = 1;
    } else {
      this.#persistedNextNumber = this.#nextNumber;
    }
    this.#root = await asyncNew(EndpointStore, this.#storage);
    this.#root.visit(({ number }) => {
      if (number !== void 0) {
        this.#preAllocatedNumbers.add(number);
      }
    });
  }
  async erase() {
    if (this.#numbersPersisted) {
      await this.#numbersPersisted;
    }
    await this.#storage.clearAll();
    this.#construction.setStatus(Lifecycle.Status.Inactive);
    this.#construction.start();
    this.#allocatedNumbers = /* @__PURE__ */ new Set();
    this.#nextNumber = (this.#defaultNextNumber ?? 1) % 65535;
    this.#persistedNextNumber = void 0;
    this.#root = new EndpointStore(this.#storage, false);
    await this.construction;
  }
  async close() {
    if (this.#numbersPersisted) {
      await this.#numbersPersisted;
    }
  }
  assignNumber(endpoint) {
    if (this.#nextNumber === void 0) {
      throw new InternalError("Endpoint number assigned prior to store initialization");
    }
    this.#construction.assert();
    const store = this.storeForEndpoint(endpoint);
    if (endpoint.lifecycle.hasNumber) {
      if (this.#allocatedNumbers.has(endpoint.number)) {
        if (this.storeForEndpoint(endpoint).number !== endpoint.number) {
          throw new IdentityConflictError(
            `Endpoint ${endpoint.id} number ${endpoint.number} is allocated to another endpoint`
          );
        }
        return;
      }
    } else {
      const knownNumber = store.number;
      if (knownNumber) {
        if (this.#allocatedNumbers.has(knownNumber)) {
          logger.warn(`Stored number ${knownNumber} is already allocated to another endpoint, ignoring`);
        } else {
          this.#preAllocatedNumbers.delete(knownNumber);
          this.#allocatedNumbers.add(knownNumber);
          endpoint.number = knownNumber;
          return;
        }
      }
      const startNumber = this.#nextNumber;
      while (this.#nextNumber < 1 || this.#allocatedNumbers.has(this.#nextNumber) || this.#preAllocatedNumbers.has(this.#nextNumber)) {
        this.#nextNumber = (this.#nextNumber + 1) % 65535;
        if (this.#nextNumber === startNumber) {
          throw new ImplementationError(
            "Cannot add additional endpoints because endpoint numbers are exhausted"
          );
        }
      }
      const number = this.#nextNumber++;
      endpoint.number = number;
    }
    this.#allocatedNumbers.add(endpoint.number);
    store.number = endpoint.number;
    this.#persistNumber(endpoint);
  }
  storeForEndpoint(endpoint) {
    this.#construction.assert();
    if (endpoint.maybeNumber === 0) {
      return this.#construction.assert("root node store", this.#root);
    }
    if (!endpoint.owner) {
      throw new InternalError(
        "Endpoint storage inaccessible because endpoint is not a node and is not owned by another endpoint"
      );
    }
    return this.storeForEndpoint(endpoint.owner).childStoreFor(endpoint);
  }
  deactivateStoreForEndpoint(endpoint) {
    this.#construction.assert();
    if (endpoint.maybeNumber === 0) {
      throw new InternalError("Cannot deactivate root node store");
    }
    if (!this.#allocatedNumbers.has(endpoint.number)) {
      return;
    }
    this.#allocatedNumbers.delete(endpoint.number);
    this.#preAllocatedNumbers.add(endpoint.number);
  }
  async eraseStoreForEndpoint(endpoint) {
    this.#construction.assert();
    const store = this.storeForEndpoint(endpoint);
    await store.erase();
    this.#allocatedNumbers.delete(endpoint.number);
    this.#preAllocatedNumbers.delete(endpoint.number);
  }
  /**
   * Lazily persist a newly allocated number and the next number.
   */
  #persistNumber(endpoint) {
    if (this.#numbersToPersist) {
      this.#numbersToPersist.push(endpoint);
      return;
    }
    this.#numbersToPersist = [endpoint];
    const numberPersister = async () => {
      await this.#construction;
      const numbersToPersist = this.#numbersToPersist;
      if (!numbersToPersist) {
        return;
      }
      this.#numbersToPersist = void 0;
      for (const endpoint2 of numbersToPersist) {
        const store = this.storeForEndpoint(endpoint2);
        await store.saveNumber();
      }
      if (this.#nextNumber !== this.#persistedNextNumber) {
        await this.#storage.set(NEXT_NUMBER_KEY, this.#nextNumber);
        this.#persistedNextNumber = this.#nextNumber;
      }
    };
    if (this.#numbersPersisted) {
      this.#numbersPersisted = this.#numbersPersisted.then(numberPersister);
    } else {
      this.#numbersPersisted = numberPersister();
    }
  }
}
export {
  EndpointStoreFactory,
  EndpointStoreService
};
//# sourceMappingURL=EndpointStoreService.js.map
