/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  ImplementationError,
  InterfaceType,
  InternalError,
  Network,
  UdpInterface
} from "#general";
import { TransactionalInteractionServer } from "#node/server/TransactionalInteractionServer.js";
import { NodeStore } from "#node/storage/NodeStore.js";
import {
  Ble,
  DeviceAdvertiser,
  DeviceCommissioner,
  ExchangeManager,
  FabricManager,
  MatterDevice,
  MdnsService,
  SessionManager
} from "#protocol";
import { CommissioningBehavior } from "../commissioning/CommissioningBehavior.js";
import { ProductDescriptionServer } from "../product-description/ProductDescriptionServer.js";
import { SessionsBehavior } from "../sessions/SessionsBehavior.js";
import { NetworkRuntime } from "./NetworkRuntime.js";
function convertNetworkEnvironmentType(type) {
  const convertedType = typeof type === "string" ? InterfaceType[type] : type;
  if (typeof convertedType !== "number" || convertedType < 1 || convertedType > 4) {
    return void 0;
  }
  return convertedType;
}
class ServerNetworkRuntime extends NetworkRuntime {
  #interactionServer;
  #matterDevice;
  #mdnsBroadcaster;
  #primaryNetInterface;
  #bleBroadcaster;
  #bleTransport;
  #commissionedListener;
  get owner() {
    return super.owner;
  }
  /**
   * Access the MDNS broadcaster for the node.
   */
  get mdnsBroadcaster() {
    if (!this.#mdnsBroadcaster) {
      this.#mdnsBroadcaster = this.owner.env.get(MdnsService).createInstanceBroadcaster(this.owner.state.network.operationalPort);
    }
    return this.#mdnsBroadcaster;
  }
  get networkInterfaceConfiguration() {
    const interfaceConfig = this.owner.env.vars.get(
      "network.interface",
      {}
    );
    return Object.entries(interfaceConfig).map(([name, { type }]) => ({
      name,
      type: convertNetworkEnvironmentType(type)
    }));
  }
  async getNetworkInterfaces() {
    const network = this.owner.env.get(Network);
    const interfaces = await network.getNetInterfaces(this.networkInterfaceConfiguration);
    const interfaceDetails = new Array();
    for (const { name, type } of interfaces) {
      const details = await network.getIpMac(name);
      if (details !== void 0) {
        interfaceDetails.push({ name, type, ...details });
      }
    }
    return interfaceDetails;
  }
  openAdvertisementWindow() {
    if (!this.#matterDevice) {
      throw new InternalError("Server runtime device instance is missing");
    }
    return this.#matterDevice.startAnnouncement();
  }
  advertiseNow() {
    if (!this.#matterDevice) {
      throw new InternalError("Server runtime device instance is missing");
    }
    return this.#matterDevice.announce(true);
  }
  /**
   * The IPv6 {@link UdpInterface}. We create this interface independently of the server so the OS can select a port
   * before we are fully online.
   */
  async getPrimaryNetInterface() {
    if (this.#primaryNetInterface === void 0) {
      const port = this.owner.state.network.port;
      this.#primaryNetInterface = await UdpInterface.create(
        this.owner.env.get(Network),
        "udp6",
        port ? port : void 0,
        this.owner.state.network.listeningAddressIpv6
      );
      await this.owner.set({ network: { operationalPort: this.#primaryNetInterface.port } });
    }
    return this.#primaryNetInterface;
  }
  /**
   * A BLE broadcaster.
   */
  get bleBroadcaster() {
    if (this.#bleBroadcaster === void 0) {
      const bleData = this.owner.state.commissioning.additionalBleAdvertisementData;
      this.#bleBroadcaster = Ble.get().getBleBroadcaster(bleData);
    }
    return this.#bleBroadcaster;
  }
  /**
   * A BLE transport.
   */
  get bleTransport() {
    if (this.#bleTransport === void 0) {
      this.#bleTransport = Ble.get().getBlePeripheralInterface();
    }
    return this.#bleTransport;
  }
  /**
   * Add transports to the {@link MatterDevice}.
   */
  async addTransports(device) {
    device.addTransportInterface(await this.getPrimaryNetInterface());
    const netconf = this.owner.state.network;
    if (netconf.ipv4) {
      device.addTransportInterface(
        await UdpInterface.create(
          this.owner.env.get(Network),
          "udp4",
          netconf.port,
          netconf.listeningAddressIpv4
        )
      );
    }
    if (netconf.ble) {
      device.addTransportInterface(this.bleTransport);
    }
  }
  /**
   * Add broadcasters to the {@link MatterDevice}.
   */
  async addBroadcasters(device) {
    const isCommissioned = !!this.#commissionedFabrics;
    let discoveryCapabilities = this.owner.state.network.discoveryCapabilities;
    if (isCommissioned) {
      discoveryCapabilities = { onIpNetwork: true };
    }
    if (discoveryCapabilities.onIpNetwork) {
      device.addBroadcaster(this.mdnsBroadcaster);
    }
    if (discoveryCapabilities.ble) {
      device.addBroadcaster(this.bleBroadcaster);
    }
  }
  /**
   * When the first Fabric gets added we need to enable MDNS broadcasting.
   */
  enableMdnsBroadcasting() {
    const mdnsBroadcaster = this.mdnsBroadcaster;
    if (!this.#matterDevice?.hasBroadcaster(mdnsBroadcaster)) {
      this.#matterDevice?.addBroadcaster(mdnsBroadcaster);
    }
  }
  /**
   * On commission we turn off bluetooth and join the IP network if we haven't already.
   *
   * On decommission we're destroyed so don't need to handle that case.
   */
  endUncommissionedMode() {
    if (this.#bleBroadcaster) {
      this.owner.env.runtime.add(this.#removeBleBroadcaster(this.#bleBroadcaster));
      this.#bleBroadcaster = void 0;
    }
    if (this.#bleTransport) {
      this.owner.env.runtime.add(this.#removeBleTransport(this.#bleTransport));
      this.#bleTransport = void 0;
    }
  }
  async #removeBleBroadcaster(bleBroadcaster) {
    await this.#matterDevice?.deleteBroadcaster(bleBroadcaster);
    await bleBroadcaster.close();
  }
  async #removeBleTransport(bleTransport) {
    await this.#matterDevice?.deleteTransportInterface(bleTransport);
    await bleTransport.close();
  }
  /**
   * Expose the internal InteractionServer for testing.
   */
  get interactionServer() {
    if (this.#interactionServer === void 0) {
      throw new ImplementationError("Interaction server is not available yet");
    }
    return this.#interactionServer;
  }
  get #commissionedFabrics() {
    return this.owner.state.operationalCredentials.commissionedFabrics;
  }
  get operationalPort() {
    return this.#primaryNetInterface?.port ?? 0;
  }
  endCommissioning() {
    if (this.#matterDevice !== void 0) {
      return this.#matterDevice.endCommissioning();
    }
  }
  async start() {
    await this.owner.env.load(MdnsService);
    await this.owner.act("start-network", (agent) => agent.load(ProductDescriptionServer));
    const { sessionStorage, fabricStorage } = this.owner.env.get(NodeStore);
    const matterDevice = await MatterDevice.create(
      sessionStorage,
      fabricStorage,
      () => ({
        ...this.owner.state.commissioning,
        productDescription: this.owner.state.productDescription,
        ble: !!this.owner.state.network.ble
      }),
      this.owner.state.basicInformation.capabilityMinima.caseSessionsPerFabric,
      // Internally it is "Session and Node", so we support even more
      (_fabricIndex, _fabricAction) => {
      },
      (_fabricIndex) => {
      },
      { maxPathsPerInvoke: this.owner.state.basicInformation.maxPathsPerInvoke }
    );
    this.#matterDevice = matterDevice;
    this.#interactionServer = await TransactionalInteractionServer.create(this.owner, matterDevice.sessionManager);
    matterDevice.addProtocolHandler(this.#interactionServer);
    matterDevice.fabricManager.events.added.on((fabric) => {
      const fabrics = this.#matterDevice?.fabricManager.getFabrics() ?? [];
      if (fabrics.length === 1 && fabrics[0].fabricIndex === fabric.fabricIndex) {
        this.enableMdnsBroadcasting();
      }
    });
    this.owner.env.set(SessionManager, matterDevice.sessionManager);
    this.owner.env.set(FabricManager, matterDevice.fabricManager);
    this.owner.env.set(ExchangeManager, matterDevice.exchangeManager);
    this.owner.env.set(DeviceCommissioner, matterDevice.commissioner);
    this.owner.env.set(DeviceAdvertiser, matterDevice.advertiser);
    await this.owner.act("load-sessions", (agent) => agent.load(SessionsBehavior));
    this.owner.eventsOf(CommissioningBehavior).commissioned.on(() => this.endUncommissionedMode());
    await this.addTransports(matterDevice);
    await this.addBroadcasters(matterDevice);
    await this.owner.set({ network: { operationalPort: this.operationalPort } });
    await this.openAdvertisementWindow();
  }
  async stop() {
    if (this.#matterDevice) {
      this.owner.env.delete(SessionManager, this.#matterDevice.sessionManager);
      this.owner.env.delete(FabricManager, this.#matterDevice.fabricManager);
      this.owner.env.delete(ExchangeManager, this.#matterDevice.exchangeManager);
      this.owner.env.delete(DeviceCommissioner, this.#matterDevice.commissioner);
      this.owner.env.delete(DeviceAdvertiser, this.#matterDevice.advertiser);
      await this.#matterDevice.close();
      this.#matterDevice = void 0;
      this.#primaryNetInterface = void 0;
    }
    if (this.#primaryNetInterface) {
      await this.#primaryNetInterface.close();
      this.#primaryNetInterface = void 0;
    }
    await this.#interactionServer?.[Symbol.asyncDispose]();
    this.#interactionServer = void 0;
    if (this.#commissionedListener) {
      const commissionedListener = this.#commissionedListener;
      this.#commissionedListener = void 0;
      this.owner.eventsOf(CommissioningBehavior).commissioned.off(commissionedListener);
    }
  }
  blockNewActivity() {
    this.#interactionServer?.blockNewActivity();
  }
}
export {
  ServerNetworkRuntime
};
//# sourceMappingURL=ServerNetworkRuntime.js.map
