/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  ImplementationError,
  InterfaceType,
  Network,
  ObserverGroup,
  TransportInterfaceSet,
  UdpInterface
} from "#general";
import { TransactionalInteractionServer } from "#node/server/TransactionalInteractionServer.js";
import {
  Ble,
  ChannelManager,
  CommissioningConfigProvider,
  DeviceAdvertiser,
  DeviceCommissioner,
  ExchangeManager,
  MdnsService,
  SecureChannelProtocol,
  SessionManager
} from "#protocol";
import { CommissioningServer } from "../commissioning/CommissioningServer.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;
  #mdnsBroadcaster;
  #bleBroadcaster;
  #bleTransport;
  #observers = new ObserverGroup(this);
  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() {
    return this.owner.env.get(DeviceAdvertiser).startAdvertising();
  }
  advertiseNow() {
    return this.owner.env.get(DeviceAdvertiser).advertise(true);
  }
  /**
   * 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 TransportInterfaceSet}.
   */
  async addTransports(interfaces) {
    const netconf = this.owner.state.network;
    const port = this.owner.state.network.port;
    const ipv6Intf = await UdpInterface.create(
      this.owner.env.get(Network),
      "udp6",
      port ? port : void 0,
      netconf.listeningAddressIpv6
    );
    interfaces.add(ipv6Intf);
    await this.owner.set({ network: { operationalPort: ipv6Intf.port } });
    if (netconf.ipv4) {
      interfaces.add(
        await UdpInterface.create(
          this.owner.env.get(Network),
          "udp4",
          netconf.port,
          netconf.listeningAddressIpv4
        )
      );
    }
    if (netconf.ble) {
      interfaces.add(this.bleTransport);
    }
  }
  /**
   * Add broadcasters to the {@link DeviceAdvertiser}.
   */
  async addBroadcasters(advertiser) {
    await advertiser.clearBroadcasters();
    const isCommissioned = !!this.#commissionedFabrics;
    let discoveryCapabilities = this.owner.state.network.discoveryCapabilities;
    if (isCommissioned) {
      discoveryCapabilities = { onIpNetwork: true };
    }
    if (discoveryCapabilities.onIpNetwork) {
      advertiser.addBroadcaster(this.mdnsBroadcaster);
    }
    if (discoveryCapabilities.ble) {
      advertiser.addBroadcaster(this.bleBroadcaster);
    }
  }
  /**
   * When the first Fabric gets added we need to enable MDNS broadcasting.
   */
  enableMdnsBroadcasting() {
    const advertiser = this.owner.env.get(DeviceAdvertiser);
    const mdnsBroadcaster = this.mdnsBroadcaster;
    if (!advertiser.hasBroadcaster(mdnsBroadcaster)) {
      advertiser.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() {
    this.enableMdnsBroadcasting();
    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) {
    const advertiser = this.owner.env.get(DeviceAdvertiser);
    await advertiser.deleteBroadcaster(bleBroadcaster);
    await bleBroadcaster.close();
  }
  async #removeBleTransport(bleTransport) {
    const transportInterfaces = this.owner.env.get(TransportInterfaceSet);
    transportInterfaces.delete(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;
  }
  endCommissioning() {
    return this.owner.env.get(DeviceCommissioner).endCommissioning();
  }
  async start() {
    const { owner } = this;
    const { env } = owner;
    await env.load(MdnsService);
    await this.addTransports(env.get(TransportInterfaceSet));
    await this.addBroadcasters(env.get(DeviceAdvertiser));
    await owner.act("start-network", (agent) => agent.load(ProductDescriptionServer));
    env.get(ChannelManager).caseSessionsPerFabricAndNode = // Note that this is "sessions per fabric and node", so we support more than indicated by capabilityMinima
    owner.state.basicInformation.capabilityMinima.caseSessionsPerFabric;
    env.get(SessionManager).sessionParameters = {
      maxPathsPerInvoke: this.owner.state.basicInformation.maxPathsPerInvoke
    };
    this.#interactionServer = await TransactionalInteractionServer.create(this.owner, env.get(SessionManager));
    env.get(ExchangeManager).addProtocolHandler(this.#interactionServer);
    await this.owner.act("load-sessions", (agent) => agent.load(SessionsBehavior));
    this.#observers.on(this.owner.eventsOf(CommissioningServer).commissioned, this.endUncommissionedMode);
    if (!env.has(CommissioningConfigProvider)) {
      if (owner.state.commissioning.enabled === void 0) {
        await owner.set({
          commissioning: { enabled: true }
        });
      }
      env.set(
        CommissioningConfigProvider,
        new class extends CommissioningConfigProvider {
          get values() {
            const config = {
              ...owner.state.commissioning,
              productDescription: owner.state.productDescription,
              ble: !!owner.state.network.ble
            };
            return config;
          }
        }()
      );
    }
    await this.configureCommissioning();
    this.#observers.on(this.owner.eventsOf(CommissioningServer).enabled$Changed, this.configureCommissioning);
    await this.openAdvertisementWindow();
  }
  async stop() {
    this.#observers.close();
    await this.owner.env.close(DeviceCommissioner);
    await this.owner.env.close(DeviceAdvertiser);
    await this.owner.env.close(ExchangeManager);
    await this.owner.env.close(SecureChannelProtocol);
    await this.owner.env.close(TransportInterfaceSet);
    await this.#interactionServer?.[Symbol.asyncDispose]();
    this.#interactionServer = void 0;
  }
  blockNewActivity() {
    this.#interactionServer?.blockNewActivity();
  }
  async configureCommissioning() {
    if (this.owner.state.commissioning.enabled) {
      this.owner.env.get(DeviceCommissioner);
    } else if (this.owner.env.has(DeviceCommissioner)) {
      await this.owner.env.close(DeviceCommissioner);
    }
  }
}
export {
  ServerNetworkRuntime
};
//# sourceMappingURL=ServerNetworkRuntime.js.map
