/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { CommissioningClient } from "#behavior/system/commissioning/CommissioningClient.js";
import { ClientNetworkRuntime } from "#behavior/system/network/ClientNetworkRuntime.js";
import { NetworkClient } from "#behavior/system/network/NetworkClient.js";
import { ClientNodeEndpoints } from "#endpoint/properties/ClientNodeEndpoints.js";
import { EndpointInitializer } from "#endpoint/properties/EndpointInitializer.js";
import { EndpointLifecycle } from "#endpoint/properties/EndpointLifecycle.js";
import { EndpointType } from "#endpoint/type/EndpointType.js";
import { MutableEndpoint } from "#endpoint/type/MutableEndpoint.js";
import { ClientCacheBuffer } from "#storage/client/ClientCacheBuffer.js";
import { ClientNodeStore } from "#storage/client/ClientNodeStore.js";
import { RemoteWriter } from "#storage/client/RemoteWriter.js";
import { ServerNodeStore } from "#storage/server/ServerNodeStore.js";
import {
  DependencyLifecycleError,
  Diagnostic,
  ImplementationError,
  InternalError,
  Lifecycle,
  Logger
} from "@matter/general";
import { Matter } from "@matter/model";
import { OccurrenceManager, PeerAddress, PeerSet } from "@matter/protocol";
import { ClientEndpointInitializer } from "./client/ClientEndpointInitializer.js";
import { ClientNodeInteraction } from "./client/ClientNodeInteraction.js";
import { Node } from "./Node.js";
const logger = Logger.get("ClientNode");
class ClientNode extends Node {
  #matter;
  #interaction;
  #blockInteractions = false;
  #cachedPeerAddress;
  constructor(options) {
    const opts = {
      ...options,
      number: 0,
      // Create an unfrozen type so we can set the revision when we see the descriptor
      type: MutableEndpoint(ClientNode.RootEndpoint)
    };
    super(opts);
    this.env.close(OccurrenceManager);
    this.env.set(Node, this);
    this.env.set(ClientNode, this);
    this.#matter = options.matter ?? Matter;
  }
  nodeType = "client";
  /**
   * Model of Matter semantics understood by this node.
   *
   * Matter elements missing from this model will not support all functionality.
   */
  get matter() {
    return this.#matter;
  }
  get endpoints() {
    return new ClientNodeEndpoints(this);
  }
  get store() {
    return this.env.get(ServerNodeStore).clientStores.storeForNode(this);
  }
  // This needs to be sync to ensure a sync initialization
  initialize() {
    const store = this.store;
    this.env.set(ClientNodeStore, store);
    const initializer = new ClientEndpointInitializer(this);
    this.env.set(EndpointInitializer, initializer);
    store.write = RemoteWriter(this, initializer.structure);
    ClientCacheBuffer.configure(this, store);
    initializer.structure.loadCache(store.endpointStores);
    const promise = super.initialize();
    if (store.isPreexisting && promise !== void 0) {
      throw new InternalError("Unsupported async initialization detected when loading known peer");
    }
    return promise;
  }
  get owner() {
    const owner = super.owner;
    if (owner === void 0) {
      throw new InternalError("Client node is missing owner");
    }
    return super.owner;
  }
  set owner(owner) {
    super.owner = owner;
  }
  /**
   * Add this node to a fabric.
   */
  async commission(options) {
    await this.act("commission", (agent) => agent.commissioning.commission(options));
  }
  /**
   * Remove this node from the fabric (if commissioned) and locally.
   * This method tries to communicate with the device to decommission it properly and will fail if the device is
   * unreachable.
   * If you cannot reach the device, use {@link delete} instead.
   */
  async decommission() {
    this.lifecycle.change(EndpointLifecycle.Change.Destroying);
    if (this.lifecycle.isCommissioned) {
      this.statusUpdate("decommissioning");
      await this.act("decommission", (agent) => agent.commissioning.decommission());
    }
    await this.delete();
  }
  /**
   * Force-remove the node without first decommissioning.
   *
   * If the node is still available, you should use {@link decommission} to remove it properly from the fabric and only use
   * this method as fallback.  You should inform the user that manual factory-reset may be necessary.
   */
  async delete() {
    const address = this.peerAddress;
    await super.delete();
    if (address) {
      await this.env.maybeGet(PeerSet)?.get(address)?.delete();
    }
  }
  async erase() {
    await this.lifecycle.mutex.produce(this.eraseWithMutex.bind(this));
  }
  /**
   * Disable the node.
   *
   * This shuts down any active connections and prevents future connections until re-enabled.
   */
  async disable() {
    if (this.state.network.isDisabled) {
      return;
    }
    await this.lifecycle.mutex.produce(async () => {
      await this.cancelWithMutex();
      await this.setStateOf(NetworkClient, { isDisabled: true });
    });
  }
  /**
   * Enable the node.
   *
   * If the node is disabled but reachable, this brings it online.
   */
  async enable() {
    if (!this.state.network.isDisabled) {
      return;
    }
    await this.setStateOf(NetworkClient, { isDisabled: false });
    await this.start();
  }
  async eraseWithMutex() {
    await this.cancelWithMutex();
    await super.resetWithMutex();
    this.#cachedPeerAddress = void 0;
    await this.env.get(EndpointInitializer).eraseDescendant(this);
  }
  async resetWithMutex() {
    this.#cachedPeerAddress = void 0;
    await super.resetWithMutex();
  }
  createRuntime() {
    return new ClientNetworkRuntime(this);
  }
  async prepareRuntimeShutdown() {
  }
  async cancelWithMutex() {
    this.#blockInteractions = true;
    try {
      const interaction = this.#interaction;
      this.#interaction = void 0;
      await interaction?.close();
      await super.cancelWithMutex();
    } finally {
      this.#blockInteractions = false;
    }
  }
  get container() {
    return this.owner?.peers;
  }
  act(actorOrPurpose, actor) {
    if (this.construction.status === Lifecycle.Status.Inactive) {
      this.construction.start();
    }
    if (this.construction.status === Lifecycle.Status.Initializing) {
      return this.construction.then(() => super.act(actorOrPurpose, actor));
    }
    return super.act(actorOrPurpose, actor);
  }
  get interaction() {
    if (this.#interaction === void 0) {
      if (this.#blockInteractions) {
        throw new ImplementationError("Cannot access interaction of a shutting-down node");
      }
      this.#interaction = new ClientNodeInteraction(this);
    }
    return this.#interaction;
  }
  get peerAddress() {
    const state = this.behaviors.maybeStateOf("commissioning");
    if (state !== void 0) {
      const live = PeerAddress(state.peerAddress);
      this.#cachedPeerAddress = live;
      return live;
    }
    if (this.#cachedPeerAddress !== void 0) {
      return this.#cachedPeerAddress;
    }
    try {
      const stored = PeerAddress(this.store.storeForEndpoint(this).peerAddress);
      if (stored !== void 0) {
        this.#cachedPeerAddress = stored;
        return stored;
      }
    } catch (error) {
      DependencyLifecycleError.accept(error);
      logger.info("Cannot resolve peer address from storage:", error);
    }
    return void 0;
  }
  get identity() {
    const peerAddress = this.peerAddress;
    if (peerAddress) {
      return PeerAddress(peerAddress).toString();
    }
    return super.identity;
  }
  statusUpdate(message) {
    logger.info(Diagnostic.strong(this.toString()), message);
  }
}
((ClientNode2) => {
  ClientNode2.RootEndpoint = MutableEndpoint({
    ...Node.CommonRootEndpoint,
    deviceRevision: EndpointType.UNKNOWN_DEVICE_REVISION
  }).with(CommissioningClient, NetworkClient);
})(ClientNode || (ClientNode = {}));
Object.freeze(ClientNode.RootEndpoint);
export {
  ClientNode
};
//# sourceMappingURL=ClientNode.js.map
