"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 Model_exports = {};
__export(Model_exports, {
  Model: () => Model,
  StructuralModelError: () => StructuralModelError
});
module.exports = __toCommonJS(Model_exports);
var import_general = require("@matter/general");
var import_common = require("../common/index.js");
var import_ModelTraversal = require("../logic/ModelTraversal.js");
var import_Children = require("./Children.js");
var import_ModelTreePosition = require("./ModelTreePosition.js");
var import_Resource = require("./Resource.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
class StructuralModelError extends import_general.ImplementationError {
}
class Model {
  type;
  isSeed;
  /**
   * Indicates that an element defines a datatype.
   */
  isType;
  /**
   * Normally {@link base} performs lookup based on {@link type}.  Setting this field short circuits this resolution.
   *
   * The operational base also enables resolution from the operational base's tree.  This enables resolution on
   * operational models that are not installed in a parent hierarchy.
   *
   * We set the operational base when freezing the model.
   */
  operationalBase;
  /**
   * This is like {@link operationalBase} except for the element's shadow.
   */
  operationalShadow;
  /**
   * Indicates whether the corresponding element is supported by an implementation.
   *
   * This is an operational implementation hint and does not override conformance.
   */
  operationalIsSupported;
  #id;
  #name;
  #isFinal;
  #resource;
  #children;
  #position;
  get id() {
    return this.#id;
  }
  set id(value) {
    const oldId = this.effectiveId;
    this.#id = value;
    this.#position.parent?.children?.updateId(this, oldId);
  }
  get name() {
    return this.#name;
  }
  set name(value) {
    const oldName = this.#name;
    this.#name = value;
    this.#position.parent?.children?.updateName(this, oldName);
  }
  /**
   * The camelCase form of {@link name}, suitable for use as a JS property key.
   */
  get propertyName() {
    return (0, import_general.camelize)(this.name);
  }
  /**
   * Did validation find errors?
   */
  get valid() {
    return !this.errors;
  }
  /**
   * The path ("." delimited) in the Matter tree.
   *
   * This is informational and generally tries to adhere to JS API conventions.
   */
  get path() {
    if (this.parent && this.parent.tag !== import_common.ElementTag.Matter) {
      if (this.parent.tag === import_common.ElementTag.Field) {
        return `${this.parent.path}.${this.propertyName}`;
      }
      if (this.parent.tag === import_common.ElementTag.Cluster) {
        switch (this.tag) {
          case import_common.ElementTag.Attribute:
            return `${this.parent.path}.state.${this.propertyName}`;
          case import_common.ElementTag.Command:
            return `${this.parent.path}.${this.propertyName}`;
          case import_common.ElementTag.Event:
            return `${this.parent.path}.events.${this.propertyName}`;
        }
      }
      const parent = this.parent;
      if (parent.tag !== import_common.ElementTag.Cluster) {
        const parentMetatype = parent?.effectiveMetatype;
        if (parentMetatype === import_common.Metatype.object || parentMetatype === import_common.Metatype.array) {
          return `${parent.path}.${this.propertyName}`;
        }
      }
      return `${parent.path}.${this.name}`;
    } else {
      return this.name;
    }
  }
  /**
   * Determine if this model resides in the global namespace.
   */
  get isGlobal() {
    return this.tag === "matter" || this.parent?.tag === "matter";
  }
  /**
   * The structural parent.  This is the model for the element that contains this element's definition.
   */
  get parent() {
    return this.#position.parent;
  }
  /**
   * The tree position object for this model.  Used by {@link Children} to manage parent/root state.
   */
  get treePosition() {
    return this.#position;
  }
  /**
   * The structural root.  This is the MatterModel that owns this model.
   */
  get root() {
    return this.#position.root;
  }
  set parent(parent) {
    if (this.parent === parent) {
      return;
    }
    if (this.parent) {
      const index = this.parent.children.indexOf(this);
      if (index !== -1) {
        this.parent.children.splice(index, 1);
      }
    }
    if (parent) {
      parent.children.push(this);
    }
  }
  /**
   * Allows subclasses to pull a working ID from an alternate source.
   */
  get effectiveId() {
    return this.id;
  }
  /**
   * Get a string that uniquely identifies this model.  This is normally the effective ID but some models require a
   * generated identifier.
   */
  get key() {
    const key = this.effectiveId?.toString();
    if (key === void 0) {
      return key;
    }
    const discriminator = this.discriminator;
    if (discriminator === void 0) {
      return key;
    }
    return `${key}\u241C${discriminator}`;
  }
  /**
   * Obtain a discriminator that differentiates different models with the same name
   */
  get discriminator() {
    return;
  }
  /**
   * Children of models are always models.
   */
  get children() {
    if (!this.#children) {
      this.children = [];
    }
    return this.#children;
  }
  get hasChildren() {
    return !!this.#children?.length;
  }
  /**
   * Set the children of the model.
   */
  set children(children) {
    this.#children = (0, import_Children.Children)(children, this.#position);
  }
  /**
   * Factory support.
   */
  static types = {};
  /**
   * Add a new model implementation.
   */
  static register() {
    Model.types[this.Tag] = this;
  }
  /**
   * All possible tags for registered models of this type.
   */
  static get tags() {
    return Object.values(Model.types).filter((type) => type instanceof this).map((type) => type.Tag);
  }
  /**
   * In some circumstances the base type can be inferred.  This inference happens here.
   *
   * Does not recurse so only returns the direct base type.
   */
  get effectiveType() {
    return this.type;
  }
  /**
   * Get a model for my base type as defined by {@link type}, if any.
   */
  get base() {
    if (this.operationalBase !== void 0) {
      return this.operationalBase ?? void 0;
    }
    return new import_ModelTraversal.ModelTraversal().findBase(this);
  }
  /**
   * Get shadow model, if any.  A "shadow" is an element in my parent's inheritance hierarchy that I override.
   */
  get shadow() {
    if (this.operationalShadow !== void 0) {
      return this.operationalShadow ?? void 0;
    }
    return new import_ModelTraversal.ModelTraversal().findShadow(this);
  }
  /**
   * Get the first global base type.  This may have semantic meaning more specific than the base primitive type.
   */
  get globalBase() {
    return new import_ModelTraversal.ModelTraversal().findGlobalBase(this);
  }
  /**
   * A local or parent xref.
   */
  get effectiveXref() {
    return new import_ModelTraversal.ModelTraversal().findXref(this);
  }
  /**
   * The set of tags from which this model may derive.
   */
  get allowedBaseTags() {
    return [this.tag];
  }
  /**
   * Obtain the "is supported" flag from inheritance.
   */
  get effectiveIsSupported() {
    if (this.operationalIsSupported !== void 0) {
      return this.operationalIsSupported;
    }
    return this.base?.effectiveIsSupported;
  }
  /**
   * Update a subset of fields.
   *
   * Only allows for updates to element properties.  Recurses to children.
   */
  patch(values) {
    for (const [name, value] of Object.entries(values.valueOf())) {
      this[name] = value;
    }
  }
  /**
   * Determine whether this element applies to a specific revision.
   */
  appliesTo(revision) {
    return (this.asOf === void 0 || revision >= this.asOf) && (this.until === void 0 || revision < this.until);
  }
  /**
   * Create a model for an element.
   */
  static create(definition) {
    if (typeof definition !== "object") {
      throw new StructuralModelError(`Model definition must be object, not ${typeof definition}`);
    }
    const t = definition["tag"];
    const constructor = Model.types[t];
    if (!constructor) {
      throw new StructuralModelError(`Unknown element tag "${t}"`);
    }
    return new constructor(definition);
  }
  /**
   * Retrieve all models of a specific element type from local scope.
   *
   * @param type model class or a predicate object
   * @param key filters to models matching a specific type
   */
  all(type, key) {
    return this.children.all(type, key);
  }
  get(type, key) {
    if (key === void 0) {
      return void 0;
    }
    return this.children.get(type, key);
  }
  /**
   * Retrieve a model of a specific type from the ownership hierarchy.
   */
  owner(constructor) {
    return new import_ModelTraversal.ModelTraversal().findOwner(constructor, this);
  }
  /**
   * Record a validation error for this model.
   */
  error(code, message) {
    if (!this.errors) {
      this.errors = [];
    }
    this.errors.push({
      code,
      source: this.path,
      message,
      xref: this.effectiveXref?.toString()
    });
  }
  /**
   * Convert model to JSON.
   */
  toJSON() {
    const fields = this.valueOf();
    if (this.children.length) {
      fields.children = this.children.map((child) => child.toJSON());
    }
    return fields;
  }
  /**
   * Convert to non-class structure.
   */
  valueOf() {
    return this.toElement();
  }
  /**
   * Convert to elemental form.
   */
  toElement(omitResources = false, extra) {
    if (omitResources) {
      return {
        tag: this.tag,
        type: this.type,
        isSeed: this.isSeed,
        id: this.id,
        name: this.name,
        ...extra
      };
    }
    const result = {
      tag: this.tag,
      type: this.type,
      isSeed: this.isSeed,
      id: this.#id,
      name: this.#name,
      ...this.resource,
      ...extra
    };
    delete result.errors;
    return result;
  }
  /**
   * Apply a function to all tree elements.
   */
  visit(visitor) {
    return new import_ModelTraversal.ModelTraversal().visit(this, visitor);
  }
  /**
   * Visit this model and each of its ancestors.
   */
  forEachAncestor(fn) {
    new import_ModelTraversal.ModelTraversal().visitInheritance(this, fn);
  }
  /**
   * Find all children that reference a specific type.
   */
  references(type) {
    return new import_ModelTraversal.ModelTraversal().findReferences(this, type);
  }
  /**
   * Search the inheritance chain for a child property.
   */
  member(key, allowedTags = [import_common.ElementTag.Field, import_common.ElementTag.Attribute]) {
    return new import_ModelTraversal.ModelTraversal().findMember(this, key, allowedTags);
  }
  /**
   * Resolve a qualified name (e.g., "Foo.Bar") to a model.
   *
   * Searches for a member by name within this model's scope.  For qualified paths, each segment narrows into the
   * previous match's children.
   */
  resolve(name, options) {
    const path = typeof name === "string" ? name.split(".") : name;
    const leafTags = options?.tags ?? [import_common.ElementTag.Field, import_common.ElementTag.Attribute];
    const traversal = new import_ModelTraversal.ModelTraversal();
    return traversal.findQualified(
      this,
      path,
      (name2, scope, isLeaf) => traversal.findMember(scope, name2, isLeaf ? leafTags : void 0),
      {
        boundary: options?.boundary ?? ((scope) => !!scope.isScope),
        outerResolve: options?.outerResolve
      }
    );
  }
  /**
   * Access a member that must exist.
   */
  require(type, key) {
    let member = this.member(key, [type.Tag]);
    if (member === void 0 && typeof key === "string") {
      member = this.member((0, import_general.camelize)(key, true));
    }
    if (member !== void 0) {
      return member;
    }
    let what;
    switch (typeof key) {
      case "string":
        what = `"${key}"`;
        break;
      case "number":
        what = `#${key}"`;
        break;
      default:
        what = `selected`;
    }
    throw new StructuralModelError(`No ${type.Tag} ${what} for ${this.tag} ${this.name}`);
  }
  /**
   * Does this model derive from another?
   */
  instanceOf(other) {
    return new import_ModelTraversal.ModelTraversal().instanceOf(this, other);
  }
  /**
   * Clone the model.  This deep copies all descendant child models but not other properties.
   */
  clone() {
    const Type = this.constructor;
    return new Type(this);
  }
  /**
   * Create an operational extension of the model.  This creates a new model that inherits from this model for
   * operational purposes.
   */
  extend(properties, ...children) {
    const constructor = this.constructor;
    const definition = {
      id: this.id,
      name: this.name,
      ...properties,
      // This assertion may be a lie, but necessary due to apparent TS bug
      tag: this.tag,
      operationalBase: this
    };
    if (children.length) {
      if (definition.children) {
        definition.children = [...definition.children, ...children];
      } else {
        definition.children = children;
      }
    }
    const extension = new constructor(definition);
    return extension;
  }
  constructor(definition, ...children) {
    if (typeof definition !== "object") {
      throw new StructuralModelError(`Model definition must be an object, not ${typeof definition}`);
    }
    this.#position = new import_ModelTreePosition.ModelTreePosition(this);
    const isClone = definition instanceof Model;
    this.#id = definition.id;
    this.#name = definition.name;
    this.type = definition.type;
    this.isSeed = definition.isSeed;
    this.operationalBase = definition.operationalBase;
    this.operationalShadow = definition.operationalShadow;
    this.operationalIsSupported = definition.operationalIsSupported;
    if (isClone) {
      if (definition.hasLocalResource) {
        this.resource = definition.resource;
      }
    } else {
      if ("description" in definition || "xref" in definition || "details" in definition || "xref" in definition || "errors" in definition || "asOf" in definition || "until" in definition || "matchTo" in definition) {
        this.resource = new import_Resource.Resource(definition);
      }
    }
    if (isClone) {
      for (const child of definition.children) {
        this.children.push(child.clone());
      }
    } else if (definition.children) {
      this.children.push(...definition.children);
    }
    if (children.length) {
      this.children.push(...children);
    }
    if (!isClone && definition.parent) {
      this.parent = definition.parent;
    }
  }
  /**
   * Finalize the model hierarchy rooted at this model.
   *
   * When using a model as operational schema we implement various optimizations that assume the schema is immutable.
   * This function enforces that assumption and caches a few values that only make sense with frozen schema.
   *
   * To make changes to a final model use {@link clone}.
   */
  finalize() {
    if (this.#isFinal) {
      return;
    }
    const base = this.operationalBase ?? (this.operationalBase = this.base ?? null);
    const shadow = this.operationalShadow ?? (this.operationalShadow = this.shadow ?? null);
    this.#isFinal = true;
    this.children.freeze();
    Object.freeze(this);
    base?.finalize();
    shadow?.finalize();
  }
  get isFinal() {
    return this.#isFinal;
  }
  toString() {
    return `${this.tag}${this.type ? `<${this.type}>` : ""}#${this.path}`;
  }
  get resource() {
    return this.#resource || (this.#position.root?.resources || import_Resource.ResourceBundle.default).get(this);
  }
  set resource(resource) {
    if (resource instanceof import_Resource.Resource) {
      this.#resource = resource;
    } else {
      this.#resource = new import_Resource.Resource(resource);
    }
  }
  get description() {
    return this.resource?.description;
  }
  set description(description) {
    this.localResource.description = description;
  }
  get details() {
    return this.resource?.details;
  }
  set details(details) {
    this.localResource.details = details;
  }
  get xref() {
    return this.resource?.xref;
  }
  set xref(xref) {
    this.localResource.xref = xref;
  }
  get errors() {
    return this.resource?.errors;
  }
  set errors(errors) {
    this.localResource.errors = errors;
  }
  get asOf() {
    return this.resource?.asOf;
  }
  set asOf(asOf) {
    this.localResource.asOf = asOf;
  }
  get until() {
    return this.resource?.until;
  }
  set until(until) {
    this.localResource.until = until;
  }
  get matchTo() {
    return this.resource?.matchTo;
  }
  set matchTo(matchTo) {
    this.localResource.matchTo = matchTo;
  }
  get localResource() {
    return this.resource ?? (this.resource = new import_Resource.Resource());
  }
  get hasLocalResource() {
    return !!(this.#resource || (this.#position.root?.resources || import_Resource.ResourceBundle.default).get(this));
  }
}
(0, import_general.NodeJsStyleInspectable)(Model.prototype, function(_depth, options, inspect) {
  const json = this.valueOf();
  const props = {
    name: json.name
  };
  if (json.id !== void 0) {
    props.id = json.id;
  }
  for (const key in json) {
    if (key === "id" || key === "name" || key === "tag") {
      continue;
    }
    props[key] = json[key];
  }
  if (this.children.length) {
    props.children = this.children.length;
  }
  if (!inspect) {
    inspect = (value) => `${value}`;
  }
  return `${inspect(props, options)}`.replace(/^\{/, `${(0, import_general.decamelize)(this.tag)} {`);
});
import_Children.Children.installModelConstructor(Model);
//# sourceMappingURL=Model.js.map
