/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { camelize, InternalError } from "#general";
import { AttributeModel, ClusterModel, DataModelPath, FeatureMap, Metatype } from "#model";
import { ConformanceError, DatatypeError, SchemaImplementationError, Val } from "#protocol";
import { Status } from "#types";
import { Internal } from "../managed/Internal.js";
import {
  assertArray,
  assertBoolean,
  assertBytes,
  assertInt,
  assertNumber,
  assertNumeric,
  assertObject,
  assertString
} from "./assertions.js";
import { createConformanceValidator } from "./conformance.js";
import { createConstraintValidator } from "./constraint.js";
function ValueValidator(schema, supervisor) {
  if (schema instanceof ClusterModel) {
    return createStructValidator(schema, supervisor);
  }
  let validator;
  const metabase = schema.metabase;
  switch (metabase?.metatype) {
    case Metatype.enum:
      validator = createEnumValidator(schema, supervisor);
      break;
    case Metatype.bitmap:
      validator = createBitmapValidator(schema, supervisor);
      break;
    case Metatype.integer:
      validator = createIntegerValidator(schema, supervisor, metabase.name);
      break;
    case Metatype.float:
      validator = createSimpleValidator(schema, supervisor, assertNumeric);
      break;
    case Metatype.boolean:
      validator = createSimpleValidator(schema, supervisor, assertBoolean);
      break;
    case Metatype.string:
      validator = createSimpleValidator(schema, supervisor, assertString);
      break;
    case Metatype.bytes:
      validator = createSimpleValidator(schema, supervisor, assertBytes);
      break;
    case Metatype.object:
      validator = createStructValidator(schema, supervisor);
      break;
    case Metatype.array:
      validator = createListValidator(schema, supervisor);
      break;
    case Metatype.date:
    case Metatype.duration:
    case Metatype.any:
      break;
    case void 0:
      const type = schema.effectiveType;
      if (type === void 0) {
        if (schema.isDisallowed || schema.isDeprecated && !schema.type) {
          break;
        }
        throw new SchemaImplementationError(DataModelPath(schema.path), `No type defined`);
      }
      throw new SchemaImplementationError(
        DataModelPath(schema.path),
        `Cannot determine metatype for type "${type}"`
      );
    default:
      throw new SchemaImplementationError(
        DataModelPath(schema.path),
        `Unsupported validation metatype ${metabase?.metatype}`
      );
  }
  validator = createNullValidator(schema, validator);
  validator = createConformanceValidator(schema, supervisor, validator);
  return validator;
}
function createNullValidator(schema, nextValidator) {
  if (schema.effectiveQuality.nullable === true) {
    return (value, options, location) => {
      if (value !== null) {
        nextValidator?.(value, options, location);
      }
    };
  }
  return nextValidator;
}
function createEnumValidator(schema, supervisor) {
  const valid = new Set(
    supervisor.membersOf(schema).map((member) => member.id).filter((e) => e !== void 0)
  );
  const constraint = schema.effectiveConstraint;
  const constraintValidator = constraint.in ? createConstraintValidator(schema.effectiveConstraint, schema, supervisor) : void 0;
  return (value, session, location) => {
    assertNumber(value, location);
    if (!valid.has(value)) {
      throw new DatatypeError(location, "defined in enum", value, Status.ConstraintError);
    }
    constraintValidator?.(value, session, location);
  };
}
function createBitmapValidator(schema, supervisor) {
  const fields = {};
  for (const field of supervisor.membersOf(schema)) {
    const constraint = field.effectiveConstraint;
    let max;
    if (typeof constraint.min === "number" && typeof constraint.max === "number") {
      max = Math.pow(2, constraint.max - constraint.min + 1) - 1;
    } else {
      max = 1;
    }
    let name;
    if (field?.parent?.id === FeatureMap.id) {
      name = camelize(field.title ?? field.name);
    } else {
      name = camelize(field.name);
    }
    fields[name] = {
      schema: field,
      max
    };
  }
  return (value, _session, location) => {
    assertObject(value, location);
    for (const key in value) {
      const field = fields[key];
      const subpath = location.path.at(key);
      if (field === void 0) {
        throw new DatatypeError(subpath, "defined in bitmap", key);
      }
      const fieldValue = value[key];
      if (fieldValue === void 0) {
        continue;
      }
      if (field.max === 1) {
        assertBoolean(fieldValue, subpath);
      } else {
        assertNumber(fieldValue, subpath);
        if (fieldValue > field.max) {
          throw new DatatypeError(subpath, "in range of bit field", fieldValue);
        }
      }
    }
  };
}
function createSimpleValidator(schema, supervisor, validateType) {
  const validateConstraint = createConstraintValidator(schema.effectiveConstraint, schema, supervisor);
  return (value, session, location) => {
    if (value === void 0) {
      return;
    }
    validateType(value, location);
    validateConstraint?.(value, session, location);
  };
}
function createIntegerValidator(schema, supervisor, name) {
  const nullable = schema.effectiveQuality.nullable;
  let assertion;
  if (schema.type === "epoch-s" || schema.type === "epoch-us") {
    if (nullable) {
      assertion = assertInt.nullable[schema.type];
    } else {
      assertion = assertInt.notNullable[schema.type];
    }
  }
  if (assertion === void 0) {
    if (nullable) {
      assertion = assertInt.nullable[name];
    } else {
      assertion = assertInt.notNullable[name];
    }
  }
  if (assertion === void 0) {
    throw new InternalError(`No integer assertion implemented for integer type ${name}`);
  }
  return createSimpleValidator(schema, supervisor, assertion);
}
function createStructValidator(schema, supervisor) {
  const validators = {};
  for (const field of supervisor.membersOf(schema)) {
    if (AttributeModel.isGlobal(field) || field.isDeprecated && !field.type) {
      continue;
    }
    const validate = supervisor.get(field).validate;
    if (validate) {
      validators[camelize(field.name)] = validate;
    }
  }
  const validateStruct = (struct, session, location) => {
    assertObject(struct, location);
    const sublocation = {
      path: location.path.at(""),
      siblings: struct,
      choices: {}
    };
    for (const name in validators) {
      let value;
      if (struct[Val.properties]) {
        const rootOwner = struct[Internal.reference];
        const properties = struct[Val.properties](rootOwner, session);
        if (name in properties) {
          value = properties[name];
        } else {
          value = struct[name];
        }
      } else {
        value = struct[name];
      }
      sublocation.path.id = name;
      validators[name](value, session, sublocation);
    }
    for (const name in sublocation.choices) {
      const choice = sublocation.choices[name];
      if (choice.count < choice.target && !choice.orLess) {
        throw new ConformanceError(
          schema,
          location,
          `Too few fields present (${choice.count} of min ${choice.target})`,
          name
        );
      }
      if (choice.count > choice.target && !choice.orMore) {
        throw new ConformanceError(
          schema,
          location,
          `Too many fields present (${choice.count} of max ${choice.target})`,
          name
        );
      }
    }
  };
  return validateStruct;
}
function createListValidator(schema, supervisor) {
  const entry = schema.listEntry;
  let validateEntries;
  if (entry) {
    const entryValidator = supervisor.get(entry).validate;
    if (entryValidator) {
      validateEntries = (list, session, location) => {
        if (!list || typeof list[Symbol.iterator] !== "function") {
          throw new DatatypeError(location, "a list", list);
        }
        let index = 0;
        const sublocation = {
          path: location.path.at("")
        };
        for (const e of list) {
          if (e === void 0 || e === null) {
            continue;
          }
          sublocation.path.id = index;
          entryValidator(e, session, sublocation);
          index++;
        }
      };
    }
  }
  const validateConstraint = createConstraintValidator(schema.constraint, schema, supervisor);
  return (value, session, location) => {
    assertArray(value, location);
    validateConstraint?.(value, session, location);
    validateEntries?.(value, session, location);
  };
}
export {
  ValueValidator
};
//# sourceMappingURL=ValueValidator.js.map
