/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { CommissioningMode } from "#advertisement/CommissioningMode.js";
import { ServiceDescription } from "#advertisement/ServiceDescription.js";
import { FabricManager } from "#fabric/FabricManager.js";
import { SecureChannelProtocol } from "#securechannel/SecureChannelProtocol.js";
import { PaseServer } from "#session/pase/PaseServer.js";
import { SessionManager } from "#session/SessionManager.js";
import {
  Environmental,
  InternalError,
  Lifecycle,
  Logger,
  MatterFlowError,
  ObserverGroup,
  Time
} from "@matter/general";
import { STANDARD_COMMISSIONING_TIMEOUT, StatusCode, StatusResponseError } from "@matter/types";
import { AdministratorCommissioning } from "@matter/types/clusters/administrator-commissioning";
import { DeviceAdvertiser } from "./DeviceAdvertiser.js";
const logger = Logger.get("DeviceCommissioner");
class CommissioningConfigProvider {
}
class DeviceCommissioner {
  #context;
  #failsafeContext;
  #windowStatus = AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen;
  #activeDiscriminator;
  #activeCommissioningEndCallback;
  #observers = new ObserverGroup(this);
  #commissioningTimeout;
  constructor(context) {
    this.#context = context;
    this.#observers.on(this.#context.secureChannelProtocol.tooManyPaseErrors, async () => {
      logger.info("Maximum number of PASE pairing errors reached, canceling commissioning");
      await this.endCommissioning();
    });
  }
  static [Environmental.create](env) {
    const instance = new DeviceCommissioner({
      fabrics: env.get(FabricManager),
      sessions: env.get(SessionManager),
      advertiser: env.get(DeviceAdvertiser),
      secureChannelProtocol: env.get(SecureChannelProtocol),
      commissioningConfig: env.get(CommissioningConfigProvider)
    });
    env.set(DeviceCommissioner, instance);
    return instance;
  }
  get failsafeContext() {
    this.assertFailsafeArmed();
    return this.#failsafeContext;
  }
  async allowEnhancedCommissioning(discriminator, paseServer, commissioningEndCallback) {
    if (this.#windowStatus === AdministratorCommissioning.CommissioningWindowStatus.BasicWindowOpen) {
      throw new MatterFlowError(
        "Basic commissioning window is already open! Cannot set enhanced commissioning mode."
      );
    }
    this.#context.secureChannelProtocol.setPaseCommissioner(paseServer);
    this.#becomeCommissionable(
      AdministratorCommissioning.CommissioningWindowStatus.EnhancedWindowOpen,
      commissioningEndCallback,
      discriminator
    );
  }
  async allowBasicCommissioning(commissioningEndCallback) {
    if (this.#windowStatus === AdministratorCommissioning.CommissioningWindowStatus.EnhancedWindowOpen) {
      throw new MatterFlowError(
        "Enhanced commissioning window is already open! Cannot set basic commissioning mode."
      );
    }
    this.#context.secureChannelProtocol.setPaseCommissioner(
      await PaseServer.fromPin(this.#context.sessions, this.#context.commissioningConfig.values.passcode, {
        iterations: 1e3,
        salt: this.#context.fabrics.crypto.randomBytes(32)
      })
    );
    this.#becomeCommissionable(
      AdministratorCommissioning.CommissioningWindowStatus.BasicWindowOpen,
      commissioningEndCallback
    );
  }
  beginTimed(failsafeContext) {
    this.#failsafeContext = failsafeContext;
    failsafeContext.commissioned.on(async () => await this.endCommissioning());
    failsafeContext.construction.change.on((status) => {
      if (status === Lifecycle.Status.Destroyed) {
        this.#failsafeContext = void 0;
      }
    });
  }
  get isFailsafeArmed() {
    return this.#failsafeContext !== void 0;
  }
  assertFailsafeArmed(message) {
    if (this.isFailsafeArmed) return;
    throw new StatusResponseError(
      message ?? "Failsafe timer needs to be armed to execute this action.",
      StatusCode.FailsafeRequired
    );
  }
  #enterCommissioningMode(windowStatus, discriminator) {
    this.#cancelTimeout();
    this.#windowStatus = windowStatus;
    const commissioningConfig = this.#context.commissioningConfig.values;
    const advertisementWindowS = commissioningConfig.advertisementWindow ?? STANDARD_COMMISSIONING_TIMEOUT;
    const mode = windowStatus === AdministratorCommissioning.CommissioningWindowStatus.EnhancedWindowOpen ? CommissioningMode.Enhanced : CommissioningMode.Basic;
    this.#context.advertiser.enterCommissioningMode(
      ServiceDescription.Commissionable({
        ...commissioningConfig.productDescription,
        mode,
        discriminator: discriminator ?? commissioningConfig.discriminator
      })
    );
    this.#commissioningTimeout = Time.getTimer(
      "Commissioning timeout",
      advertisementWindowS,
      this.endCommissioning.bind(this)
    );
  }
  #becomeCommissionable(windowStatus, activeCommissioningEndCallback, discriminator) {
    if (this.#windowStatus !== AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen) {
      if (this.#windowStatus !== windowStatus) {
        throw new InternalError(
          `Commissioning mode ${windowStatus} request but already in mode ${this.#windowStatus}`
        );
      }
      if (this.#activeCommissioningEndCallback && this.#activeCommissioningEndCallback !== activeCommissioningEndCallback) {
        throw new InternalError(`Already in commissioning mode with a different callback`);
      }
    }
    if (this.#windowStatus === windowStatus && (discriminator === void 0 || discriminator === this.#activeDiscriminator)) {
      this.#enterCommissioningMode(this.#windowStatus, this.#activeDiscriminator);
      return;
    }
    if (this.#windowStatus !== AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen) {
      throw new InternalError(`Commissioning window already open with different mode (${this.#windowStatus})!`);
    }
    if (this.#activeCommissioningEndCallback !== void 0) {
      throw new InternalError("Commissioning window already open with different callback!");
    }
    this.#activeCommissioningEndCallback = activeCommissioningEndCallback;
    this.#activeDiscriminator = discriminator;
    this.#enterCommissioningMode(windowStatus, discriminator);
  }
  async endCommissioning() {
    if (this.#windowStatus === AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen) {
      return;
    }
    this.#cancelTimeout();
    await this.#context.secureChannelProtocol.removePaseCommissioner();
    this.#windowStatus = AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen;
    if (this.#activeCommissioningEndCallback !== void 0) {
      const activeCommissioningEndCallback = this.#activeCommissioningEndCallback;
      this.#activeCommissioningEndCallback = void 0;
      await activeCommissioningEndCallback();
    }
    await this.#context.advertiser.exitCommissioningMode();
    logger.debug("No longer commissioning");
  }
  async close() {
    this.#cancelTimeout();
    this.#observers.close();
    await this.endCommissioning();
    if (this.#failsafeContext) {
      await this.#failsafeContext.close();
      this.#failsafeContext = void 0;
    }
  }
  #cancelTimeout() {
    if (!this.#commissioningTimeout) {
      return;
    }
    this.#commissioningTimeout.stop();
    this.#commissioningTimeout = void 0;
  }
}
export {
  CommissioningConfigProvider,
  DeviceCommissioner
};
//# sourceMappingURL=DeviceCommissioner.js.map
