/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { ModelTraversal } from "#logic/ModelTraversal.js";
import { camelize } from "@matter/general";
import { Access, Conformance, Constraint, Quality } from "../../aspects/index.js";
import { FieldValue, Metatype } from "../../common/index.js";
import { ClusterModel, Globals, ValueModel } from "../../models/index.js";
import { ModelValidator } from "./ModelValidator.js";
import { ValidationExceptions } from "./ValidationExceptions.js";
class ValueValidator extends ModelValidator {
  validate() {
    this.validateProperty({ name: "type", type: "string" });
    this.validateProperty({ name: "byteSize", type: "number" });
    this.validateProperty({ name: "constraint", type: Constraint });
    this.validateProperty({ name: "conformance", type: Conformance });
    this.validateProperty({ name: "access", type: Access });
    this.validateProperty({ name: "quality", type: Quality });
    this.validateProperty({ name: "metatype", type: Metatype });
    this.#validateAspect("conformance");
    this.model.conformance.validateReferences(this, (name) => {
      if (name.match(/^[A-Z0-9_$]+$/)) {
        const cluster = this.model.owner(ClusterModel);
        return cluster?.features.find((f) => f.name === name);
      }
      name = camelize(name, true);
      for (let model = this.model.parent; model; model = model.parent) {
        const member = model.member(name);
        if (member) {
          return member;
        }
      }
    });
    this.model.conformance.validateComputation(this, this.model.owner(ClusterModel)?.definedFeatures);
    this.#validateAspect("constraint");
    this.#validateAspect("access");
    this.#validateAspect("quality");
    this.#validateType();
    this.#validateEntries();
    super.validate();
  }
  #validateAspect(name) {
    const aspect = this.model[name];
    if (aspect?.errors) {
      aspect.errors.forEach((e) => this.model.error(e.code, `${e.source}: ${e.message}`));
    }
  }
  #validateType() {
    if (this.model.effectiveType === void 0) {
      if (this.model.metatype) {
        return;
      }
      if (this.model.isDeprecated || this.model.isDisallowed) {
        return;
      }
      if (!this.#correctCaseFromShadow()) {
        this.error("NO_TYPE", "No type information");
        return;
      }
    }
    const base = this.model.base;
    if (base === void 0) {
      return;
    }
    const metabase = this.model.metabase;
    if (metabase === void 0) {
      this.error("METATYPE_UNKNOWN", `No metatype for ${this.model.name}`);
      return;
    }
    const metatype = metabase.metatype;
    if (metatype === void 0) {
      this.error("METATYPE_MISSING", `Metabase ${metabase.name} has no metatype`);
      return;
    }
    let defaultValue = this.model.default;
    if (defaultValue === void 0) {
      return;
    }
    if (this.#validateSpecialDefault(metatype, defaultValue)) {
      return;
    }
    if (metatype === Metatype.string && defaultValue === "empty") {
      delete this.model.default;
      return;
    }
    const cast = FieldValue.cast(metatype, defaultValue);
    if (typeof defaultValue === "string") {
      let referenced = this.model?.member(defaultValue);
      if (referenced === void 0) {
        referenced = this.model.owner(ClusterModel)?.member(defaultValue);
      }
      if (referenced instanceof ValueModel && referenced.effectiveType === this.model.effectiveType) {
        this.model.default = FieldValue.Reference(referenced.name);
        return;
      }
    }
    if (cast === FieldValue.Invalid) {
      this.error("INVALID_VALUE", `Value "${defaultValue}" is not a ${metatype}`);
      return;
    }
    defaultValue = cast;
    if (metatype === Metatype.enum) {
      if (typeof defaultValue === "string") {
        let member = this.model.member(defaultValue);
        if (!member) {
          member = this.model.member(
            (model) => model.name.toLowerCase() === defaultValue.toLowerCase()
          );
        }
        if (member && member.effectiveId !== void 0) {
          defaultValue = member.effectiveId;
        } else {
          this.error("INVALID_ENTRY", `"${defaultValue}" is not in ${metatype} ${this.model.type}`);
        }
      }
    }
    this.model.default = defaultValue;
  }
  #validateEntries() {
    const metatype = this.model.type === void 0 ? void 0 : Globals[this.model.type]?.metatype;
    switch (metatype) {
      case Metatype.object:
        if (!this.model.children.length) {
          this.error("CHILDLESS_STRUCT", `struct element with no children`);
        }
        break;
      case Metatype.enum:
      case Metatype.bitmap:
        const base = this.model.base;
        if (!base || !base.isSeed || !base.name.startsWith("enum") || this.model.parent?.name === "semtag") {
          break;
        }
        if (!this.model.members.length && !ValidationExceptions.AllowedEmptyEnums.has(this.model.path)) {
          this.error(`CHILDLESS_${metatype.toUpperCase()}`, `${this.model.type} with no children`);
        }
        if (metatype === Metatype.enum) {
          this.#validateEnumKeys();
        } else {
          this.#validateBitFields();
        }
        break;
      case Metatype.array:
        if (!this.model.children.length) {
          this.error("UNTYPED_ARRAY", `array element with no entry type`);
        } else if (this.model.children.length > 1) {
          this.error("OVERLY_TYPED_ARRAY", `array element with multiple entry types`);
        }
        break;
    }
  }
  #validateEnumKeys() {
    const ids = /* @__PURE__ */ new Set();
    const names = /* @__PURE__ */ new Set();
    for (const c of this.model.children) {
      if (c.id) {
        if (ids.has(c.id)) {
          this.error(
            "DUPLICATE_ENUM_ID",
            `${this.model.type} ID 0x${c.id.toString(16)} appears more than once`
          );
        } else {
          ids.add(c.id);
        }
      }
      if (names.has(c.name)) {
        this.error("DUPLICATE_ENUM_NAME", `${this.model.type} name "${c.name}" appears more than once`);
      }
    }
  }
  #validateBitFields() {
    const ranges = Array();
    for (const c of this.model.children) {
      let min, max;
      if (typeof c.constraint.value === "number") {
        min = c.constraint.value;
        max = c.constraint.value + 1;
      } else {
        min = c.constraint.min;
        max = c.constraint.max;
        if (typeof min !== "number" || typeof max !== "number" || min < 0 || min > max) {
          this.error(
            "UNCONSTRAINED_BIT_RANGE",
            `${this.model.type} bit field "${c.name}" is not properly constrained`
          );
          continue;
        }
      }
      for (const r of ranges) {
        if (min < r.max && max > r.min) {
          this.error(
            "OVERLAPPING_BIT_RANGE",
            `${this.model.type} bit fields "${r.name}" and "${c.name}" overlap`
          );
        }
      }
      ranges.push({ name: c.name, min, max });
    }
  }
  #validateSpecialDefault(metatype, def) {
    if (typeof def === "object" && FieldValue.is(def, FieldValue.reference)) {
      const reference = def.name;
      const parent = this.model.parent;
      let other = parent?.member(reference);
      if (!other) {
        const cluster = parent?.owner(ClusterModel);
        other = cluster?.member(reference);
        if (other === void 0) {
          this.error("MEMBER_UNKNOWN", `Default value references unknown property ${reference}`);
        }
      }
      return true;
    }
    if (typeof def === "string") {
      const other = this.model.parent?.member(def);
      if (other) {
        this.model.default = FieldValue.Reference(other.name);
        return true;
      }
    }
    if (metatype === Metatype.bitmap && Array.isArray(def)) {
      for (const value of def) {
        if (typeof value !== "string" && typeof value !== "number") {
          this.error("INVALID_BIT_FLAG", `Default bit flag ${def} is not a string or number`);
          continue;
        }
        if (!this.model.member(value)) {
          this.error("UNRESOLVED_BIT_FLAG", `Default bit flag ${def} is not a valid bit value`);
        }
      }
      return true;
    }
  }
  #correctCaseFromShadow() {
    const tag = this.model.tag;
    const name = this.model.name.toLowerCase();
    return false === new ModelTraversal().visitInheritance(this.model.parent?.base, (owner) => {
      for (const child of owner.children) {
        if (child.tag === tag && child.name.toLowerCase() === name) {
          this.model.name = child.name;
          return false;
        }
      }
    });
  }
}
export {
  ValueValidator
};
//# sourceMappingURL=ValueValidator.js.map
