/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { MetadataConflictError } from "#decoration/errors.js";
import { InternalError } from "#general";
import * as models from "#standard/elements/models.js";
const standardModels = new Set(Object.values(models));
class Semantics {
  #localModel;
  #isFinal = false;
  /**
   * Determine whether these semantics are final.
   *
   * Once final no further mutation is allowed.
   */
  get isFinal() {
    return this.#isFinal;
  }
  /**
   * Finalize the model.
   */
  finalize() {
    if (this.#isFinal) {
      return;
    }
    this.#isFinal = true;
    Object.freeze(this);
    this.#localModel?.finalize();
  }
  /**
   * The {@link Model} defined by local decoration, if any.
   */
  get localModel() {
    return this.#localModel;
  }
  /**
   * Set the model.
   *
   * When "setting" the model we need to merge semantics coming from decorators, the prototype hierarchy, and models
   * provided programmatically.
   *
   * This may set the local model, change it or set its operational base.  We use local state and the finalization
   * status of the model to determine how to integrate.
   */
  set mutableModel(model) {
    if (this.#isFinal) {
      throw new MetadataConflictError(`Cannot modify final semantics of ${this.#localModel?.name ?? "(none)"}`);
    }
    if (this.#localModel === model) {
      return;
    }
    if (standardModels.has(model)) {
      model.finalize();
    }
    this.#localModel = this.integrateModel(model);
  }
  /**
   * Obtain a model unconditionally for mutation purposes.
   */
  get mutableModel() {
    if (this.#isFinal) {
      throw new MetadataConflictError(`Cannot modify final semantics of ${this.#localModel?.name ?? "(none)"}`);
    }
    if (this.#localModel === void 0) {
      this.#localModel = this.createModel();
    } else if (this.#localModel.isFinal) {
      this.#localModel = this.#localModel.extend();
    }
    return this.#localModel;
  }
  /**
   * The model's {@link Model.Type}.
   *
   * Field decorators operate prior to class decorators, so we may need to transition the model to a new type when the
   * class is decorated.  This is slightly lossy so type-specific decoration should always occur after assigning kind.
   */
  get modelType() {
    if (this.#localModel === void 0) {
      return;
    }
    return this.mutableModel.constructor;
  }
  set modelType(type) {
    if (this.#localModel === void 0) {
      this.#localModel = this.createModel(type);
      return;
    }
    if (this.#localModel instanceof type) {
      return;
    }
    const original = this.#localModel;
    const { parent } = original;
    original.parent = void 0;
    const replacement = new type({
      name: original.name,
      id: original.id,
      type: original.type,
      parent,
      operationalBase: original.operationalBase,
      children: original.children
    });
    if ("quality" in original && "quality" in replacement) {
      replacement.quality = original.quality;
    }
    this.#localModel = replacement;
  }
}
((Semantics2) => {
  function of(context) {
    const classSemantics = (0, Semantics2.classOf)(context);
    if (context.kind === "class") {
      return classSemantics;
    }
    return classSemantics.fieldFor(context.name);
  }
  Semantics2.of = of;
  Semantics2.classOf = (_source) => {
    throw new InternalError(`Class decoration lookup not installed`);
  };
})(Semantics || (Semantics = {}));
export {
  Semantics
};
//# sourceMappingURL=Semantics.js.map
