/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { RemoteDescriptor } from "#behavior/system/commissioning/RemoteDescriptor.js";
import { ClientNodePhysicalProperties } from "#node/client/ClientNodePhysicalProperties.js";
import { Node } from "#node/Node.js";
import { ClientCacheBuffer } from "#storage/client/ClientCacheBuffer.js";
import { ChannelType, Observable, ServerAddress } from "@matter/general";
import { DatatypeModel, FieldElement } from "@matter/model";
import {
  ClientSubscription,
  OperationalAddress,
  PeerSet,
  Subscribe,
  SustainedSubscription
} from "@matter/protocol";
import { EventNumber } from "@matter/types";
import { NetworkBehavior } from "./NetworkBehavior.js";
class NetworkClient extends NetworkBehavior {
  initialize() {
    if (this.#node.nodeType === "group") {
      this.state.autoSubscribe = false;
      this.state.defaultSubscription = void 0;
    } else {
      this.reactTo(this.events.autoSubscribe$Changed, this.#syncAutoSubscribe, { offline: true });
      this.reactTo(this.events.defaultSubscription$Changed, this.#handleDefaultSubscriptionChange);
      this.reactTo(this.events.subscriptionStatusChanged, this.#flushCacheOnSubscribed);
    }
  }
  async startup() {
    const peerAddress = this.#node.state.commissioning.peerAddress;
    if (peerAddress !== void 0) {
      const peerSet = this.env.get(PeerSet);
      if (!peerSet.has(peerAddress)) {
        const ipAddresses = this.#node.state.commissioning.addresses?.filter((a) => ServerAddress.isIp(a)) ?? [];
        if (ipAddresses.length) {
          const operationalAddress = OperationalAddress.from(ServerAddress(ipAddresses[0]));
          if (operationalAddress !== void 0) {
            peerSet.addKnownPeer({
              address: peerAddress,
              operationalAddress,
              discoveryData: RemoteDescriptor.fromLongForm(this.#node.state.commissioning)
            });
          }
        }
      }
      const peer = peerSet.get(peerAddress);
      if (peer) {
        peer.protocol = this.#node.protocol;
        peer.physicalProperties = ClientNodePhysicalProperties(this.#node);
        const pref = this.state.transportPreference ?? this.#node.owner?.state?.network?.transportPreference;
        peer.transportPreference = pref === "tcp" ? ChannelType.TCP : void 0;
      }
    }
    await this.#syncAutoSubscribe();
    this.internal.runtime.isReady = true;
  }
  async #handleDefaultSubscriptionChange() {
    await this.#syncAutoSubscribe(false);
    if (this.state.autoSubscribe && !this.state.isDisabled) {
      await this.#syncAutoSubscribe(true);
    }
  }
  async #syncAutoSubscribe(desiredState = this.state.autoSubscribe) {
    if (!this.internal.runtime) {
      return;
    }
    const { isDisabled } = this.state;
    const subscriptionDesired = desiredState && !isDisabled;
    const isNewlyCommissioned = this.internal.isNewlyCommissioned;
    if (isNewlyCommissioned) {
      this.internal.isNewlyCommissioned = false;
    }
    if (subscriptionDesired === !!this.internal.activeSubscription) {
      return;
    }
    if (subscriptionDesired) {
      const subscribe = Subscribe({
        fabricFilter: true,
        keepSubscriptions: false,
        attributes: [{}],
        events: [{ isUrgent: true }],
        ...this.state.defaultSubscription
      });
      let bootstrapWithRead;
      if (isNewlyCommissioned) {
        for await (const _chunk of this.#node.interaction.read(subscribe)) ;
        bootstrapWithRead = false;
      } else {
        bootstrapWithRead = true;
      }
      this.internal.activeSubscription = await this.#node.interaction.subscribe({
        ...subscribe,
        sustain: true,
        eventFilters: [{ eventMin: this.state.maxEventNumber + 1n }],
        bootstrapWithRead,
        updated: async (update) => {
          for await (const _chunk of update) ;
          if (this.internal.activeSubscription?.subscriptionId !== ClientSubscription.NO_SUBSCRIPTION) {
            this.events.subscriptionAlive.emit();
          }
        },
        closed: () => {
          if (!(this.internal.activeSubscription instanceof SustainedSubscription)) {
            this.events.subscriptionStatusChanged.emit(false);
          }
          this.internal.activeSubscription = void 0;
        }
      });
      if (this.internal.activeSubscription instanceof SustainedSubscription) {
        this.internal.activeSubscription.active.on(
          (isActive) => this.events.subscriptionStatusChanged.emit(isActive)
        );
      } else {
        this.events.subscriptionStatusChanged.emit(true);
      }
    } else {
      this.internal.activeSubscription?.close();
      this.internal.activeSubscription = void 0;
    }
  }
  /**
   * Returns if we actually have an active and established subscription
   * When a Sustained subscription is used we return the active value, otherwise we know when the subscription instance
   * is set.
   */
  get subscriptionActive() {
    const activeSubscription = this.internal.activeSubscription;
    if (activeSubscription === void 0) {
      return false;
    }
    return activeSubscription instanceof SustainedSubscription ? activeSubscription.active.value : true;
  }
  async [Symbol.asyncDispose]() {
    this.internal.activeSubscription?.close();
    this.internal.activeSubscription = void 0;
  }
  #flushCacheOnSubscribed(isActive) {
    if (isActive) {
      const parentEnv = this.#node.owner.env;
      if (parentEnv.has(ClientCacheBuffer)) {
        parentEnv.get(ClientCacheBuffer).initiateFlush();
      }
    }
  }
  get #node() {
    return this.env.get(Node);
  }
  /**
   * Define logical schema for fields that should persist.
   */
  static schema = new DatatypeModel({
    name: "NetworkState",
    type: "struct",
    children: [
      FieldElement({
        name: "defaultSubscription",
        type: "any",
        default: { type: "properties", properties: {} },
        conformance: "O",
        quality: "N"
      }),
      FieldElement({
        name: "isDisabled",
        type: "bool",
        quality: "N",
        default: false
      }),
      FieldElement({
        name: "autoSubscribe",
        type: "bool",
        quality: "N",
        default: false
      }),
      FieldElement({
        name: "maxEventNumber",
        type: "event-no",
        quality: "N",
        default: EventNumber(0)
      }),
      FieldElement({
        name: "transportPreference",
        type: "string",
        conformance: "O",
        quality: "N"
      })
    ]
  });
}
((NetworkClient2) => {
  class Internal extends NetworkBehavior.Internal {
    /**
     * The active default subscription.
     */
    activeSubscription;
    /**
     * Indicates the node is newly commissioned.
     *
     * When newly commissioned, if automatic subscription is enabled we perform a read before returning from
     * {@link NetworkClient#startup}.  This ensures we have a complete snapshot of the node's state when
     * commissioning logic is complete.
     */
    isNewlyCommissioned = false;
  }
  NetworkClient2.Internal = Internal;
  class State extends NetworkBehavior.State {
    /**
     * This subscription defines the default set of attributes and events to which the node will automatically
     * subscribe when started, if autoSubscribe is true. Alternatively, also just Subscribe.Options can be provided
     * to adjust chosen default subscription parameters (see below).
     *
     * The default subscription is a wildcard for all attributes of the node.  You can set to undefined or filter
     * the fields and values but only values selected by this subscription will update automatically.
     *
     * The default subscription updates automatically if you change this property.
     */
    defaultSubscription;
    /**
     * Represents the current operational network state of the node. When true the node is enabled and operational.
     * When false the node is disabled and not operational.
     *
     * This state can be changed at any time to enable or disable the node.
     */
    isDisabled = false;
    /**
     * If true, automatically subscribe to the provided default subscription (or all attributes and events) when
     * the node is started. If false, do not automatically subscribe.
     *
     * The subscription will activate or deactivate automatically if you change this property.
     *
     * Newly commissioned nodes default to true.
     */
    autoSubscribe = false;
    /**
     * The highest event number seen from this node for the default read/subscription.
     */
    maxEventNumber = EventNumber(0);
    /**
     * Per-peer transport preference override.
     * If not set, inherits from NetworkServer.transportPreference.
     */
    transportPreference;
  }
  NetworkClient2.State = State;
  class Events extends NetworkBehavior.Events {
    autoSubscribe$Changed = new Observable();
    defaultSubscription$Changed = new Observable();
    subscriptionStatusChanged = new Observable();
    subscriptionAlive = new Observable();
  }
  NetworkClient2.Events = Events;
})(NetworkClient || (NetworkClient = {}));
export {
  NetworkClient
};
//# sourceMappingURL=NetworkClient.js.map
