/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  Diagnostic,
  EventEmitter,
  ImplementationError,
  Lifecycle,
  Logger,
  MatterFlowError,
  Observable
} from "#general";
import { DatatypeModel, FieldElement } from "#model";
import { FabricAction, FabricManager, FailsafeContext, PaseClient } from "#protocol";
import {
  CommissioningFlowType,
  CommissioningOptions,
  DiscoveryCapabilitiesSchema,
  ManualPairingCodeCodec,
  QrCode,
  QrPairingCodeCodec
} from "#types";
import { BasicInformationBehavior } from "../../../behaviors/basic-information/BasicInformationBehavior.js";
import { OperationalCredentialsBehavior } from "../../../behaviors/operational-credentials/OperationalCredentialsBehavior.js";
import { Behavior } from "../../Behavior.js";
import { Val } from "../../state/Val.js";
import { NetworkServer } from "../network/NetworkServer.js";
import { SessionsBehavior } from "../sessions/SessionsBehavior.js";
const logger = Logger.get("Commissioning");
class CommissioningBehavior extends Behavior {
  static id = "commissioning";
  static early = true;
  initialize() {
    if (this.state.passcode === -1) {
      this.state.passcode = PaseClient.generateRandomPasscode();
    } else if (CommissioningOptions.FORBIDDEN_PASSCODES.includes(this.state.passcode)) {
      throw new ImplementationError(`Passcode ${this.state.passcode} is not allowed`);
    }
    if (this.state.discriminator === -1) {
      this.state.discriminator = PaseClient.generateRandomDiscriminator();
    }
    this.reactTo(this.endpoint.lifecycle.online, this.#nodeOnline);
    this.reactTo(this.endpoint.lifecycle.partsReady, this.#initializeNode);
  }
  [Symbol.asyncDispose]() {
    this.internal.unregisterFailsafeListener?.();
  }
  handleFabricChange(fabricIndex, fabricAction) {
    if (this.env.has(FailsafeContext)) {
      const failsafe = this.env.get(FailsafeContext);
      if (fabricAction === FabricAction.Added || fabricAction === FabricAction.Updated) {
        if (failsafe.construction.status !== Lifecycle.Status.Destroyed) {
          if (failsafe.fabricIndex === fabricIndex) {
            this.#monitorFailsafe(failsafe);
            return;
          } else {
            throw new MatterFlowError(
              `Failsafe owns a different fabricIndex then ${failsafe.forUpdateNoc ? "updated" : "added"}.`
            );
          }
        }
      } else if (fabricAction === FabricAction.Removed) {
        if (failsafe.fabricIndex !== fabricIndex) {
          throw new MatterFlowError("Failsafe owns a different fabricIndex then removed.");
        }
      }
    }
    const fabrics = this.env.get(FabricManager).getFabrics();
    const commissioned = !!fabrics.length;
    if (fabricAction === FabricAction.Removed) {
      delete this.state.fabrics[fabricIndex];
    } else {
      const fabric = fabrics.find((fabric2) => fabric2.fabricIndex === fabricIndex);
      if (fabric !== void 0) {
        this.state.fabrics[fabricIndex] = {
          fabricIndex: fabric.fabricIndex,
          fabricId: fabric.fabricId,
          nodeId: fabric.nodeId,
          rootNodeId: fabric.rootNodeId,
          rootVendorId: fabric.rootVendorId,
          label: fabric.label
        };
      }
    }
    let doFactoryReset = false;
    if (commissioned !== this.state.commissioned) {
      this.state.commissioned = commissioned;
      if (commissioned) {
        this.events.commissioned.emit(this.context);
        this.endpoint.lifecycle.commissioned.emit(this.context);
      } else {
        this.events.decommissioned.emit(this.context);
        this.endpoint.lifecycle.decommissioned.emit(this.context);
        doFactoryReset = true;
      }
    }
    this.events.fabricsChanged.emit(fabricIndex, fabricAction);
    if (doFactoryReset) {
      const sessions = this.agent.get(SessionsBehavior);
      if (Object.keys(sessions.state.sessions).length > 0) {
        this.reactTo(sessions.events.closed, this.#handleSessionClosed);
      } else {
        this.#triggerFactoryReset();
      }
    }
  }
  #handleSessionClosed() {
    const sessions = this.agent.get(SessionsBehavior);
    if (Object.keys(sessions.state.sessions).length === 0) {
      this.#triggerFactoryReset();
    }
  }
  #triggerFactoryReset() {
    this.env.runtime.add(
      this.endpoint.factoryReset().then(this.callback(this.initiateCommissioning))
    );
  }
  #monitorFailsafe(failsafe) {
    if (this.internal.unregisterFailsafeListener) {
      return;
    }
    const listener = this.callback(function(status) {
      if (status === Lifecycle.Status.Destroyed) {
        if (failsafe.fabricIndex !== void 0) {
          this.handleFabricChange(
            failsafe.fabricIndex,
            failsafe.forUpdateNoc ? FabricAction.Updated : FabricAction.Added
          );
        }
        this.internal.unregisterFailsafeListener?.();
      }
    });
    this.internal.unregisterFailsafeListener = this.callback(function() {
      failsafe.construction.change.off(listener);
      this.internal.unregisterFailsafeListener = void 0;
    });
    failsafe.construction.change.on(listener);
  }
  /**
   * The server invokes this method when the node is active but not yet commissioned.
   *
   * An uncommissioned node is not yet associated with fabrics.  It cannot be used until commissioned by a controller.
   *
   * The default implementation logs the QR code and credentials.
   */
  initiateCommissioning() {
    const { passcode, discriminator } = this.state;
    const { qrPairingCode, manualPairingCode } = this.state.pairingCodes;
    logger.notice(
      Diagnostic.strong(this.endpoint.toString()),
      "is uncommissioned",
      Diagnostic.dict({
        passcode,
        discriminator,
        "manual pairing code": manualPairingCode
      }),
      Diagnostic.list([
        QrCode.get(qrPairingCode).trim(),
        `QR code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}
`
      ])
    );
  }
  /**
   * Obtain pairing codes for a node.
   */
  static pairingCodesFor(node) {
    const bi = node.stateOf(BasicInformationBehavior);
    const comm = node.stateOf(CommissioningBehavior);
    const net = node.stateOf(NetworkServer);
    const qrPairingCode = QrPairingCodeCodec.encode([
      {
        version: 0,
        vendorId: bi.vendorId,
        productId: bi.productId,
        flowType: comm.flowType,
        discriminator: comm.discriminator,
        passcode: comm.passcode,
        discoveryCapabilities: DiscoveryCapabilitiesSchema.encode(net.discoveryCapabilities)
      }
    ]);
    return {
      manualPairingCode: ManualPairingCodeCodec.encode({
        discriminator: comm.discriminator,
        passcode: comm.passcode
      }),
      qrPairingCode
    };
  }
  /**
   * Define logical schema to make passcode and discriminator persistent.
   */
  static schema = new DatatypeModel({
    name: "CommissioningState",
    type: "struct",
    children: [
      FieldElement({ name: "passcode", type: "uint32", quality: "N" }),
      FieldElement({ name: "discriminator", type: "uint16", quality: "N" })
    ]
  });
  #nodeOnline() {
    const fabrics = this.env.get(FabricManager).getFabrics();
    if (!fabrics.length) {
      this.initiateCommissioning();
    } else {
      const exposedFabrics = {};
      fabrics.forEach(
        ({ fabricIndex, externalInformation }) => exposedFabrics[fabricIndex] = externalInformation
      );
      this.state.fabrics = exposedFabrics;
    }
  }
  #initializeNode() {
    this.state.commissioned = !!this.agent.get(OperationalCredentialsBehavior).state.commissionedFabrics;
    this.endpoint.lifecycle.initialized.emit(this.state.commissioned);
  }
}
((CommissioningBehavior2) => {
  class Internal {
    unregisterFailsafeListener = void 0;
  }
  CommissioningBehavior2.Internal = Internal;
  class State {
    commissioned = false;
    fabrics = {};
    passcode = -1;
    discriminator = -1;
    flowType = CommissioningFlowType.Standard;
    additionalBleAdvertisementData = void 0;
    pairingCodes = {};
    [Val.properties](endpoint) {
      return {
        get pairingCodes() {
          return CommissioningBehavior2.pairingCodesFor(endpoint);
        }
      };
    }
  }
  CommissioningBehavior2.State = State;
  class Events extends EventEmitter {
    commissioned = Observable();
    decommissioned = Observable();
    fabricsChanged = Observable();
  }
  CommissioningBehavior2.Events = Events;
})(CommissioningBehavior || (CommissioningBehavior = {}));
export {
  CommissioningBehavior
};
//# sourceMappingURL=CommissioningBehavior.js.map
