/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { camelize, InternalError, Logger } from "@matter/general";
import { AttributeModel, ClusterModel, DataModelPath, FeatureMap, Metatype } from "@matter/model";
import { ConformanceError, DatatypeError, SchemaImplementationError, Val } from "@matter/protocol";
import { BitmapEncodedValue, FabricIndex, Status } from "@matter/types";
import { maybeConfigOf } from "../../supervision/SupervisionConfig.js";
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";
import { isFabricIndexSentinel } from "./FabricIndexSentinel.js";
import { forwardValidationToPeer } from "./peer-forwarding.js";
const logger = Logger.get("ValueValidator");
function forwardableForClientPeer(inner) {
  return (value, session, location) => {
    try {
      inner(value, session, location);
    } catch (e) {
      if (!forwardValidationToPeer(session, e)) {
        throw e;
      }
    }
  };
}
function ValueValidator(schema, supervisor) {
  if (schema instanceof ClusterModel) {
    return forwardableForClientPeer(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(new DataModelPath(schema.path), `No type defined`);
      }
      throw new SchemaImplementationError(
        new DataModelPath(schema.path),
        `Cannot determine metatype for type "${type}"`
      );
    default:
      throw new SchemaImplementationError(
        new DataModelPath(schema.path),
        `Unsupported validation metatype ${metabase?.metatype}`
      );
  }
  validator = createNullValidator(schema, validator);
  validator = validator && forwardableForClientPeer(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 addBitsToMask(mask, start, length) {
  if (mask === void 0 || start < 0 || length <= 0 || start + length > 32) {
    return void 0;
  }
  const lowMask = length >= 32 ? 4294967295 : 2 ** length - 1;
  return (mask | lowMask << start >>> 0) >>> 0;
}
function createBitmapValidator(schema, supervisor) {
  const fields = {};
  let definedMask = 0;
  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;
      definedMask = addBitsToMask(definedMask, constraint.min, constraint.max - constraint.min + 1);
    } else {
      max = 1;
      if (typeof constraint.value === "number") {
        definedMask = addBitsToMask(definedMask, constraint.value, 1);
      } else {
        definedMask = void 0;
      }
    }
    let name;
    if (field?.parent?.id === FeatureMap.id) {
      name = camelize(field.title ?? field.name);
    } else {
      name = field.propertyName;
    }
    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);
        }
      }
    }
    if (definedMask !== void 0) {
      const encoded = value[BitmapEncodedValue];
      if (typeof encoded === "number" && (encoded & ~definedMask) >>> 0 !== 0) {
        throw new DatatypeError(location, "free of reserved bits", encoded, Status.ConstraintError);
      }
    }
  };
}
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}`);
  }
  const inner = createSimpleValidator(schema, supervisor, assertion);
  if (isFabricIndexSentinel(schema)) {
    const fabricSentinelOrSubstitute = (value, session, location) => {
      const peerContext = session.clientPeerContext;
      if (value !== FabricIndex.OMIT_FABRIC || peerContext === void 0) {
        inner(value, session, location);
        return;
      }
      const siblings = location.siblings;
      if (siblings !== void 0 && peerContext.fabricIndexOnPeer !== void 0) {
        siblings.fabricIndex = peerContext.fabricIndexOnPeer;
        return;
      }
      logger.debug(
        "fabricIndex was not replaced with the peer's fabric index because it is not yet known; local cache will hold the OMIT_FABRIC sentinel (-1) until the peer's subscription delivers the real value at",
        location.path
      );
    };
    return fabricSentinelOrSubstitute;
  }
  return inner;
}
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[field.propertyName] = validate;
    }
  }
  const validateStruct = (struct, session, location) => {
    assertObject(struct, location);
    const config = location.config ?? maybeConfigOf(struct);
    const sublocation = {
      path: location.path.at(""),
      siblings: struct,
      choices: {},
      outerResolve: location.outerResolve,
      config
    };
    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;
      sublocation.config = config?.readonlyChild?.(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);
        }
        const entryConfig = location.config?.readonlyChild?.("entry");
        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;
          sublocation.config = location.config?.readonlyChild?.(index) ?? entryConfig;
          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
