"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var ClusterBehaviorUtil_exports = {};
__export(ClusterBehaviorUtil_exports, {
  createType: () => createType,
  introspectionInstanceOf: () => introspectionInstanceOf
});
module.exports = __toCommonJS(ClusterBehaviorUtil_exports);
var import_general = require("#general");
var import_model = require("#model");
var import_Behavior = require("../Behavior.js");
var import_StateType = require("../state/StateType.js");
/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const KNOWN_DEFAULTS = Symbol("knownDefaults");
function introspectionInstanceOf(type) {
  return new type();
}
function createType(cluster, base, schema) {
  const namesUsed = /* @__PURE__ */ new Set();
  if (schema === void 0) {
    if (base.schema) {
      schema = base.schema;
    }
    if (!schema) {
      schema = schemaForCluster(cluster);
    }
  }
  schema = syncFeatures(schema, cluster);
  let name;
  if (base.name.startsWith(cluster.name)) {
    name = base.name;
  } else {
    name = `${cluster.name}Behavior`;
  }
  schema.freeze();
  return (0, import_general.GeneratedClass)({
    name,
    base,
    // These are really read-only but installing as getters on the prototype prevents us from overriding using
    // namespace overrides.  If we instead override as static properties then we lose the automatic interface type.
    // So just publish as static properties.
    staticProperties: {
      State: createDerivedState(cluster, schema, base, namesUsed),
      Events: createDerivedEvents(cluster, base, namesUsed)
    },
    staticDescriptors: {
      id: {
        value: (0, import_general.camelize)(cluster.name),
        enumerable: true
      },
      cluster: {
        value: cluster,
        enumerable: true
      },
      schema: {
        value: schema
      }
    },
    instanceDescriptors: createDefaultCommandDescriptors(cluster, base)
  });
}
function createDerivedState(cluster, schema, base, namesUsed) {
  const BaseState = base["State"];
  if (BaseState === void 0) {
    throw new import_general.ImplementationError(`No state class defined for behavior class ${base.name}`);
  }
  const oldDefaults = new BaseState();
  let knownDefaults = BaseState[KNOWN_DEFAULTS];
  let featuresAvailable, featuresSupported;
  if (schema instanceof import_model.ClusterModel) {
    const normalized = import_model.FeatureSet.normalize(schema.featureMap, schema.supportedFeatures);
    featuresAvailable = normalized.featuresAvailable;
    featuresSupported = normalized.featuresSupported;
  } else {
    featuresAvailable = new import_model.FeatureSet();
    featuresSupported = new import_model.FeatureSet();
  }
  const props = {};
  for (const member of schema.activeMembers) {
    const name = (0, import_general.camelize)(member.name);
    if (props[name]) {
      props[name].push(member);
    } else {
      props[name] = [member];
    }
  }
  const defaults = {};
  for (const name in props) {
    const attrs = props[name];
    let propSchema;
    let applies = false;
    for (const attr of attrs) {
      if (attr.effectiveConformance.isApplicable(featuresAvailable, featuresSupported)) {
        propSchema = attr;
        applies = true;
        break;
      }
    }
    if (!applies) {
      if (oldDefaults[name] !== void 0) {
        if (!knownDefaults) {
          knownDefaults = {};
        } else if (knownDefaults === BaseState[KNOWN_DEFAULTS]) {
          knownDefaults = { ...knownDefaults };
        }
        knownDefaults[name] = oldDefaults[name];
        defaults[name] = void 0;
      }
      continue;
    }
    const attribute = cluster.attributes[name];
    if (attribute?.id === import_model.FeatureMap.id) {
      defaults[name] = cluster.supportedFeatures;
      continue;
    }
    defaults[name] = selectDefaultValue(
      oldDefaults[name] === void 0 ? knownDefaults?.[name] : oldDefaults[name],
      attribute,
      propSchema
    );
  }
  for (const name in defaults) {
    namesUsed.add(name);
  }
  const StateType = (0, import_StateType.DerivedState)({
    name: `${cluster.name}$State`,
    base: base.State,
    values: defaults
  });
  if (knownDefaults) {
    StateType[KNOWN_DEFAULTS] = knownDefaults;
  }
  return StateType;
}
function createDerivedEvents(cluster, base, stateNames) {
  const names = /* @__PURE__ */ new Set(["interactionBegin", "interactionEnd"]);
  const baseInstance = new base.Events();
  const applicableClusterEvents = /* @__PURE__ */ new Set();
  for (const name in cluster.events) {
    applicableClusterEvents.add(name);
    if (!cluster.events[name].optional && baseInstance[name] === void 0) {
      names.add(name);
    }
  }
  for (const attrName of stateNames) {
    const changing = `${attrName}$Changing`;
    if (baseInstance[changing] === void 0) {
      names.add(changing);
    }
    const changed = `${attrName}$Changed`;
    if (baseInstance[changed] === void 0) {
      names.add(changed);
    }
  }
  return (0, import_general.GeneratedClass)({
    name: `${cluster.name}$Events`,
    base: base.Events,
    initialize() {
      for (const name of names) {
        this[name] = (0, import_general.AsyncObservable)();
      }
    }
  });
}
function schemaForCluster(cluster) {
  let schema;
  for (const child of import_model.Matter.children) {
    if (child.tag === import_model.ElementTag.Cluster && child.id === cluster.id) {
      schema = child;
      break;
    }
  }
  if (schema === void 0) {
    throw new import_general.ImplementationError(`Cannot locate schema for cluster ${cluster.id}, please supply manually`);
  }
  return schema;
}
const configuredSchemaCache = /* @__PURE__ */ new Map();
function syncFeatures(schema, cluster) {
  if (!(schema instanceof import_model.ClusterModel)) {
    return schema;
  }
  const incomingFeatures = new import_model.FeatureSet(cluster.supportedFeatures);
  if (new import_model.FeatureSet(cluster.supportedFeatures).is(schema.supportedFeatures)) {
    return schema;
  }
  const featureKey = [...incomingFeatures].sort().join(",");
  let schemaBucket = configuredSchemaCache.get(schema);
  if (schemaBucket === void 0) {
    schemaBucket = {};
    configuredSchemaCache.set(schema, schemaBucket);
  } else {
    if (featureKey in schemaBucket) {
      return schemaBucket[featureKey];
    }
  }
  schema = schema.clone();
  schema.supportedFeatures = incomingFeatures;
  schemaBucket[featureKey] = schema;
  return schema;
}
function createDefaultCommandDescriptors(cluster, base) {
  const result = {};
  const instance = introspectionInstanceOf(base);
  for (const name in cluster.commands) {
    if (!instance[name]) {
      result[name] = {
        value: import_Behavior.Behavior.unimplemented,
        writable: true
      };
    }
  }
  return result;
}
function selectDefaultValue(oldDefault, clusterAttr, schemaProp) {
  if (oldDefault !== void 0) {
    return oldDefault;
  }
  if (clusterAttr?.optional) {
    return;
  }
  if (clusterAttr?.default !== void 0) {
    return clusterAttr.default;
  }
  if (!schemaProp) {
    return;
  }
  if (schemaProp.nullable) {
    return null;
  }
  const effectiveDefault = schemaProp.effectiveDefault;
  if (effectiveDefault) {
    return effectiveDefault;
  }
  const conformance = schemaProp.effectiveConformance;
  if (!conformance.isMandatory) {
    return;
  }
  switch (schemaProp.effectiveMetatype) {
    case import_model.Metatype.bitmap:
    case import_model.Metatype.object:
      return {};
    case import_model.Metatype.array:
      return [];
  }
}
//# sourceMappingURL=ClusterBehaviorUtil.js.map
