/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { KeySets } from "#groups/KeySets.js";
import { MessagingState } from "#groups/MessagingState.js";
import { Bytes, hex, InternalError, MatterFlowError } from "@matter/general";
import { MATTER_EPOCH_OFFSET_US } from "@matter/types";
import { Groups } from "./Groups.js";
const GROUP_SECURITY_INFO = Bytes.fromString("GroupKey v1.0");
const IPK_DEFAULT_EPOCH_START_TIME = MATTER_EPOCH_OFFSET_US;
class FabricGroups {
  #fabric;
  #groups;
  #messagingState;
  /** Operationally enhanced variants of the group key sets based on their ID. */
  #keySets = new KeySets();
  constructor(fabric, storage) {
    this.#fabric = fabric;
    this.#groups = new Groups(fabric, this.#keySets);
    this.#messagingState = new MessagingState(fabric.crypto, storage);
    this.#keySets.add({
      groupKeySetId: 0,
      epochKey0: fabric.identityProtectionKey,
      operationalEpochKey0: fabric.operationalIdentityProtectionKey,
      epochStartTime0: IPK_DEFAULT_EPOCH_START_TIME,
      // with the Year 2000-offset that's basically 0 and always ok, but only for the IPK key
      groupSessionId0: null,
      epochKey1: null,
      operationalEpochKey1: null,
      epochStartTime1: null,
      groupSessionId1: null,
      epochKey2: null,
      operationalEpochKey2: null,
      epochStartTime2: null,
      groupSessionId2: null,
      groupKeySecurityPolicy: 0,
      // GroupKeyManagement.GroupKeySecurityPolicy.TrustFirst, the other option is provisional
      groupKeyMulticastPolicy: 0
      // GroupKeyManagement.GroupKeyMulticastPolicy.PerGroupId, provisional!
    });
    if (storage !== void 0) {
      this.storage = storage;
    }
  }
  set storage(storage) {
    this.#messagingState.storage = storage;
  }
  /** Operative lookup of the group key sets by their id. */
  get keySets() {
    return this.#keySets;
  }
  get messaging() {
    return this.#messagingState;
  }
  /** Operative lookup of the group key set id and the key by a group session Id. */
  get sessions() {
    return this.#keySets.sessions;
  }
  get groupKeyIdMap() {
    return this.#groups.idMap;
  }
  set groupKeyIdMap(map) {
    this.#groups.idMap = map;
  }
  get endpoints() {
    return this.#groups.endpointMap;
  }
  currentKeyForGroup(groupId) {
    return this.#groups.currentKeyForId(groupId);
  }
  subjectForGroup(id, keySetId) {
    return this.#groups.subjectForGroup(id, keySetId);
  }
  multicastAddressFor(groupId) {
    return this.#groups.multicastAddress(groupId);
  }
  /*
  TODO for Controller to generate new epochs
  addGroupEpoch(groupKeySetId: number, startTimeMs = Time.nowMs) {
      // TODO for Controller to generate new epochs
      const epochKey = Crypto.getRandomData(CRYPTO_SYMMETRIC_KEY_LENGTH);
      const operationalEpochKey = Crypto.hkdf(epochKey, this.#fabric.operationalId, GROUP_SECURITY_INFO);
      const epochStartTime = startTimeMs * 1000;
      logger.debug(`addGroupEpoch: epochStartTime=${epochStartTime}`, operationalEpochKey);
      // TODO extend in structure and such
  }
  */
  /**
   * Sets new group key set data and pre-calculates all operative data like session ids and operational keys.
   * Overwriting the existing one if it exists.
   */
  async setFromGroupKeySet(groupKeySet) {
    const { groupKeySetId, epochKey0, epochKey1, epochKey2 } = groupKeySet;
    if (epochKey0 === null) {
      throw new MatterFlowError("EpochKey0 must be set");
    }
    await this.#cleanUpCounters(groupKeySetId);
    const globalId = Bytes.fromHex(hex.fixed(this.#fabric.globalId, 16));
    const operationalEpochKey0 = await this.#fabric.crypto.createHkdfKey(epochKey0, globalId, GROUP_SECURITY_INFO);
    const operationalEpochKey1 = epochKey1 !== null ? await this.#fabric.crypto.createHkdfKey(epochKey1, globalId, GROUP_SECURITY_INFO) : null;
    const operationalEpochKey2 = epochKey2 !== null ? await this.#fabric.crypto.createHkdfKey(epochKey2, globalId, GROUP_SECURITY_INFO) : null;
    this.#keySets.add({
      ...groupKeySet,
      operationalEpochKey0,
      groupSessionId0: await this.#keySets.sessionIdFromKey(this.#fabric.crypto, operationalEpochKey0),
      operationalEpochKey1,
      groupSessionId1: operationalEpochKey1 !== null ? await this.#keySets.sessionIdFromKey(this.#fabric.crypto, operationalEpochKey1) : null,
      operationalEpochKey2,
      groupSessionId2: operationalEpochKey2 !== null ? await this.#keySets.sessionIdFromKey(this.#fabric.crypto, operationalEpochKey2) : null
    });
  }
  /** Removes a group key set by its id and cleans up the counters and data. */
  async removeGroupKeySet(groupKeySetId) {
    if (groupKeySetId === 0) {
      throw new InternalError("Cannot remove the group key set 0.");
    }
    await this.#cleanUpCounters(groupKeySetId, true);
    return this.#keySets.delete("groupKeySetId", groupKeySetId);
  }
  /** Cleans up the counters and data for a group key set by its id. */
  async #cleanUpCounters(groupKeySetId, forDelete = false) {
    if (this.#keySets.forId(groupKeySetId) === void 0) {
      return;
    }
    const operationalKeys = this.#keySets.allKeysForId(groupKeySetId);
    for (const { key } of operationalKeys) {
      await this.#messagingState.removeCounter(key, forDelete);
    }
  }
}
export {
  FabricGroups,
  GROUP_SECURITY_INFO,
  IPK_DEFAULT_EPOCH_START_TIME
};
//# sourceMappingURL=FabricGroups.js.map
