/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  AsyncObservable,
  Construction,
  Duration,
  Logger,
  MatterFlowError,
  UnexpectedDataError,
  UninitializedDependencyError
} from "#general";
import { CaseAuthenticatedTag, NodeId, ValidationError } from "#types";
import { FabricBuilder } from "../fabric/Fabric.js";
import { FailsafeTimer, MatterFabricConflictError } from "./FailsafeTimer.js";
const logger = Logger.get("FailsafeContext");
class MatterFabricInvalidAdminSubjectError extends MatterFlowError {
}
class FailsafeContext {
  #sessions;
  #fabrics;
  #failsafe;
  #construction;
  #associatedFabric;
  #csrSessionId;
  #forUpdateNoc;
  #fabricBuilder;
  #rootCertSet = false;
  #commissioned = AsyncObservable();
  constructor(options) {
    const { sessions, fabrics, expiryLength, session, maxCumulativeFailsafe } = options;
    this.#sessions = sessions;
    this.#fabrics = fabrics;
    this.#associatedFabric = session.fabric;
    this.#construction = Construction(this, async () => {
      this.#fabricBuilder = await FabricBuilder.create(this.#fabrics.crypto);
      await Promise.resolve();
      await this.storeEndpointState();
      this.#failsafe = new FailsafeTimer(
        this.#associatedFabric,
        expiryLength,
        maxCumulativeFailsafe,
        this.#failSafeExpired.bind(this)
      );
      logger.debug(`Arm failSafe timer for ${Duration.format(expiryLength)}`);
      session.closedByPeer.on(() => {
        if (!this.#failsafe?.completed) {
          return this.#failSafeExpired();
        }
      });
    });
  }
  async extend(fabric, expiryLength, currentExchange) {
    await this.#construction;
    await this.#failsafe?.reArm(fabric, expiryLength, currentExchange);
    if (expiryLength > 0) {
      logger.debug(`Extend failSafe timer for ${Duration.format(expiryLength)}`);
    }
  }
  get fabricIndex() {
    return this.#builder.fabricIndex;
  }
  get construction() {
    return this.#construction;
  }
  get commissioned() {
    return this.#commissioned;
  }
  get associatedFabric() {
    return this.#associatedFabric;
  }
  get csrSessionId() {
    return this.#csrSessionId;
  }
  get forUpdateNoc() {
    return this.#forUpdateNoc;
  }
  get rootCertSet() {
    return this.#rootCertSet;
  }
  get hasRootCert() {
    return this.#builder.rootCert !== void 0;
  }
  get rootCert() {
    return this.#builder.rootCert;
  }
  async completeCommission() {
    if (this.#failsafe === void 0) {
      throw new MatterFlowError("armFailSafe should be called first!");
    }
    this.#failsafe.complete();
    if (this.fabricIndex !== void 0) {
      await this.#fabrics.persistFabrics();
    }
    this.#failsafe = void 0;
    await this.commissioned.emit();
    await this.closePaseSession();
    await this.close();
  }
  getFailSafeContext() {
    if (this.#failsafe === void 0) throw new MatterFlowError("armFailSafe should be called first!");
    return this.#failsafe;
  }
  getNextFabricIndex() {
    return this.#fabrics.allocateFabricIndex();
  }
  async replaceFabric(fabric) {
    await this.#fabrics.replaceFabric(fabric);
    await this.#sessions.deleteResumptionRecordsForFabric(fabric);
  }
  /**
   * Handles a CSR from OperationalCredentials cluster and stores additional internal information for further
   * validity checks.
   */
  createCertificateSigningRequest(isForUpdateNoc, sessionId) {
    if (this.#fabrics.findByKeypair(this.#builder.keyPair)) {
      throw new MatterFlowError("Key pair already exists.");
    }
    const result = this.#builder.createCertificateSigningRequest();
    this.#csrSessionId = sessionId;
    this.#forUpdateNoc = isForUpdateNoc;
    return result;
  }
  async closePaseSession(currentExchange) {
    const session = this.#sessions.getPaseSession();
    if (session) {
      await session.initiateForceClose(currentExchange);
    }
  }
  async close(currentExchange) {
    await this.#construction.close(async () => {
      if (this.#failsafe) {
        await this.#failsafe.close();
        this.#failsafe = void 0;
        await this.rollback(currentExchange);
      }
    });
  }
  /** Handles adding a trusted root certificate from Operational Credentials cluster. */
  async setRootCert(rootCert) {
    await this.#builder.setRootCert(rootCert);
    this.#rootCertSet = true;
  }
  /**
   * Build a new Fabric object based on an existing fabric for the "UpdateNoc" case of the Operational Credentials
   * cluster.
   */
  async buildUpdatedFabric(nocValue, icacValue) {
    if (this.associatedFabric === void 0) {
      throw new MatterFlowError("No fabric associated with failsafe context, but we prepare an Fabric update.");
    }
    this.#builder.initializeFromFabricForUpdate(this.associatedFabric);
    await this.#builder.setOperationalCert(nocValue, icacValue);
    return await this.#builder.build(this.associatedFabric.fabricIndex);
  }
  /** Build a new Fabric object for a new fabric for the "AddNoc" case of the Operational Credentials cluster. */
  async buildFabric(nocData) {
    const builder = this.#builder;
    const { nocValue, icacValue, adminVendorId, ipkValue, caseAdminSubject } = nocData;
    if (!NodeId.isOperationalNodeId(caseAdminSubject) && !NodeId.isCaseAuthenticatedTag(caseAdminSubject)) {
      try {
        if (CaseAuthenticatedTag.getVersion(NodeId.extractAsCaseAuthenticatedTag(caseAdminSubject)) === 0) {
          throw new MatterFabricInvalidAdminSubjectError();
        }
      } catch (error) {
        if (error instanceof ValidationError || error instanceof UnexpectedDataError) {
          throw new MatterFabricInvalidAdminSubjectError();
        } else {
          throw error;
        }
      }
    }
    await builder.setOperationalCert(nocValue, icacValue);
    const newGlobalId = await builder.globalId;
    if (this.#fabrics.has(newGlobalId)) {
      throw new MatterFabricConflictError(
        `Fabric with Id ${builder.fabricId} and Node Id ${builder.nodeId} already exists.`
      );
    }
    this.#associatedFabric = await builder.setRootVendorId(adminVendorId).setIdentityProtectionKey(ipkValue).setRootNodeId(caseAdminSubject).build(this.#fabrics.allocateFabricIndex());
    this.#fabrics.addFabric(this.#associatedFabric);
    if (this.#failsafe) {
      this.#failsafe.associatedFabric = this.#associatedFabric;
    }
    return this.#associatedFabric;
  }
  async #failSafeExpired(currentExchange) {
    logger.info("Failsafe timer expired; resetting fabric builder");
    await this.close(currentExchange);
  }
  async rollback(currentExchange) {
    if (this.fabricIndex !== void 0 && !this.#forUpdateNoc) {
      logger.debug(`Revoking fabric index ${this.fabricIndex}`);
      await this.#associatedFabric?.delete(currentExchange);
    }
    await this.closePaseSession(currentExchange);
    let fabric = void 0;
    if (this.fabricIndex !== void 0) {
      const fabricIndex = this.fabricIndex;
      if (this.#fabrics.has(fabricIndex)) {
        fabric = this.#fabrics.for(fabricIndex);
        for (const session of this.#sessions.sessionsForFabricIndex(fabricIndex)) {
          await session.initiateForceClose(currentExchange);
        }
      }
    }
    await this.restoreNetworkState();
    if (this.#forUpdateNoc && this.associatedFabric !== void 0) {
      await this.restoreFabric(this.associatedFabric);
    }
    if (!this.#forUpdateNoc && fabric !== void 0) {
      await this.#associatedFabric?.delete();
    }
    await this.restoreBreadcrumb();
  }
  async restoreFabric(fabric) {
    await this.replaceFabric(fabric);
  }
  get #builder() {
    if (this.#fabricBuilder === void 0) {
      throw new UninitializedDependencyError("FailsafeContext", "Fabric builder has not been initialized");
    }
    return this.#fabricBuilder;
  }
}
export {
  FailsafeContext,
  MatterFabricInvalidAdminSubjectError
};
//# sourceMappingURL=FailsafeContext.js.map
