/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { camelize, MatterError, serialize } from "@matter/general";
import { FeatureSet } from "@matter/model";
import { BitFlags } from "../../schema/BitmapSchema.js";
import { ClusterType } from "../ClusterType.js";
class IllegalClusterError extends MatterError {
}
const featureSelectionCache = /* @__PURE__ */ new WeakMap();
class ClusterComposer {
  constructor(cluster, allowInvalidFeatures = false) {
    this.cluster = cluster;
    this.#allowInvalidFeatureCombinations = allowInvalidFeatures;
  }
  #allowInvalidFeatureCombinations;
  /**
   * Build a cluster using selected feature flags.
   *
   * @param selection the name(s) of optional features to support
   * @throws {IllegalClusterError} if the feature combination is disallowed by the Matter specification
   */
  compose(selection) {
    this.#validateFeatureSelection(selection);
    const extensions = this.cluster.extensions;
    let cluster;
    const cacheKey = [...selection].sort().join("\u241C");
    cluster = featureSelectionCache.get(this.cluster)?.[cacheKey]?.deref();
    if (!cluster) {
      const currentCacheKey = [...new FeatureSet(this.cluster.supportedFeatures)].sort().join("\u241C");
      if (currentCacheKey === cacheKey) {
        cluster = featureSelectionCache.get(this.cluster)?.[cacheKey]?.deref();
      }
    }
    if (cluster) {
      return cluster;
    }
    if (extensions) {
      const base = this.cluster.base ?? this.cluster;
      const baseElements = (type) => {
        const elements = {};
        const clusterElements = this.cluster[type];
        for (const name in base[type]) {
          elements[name] = clusterElements[name] ?? base[type][name];
        }
        return elements;
      };
      cluster = ClusterType({
        ...base,
        attributes: baseElements("attributes"),
        commands: baseElements("commands"),
        events: baseElements("events"),
        supportedFeatures: BitFlags(this.cluster.features, ...selection),
        base
      });
      for (const selector of extensions) {
        if (selector.component) {
          this.#accept(cluster, selector.component, this.cluster, selector.flags);
        } else {
          this.reject(cluster, selector.flags);
        }
      }
    } else {
      const supportedFeatures = BitFlags(this.cluster.features, ...selection);
      cluster = ClusterType({
        ...this.cluster,
        supportedFeatures,
        base: this.cluster.base ?? this.cluster
      });
    }
    const baseVariants = featureSelectionCache.get(this.cluster);
    if (baseVariants === void 0) {
      featureSelectionCache.set(this.cluster, { [cacheKey]: new WeakRef(cluster) });
    } else {
      baseVariants[cacheKey] = new WeakRef(cluster);
    }
    return cluster;
  }
  /**
   * Validates a set of feature flags against the features supported by a cluster.
   */
  #validateFeatureSelection(features) {
    for (const f of features) {
      if (!this.cluster.features[camelize(f)]) {
        throw new IllegalClusterError(`"${f}" is not a valid feature identifier`);
      }
    }
  }
  /**
   * Injects a component into a cluster if the cluster supports the specified features.  Uses matching element from
   * "original" if present.  This allows for component insertion without overwrite of other changes to the named
   * element.
   */
  #accept(definition, component, original, flags) {
    for (const k in flags) {
      if (!!definition.supportedFeatures[k] !== !!flags[k]) {
        return;
      }
    }
    ClusterComposer.injectElements(definition, component, original);
  }
  static injectElements(definition, component, original) {
    function installElements(name) {
      const src = component[name];
      if (!src) {
        return;
      }
      let dest = definition[name];
      if (dest) {
        dest = { ...dest };
      } else {
        dest = {};
      }
      definition[name] = dest;
      for (const key in src) {
        const orig = original?.[name]?.[key];
        if (orig) {
          dest[key] = orig;
        } else {
          dest[key] = src[key];
        }
      }
    }
    installElements("attributes");
    installElements("commands");
    installElements("events");
  }
  /**
   * Throws an error if a feature combination is illegal per the Matter specification.
   */
  reject(definition, flags) {
    if (this.#allowInvalidFeatureCombinations) {
      return;
    }
    for (const k in flags) {
      if (!!definition.supportedFeatures[k] !== !!flags[k]) {
        return;
      }
    }
    throw new IllegalClusterError(
      `Feature combination ${serialize(flags)} is disallowed by the Matter specification`
    );
  }
}
export {
  ClusterComposer,
  IllegalClusterError
};
//# sourceMappingURL=ClusterComposer.js.map
