/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { IdentityConflictError } from "#node/server/IdentityService.js";
import { ImplementationError, InternalError, Logger } from "@matter/general";
import { ServerEndpointStore } from "./ServerEndpointStore.js";
const NEXT_NUMBER_KEY = "__nextNumber__";
const logger = Logger.get("EndpointStoreService");
class ServerEndpointStores {
  #storage;
  #allocatedNumbers = /* @__PURE__ */ new Set();
  #preAllocatedNumbers = /* @__PURE__ */ new Set();
  #persistedNextNumber;
  #numbersPersisted;
  #numbersToPersist;
  #nextNumber = 1;
  #root;
  async load(storage) {
    this.#storage = storage;
    this.#nextNumber = await this.#storage.get(NEXT_NUMBER_KEY, 1) % 65535;
    if (this.#nextNumber < 1) {
      this.#nextNumber = 1;
    } else {
      this.#persistedNextNumber = this.#nextNumber;
    }
    this.#root = new ServerEndpointStore(this.#storage);
    await this.#root.load();
    this.#root.visit(({ number }) => {
      if (number !== void 0) {
        this.#preAllocatedNumbers.add(number);
      }
    });
  }
  async erase() {
    const storage = this.#storage;
    if (!storage) {
      return;
    }
    await this.#awaitPendingPersistence();
    this.#storage = void 0;
    await storage.clearAll();
    this.#allocatedNumbers = /* @__PURE__ */ new Set();
    this.#nextNumber = 1;
    this.#persistedNextNumber = void 0;
    await this.load(storage);
  }
  async close() {
    await this.#awaitPendingPersistence();
  }
  async #awaitPendingPersistence() {
    if (this.#numbersPersisted) {
      await this.#numbersPersisted;
    }
  }
  #premature(what) {
    throw new InternalError(`${what} prior to storage initialization`);
  }
  /**
   * Allocate an endpoint number.
   *
   * Either allocates a new number for a {@link Endpoint} or reserves the endpoint's number.  If the {@link Endpoint}
   * already has a number but it is allocated to a different endpoint it is an error.
   *
   * We must persist the assigned number and next endpoint number.  We are fairly resilient to the small chance that
   * persistence fails so we persist lazily and return synchronously.
   */
  assignNumber(endpoint) {
    const store = this.storeForEndpoint(endpoint);
    if (endpoint.lifecycle.hasNumber) {
      if (this.#allocatedNumbers.has(endpoint.number)) {
        if (store.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(store);
  }
  /**
   * Obtain the store for a single {@link Endpoint}.
   *
   * These stores are cached internally by ID.
   */
  storeForEndpoint(endpoint) {
    if (endpoint.maybeNumber === 0) {
      if (this.#root === void 0) {
        this.#premature("Root store accessed");
      }
      return 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);
  }
  /**
   * Deactivate the store for a single {@link Endpoint}. This puts the endpoint number back into pre-allocated state
   */
  deactivateStoreForEndpoint(endpoint) {
    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);
  }
  /**
   * Erase storage for a single {@link Endpoint}.
   */
  async eraseStoreForEndpoint(endpoint) {
    if (!endpoint.owner) {
      throw new InternalError(
        "Endpoint storage inaccessible because endpoint is not a node and is not owned by another endpoint"
      );
    }
    await this.storeForEndpoint(endpoint.owner).eraseChildStoreFor(endpoint);
    this.#allocatedNumbers.delete(endpoint.number);
    this.#preAllocatedNumbers.delete(endpoint.number);
  }
  /**
   * Lazily persist a newly allocated number and the next number.
   */
  #persistNumber(store) {
    if (this.#numbersToPersist) {
      this.#numbersToPersist.push(store);
      return;
    }
    this.#numbersToPersist = [store];
    const numberPersister = async () => {
      const numbersToPersist = this.#numbersToPersist;
      if (!numbersToPersist) {
        return;
      }
      this.#numbersToPersist = void 0;
      for (const store2 of numbersToPersist) {
        await store2.saveNumber();
      }
      if (this.#nextNumber !== this.#persistedNextNumber) {
        if (this.#storage === void 0) {
          this.#premature("Number persistence");
        }
        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 {
  ServerEndpointStores
};
//# sourceMappingURL=ServerEndpointStores.js.map
