/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { camelize } from "@matter/general";
import { Conformance, DataModelPath, FeatureSet, Metatype } from "@matter/model";
import {
  ConformanceError,
  EnumValueConformanceError,
  SchemaImplementationError,
  UnknownEnumValueError
} from "@matter/protocol";
import { NameResolver } from "../managed/NameResolver.js";
import {
  Code,
  ConformantNode,
  NonconformantNode,
  UnsupportedConformanceNodeError,
  asBoolean,
  asConformance,
  createBooleanTest,
  createComparison,
  createLogicalBinaryEvaluator,
  createLogicalInversion,
  evaluateNode,
  isStatic
} from "./conformance-util.js";
const enumValueOk = () => {
};
function astToFunction(schema, supervisor) {
  const ast = schema.conformance.ast;
  const { featuresAvailable, featuresSupported } = FeatureSet.normalize(
    supervisor.featureMap,
    supervisor.supportedFeatures
  );
  let createNameReference = (name) => {
    const resolver = NameResolver(supervisor, schema.parent, camelize(name));
    if (resolver) {
      return {
        code: Code.Evaluate,
        evaluate: (_value, options) => {
          return {
            code: Code.Value,
            value: resolver(options?.siblings)
          };
        }
      };
    }
    return { code: Code.Value, value: void 0 };
  };
  const isNullable = schema.quality.nullable;
  const compiledNode = compile(ast);
  let validator;
  switch (compiledNode.code) {
    case Code.Conformant:
      if (isNullable) {
        break;
      }
      validator = requireValue;
      break;
    case Code.Nonconformant:
    case Code.Disallowed:
      validator = disallowValue;
      break;
    case Code.Value:
      validator = compiledNode.value === void 0 ? disallowValue : isNullable ? void 0 : requireValue;
      break;
    case Code.Optional:
      break;
    case Code.Evaluate:
      const { evaluate } = compiledNode;
      validator = (value, session, location) => {
        const staticNode = evaluate(value, location);
        switch (staticNode.code) {
          case Code.Conformant:
            if (!isNullable) {
              requireValue(value, session, location);
            }
            break;
          case Code.Nonconformant:
          case Code.Disallowed:
            disallowValue(value, session, location);
            break;
          case Code.Optional:
            break;
          case Code.Value:
            if (staticNode.value === void 0) {
              disallowValue(value, session, location);
            } else if (!isNullable) {
              requireValue(value, session, location);
            }
            break;
          default:
            throw new UnsupportedConformanceNodeError(schema, compiledNode);
        }
      };
      break;
    default:
      throw new UnsupportedConformanceNodeError(schema, compiledNode);
  }
  if (validator !== disallowValue && schema.effectiveMetatype === Metatype.enum) {
    validator = addEnumMemberValidation(validator);
  }
  return validator;
  function compile(ast2) {
    switch (ast2?.type) {
      case Conformance.Special.Empty:
      case Conformance.Special.Desc:
      case Conformance.Flag.Provisional:
      case Conformance.Flag.Deprecated:
      case Conformance.Flag.Optional:
        return { code: Code.Optional };
      case Conformance.Special.Choice:
        return createChoice(ast2.param);
      case Conformance.Special.Otherwise:
        return createGroup(ast2.param);
      case Conformance.Special.Name:
        return createName(ast2.param);
      case Conformance.Special.OptionalIf:
        return createOptionalIf(ast2.param);
      case Conformance.Special.Value:
        return createValue(ast2.param);
      case Conformance.Flag.Disallowed:
        return createDisallowed();
      case Conformance.Flag.Mandatory:
        return createMandatory();
      case Conformance.Operator.AND:
      case Conformance.Operator.OR:
      case Conformance.Operator.XOR:
        return createLogicalBinaryOp(ast2.type, ast2.param);
      case Conformance.Operator.EQ:
      case Conformance.Operator.NE:
      case Conformance.Operator.GT:
      case Conformance.Operator.LT:
      case Conformance.Operator.GTE:
      case Conformance.Operator.LTE:
        return createComparisonOp(ast2.type, ast2.param);
      case Conformance.Operator.NOT:
        return createNotOp(ast2.param);
      default:
        ast2;
        throw new SchemaImplementationError(
          DataModelPath(schema.path),
          `Unsupported conformance AST node type ${ast2.type}`
        );
    }
  }
  function createChoice(param) {
    const compiled = compile(param.expr);
    const name = param.name;
    const template = {
      count: 0,
      target: param.num,
      orMore: !!param.orMore,
      orLess: !!param.orLess
    };
    return {
      code: Code.Evaluate,
      evaluate: (value, options) => {
        const choices = options?.choices;
        let choice;
        if (choices) {
          choice = choices[name];
          if (!choice) {
            choice = choices[name] = { ...template };
          }
          if (value !== void 0) {
            choice.count++;
          }
        }
        return evaluateNode(compiled, value, options);
      }
    };
  }
  function createGroup(param) {
    if (!Array.isArray(param)) {
      throw new SchemaImplementationError(
        DataModelPath(schema.path),
        "Conformance AST group parameter is not an array"
      );
    }
    if (param[param.length - 1]?.type === Conformance.Flag.Deprecated) {
      param = param.slice(0, param.length - 1);
    }
    const members = param.map((test) => compile(test));
    const reduced = Array();
    for (const member of members) {
      if (member.code === Code.Nonconformant) {
        continue;
      }
      if (member.code === Code.Optional || member.code === Code.Conformant) {
        if (reduced.length) {
          reduced.push(member);
          break;
        }
        return member;
      }
      reduced.push(member);
    }
    return {
      code: Code.Evaluate,
      evaluate: (value, options) => {
        for (const member of reduced) {
          const node = evaluateNode(member, value, options);
          if (node.code !== Code.Nonconformant) {
            return node;
          }
        }
        return NonconformantNode;
      }
    };
  }
  function createName(param) {
    if (featuresAvailable.has(param)) {
      if (featuresSupported.has(param)) {
        return ConformantNode;
      }
      return NonconformantNode;
    }
    return createNameReference(param);
  }
  function createOptionalIf(param) {
    let node = compile(param) ?? {};
    node = asConformance(node);
    switch (node.code) {
      case Code.Conformant:
        return { code: Code.Optional };
      case Code.Evaluate:
        const evaluate = node.evaluate;
        return {
          code: Code.Evaluate,
          evaluate: (value, options) => {
            let staticNode = evaluate(value, options);
            staticNode = asConformance(staticNode);
            if (staticNode.code === Code.Conformant) {
              return { code: Code.Optional };
            }
            return staticNode;
          }
        };
      default:
        return node;
    }
  }
  function createValue(param) {
    return {
      code: Code.Value,
      value: param
    };
  }
  function createDisallowed() {
    return {
      code: Code.Disallowed
    };
  }
  function createMandatory() {
    return ConformantNode;
  }
  function createLogicalBinaryOp(operator, { lhs, rhs }) {
    const compiledLhs = createBooleanTest(compile(lhs));
    const compiledRhs = createBooleanTest(compile(rhs));
    switch (operator) {
      case Conformance.Operator.AND:
        if (isStatic(compiledLhs)) {
          if (asBoolean(compiledLhs)) {
            return asNonvalue(compiledRhs);
          } else {
            return NonconformantNode;
          }
        }
        if (isStatic(compiledRhs)) {
          if (asBoolean(compiledRhs)) {
            return asNonvalue(compiledLhs);
          } else {
            return NonconformantNode;
          }
        }
        return createLogicalBinaryEvaluator(compiledLhs, compiledRhs, (lhs2, rhs2) => lhs2 && rhs2);
      case Conformance.Operator.OR:
        if (isStatic(compiledLhs)) {
          if (asBoolean(compiledLhs)) {
            return ConformantNode;
          } else {
            return asNonvalue(compiledRhs);
          }
        }
        if (isStatic(compiledRhs)) {
          if (asBoolean(compiledRhs)) {
            return ConformantNode;
          } else {
            return asNonvalue(compiledLhs);
          }
        }
        return createLogicalBinaryEvaluator(compiledLhs, compiledRhs, (lhs2, rhs2) => lhs2 || rhs2);
      case Conformance.Operator.XOR:
        if (isStatic(compiledLhs)) {
          if (asBoolean(compiledLhs)) {
            return createLogicalInversion(compiledRhs);
          } else {
            return asNonvalue(compiledRhs);
          }
        }
        if (isStatic(compiledRhs)) {
          if (asBoolean(compiledRhs)) {
            return createLogicalInversion(compiledLhs);
          } else {
            return asNonvalue(compiledLhs);
          }
        }
        return createLogicalBinaryEvaluator(compiledLhs, compiledRhs, (lhs2, rhs2) => lhs2 ? !rhs2 : rhs2);
      default:
        throw new SchemaImplementationError(
          DataModelPath(schema.path),
          `Unknown logical binary operator ${operator}`
        );
    }
  }
  function createComparisonOp(operator, { lhs, rhs }) {
    const originalCreateNameReference = createNameReference;
    try {
      if (lhs.type === Conformance.Special.Name) {
        const name = camelize(lhs.param, false);
        const field = supervisor.membersOf(schema).find((model) => camelize(model.name, false) === name);
        if (field?.effectiveMetatype === Metatype.enum) {
          let enumValues;
          createNameReference = (name2) => {
            if (enumValues === void 0) {
              enumValues = {};
              for (const member of supervisor.membersOf(field)) {
                enumValues[camelize(member.name, true)] = member.id;
              }
            }
            const id = enumValues[camelize(name2, true)];
            if (id !== void 0) {
              return {
                code: Code.Value,
                value: id
              };
            }
            return originalCreateNameReference(name2);
          };
        }
      }
      return createComparison(operator, compile(lhs), compile(rhs), schema);
    } finally {
      createNameReference = originalCreateNameReference;
    }
  }
  function createNotOp(param) {
    const operand = compile(param);
    return createLogicalInversion(operand);
  }
  function requireValue(value, _session, location) {
    if (value === void 0) {
      throw new ConformanceError(schema, location, "Matter requires you to set this attribute");
    }
  }
  function disallowValue(value, _session, location) {
    if (value !== void 0) {
      throw new ConformanceError(schema, location, "Matter does not allow you to set this attribute");
    }
  }
  function disallowEnumValue(schema2) {
    return (location) => {
      throw new EnumValueConformanceError(
        schema2,
        location,
        `Matter does not allow enum value ${schema2.name} (ID ${schema2.effectiveId}) here`
      );
    };
  }
  function asNonvalue(node) {
    if (node.code === Code.Value) {
      return asBoolean(node) ? ConformantNode : NonconformantNode;
    }
    return node;
  }
  function addEnumMemberValidation(mainValidator) {
    const members = supervisor.membersOf(schema);
    if (!members.length) {
      return mainValidator;
    }
    let memberValidators;
    for (const member of members) {
      const id = member.effectiveId;
      if (id === void 0) {
        continue;
      }
      let ast2 = member.conformance.ast;
      if (ast2.type === Conformance.Special.Choice) {
        ast2 = ast2.param.expr;
      }
      const compiledNode2 = compile(ast2);
      let memberValidator;
      switch (compiledNode2.code) {
        case Code.Conformant:
        case Code.Optional:
          memberValidator = enumValueOk;
          break;
        case Code.Nonconformant:
        case Code.Disallowed:
          memberValidator = disallowEnumValue(member);
          break;
        case Code.Evaluate:
          const { evaluate } = compiledNode2;
          const disallow = disallowEnumValue(member);
          memberValidator = (location) => {
            const staticNode = evaluate(true, location);
            switch (staticNode.code) {
              case Code.Conformant:
              case Code.Optional:
                break;
              case Code.Nonconformant:
              case Code.Disallowed:
                disallow(location);
                break;
              default:
                throw new UnsupportedConformanceNodeError(member, compiledNode2);
            }
          };
          break;
        default:
          throw new UnsupportedConformanceNodeError(member, compiledNode2);
      }
      if (!memberValidators) {
        memberValidators = {};
      }
      memberValidators[id] = memberValidator;
    }
    if (memberValidators === void 0) {
      return mainValidator;
    }
    return (value, session, location) => {
      mainValidator?.(value, session, location);
      if (typeof value === "number") {
        if (!memberValidators[value]) {
          throw new UnknownEnumValueError(
            location,
            `Matter does not define the enum value ${value} for ${schema.name}`
          );
        }
        memberValidators[value](location);
      }
    };
  }
}
export {
  astToFunction
};
//# sourceMappingURL=conformance-compiler.js.map
