/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { DatatypeModel } from "#models/index.js";
import { camelize, InternalError } from "#general";
import { Scope } from "#logic/Scope.js";
import { any, struct } from "#standard/elements/models.js";
import { InvalidMetadataError, MetadataConflictError } from "../errors.js";
import { FieldSemantics } from "./FieldSemantics.js";
import { Semantics } from "./Semantics.js";
const matter = /* @__PURE__ */ Symbol("matter");
class ClassSemantics extends Semantics {
  #new;
  #definedFields;
  /**
   * The model that represents the semantics for this class.
   *
   * This may be {@link localModel} or {@link prototypeBaseModel}
   */
  get semanticModel() {
    if (this.localModel) {
      return this.localModel;
    }
    if (this.#definedFields) {
      return this.mutableModel;
    }
    return this.prototypeBaseModel;
  }
  /**
   * Get the class constructor
   */
  get new() {
    return this.#new;
  }
  /**
   * Assign the constructor for the class.
   */
  set new(fn) {
    if (this.#new === fn) {
      return;
    }
    if (this.isFinal) {
      throw new MetadataConflictError(
        `Cannot install semantic constructor ${fn.name} because semantics are final`
      );
    }
    this.#new = fn;
    if (this.localModel && !this.localModel.isFinal && this.localModel.name !== this.#new?.name) {
      this.localModel.name = this.#new?.name;
    }
    if (this.localModel) {
      this.#applyBaseSemantics();
    } else {
      const base = this.prototypeBaseModel;
      if (base !== void 0) {
        this.mutableModel = base;
      }
    }
  }
  /**
   * The class {@link Model} inherited from {@link new}'s prototype chain, if any.
   */
  get prototypeBaseModel() {
    let current = this.#new?.prototype;
    let base;
    while (current) {
      current = Object.getPrototypeOf(current);
      if (typeof current !== "object" || current === null) {
        return;
      }
      const constructor = current.constructor;
      if (constructor === Object) {
        return;
      }
      base = ClassSemantics.maybeOf(constructor);
      if (base?.localModel) {
        base.finalize();
        return base.localModel;
      }
    }
  }
  /**
   * Obtain a {@link FieldSemantics} for the named field.
   */
  fieldFor(name) {
    if (typeof name !== "string") {
      throw new InvalidMetadataError(`Cannot decorate symbolic function ${String(name)}`);
    }
    if (this.#definedFields === void 0) {
      this.#definedFields = /* @__PURE__ */ new Map();
    }
    let field = this.#definedFields.get(name);
    if (field === void 0) {
      if (this.isFinal) {
        throw new MetadataConflictError(`Cannot install field ${name} because semantics are final`);
      }
      this.#definedFields.set(name, field = new FieldSemantics(this, name));
    }
    return field;
  }
  /**
   * Add logical fields for unrecognized members of an object.
   *
   * This extends the model with untyped fields for properties that are not otherwise typed.
   */
  defineUnknownMembers(instance) {
    if (instance === void 0 || instance === null) {
      return;
    }
    const known = this.semanticModel ? new Set(
      Scope(this.semanticModel).membersOf(this.semanticModel).map((model) => camelize(model.name))
    ) : /* @__PURE__ */ new Set();
    const descriptors = Object.getOwnPropertyDescriptors(instance);
    for (const name in descriptors) {
      if (known.has(camelize(name))) {
        continue;
      }
      if (typeof name !== "string") {
        continue;
      }
      const descriptor = descriptors[name];
      if (descriptor.set && !descriptor.get) {
        continue;
      }
      try {
        if (typeof instance[name] === "function") {
          continue;
        }
      } catch (e) {
        continue;
      }
      const model = this.fieldFor(name).mutableModel;
      model.operationalBase = any;
      if (!descriptor.writable && !descriptor.set) {
        model.quality = { ...model.quality, fixed: true };
      }
      this.fieldFor(name).mutableModel.operationalBase = any;
    }
  }
  /**
   * Obtain the {@link ClassSemantics} for {@link source}.
   */
  static of(source) {
    if (source instanceof ClassSemantics) {
      return source;
    }
    if (typeof source !== "function") {
      const metadata2 = source.metadata;
      if (Object.hasOwn(metadata2, matter)) {
        return metadata2[matter];
      }
      return metadata2[matter] = new ClassSemantics();
    }
    let metadata;
    if (!Object.hasOwn(source, Symbol.metadata)) {
      metadata = source[Symbol.metadata] = {};
    } else {
      metadata = source[Symbol.metadata];
    }
    let semantics;
    if (!Object.hasOwn(metadata, matter)) {
      semantics = metadata[matter] = new ClassSemantics();
    } else {
      semantics = metadata[matter];
    }
    if (!semantics.new) {
      semantics.new = source;
    }
    return semantics;
  }
  /**
   * Determine {@link new} has semantic decoration.
   */
  static hasOwnSemantics(source) {
    if (typeof source !== "function") {
      throw new InternalError(`Missing constructor for class semantic check`);
    }
    return Object.hasOwn(source, Symbol.metadata) && Object.hasOwn(source[Symbol.metadata], matter);
  }
  /**
   * Access the {@link ClassSemantics} of {@link new} if it is defined.
   */
  static maybeOf(source) {
    if (this.hasOwnSemantics(source)) {
      return ClassSemantics.of(source);
    }
  }
  static {
    Semantics.classOf = this.of;
  }
  finalize() {
    if (this.isFinal) {
      return;
    }
    this.#new?.[ClassSemantics.extend]?.(this);
    if (this.#definedFields) {
      for (const field of this.#definedFields.values()) {
        field.finalize();
      }
    }
    super.finalize();
  }
  #applyBaseSemantics() {
    const base = this.prototypeBaseModel;
    if (base === void 0) {
      return;
    }
    const { type, base: currentBase } = this.mutableModel;
    if (type === void 0 && (currentBase === void 0 || currentBase === struct)) {
      const operationalBase = this.prototypeBaseModel;
      if (operationalBase) {
        this.mutableModel.operationalBase = operationalBase;
      }
    }
    if (base && base.tag !== "datatype" && this.mutableModel.tag !== base.tag) {
      this.modelType = base.constructor;
    }
    if (this.mutableModel.id === void 0 && base.id !== void 0) {
      this.mutableModel.id = base.id;
    }
  }
  integrateModel(model) {
    if (this.localModel === void 0 || this.localModel.isFinal) {
      return model;
    }
    if (model.isFinal) {
      this.localModel.operationalBase = model;
      return this.localModel;
    }
    model.children.push(...this.localModel.children);
    return model;
  }
  createModel(type = DatatypeModel) {
    let name = this.#new?.name;
    if (name === void 0 || name === "") {
      name = "Unnamed";
    }
    return new type({ name, operationalBase: struct });
  }
  get owner() {
    return this;
  }
}
((ClassSemantics2) => {
  ClassSemantics2.extend = /* @__PURE__ */ Symbol("extend");
})(ClassSemantics || (ClassSemantics = {}));
export {
  ClassSemantics
};
//# sourceMappingURL=ClassSemantics.js.map
