/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { WriteResult } from "#action/index.js";
import { Invoke } from "#action/request/Invoke.js";
import { Read } from "#action/request/Read.js";
import { Write } from "#action/request/Write.js";
import { Certificate } from "#certificate/kinds/Certificate.js";
import {
  asError,
  Bytes,
  causedBy,
  ChannelType,
  Diagnostic,
  Duration,
  ImplementationError,
  Instant,
  Logger,
  Minutes,
  repackErrorAs,
  Seconds,
  Time,
  Timespan,
  Timestamp,
  UnexpectedDataError
} from "@matter/general";
import {
  EndpointNumber,
  Global,
  Status,
  StatusResponseError
} from "@matter/types";
import { BasicInformation } from "@matter/types/clusters/basic-information";
import { Descriptor } from "@matter/types/clusters/descriptor";
import { GeneralCommissioning } from "@matter/types/clusters/general-commissioning";
import { NetworkCommissioning } from "@matter/types/clusters/network-commissioning";
import { OperationalCredentials } from "@matter/types/clusters/operational-credentials";
import { OtaSoftwareUpdateRequestor } from "@matter/types/clusters/ota-software-update-requestor";
import { TimeSynchronizationCluster } from "@matter/types/clusters/time-synchronization";
import { TlvCertSigningRequest } from "../common/OperationalCredentialsTypes.js";
import { CommissioningError } from "./CommissioningError.js";
const logger = Logger.get("ControllerCommissioner");
var CommissioningStepResultCode = /* @__PURE__ */ ((CommissioningStepResultCode2) => {
  CommissioningStepResultCode2[CommissioningStepResultCode2["Success"] = 0] = "Success";
  CommissioningStepResultCode2[CommissioningStepResultCode2["Failure"] = 1] = "Failure";
  CommissioningStepResultCode2[CommissioningStepResultCode2["Skipped"] = 2] = "Skipped";
  CommissioningStepResultCode2[CommissioningStepResultCode2["Stop"] = 3] = "Stop";
  return CommissioningStepResultCode2;
})(CommissioningStepResultCode || {});
class MaximumCommissionedFabricsReachedError extends CommissioningError {
}
class CommissioningTimeoutError extends CommissioningError {
}
class DeviceAlreadyCommissionedToThisFabricError extends CommissioningError {
}
class FabricLabelConflictError extends CommissioningError {
}
class WifiOrThreadNetworkCredentialsNotConfiguredError extends CommissioningError {
}
class WifiNetworkSetupFailedError extends CommissioningError {
}
class ThreadNetworkSetupFailedError extends CommissioningError {
}
class NodeIdConflictError extends CommissioningError {
}
class OperativeConnectionFailedError extends CommissioningError {
}
class RecoverableCommissioningError extends CommissioningError {
}
const DEFAULT_FAILSAFE_TIME = Minutes.one;
const BTP_IDLE_ALIVE_INTERVAL = Seconds(25);
const RootEndpointNumber = EndpointNumber(0);
class ControllerCommissioningFlow {
  interaction;
  ca;
  fabric;
  transitionToCase;
  commissioningOptions;
  commissioningSteps = new Array();
  commissioningStepResults = /* @__PURE__ */ new Map();
  #clusterClients = /* @__PURE__ */ new Map();
  #commissioningStartedTime;
  #commissioningExpiryTime;
  #currentFailSafeEndTime;
  lastBreadcrumb = 1;
  collectedCommissioningData = {};
  #defaultFailSafeTime = DEFAULT_FAILSAFE_TIME;
  #armFailsafeInterval;
  constructor(interaction, ca, fabric, commissioningOptions, transitionToCase) {
    this.interaction = interaction;
    this.ca = ca;
    this.fabric = fabric;
    this.transitionToCase = transitionToCase;
    this.commissioningOptions = commissioningOptions;
    logger.debug(`Commissioning options: ${Diagnostic.json(commissioningOptions)}`);
    this.#initializeCommissioningSteps();
  }
  async [Symbol.asyncDispose]() {
    await this.interaction.close();
  }
  /**
   * Execute the commissioning process in the defined order. The steps are sorted before execution based on the step
   * number and sub step number.
   * If >50% of the failsafe time has passed, the failsafe timer is re-armed (50% of 60s default are 30s and each
   * action is allowed to take 30s at minimum based on specs).
   */
  async executeCommissioning() {
    this.#sortSteps();
    let failSafeTimerReArmedAfterPreviousStep = false;
    for (const step of this.commissioningSteps) {
      logger.info(`Executing commissioning step ${step.stepNumber}.${step.subStepNumber}: ${step.name}`);
      try {
        if (step.reArmFailsafe && !failSafeTimerReArmedAfterPreviousStep && !this.#armFailsafeInterval?.isRunning) {
          logger.debug(`Re-Arming failsafe timer before executing step`);
          await this.#armFailsafe();
        }
        failSafeTimerReArmedAfterPreviousStep = false;
        const result = await step.stepLogic();
        this.#setCommissioningStepResult(step, result);
        if (this.#currentFailSafeEndTime !== void 0) {
          if (this.#commissioningExpiryTime !== void 0 && Time.nowMs > this.#commissioningExpiryTime) {
            logger.error(
              `Commissioning step ${step.stepNumber}.${step.subStepNumber}: ${step.name} succeeded, but commissioning took too long in general!`
            );
            const maxTimeS = this.collectedCommissioningData.basicCommissioningInfo?.maxCumulativeFailsafeSeconds;
            throw new CommissioningTimeoutError(
              `Commissioning time exceeds the maximum timeframe${maxTimeS ? ` of ${maxTimeS}s` : ""}`
            );
          }
          const timeLeft = Timespan(Time.nowMs, this.#currentFailSafeEndTime).duration;
          if (timeLeft < this.#defaultFailSafeTime / 2) {
            logger.info(
              `After Commissioning step ${step.stepNumber}.${step.subStepNumber}: ${step.name} succeeded, ${Duration.format(timeLeft)} left for failsafe timer, re-arming failsafe`
            );
            await this.#armFailsafe();
            failSafeTimerReArmedAfterPreviousStep = true;
          }
        }
        if (result.code === 3 /* Stop */) {
          break;
        }
      } catch (e) {
        const error = asError(e);
        if (error instanceof RecoverableCommissioningError) {
          logger.warn(
            `Commissioning step ${step.stepNumber}.${step.subStepNumber}: ${step.name} failed with recoverable error:`,
            Diagnostic.errorMessage(error),
            Diagnostic.weak("(continuing commissioning)")
          );
        } else if (causedBy(error, CommissioningError, StatusResponseError)) {
          logger.error(
            `Commissioning step ${step.stepNumber}.${step.subStepNumber}: ${step.name} failed with error:`,
            Diagnostic.errorMessage(error),
            Diagnostic.weak("(terminating commissioning)")
          );
          await this.#resetFailsafeTimer();
          StatusResponseError.accept(error);
          throw repackErrorAs(error, CommissioningError);
        } else {
          throw error;
        }
      }
    }
  }
  /**
   * Convenience method to get a number of attributes in a read and return exactly these values in the order as
   * defined in the request
   */
  async #readConcreteAttributeValues(request) {
    const attributeMap = /* @__PURE__ */ new Map();
    if (request.attributeRequests === void 0) {
      throw new ImplementationError("Can only handle Attribute reads");
    }
    for (const { endpointId, clusterId, attributeId } of request.attributeRequests) {
      if (endpointId === void 0 || clusterId === void 0) {
        continue;
      }
      attributeMap.set(`${endpointId}-${clusterId}-${attributeId}`, void 0);
    }
    for await (const data of this.interaction.read(request)) {
      for (const entry of data) {
        if (entry.kind !== "attr-value") {
          continue;
        }
        const {
          path: { endpointId, clusterId, attributeId },
          value
        } = entry;
        const key = `${endpointId}-${clusterId}-${attributeId}`;
        if (!attributeMap.has(key)) {
          continue;
        }
        attributeMap.set(key, value);
      }
    }
    return [...attributeMap.values()];
  }
  // TODO improve response typing
  async #invokeCommand(request, options = {}) {
    for await (const data of this.interaction.invoke(
      Invoke({
        commands: [request],
        ...options
      })
    )) {
      for (const entry of data) {
        switch (entry.kind) {
          case "cmd-status":
            if (entry.status !== Status.Success) {
              throw StatusResponseError.create(entry.status, void 0, entry.clusterStatus);
            }
            return;
          case "cmd-response":
            return entry.data;
        }
      }
    }
  }
  /**
   * Initialize commissioning steps and add them in the default order as defined by
   * @see {@link MatterSpecification.v13.Core} § 5.5
   */
  #initializeCommissioningSteps() {
    this.commissioningSteps.push({
      stepNumber: 0,
      // Preparation
      subStepNumber: 1,
      name: "GetInitialData",
      stepLogic: () => this.#getInitialData()
    });
    this.commissioningSteps.push({
      stepNumber: 7,
      subStepNumber: 1,
      name: "GeneralCommissioning.ArmFailsafe",
      stepLogic: () => this.#armFailsafe()
    });
    this.commissioningSteps.push({
      stepNumber: 8,
      subStepNumber: 1,
      name: "GeneralCommissioning.ConfigureRegulatoryInformation",
      stepLogic: () => this.#configureRegulatoryInformation()
    });
    this.commissioningSteps.push({
      stepNumber: 8,
      subStepNumber: 2,
      name: "TimeSynchronization.SynchronizeTime",
      stepLogic: () => this.#synchronizeTime()
    });
    this.commissioningSteps.push({
      stepNumber: 10,
      subStepNumber: 1,
      name: "OperationalCredentials.DeviceAttestation",
      stepLogic: () => this.#deviceAttestation()
    });
    this.commissioningSteps.push({
      stepNumber: 11,
      // includes 11-13
      subStepNumber: 1,
      name: "OperationalCredentials.Certificates",
      stepLogic: () => this.#certificates()
    });
    this.commissioningSteps.push({
      stepNumber: 15,
      subStepNumber: 1,
      name: "AccessControl",
      stepLogic: () => this.#configureAccessControlLists()
    });
    if (this.interaction.channelType === ChannelType.BLE) {
      this.commissioningSteps.push({
        stepNumber: 16,
        subStepNumber: 1,
        name: "NetworkCommissioning.Validate",
        stepLogic: () => this.#validateNetwork()
      });
      if (this.commissioningOptions.wifiNetwork !== void 0) {
        this.commissioningSteps.push({
          stepNumber: 16,
          // includes step 17
          subStepNumber: 2,
          name: "NetworkCommissioning.Wifi",
          reArmFailsafe: true,
          stepLogic: () => this.#configureNetworkWifi()
        });
      }
      if (this.commissioningOptions.threadNetwork !== void 0) {
        this.commissioningSteps.push({
          stepNumber: 16,
          // includes step 17
          subStepNumber: 3,
          name: "NetworkCommissioning.Thread",
          reArmFailsafe: true,
          stepLogic: () => this.#configureNetworkThread()
        });
      }
    } else {
      logger.info(
        `Skipping NetworkCommissioning steps because the device is already on IP network (${this.interaction.channelType})`
      );
    }
    this.commissioningSteps.push({
      stepNumber: 18,
      // includes step 19 (CASE connection)
      subStepNumber: 1,
      name: "Reconnect",
      stepLogic: () => this.#reconnectWithDevice()
    });
    this.commissioningSteps.push({
      stepNumber: 20,
      subStepNumber: 1,
      name: "GeneralCommissioning.Complete",
      stepLogic: () => this.#completeCommissioning()
    });
    this.commissioningSteps.push({
      stepNumber: 98,
      // Should be allowed in Step 13, but Tasmota is not supporting this
      subStepNumber: 1,
      name: "OperationalCredentials.UpdateFabricLabel",
      stepLogic: () => this.#updateFabricLabel()
    });
    if (this.commissioningOptions.otaUpdateProviderLocation !== void 0) {
      this.commissioningSteps.push({
        stepNumber: 99,
        subStepNumber: 1,
        name: "AdditionalLogic.AddDefaultOtaProvider",
        stepLogic: () => this.#addDefaultOtaProvider()
      });
    }
  }
  #sortSteps() {
    this.commissioningSteps.sort((a, b) => {
      if (a.stepNumber !== b.stepNumber) return a.stepNumber - b.stepNumber;
      return a.subStepNumber - b.subStepNumber;
    });
  }
  #setCommissioningStepResult(step, result) {
    this.commissioningStepResults.set(`${step.stepNumber}-${step.subStepNumber}`, result);
  }
  getCommissioningStepResult(stepNumber, subStepNumber) {
    return this.commissioningStepResults.get(`${stepNumber}-${subStepNumber}`);
  }
  /** Helper method to check for errorCode/debugTest responses and throw error on failure */
  #ensureOperationalCredentialsSuccess(context, { statusCode, debugText, fabricIndex }) {
    logger.debug(
      `Commissioning step ${context} returned ${OperationalCredentials.NodeOperationalCertStatus[statusCode]} (${statusCode}), ${debugText}${fabricIndex !== void 0 ? `, fabricIndex: ${fabricIndex}` : ""}`
    );
    if (statusCode === OperationalCredentials.NodeOperationalCertStatus.Ok) return;
    if (context === "addNoc") {
      if (statusCode === OperationalCredentials.NodeOperationalCertStatus.FabricConflict) {
        throw new DeviceAlreadyCommissionedToThisFabricError(
          `Commission error: This device is already commissioned into this fabric. You cannot commission it again.`
        );
      } else if (statusCode === OperationalCredentials.NodeOperationalCertStatus.TableFull) {
        throw new MaximumCommissionedFabricsReachedError(
          `Commission error: This device reached the maximum number of fabrics it can be part of. Please remove a fabric before trying to add another one.`
        );
      } else if (statusCode === OperationalCredentials.NodeOperationalCertStatus.LabelConflict) {
        throw new FabricLabelConflictError(
          `Commission error: This device is already commissioned with a fabric with the same label. Please choose a different label.`
        );
      }
    }
    throw new CommissioningError(
      `Commission error for "${context}": ${OperationalCredentials.NodeOperationalCertStatus[statusCode]} (${statusCode}), ${debugText}${fabricIndex !== void 0 ? `, fabricIndex: ${fabricIndex}` : ""}`
    );
  }
  /** Helper method to check for errorCode/debugTest responses and throw error on failure */
  #ensureGeneralCommissioningSuccess(context, { errorCode, debugText }) {
    logger.debug(`Commissioning step ${context} returned ${errorCode}, ${debugText}`);
    if (errorCode === GeneralCommissioning.CommissioningError.Ok) return;
    throw new CommissioningError(
      `Commission error for "${context}": ${GeneralCommissioning.CommissioningError[errorCode]} (${errorCode})${debugText ? `, ${debugText}` : ""}`
    );
  }
  /**
   * Initial Step to receive some common data used by other steps
   */
  async #getInitialData() {
    const [
      supportedFabrics,
      commissionedFabrics,
      rootPartsList,
      rootServerList,
      vendorId,
      productId,
      productName,
      supportsConcurrentConnection
    ] = await this.#readConcreteAttributeValues(
      Read(
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: OperationalCredentials,
          attributes: ["supportedFabrics", "commissionedFabrics"]
        }),
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: Descriptor,
          attributes: ["partsList", "serverList"]
        }),
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: BasicInformation,
          attributes: ["vendorId", "productId", "productName"]
        }),
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: GeneralCommissioning,
          attributes: ["supportsConcurrentConnection"]
        })
      )
    );
    if (commissionedFabrics >= supportedFabrics) {
      throw new MaximumCommissionedFabricsReachedError(
        `Commissioned fabrics (${commissionedFabrics}) exceed supported fabrics (${supportedFabrics}). Please remove some fabrics before commissioning.`
      );
    }
    this.collectedCommissioningData.rootPartsList = rootPartsList;
    this.collectedCommissioningData.rootServerList = rootServerList;
    this.collectedCommissioningData.vendorId = vendorId;
    this.collectedCommissioningData.productId = productId;
    this.collectedCommissioningData.productName = productName;
    this.collectedCommissioningData.supportsConcurrentConnection = supportsConcurrentConnection;
    const networkData = this.interaction.read(
      Read(
        Read.Attribute({
          cluster: NetworkCommissioning,
          attributes: ["featureMap", "networks"]
        }),
        Read.Attribute({
          cluster: OtaSoftwareUpdateRequestor,
          attributes: ["defaultOtaProviders"]
        })
      )
    );
    const networkFeatures = new Array();
    const networkStatus = new Array();
    const otaRequestors = new Array();
    for await (const data of networkData) {
      for (const entry of data) {
        if (entry.kind !== "attr-value") {
          continue;
        }
        const {
          path: { endpointId, attributeId },
          value
        } = entry;
        switch (attributeId) {
          case Global.attributes.featureMap.id:
            networkFeatures.push({
              endpointId,
              value
            });
            break;
          case NetworkCommissioning.attributes.networks.id:
            networkStatus.push({
              endpointId,
              value
            });
            break;
          case OtaSoftwareUpdateRequestor.attributes.defaultOtaProviders.id:
            otaRequestors.push(endpointId);
            break;
        }
      }
    }
    this.collectedCommissioningData.networkFeatures = networkFeatures;
    this.collectedCommissioningData.networkStatus = networkStatus;
    this.collectedCommissioningData.otaRequestorList = otaRequestors;
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 7
   * Commissioner SHALL re-arm the Fail-safe timer on the Commissionee to the desired commissioning
   * timeout within 60 seconds of the completion of a PASE session establishment, using the
   * ArmFailSafe command (see Section 11.10.6.2, “ArmFailSafe Command”). A Commissioner MAY
   * collect device information including guidance on the fail-safe value from the Commissionee by
   * reading BasicCommissioningInfo attribute (see Section 11.10.5.2, “BasicCommissioningInfo
   * Attribute”) before invoking the ArmFailSafe command.
   */
  async #armFailsafe(time) {
    if (this.collectedCommissioningData.basicCommissioningInfo === void 0) {
      const [basicCommissioningInfo] = await this.#readConcreteAttributeValues(
        Read(
          Read.Attribute({
            endpoint: RootEndpointNumber,
            cluster: GeneralCommissioning,
            attributes: ["basicCommissioningInfo"]
          })
        )
      );
      this.collectedCommissioningData.basicCommissioningInfo = basicCommissioningInfo;
      this.#defaultFailSafeTime = Seconds(basicCommissioningInfo.failSafeExpiryLengthSeconds);
      this.#commissioningStartedTime = Time.nowMs;
      this.#commissioningExpiryTime = Timestamp(
        this.#commissioningStartedTime + Seconds(basicCommissioningInfo.maxCumulativeFailsafeSeconds)
      );
    }
    const expiryLength = time ?? this.#defaultFailSafeTime;
    this.#ensureGeneralCommissioningSuccess(
      "armFailSafe",
      await this.#invokeCommand({
        endpoint: RootEndpointNumber,
        cluster: GeneralCommissioning,
        command: "armFailSafe",
        fields: {
          breadcrumb: this.lastBreadcrumb,
          expiryLengthSeconds: Seconds.of(expiryLength)
        }
      })
    );
    this.#currentFailSafeEndTime = Timestamp(Time.nowMs + expiryLength);
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  get #failSafeTimeLeft() {
    if (this.#currentFailSafeEndTime === void 0) {
      return Instant;
    }
    return Duration.max(Timespan(Time.nowMs, this.#currentFailSafeEndTime).duration, 0);
  }
  async #ensureFailsafeTimerFor(maxProcessingTime) {
    const minFailsafeTime = this.interaction.maximumPeerResponseTime(maxProcessingTime, true);
    if (this.interaction.channelType === ChannelType.BLE) {
      this.#armFailsafeInterval?.stop();
      this.#armFailsafeInterval = Time.getPeriodicTimer(
        "Re-Arm Failsafe during longer interactions",
        BTP_IDLE_ALIVE_INTERVAL,
        () => {
          const now = Time.nowMs;
          if (this.#commissioningExpiryTime !== void 0 && now < this.#commissioningExpiryTime) {
            logger.debug(
              `Re-Arm Failsafe Timer during longer actions with device. Time left: ${Duration.format(Timespan(now, this.#commissioningExpiryTime).duration)}`
            );
            this.#armFailsafe().catch((error) => {
              logger.info("Error while re-arming failsafe during reconnect", error);
              this.#armFailsafeInterval?.stop();
            });
          } else {
            this.#armFailsafeInterval?.stop();
          }
        }
      ).start();
    }
    const timeLeft = this.#failSafeTimeLeft;
    if (timeLeft < minFailsafeTime) {
      logger.debug(
        `Failsafe timer has only ${Duration.format(timeLeft)} left, re-arming for at least ${Duration.format(minFailsafeTime)}`
      );
      await this.#armFailsafe(Duration.max(minFailsafeTime, this.#defaultFailSafeTime));
    } else {
      logger.debug(`Failsafe timer is already set for at least ${Duration.format(timeLeft)}`);
    }
  }
  async #resetFailsafeTimer() {
    if (this.#currentFailSafeEndTime === void 0) return;
    try {
      await this.#invokeCommand({
        endpoint: RootEndpointNumber,
        cluster: GeneralCommissioning,
        command: "armFailSafe",
        fields: {
          breadcrumb: this.lastBreadcrumb,
          expiryLengthSeconds: 0
        }
      });
      this.#currentFailSafeEndTime = void 0;
    } catch (error) {
      logger.error(`Error while resetting failsafe timer`, error);
    }
  }
  /**
   * Step 8 - 1
   * Commissioner SHALL configure regulatory information if the Commissionee has at least one instance of
   * the Network Commissioning cluster on any endpoint with either the WI (i.e. Wi-Fi) or TH (i.e. Thread)
   * feature flags set in its FeatureMap, Commissioner SHALL configure regulatory information in the
   * Commissionee using the SetRegulatoryConfig command.
   */
  async #configureRegulatoryInformation() {
    if (this.collectedCommissioningData.networkFeatures === void 0) {
      throw new CommissioningError(
        "Initial information collection failed. No network features collected. This should never happen."
      );
    }
    const hasRadioNetwork = this.collectedCommissioningData.networkFeatures.some(
      ({ value: { wiFiNetworkInterface, threadNetworkInterface } }) => wiFiNetworkInterface || threadNetworkInterface
    );
    if (hasRadioNetwork) {
      let [locationCapability] = await this.#readConcreteAttributeValues(
        Read(
          Read.Attribute({
            endpoint: RootEndpointNumber,
            cluster: GeneralCommissioning,
            attributes: ["locationCapability"]
          })
        )
      );
      if (locationCapability === GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor) {
        locationCapability = this.commissioningOptions.regulatoryLocation;
      } else {
        logger.debug(
          `Device does not support a configurable regulatory location type. Location is set to "${locationCapability === GeneralCommissioning.RegulatoryLocationType.Indoor ? "Indoor" : "Outdoor"}"`
        );
      }
      let countryCode = this.commissioningOptions.regulatoryCountryCode;
      const regulatoryResult = await this.#invokeCommand(
        {
          endpoint: RootEndpointNumber,
          cluster: GeneralCommissioning,
          command: "setRegulatoryConfig",
          fields: {
            breadcrumb: this.lastBreadcrumb++,
            newRegulatoryConfig: locationCapability,
            countryCode
          }
        },
        {
          useExtendedFailSafeMessageResponseTimeout: true
        }
      );
      if (regulatoryResult.errorCode === GeneralCommissioning.CommissioningError.ValueOutsideRange && countryCode !== "XX") {
        logger.debug(
          `Device does not support a configurable country code. Use "XX" instead of "${countryCode}"`
        );
        countryCode = "XX";
        this.#ensureGeneralCommissioningSuccess(
          "setRegulatoryConfig",
          await this.#invokeCommand(
            {
              endpoint: RootEndpointNumber,
              cluster: GeneralCommissioning,
              command: "setRegulatoryConfig",
              fields: {
                breadcrumb: this.lastBreadcrumb,
                newRegulatoryConfig: locationCapability,
                countryCode
              }
            },
            {
              useExtendedFailSafeMessageResponseTimeout: true
            }
          )
        );
      } else {
        this.#ensureGeneralCommissioningSuccess("setRegulatoryConfig", regulatoryResult);
      }
      return {
        code: 0 /* Success */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    return {
      code: 2 /* Skipped */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 8 - 2
   * Commissioner SHOULD configure UTC time, timezone, and DST offset, if the Commissionee supports the
   * time synchronization cluster.
   * ▪ The Commissioner SHOULD configure UTC time using the SetUTCTime command.
   * ▪ The Commissioner SHOULD set the time zone using the SetTimeZone command, if the TimeZone Feature is supported.
   * ▪ The Commissioner SHOULD set the DST offsets using the SetDSTOffset command if the TimeZone Feature is supported, and the SetTimeZoneResponse from the Commissionee had the DSTOffsetsRequired field set to True.
   * ▪ The Commissioner SHOULD set a Default NTP server using the SetDefaultNTP command if the NTPClient Feature is supported and the DefaultNTP attribute is null. If the current value is non-null, Commissioners MAY opt to overwrite the current value.
   */
  async #synchronizeTime() {
    if (this.collectedCommissioningData.rootServerList !== void 0 && this.collectedCommissioningData.rootServerList.find(
      (clusterId) => clusterId === TimeSynchronizationCluster.id
    )) {
      logger.debug("TimeSynchronization cluster is supported");
    }
    return {
      code: 2 /* Skipped */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 10
   * Commissioner SHALL establish the authenticity of the Commissionee as a certified Matter device
   * (see Section 6.2.3, “Device Attestation Procedure”).
   */
  async #deviceAttestation() {
    const { certificate: deviceAttestation } = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: OperationalCredentials,
        command: "certificateChainRequest",
        fields: {
          certificateType: OperationalCredentials.CertificateChainType.DacCertificate
        }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    const { certificate: productAttestation } = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: OperationalCredentials,
        command: "certificateChainRequest",
        fields: {
          certificateType: OperationalCredentials.CertificateChainType.PaiCertificate
        }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    const { attestationElements, attestationSignature } = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: OperationalCredentials,
        command: "attestationRequest",
        fields: {
          attestationNonce: this.fabric.crypto.randomBytes(32)
        }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    if (deviceAttestation.byteLength === 0 || productAttestation.byteLength === 0 || attestationElements.byteLength === 0 || attestationSignature.byteLength === 0) {
      throw new CommissioningError("Device Attestation data missing from device");
    }
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 11-13
   * 11: Following the Device Attestation Procedure yielding a decision to proceed with commissioning, the Commissioner
   *     SHALL request operational CSR from Commissionee using the CSRRequest command (see Section 11.17.6.5,
   *     “CSRRequest Command”). The CSRRequest command will cause the generation of a new operational key pair at the
   *     Commissionee.
   * 12: Commissioner SHALL generate or otherwise obtain an Operational Certificate containing Operational ID after
   *     receiving the CSRResponse command from the Commissionee (see Section 11.17.6.5, “CSRRequest Command”), using
   *     implementation-specific means.
   * 13: Commissioner SHALL install operational credentials (see Figure 40, “Node Operational Credentials
   *     flow”) on the Commissionee using the AddTrustedRootCertificate and AddNOC commands,
   *     and SHALL use the UpdateFabricLabel command to set a string that the user can recognize and
   *     relate to this Commissioner/Administrator.
   *     The AdminVendorId field of the AddNOC command SHALL be set to a value for which the Vendor Schema in
   *     DCL contains the name and other information of the Commissioner’s manufacturer.
   */
  async #certificates() {
    const { nocsrElements, attestationSignature: csrSignature } = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: OperationalCredentials,
        command: "csrRequest",
        fields: { csrNonce: this.fabric.crypto.randomBytes(32) }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    if (nocsrElements.byteLength === 0 || csrSignature.byteLength === 0) {
      throw new UnexpectedDataError("Invalid response from device");
    }
    const { certSigningRequest } = TlvCertSigningRequest.decode(nocsrElements);
    const operationalPublicKey = await Certificate.getPublicKeyFromCsr(this.ca.crypto, certSigningRequest);
    await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: OperationalCredentials,
        command: "addTrustedRootCertificate",
        fields: {
          rootCaCertificate: this.ca.rootCert
        }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    const peerOperationalCert = await this.ca.generateNoc(
      operationalPublicKey,
      this.fabric.fabricId,
      this.interaction.address.nodeId
    );
    const addNocResponse = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: OperationalCredentials,
        command: "addNoc",
        fields: {
          nocValue: peerOperationalCert,
          icacValue: this.ca.icacCert ?? new Uint8Array(0),
          ipkValue: this.fabric.identityProtectionKey,
          adminVendorId: this.fabric.rootVendorId,
          caseAdminSubject: this.fabric.rootNodeId
        }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    this.#ensureOperationalCredentialsSuccess("addNoc", addNocResponse);
    const { fabricIndex } = addNocResponse;
    this.collectedCommissioningData.fabricIndex = fabricIndex;
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 13-2 (we do as 99 at the end because)
   * The Administrator having established a CASE session with the Commissionee over the operational network in the
   * previous steps SHALL invoke the CommissioningComplete command (see Section 11.9.6.6,
   * “CommissioningComplete Command”). A success response after invocation of the CommissioningComplete command ends
   * the commissioning process.
   */
  async #updateFabricLabel() {
    const { fabricIndex } = this.collectedCommissioningData;
    if (fabricIndex === void 0) {
      logger.error("No fabric index available after addNoc. This should never happen.");
      return {
        code: 1 /* Failure */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    try {
      this.#ensureOperationalCredentialsSuccess(
        "updateFabricLabel",
        await this.#invokeCommand({
          endpoint: RootEndpointNumber,
          cluster: OperationalCredentials,
          command: "updateFabricLabel",
          fields: {
            label: this.fabric.label,
            fabricIndex
          }
        })
      );
    } catch (error) {
      throw repackErrorAs(error, RecoverableCommissioningError);
    }
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 15
   * Commissioner MAY configure the Access Control List (see Access Control Cluster) on the Commissionee in any way
   * it sees fit, if the singular entry added by the AddNOC command in the previous step granting Administer
   * privilege over CASE authentication type for the Node ID provided with the command is not sufficient to express
   * its desired access control policies.
   */
  async #configureAccessControlLists() {
    return {
      code: 2 /* Skipped */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 16-17
   * 16: If the Commissionee both supports it and requires it, the Commissioner SHALL configure the operational network
   *     at the Commissionee using commands such as AddOrUpdateWiFiNetwork (see Section 11.8.7.3, “AddOrUpdateWiFiNetwork
   *     Command”) and AddOrUpdateThreadNetwork (see Section 11.8.7.4, “AddOrUpdateThreadNetwork Command”).
   *     A Commissionee requires network commissioning if it is not already on the desired operational network.
   *     A Commissionee supports network commissioning if it has any NetworkCommissioning cluster instances.
   *     A Commissioner MAY learn about the networks visible to the Commissionee using ScanNetworks command
   *     (see Section 11.8.7.1, “ScanNetworks Command”).
   * 17: The Commissioner SHALL trigger the Commissionee to connect to the operational network using ConnectNetwork
   *     command (see Section 11.8.7.9, “ConnectNetwork Command”) unless the Commissionee is already on the desired
   *     operational network.
   */
  async #validateNetwork() {
    if (this.collectedCommissioningData.networkFeatures === void 0 || this.collectedCommissioningData.networkStatus === void 0) {
      throw new CommissioningError(
        "Initial information collection failed. No network features or status collected. This should never happen."
      );
    }
    if ((this.commissioningOptions.wifiNetwork === void 0 || !this.commissioningOptions.wifiNetwork.wifiSsid || !this.commissioningOptions.wifiNetwork.wifiCredentials) && (this.commissioningOptions.threadNetwork === void 0 || !this.commissioningOptions.threadNetwork.operationalDataset)) {
      const anyEthernetInterface = this.collectedCommissioningData.networkFeatures.length === 0 || this.collectedCommissioningData.networkFeatures.some(
        ({ value: { ethernetNetworkInterface } }) => ethernetNetworkInterface === true
      );
      const anyInterfaceConnected = this.collectedCommissioningData.networkStatus.length === 0 || this.collectedCommissioningData.networkStatus.some(
        ({ value }) => value.some(({ connected }) => connected)
      );
      if (!anyEthernetInterface && !anyInterfaceConnected) {
        throw new WifiOrThreadNetworkCredentialsNotConfiguredError(
          "No Wi-Fi/Thread network credentials are configured for commissioning and no Ethernet interface is available on the device and no interface already connected."
        );
      }
    }
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  async #configureNetworkWifi() {
    if (this.commissioningOptions.wifiNetwork === void 0) {
      logger.debug("WiFi network is not configured");
      return {
        code: 2 /* Skipped */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    if (this.collectedCommissioningData.networkFeatures !== void 0 && this.collectedCommissioningData.networkStatus !== void 0) {
      const rootNetworkFeatures = this.collectedCommissioningData.networkFeatures.find(
        ({ endpointId }) => endpointId === 0
      )?.value;
      const rootNetworkStatus = this.collectedCommissioningData.networkStatus.find(
        ({ endpointId }) => endpointId === 0
      )?.value;
      logger.debug(
        `Root Networks found: ${Diagnostic.json(rootNetworkFeatures)} - ${Diagnostic.json(rootNetworkStatus)}`
      );
      if (rootNetworkFeatures?.wiFiNetworkInterface !== true) {
        logger.debug("Commissionee does not support any WiFi network interface");
        return {
          code: 2 /* Skipped */,
          breadcrumb: this.lastBreadcrumb
        };
      }
      if (rootNetworkStatus !== void 0 && rootNetworkStatus.length > 0 && rootNetworkStatus[0].connected) {
        logger.debug("Commissionee is already connected to the WiFi network");
        this.collectedCommissioningData.successfullyConnectedToNetwork = true;
        return {
          code: 2 /* Skipped */,
          breadcrumb: this.lastBreadcrumb
        };
      }
    }
    logger.debug("Configuring WiFi network ...");
    const ssid = Bytes.fromString(this.commissioningOptions.wifiNetwork.wifiSsid);
    const credentials = Bytes.fromString(this.commissioningOptions.wifiNetwork.wifiCredentials);
    const [scanMaxTimeSeconds, connectMaxTimeSeconds] = await this.#readConcreteAttributeValues(
      Read(
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: NetworkCommissioning,
          attributes: ["scanMaxTimeSeconds", "connectMaxTimeSeconds"]
        })
      )
    );
    if (this.collectedCommissioningData.supportsConcurrentConnection !== false) {
      await this.#ensureFailsafeTimerFor(Seconds(scanMaxTimeSeconds));
      const { networkingStatus, wiFiScanResults, debugText } = await this.#invokeCommand(
        {
          endpoint: RootEndpointNumber,
          cluster: NetworkCommissioning,
          command: "scanNetworks",
          fields: {
            ssid,
            breadcrumb: this.lastBreadcrumb++
          }
        },
        {
          expectedProcessingTime: Seconds(scanMaxTimeSeconds)
        }
      );
      if (networkingStatus !== NetworkCommissioning.NetworkCommissioningStatus.Success) {
        throw new WifiNetworkSetupFailedError(`Commissionee failed to scan for WiFi networks: ${debugText}`);
      }
      if (wiFiScanResults === void 0 || wiFiScanResults.length === 0) {
        throw new WifiNetworkSetupFailedError(
          `Commissionee did not return any WiFi networks for the requested SSID ${this.commissioningOptions.wifiNetwork.wifiSsid}`
        );
      }
    }
    const {
      networkingStatus: addNetworkingStatus,
      debugText: addDebugText,
      networkIndex
    } = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: NetworkCommissioning,
        command: "addOrUpdateWiFiNetwork",
        fields: {
          ssid,
          credentials,
          breadcrumb: this.lastBreadcrumb++
        }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    if (addNetworkingStatus !== NetworkCommissioning.NetworkCommissioningStatus.Success) {
      throw new WifiNetworkSetupFailedError(`Commissionee failed to add WiFi network: ${addDebugText}`);
    }
    if (networkIndex === void 0) {
      throw new WifiNetworkSetupFailedError(`Commissionee did not return network index`);
    }
    logger.debug(
      `Commissionee added WiFi network ${this.commissioningOptions.wifiNetwork.wifiSsid} with network index ${networkIndex}`
    );
    const [updatedNetworks] = await this.#readConcreteAttributeValues(
      Read(
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: NetworkCommissioning,
          attributes: ["networks"]
        })
      )
    );
    if (updatedNetworks[networkIndex] === void 0) {
      throw new WifiNetworkSetupFailedError(`Commissionee did not return network with index ${networkIndex}`);
    }
    const { networkId, connected } = updatedNetworks[networkIndex];
    if (connected) {
      this.collectedCommissioningData.successfullyConnectedToNetwork = true;
      logger.debug(
        `Commissionee is already connected to WiFi network ${this.commissioningOptions.wifiNetwork.wifiSsid} (networkId ${Bytes.toHex(networkId)})`
      );
      return {
        code: 0 /* Success */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    await this.#ensureFailsafeTimerFor(Seconds(connectMaxTimeSeconds));
    const connectResult = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: NetworkCommissioning,
        command: "connectNetwork",
        fields: {
          networkId,
          breadcrumb: this.lastBreadcrumb++
        }
      },
      {
        expectedProcessingTime: Seconds(connectMaxTimeSeconds)
      }
    );
    if (connectResult.networkingStatus !== NetworkCommissioning.NetworkCommissioningStatus.Success) {
      throw new WifiNetworkSetupFailedError(
        `Commissionee failed to connect to WiFi network: ${connectResult.debugText}`
      );
    }
    this.collectedCommissioningData.successfullyConnectedToNetwork = true;
    logger.debug(
      `Commissionee successfully connected to WiFi network ${this.commissioningOptions.wifiNetwork.wifiSsid} (networkId ${Bytes.toHex(networkId)})`
    );
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  async #configureNetworkThread() {
    if (this.collectedCommissioningData.successfullyConnectedToNetwork) {
      logger.info("Node is already connected to a network. Skipping Thread configuration.");
      return {
        code: 2 /* Skipped */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    if (this.commissioningOptions.threadNetwork === void 0) {
      logger.debug("Thread network is not configured");
      return {
        code: 2 /* Skipped */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    if (this.collectedCommissioningData.networkFeatures !== void 0 && this.collectedCommissioningData.networkStatus !== void 0) {
      const rootNetworkFeatures = this.collectedCommissioningData.networkFeatures.find(
        ({ endpointId }) => endpointId === 0
      )?.value;
      const rootNetworkStatus = this.collectedCommissioningData.networkStatus.find(
        ({ endpointId }) => endpointId === 0
      )?.value;
      logger.debug(
        `Root Networks found: ${Diagnostic.json(rootNetworkFeatures)} - ${Diagnostic.json(rootNetworkStatus)}`
      );
      if (rootNetworkFeatures?.threadNetworkInterface !== true) {
        logger.debug("Commissionee does not support any Thread network interface");
        return {
          code: 2 /* Skipped */,
          breadcrumb: this.lastBreadcrumb
        };
      }
      if (rootNetworkStatus !== void 0 && rootNetworkStatus.length > 0 && rootNetworkStatus[0].connected) {
        logger.debug("Commissionee is already connected to the Thread network");
        return {
          code: 2 /* Skipped */,
          breadcrumb: this.lastBreadcrumb
        };
      }
    }
    logger.debug("Configuring Thread network ...");
    const [scanMaxTimeSeconds, connectMaxTimeSeconds] = await this.#readConcreteAttributeValues(
      Read(
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: NetworkCommissioning,
          attributes: ["scanMaxTimeSeconds", "connectMaxTimeSeconds"]
        })
      )
    );
    if (!this.commissioningOptions.threadNetwork?.networkName) {
      logger.info("Thread network name is not configured. Skip scanning for it.");
    } else if (this.collectedCommissioningData.supportsConcurrentConnection !== false) {
      await this.#ensureFailsafeTimerFor(Seconds(scanMaxTimeSeconds));
      const { networkingStatus: networkingStatus2, threadScanResults, debugText: debugText2 } = await this.#invokeCommand(
        {
          endpoint: RootEndpointNumber,
          cluster: NetworkCommissioning,
          command: "scanNetworks",
          fields: { breadcrumb: this.lastBreadcrumb++ }
        },
        {
          expectedProcessingTime: Seconds(scanMaxTimeSeconds)
        }
      );
      if (networkingStatus2 !== NetworkCommissioning.NetworkCommissioningStatus.Success) {
        throw new ThreadNetworkSetupFailedError(
          `Commissionee failed to scan for Thread networks: ${debugText2}`
        );
      }
      if (threadScanResults === void 0 || threadScanResults.length === 0) {
        throw new ThreadNetworkSetupFailedError(
          `Commissionee did not return any Thread networks for the requested Network ${this.commissioningOptions.threadNetwork.networkName}`
        );
      }
      const wantedNetworkFound = threadScanResults.find(
        ({ networkName }) => networkName === this.commissioningOptions.threadNetwork?.networkName
      );
      if (wantedNetworkFound === void 0) {
        throw new ThreadNetworkSetupFailedError(
          `Commissionee did not return the requested Network ${this.commissioningOptions.threadNetwork.networkName}: ${Diagnostic.json(threadScanResults)}`
        );
      }
      logger.debug(
        `Commissionee found wanted Thread network ${this.commissioningOptions.threadNetwork.networkName}: ${Diagnostic.json(wantedNetworkFound)}`
      );
    }
    const {
      networkingStatus: addNetworkingStatus,
      debugText: addDebugText,
      networkIndex
    } = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: NetworkCommissioning,
        command: "addOrUpdateThreadNetwork",
        fields: {
          operationalDataset: Bytes.fromHex(this.commissioningOptions.threadNetwork.operationalDataset),
          breadcrumb: this.lastBreadcrumb++
        }
      },
      {
        useExtendedFailSafeMessageResponseTimeout: true
      }
    );
    if (addNetworkingStatus !== NetworkCommissioning.NetworkCommissioningStatus.Success) {
      throw new ThreadNetworkSetupFailedError(
        `Commissionee failed to add Thread network: status=${NetworkCommissioning.NetworkCommissioningStatus[addNetworkingStatus]} (${addNetworkingStatus})${addDebugText ? ` ${addDebugText}` : ""}`
      );
    }
    if (networkIndex === void 0) {
      throw new ThreadNetworkSetupFailedError(`Commissionee did not return network index`);
    }
    logger.debug(
      `Commissionee added Thread network ${this.commissioningOptions.threadNetwork.networkName ?? "via operational dataset"} with network index ${networkIndex}`
    );
    const [updatedNetworks] = await this.#readConcreteAttributeValues(
      Read(
        Read.Attribute({
          endpoint: RootEndpointNumber,
          cluster: NetworkCommissioning,
          attributes: ["networks"]
        })
      )
    );
    if (updatedNetworks[networkIndex] === void 0) {
      throw new ThreadNetworkSetupFailedError(`Commissionee did not return network with index ${networkIndex}`);
    }
    const { networkId, connected } = updatedNetworks[networkIndex];
    if (connected) {
      logger.debug(
        `Commissionee is already connected to Thread network ${this.commissioningOptions.threadNetwork.networkName ?? "via operational dataset"} (networkId ${Bytes.toHex(networkId)})`
      );
      return {
        code: 0 /* Success */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    await this.#ensureFailsafeTimerFor(Seconds(connectMaxTimeSeconds));
    const { networkingStatus, debugText } = await this.#invokeCommand(
      {
        endpoint: RootEndpointNumber,
        cluster: NetworkCommissioning,
        command: "connectNetwork",
        fields: {
          networkId,
          breadcrumb: this.lastBreadcrumb++
        }
      },
      {
        expectedProcessingTime: Seconds(connectMaxTimeSeconds)
      }
    );
    if (networkingStatus !== NetworkCommissioning.NetworkCommissioningStatus.Success) {
      throw new ThreadNetworkSetupFailedError(
        `Commissionee failed to connect to Thread network: status=${NetworkCommissioning.NetworkCommissioningStatus[networkingStatus]} (${networkingStatus})${debugText ? ` ${debugText}` : ""}`
      );
    }
    logger.debug(
      `Commissionee successfully connected to Thread network ${this.commissioningOptions.threadNetwork.networkName ?? "via operational dataset"} (networkId ${Bytes.toHex(networkId)})`
    );
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 18-19
   * 18: Finalization of the Commissioning process begins. An Administrator configured in the ACL of the Commissionee
   *     by the Commissioner SHALL use Operational Discovery to discover the Commissionee. This Administrator MAY be
   *     the Commissioner itself, or another Node to which the Commissioner has delegated the task.
   * 19: The Administrator SHALL open a CASE (see Section 4.13.2, “Certificate Authenticated Session Establishment
   *     (CASE)”) session with the Commissionee over the operational network.
   */
  async #reconnectWithDevice() {
    const isConcurrentFlow = this.collectedCommissioningData.supportsConcurrentConnection !== false;
    logger.debug(`Reconnecting with device with ${isConcurrentFlow ? "concurrent" : "non-concurrent"} flow ...`);
    await this.#ensureFailsafeTimerFor(Minutes(5));
    if (!isConcurrentFlow) {
      this.#armFailsafeInterval?.stop();
    }
    let transitionResult;
    try {
      transitionResult = await this.transitionToCase(
        this.interaction.address,
        // Assume concurrent connections are supported if not know (which should not be the case when we came here)
        isConcurrentFlow
      );
    } catch (cause) {
      throw new OperativeConnectionFailedError("Operative reconnection with device failed", { cause });
    } finally {
      this.#armFailsafeInterval?.stop();
    }
    if (transitionResult === void 0) {
      logger.debug("CASE commissioning handled externally, terminating commissioning flow");
      return {
        code: 3 /* Stop */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    await this.interaction.close();
    this.interaction = transitionResult;
    this.#clusterClients.clear();
    logger.debug("Successfully reconnected with device ...");
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  /**
   * Step 20
   * The Administrator having established a CASE session with the Commissionee over the operational network in the
   * previous steps SHALL invoke the CommissioningComplete command (see Section 11.9.6.6,
   * “CommissioningComplete Command”). A success response after invocation of the CommissioningComplete command ends
   * the commissioning process.
   */
  async #completeCommissioning() {
    this.#ensureGeneralCommissioningSuccess(
      "commissioningComplete",
      await this.#invokeCommand(
        {
          endpoint: RootEndpointNumber,
          cluster: GeneralCommissioning,
          command: "commissioningComplete"
        },
        {
          useExtendedFailSafeMessageResponseTimeout: true
        }
      )
    );
    this.#currentFailSafeEndTime = void 0;
    return {
      code: 0 /* Success */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  async #addDefaultOtaProvider() {
    if (this.commissioningOptions.otaUpdateProviderLocation === void 0) {
      return {
        code: 2 /* Skipped */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    if (!this.collectedCommissioningData?.otaRequestorList?.length) {
      logger.debug("No OTA Requestor found, skipping adding default OTA provider");
      return {
        code: 2 /* Skipped */,
        breadcrumb: this.lastBreadcrumb
      };
    }
    const { nodeId: providerNodeId, endpoint } = this.commissioningOptions.otaUpdateProviderLocation;
    let success = false;
    for (const requestorEndpoint of this.collectedCommissioningData.otaRequestorList) {
      try {
        const writeResult = await this.interaction.write(
          Write(
            Write.Attribute({
              endpoint: requestorEndpoint,
              cluster: OtaSoftwareUpdateRequestor,
              attributes: ["defaultOtaProviders"],
              value: [
                {
                  providerNodeId,
                  endpoint,
                  fabricIndex: this.fabric.fabricIndex
                }
              ]
            })
          )
        );
        WriteResult.assertSuccess(writeResult);
        success = true;
        logger.debug(`Added default OTA provider on endpoint ${endpoint}`);
      } catch (error) {
        logger.info("Could not set default OTA provider", error);
      }
    }
    return {
      code: success ? 0 /* Success */ : 1 /* Failure */,
      breadcrumb: this.lastBreadcrumb
    };
  }
  close() {
    this.#armFailsafeInterval?.stop();
  }
}
export {
  CommissioningTimeoutError,
  ControllerCommissioningFlow,
  DeviceAlreadyCommissionedToThisFabricError,
  FabricLabelConflictError,
  MaximumCommissionedFabricsReachedError,
  NodeIdConflictError,
  OperativeConnectionFailedError,
  ThreadNetworkSetupFailedError,
  WifiNetworkSetupFailedError,
  WifiOrThreadNetworkCredentialsNotConfiguredError
};
//# sourceMappingURL=ControllerCommissioningFlow.js.map
