/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { camelize, isObject } from "#general";
import { Lexer } from "#parser/Lexer.js";
import { TokenStream } from "#parser/TokenStream.js";
import { FieldValue } from "../common/index.js";
import { Aspect } from "./Aspect.js";
var Functions;
((Functions2) => {
  function minOf(args) {
    return Math.min(...args.filter((arg) => typeof arg === "number"));
  }
  Functions2.minOf = minOf;
  function maxOf(args) {
    return Math.max(...args.filter((arg) => typeof arg === "number"));
  }
  Functions2.maxOf = maxOf;
})(Functions || (Functions = {}));
function isFunction(name) {
  return Object.hasOwn(Functions, name);
}
class Constraint extends Aspect {
  desc;
  value;
  min;
  max;
  in;
  entry;
  cpMax;
  parts;
  /**
   * Initialize from a Constraint.Definition or the constraint DSL defined by the Matter Specification.
   */
  constructor(definition) {
    super(definition);
    let ast;
    switch (typeof definition) {
      case "string":
        if (
          // Mask
          definition.match(/^0b[0x ]+$/) || // Hack to identify mask without 0b prefix
          definition.startsWith("00") && definition.includes("x") && definition.match(/^[0x ]+$/)
        ) {
          break;
        }
        ast = Parser.parse(this, definition);
        break;
      case "number":
        ast = { value: definition };
        break;
      default:
        ast = definition;
        if (ast?.definition) {
          this.definition = ast.definition;
        }
        break;
    }
    if (!ast) {
      this.isEmpty = true;
      return;
    }
    this.desc = ast.desc;
    this.value = ast.value;
    this.min = ast.min;
    this.max = ast.max;
    this.in = ast.in;
    this.entry = ast.entry === void 0 ? void 0 : new Constraint(ast.entry);
    this.cpMax = ast.cpMax;
    this.parts = ast.parts?.length ? ast.parts.map((p) => new Constraint(p)) : void 0;
    this.isEmpty = this.desc === void 0 && this.value === void 0 && this.min === void 0 && this.max === void 0 && this.in === void 0 && this.entry === void 0 && this.cpMax === void 0 && this.parts === void 0;
    this.freeze();
  }
  extend(other) {
    if (other.isEmpty) {
      return this;
    }
    if (this.isEmpty) {
      return other;
    }
    return new Constraint({
      desc: other.desc ?? this.desc,
      value: other.value ?? this.value,
      min: other.min ?? this.min,
      max: other.max ?? this.max,
      in: other.in ?? this.in,
      entry: other.entry ?? this.entry,
      cpMax: other.cpMax ?? this.cpMax,
      parts: other.parts ?? this.parts
    });
  }
  /**
   * Test a value against a constraint.  Does not recurse into arrays.
   */
  test(value, nameResolver) {
    function valueOf(value2, raw = false) {
      if (!raw && (typeof value2 === "string" || Array.isArray(value2))) {
        return value2.length;
      }
      if (typeof value2 === "object" && value2 !== null && "type" in value2) {
        const { type } = value2;
        switch (type) {
          case FieldValue.reference:
            if (typeof value2.name === "string") {
              value2 = FieldValue(nameResolver?.(camelize(value2.name)));
              if (isObject(value2)) {
                value2 = { type: "properties", properties: value2 };
              }
            }
            break;
          case "+":
          case "-": {
            const lhs = valueOf(value2.lhs);
            const rhs = valueOf(value2.rhs);
            if (typeof lhs === "bigint" || typeof rhs === "bigint") {
              const l = typeof lhs === "number" && Number.isInteger(lhs) ? BigInt(lhs) : lhs;
              const r = typeof rhs === "number" && Number.isInteger(rhs) ? BigInt(rhs) : rhs;
              if (typeof l === "bigint" && typeof r === "bigint") {
                return type === "+" ? l + r : l - r;
              }
              return void 0;
            }
            if (typeof lhs === "number" && typeof rhs === "number") {
              return type === "+" ? lhs + rhs : lhs - rhs;
            }
            return void 0;
          }
          case "*": {
            const lhs = valueOf(value2.lhs);
            const rhs = valueOf(value2.rhs);
            if (typeof lhs === "number" && typeof rhs === "number") {
              return lhs * rhs;
            }
            return void 0;
          }
          case "/": {
            const lhs = valueOf(value2.lhs);
            const rhs = valueOf(value2.rhs);
            if (typeof lhs === "number" && typeof rhs === "number") {
              return lhs / rhs;
            }
            return void 0;
          }
          case "^": {
            const lhs = valueOf(value2.lhs);
            const rhs = valueOf(value2.rhs);
            if (typeof lhs === "number" && typeof rhs === "number") {
              const absLhs = Math.abs(lhs);
              const result = absLhs ** rhs;
              if (result > Number.MAX_SAFE_INTEGER) {
                const bigResult = BigInt(absLhs) ** BigInt(rhs);
                return lhs < 0 ? -bigResult : bigResult;
              }
              return lhs < 0 ? -result : result;
            }
            return void 0;
          }
          case ".": {
            const object = valueOf(value2.lhs);
            if (!isObject(object)) {
              return void 0;
            }
            const rhs = FieldValue.referenced(valueOf(value2.rhs));
            if (rhs === void 0) {
              return void 0;
            }
            if (Object.hasOwn(object, rhs)) {
              return object[rhs];
            }
            return void 0;
          }
          case "maxOf":
          case "minOf": {
            return Functions[type](value2.args.map((value3) => valueOf(value3)));
          }
        }
      }
      return value2;
    }
    if (value === void 0) {
      return false;
    }
    if (this.in) {
      let set = valueOf(this.in, true);
      if (!Array.isArray(set)) {
        set = set === void 0 ? [] : [set];
      }
      return set.indexOf(value) !== -1;
    }
    const v = valueOf(this.value);
    if (v === value) {
      return true;
    }
    if (typeof v === "bigint" && typeof value === "number" || typeof v === "number" && typeof value === "bigint") {
      if (v == value) {
        return true;
      }
    }
    if (v !== void 0 || value === null) {
      return false;
    }
    if (this.min !== void 0 && this.min !== null) {
      const min = valueOf(this.min);
      if (min !== void 0 && min !== null && min > value) {
        return false;
      }
    }
    if (this.max !== void 0 && this.max !== null) {
      const max = valueOf(this.max);
      if (max !== void 0 && max !== null && max < value) {
        return false;
      }
    }
    if (this.parts?.every((part) => part.test(value, nameResolver) === false)) {
      return false;
    }
    return true;
  }
  toString() {
    if (!this.valid && this.definition) {
      return this.definition.toString();
    }
    return Serializer.serialize(this);
  }
  freeze() {
    if (this.parts) {
      Object.freeze(this.parts);
    }
    super.freeze();
  }
}
((Constraint2) => {
  Constraint2.KEYWORDS = ["in", "min", "max", "to", "all", "desc", "true", "false"];
  Constraint2.keywords = new Set(Constraint2.KEYWORDS);
})(Constraint || (Constraint = {}));
var Serializer;
((Serializer2) => {
  function serialize(ast) {
    if (ast.parts) {
      return ast.parts.map(serialize).join(", ");
    }
    if (ast.entry) {
      return `${serializeAtom(ast)}[${serialize(ast.entry)}]`;
    }
    if (ast.cpMax) {
      return `${serializeAtom(ast)}{${ast.cpMax}}`;
    }
    return serializeAtom(ast);
  }
  Serializer2.serialize = serialize;
  function serializeValue(value, inExpr = false) {
    if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) {
      return FieldValue.serialize(value);
    }
    switch (value.type) {
      case "+":
      case "-":
      case "*":
      case "/":
      case ".":
      case "^":
        const sep = value.type === "." || value.type === "^" ? "" : " ";
        const sum = `${serializeValue(value.lhs, true)}${sep}${value.type}${sep}${serializeValue(value.rhs, true)}`;
        if (inExpr) {
          return `(${sum})`;
        }
        return sum;
      default:
        if (isFunction(value.type)) {
          return `${value.type}(${value.args.map((value2) => serializeValue(value2)).join(", ")})`;
        }
        return FieldValue.serialize(value);
    }
  }
  function serializeAtom(ast) {
    if (ast.desc) {
      return "desc";
    }
    if (ast.value !== void 0 && ast.value !== null) {
      return `${serializeValue(ast.value)}`;
    }
    if (ast.min !== void 0 && ast.min !== null) {
      if (ast.max === void 0 || ast.max === null) {
        return `min ${serializeValue(ast.min)}`;
      }
      return `${serializeValue(ast.min)} to ${serializeValue(ast.max)}`;
    }
    if (ast.max !== void 0 && ast.max !== null) {
      return `max ${serializeValue(ast.max)}`;
    }
    if (ast.in !== void 0) {
      return `in ${serializeValue(ast.in)}`;
    }
    return "all";
  }
})(Serializer || (Serializer = {}));
var Parser;
((Parser2) => {
  const lexer = new Lexer(Constraint.KEYWORDS);
  function parse(constraint, definition) {
    const tokens = TokenStream(lexer.lex(definition, (code, message) => constraint.error(code, message)));
    const result = parseParts();
    if (tokens.token && tokens.token?.type !== ",") {
      unexpected();
    }
    return result;
    function unexpected() {
      constraint.error("UNEXPECTED_CONSTRAINT_TOKEN", `Unexpected ${tokens.description}`);
    }
    function parseParts() {
      const parts = Array();
      while (true) {
        const part = parsePart();
        if (part !== void 0) {
          parts.push(part);
        }
        if (tokens.done) {
          break;
        }
        if (tokens.token?.type !== ",") {
          break;
        }
        tokens.next();
      }
      if (!parts.length) {
        return {};
      }
      if (parts.length === 1) {
        return parts[0];
      }
      return { parts };
    }
    function parsePart() {
      const result2 = parsePartWithoutSubconstraint();
      if (result2 === void 0) {
        return result2;
      }
      switch (tokens.token?.type) {
        case "[":
          {
            tokens.next();
            const entry = parseParts();
            if (tokens.token?.type !== "]") {
              constraint.error("MISSING_ENTRY_END", 'Entry constraint does not end with "]"');
            }
            tokens.next();
            if (entry !== void 0) {
              result2.entry = entry;
            }
          }
          break;
        case "{":
          {
            tokens.next();
            if (tokens.token?.type !== "value") {
              constraint.error(
                "MISSING_CODEPOINT_MAX",
                "Codepoint constraint does not specify maximum codepoint length"
              );
              if (tokens.peeked?.type === "}") {
                tokens.next();
              }
            } else {
              result2.cpMax = FieldValue.numericValue(
                tokens.token.value
              );
              tokens.next();
            }
            if (tokens.token?.type !== "}") {
              constraint.error("MISSING_CODEPOINT_END", 'Codepoint constraint does not end with "}"');
            }
            tokens.next();
          }
          break;
      }
      return result2;
    }
    function parsePartWithoutSubconstraint() {
      const { token } = tokens;
      if (!token) {
        return;
      }
      switch (token.type) {
        case "desc":
          tokens.next();
          return { desc: true };
        case "all":
          tokens.next();
          return {};
        case "min":
        case "max":
          tokens.next();
          return parseSingleBound(token.type);
        case "in":
          tokens.next();
          if (tokens.token?.type === "word") {
            const name = tokens.token.value;
            tokens.next();
            return { in: FieldValue.Reference(name) };
          }
          constraint.error("MISSING_IN_FIELD", 'Expected field name to follow "in"');
          break;
      }
      const value = parseExpression();
      if (value === void 0 || tokens.token?.type !== "to") {
        return { value };
      }
      tokens.next();
      const max = parseExpression();
      if (max === void 0) {
        constraint.error("MISSING_UPPER_BOUND", `"to" must be followed by upper boundary value`);
        return;
      }
      return {
        min: value,
        max
      };
    }
    function parseSingleBound(kind) {
      const bound = parseExpression();
      if (bound === void 0) {
        constraint.error("MISSING_SINGLE_BOUND", `"${kind}" must be followed by boundary value`);
        return;
      }
      return { [kind]: bound };
    }
    function parseExpression() {
      return parseAdditive();
    }
    function parseAdditive() {
      let value = parseMultiplicative();
      if (value === void 0) {
        return value;
      }
      while (tokens.token?.type === "+" || tokens.token?.type === "-") {
        const type = tokens.token.type;
        tokens.next();
        const rhs = parseMultiplicative();
        if (rhs === void 0) {
          constraint.error("MISSING_RIGHT_OPERAND", `Missing operand after "${type}"`);
          return;
        }
        value = { type, lhs: value, rhs };
      }
      return value;
    }
    function parseMultiplicative() {
      let value = parsePower();
      if (value === void 0) {
        return value;
      }
      while (tokens.token?.type === "*" || tokens.token?.type === "/") {
        const type = tokens.token.type;
        tokens.next();
        const rhs = parsePower();
        if (rhs === void 0) {
          constraint.error("MISSING_RIGHT_OPERAND", `Missing operand after "${type}"`);
          return;
        }
        value = { type, lhs: value, rhs };
      }
      return value;
    }
    function parsePower() {
      let value = parsePrimary();
      if (value === void 0) {
        return value;
      }
      while (tokens.token?.type === "^" || tokens.token?.type === ".") {
        const type = tokens.token.type;
        tokens.next();
        const rhs = parsePrimary();
        if (rhs === void 0) {
          constraint.error("MISSING_RIGHT_OPERAND", `Missing operand after "${type}"`);
          return;
        }
        value = { type, lhs: value, rhs };
      }
      return value;
    }
    function parsePrimary() {
      const value = parseValueExpression();
      if (value !== void 0 && tokens.token?.type === "(") {
        const functionName = FieldValue.referenced(value);
        if (functionName === void 0) {
          unexpected();
          return;
        }
        tokens.next();
        if (!isFunction(functionName)) {
          constraint.error("UNKNOWN_FUNCTION", `Unknown function "${functionName}"`);
          return;
        }
        const args = Array();
        while (tokens.token?.type !== ")") {
          const expr = parseExpression();
          if (expr === void 0) {
            return;
          }
          args.push(expr);
          switch (tokens.token?.type) {
            case ",":
              tokens.next();
              break;
            case ")":
              break;
            default:
              unexpected();
              return;
          }
        }
        tokens.next();
        return {
          type: functionName,
          args
        };
      }
      return value;
    }
    function parseValueExpression() {
      const { token } = tokens;
      if (token === void 0) {
        return;
      }
      switch (token.type) {
        case "value":
          tokens.next();
          return token.value;
        case "true":
          tokens.next();
          return true;
        case "false":
          tokens.next();
          return false;
        case "word":
          const ref = FieldValue.Reference(camelize(token.value));
          tokens.next();
          return ref;
        case "-":
        case "+": {
          tokens.next();
          let number = tokens.token?.type === "value" ? tokens.token.value : void 0;
          if (number !== void 0) {
            tokens.next();
            if (token.type === "-") {
              if (typeof number === "number") {
                number *= -1;
              } else if (FieldValue.is(number, FieldValue.percent) || FieldValue.is(number, FieldValue.celsius)) {
                number.value *= -1;
              } else {
                number = void 0;
              }
            }
          }
          if (number === void 0) {
            constraint.error("MISSING_NUMBER", `Unary "${token.type}" not followed by numeric value`);
            return;
          }
          return number;
        }
        case "(": {
          tokens.next();
          const result2 = parseExpression();
          if (tokens.token?.type !== ")") {
            constraint.error("MISSING_GROUP_END", 'Group does not end with ")"');
          }
          tokens.next();
          return result2;
        }
      }
    }
  }
  Parser2.parse = parse;
})(Parser || (Parser = {}));
export {
  Constraint
};
//# sourceMappingURL=Constraint.js.map
