/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { AsyncObservable, camelize, GeneratedClass, ImplementationError } from "#general";
import { ClusterModel, ElementTag, FeatureMap, FeatureSet, Matter, Metatype } from "#model";
import { Behavior } from "../Behavior.js";
import { DerivedState } from "../state/StateType.js";
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 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: 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 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 ClusterModel) {
    const normalized = FeatureSet.normalize(schema.featureMap, schema.supportedFeatures);
    featuresAvailable = normalized.featuresAvailable;
    featuresSupported = normalized.featuresSupported;
  } else {
    featuresAvailable = new FeatureSet();
    featuresSupported = new FeatureSet();
  }
  const props = {};
  for (const member of schema.activeMembers) {
    const name = 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 === 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 = 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 GeneratedClass({
    name: `${cluster.name}$Events`,
    base: base.Events,
    initialize() {
      for (const name of names) {
        this[name] = AsyncObservable();
      }
    }
  });
}
function schemaForCluster(cluster) {
  let schema;
  for (const child of Matter.children) {
    if (child.tag === ElementTag.Cluster && child.id === cluster.id) {
      schema = new ClusterModel(child);
      break;
    }
  }
  if (schema === void 0) {
    throw new ImplementationError(`Cannot locate schema for cluster ${cluster.id}, please supply manually`);
  }
  return schema;
}
function syncFeatures(schema, cluster) {
  if (!(schema instanceof ClusterModel)) {
    return schema;
  }
  const incomingFeatures = new FeatureSet(cluster.supportedFeatures);
  if (new FeatureSet(cluster.supportedFeatures).is(schema.supportedFeatures)) {
    return schema;
  }
  schema = schema.clone();
  schema.supportedFeatures = incomingFeatures;
  return schema;
}
function createDefaultCommandDescriptors(cluster, base) {
  const result = {};
  const instance = introspectionInstanceOf(base);
  for (const name in cluster.commands) {
    if (!instance[name]) {
      result[name] = {
        value: 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 Metatype.bitmap:
    case Metatype.object:
      return {};
    case Metatype.array:
      return [];
  }
}
export {
  createType,
  introspectionInstanceOf
};
//# sourceMappingURL=ClusterBehaviorUtil.js.map
