"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var validation_exports = {};
__export(validation_exports, {
  allOf: () => allOf,
  array: () => array,
  assert: () => assert,
  class: () => klass,
  date: () => date,
  enum: () => enm,
  literal: () => literal,
  nul: () => nul,
  null: () => nul,
  object: () => object,
  oneOf: () => oneOf,
  optional: () => optional,
  primitive: () => primitive,
  tuple: () => tuple,
  uint8array: () => uint8array,
  undef: () => undef,
  undefined: () => undef
});
module.exports = __toCommonJS(validation_exports);
var import_shared = require("@zwave-js/shared");
var import_helpers = require("alcalzone-shared/helpers");
var import_index_browser = require("../index_browser.js");
const primivitiveTypes = /* @__PURE__ */ new Set([
  "string",
  "number",
  "boolean",
  "null",
  "undefined"
]);
function addIndexToName(e, index) {
  if (index != void 0) {
    return {
      ...e,
      name: `${e.name}[${index}]`
    };
  }
  return e;
}
__name(addIndexToName, "addIndexToName");
function getTypeName(e) {
  switch (e.type) {
    case "primitive":
      return e.expected;
    case "union":
      if (e.typeName)
        return e.typeName;
      return e.nested.map(getTypeName).join(" | ");
    case "intersection":
      if (e.typeName)
        return e.typeName;
      return e.nested.map(getTypeName).join(" & ");
    case "enum":
      return e.enum;
    case "date":
      return "Date";
    case "array":
      return `Array<${e.itemType}>`;
    case "tuple":
      return e.tupleType;
    case "object":
      return e.objectType;
    case "uint8array":
      return "Uint8Array";
    case "class":
      return e.class;
  }
  throw new Error(`Cannot determine type name for error ${JSON.stringify(e)}`);
}
__name(getTypeName, "getTypeName");
function formatElaboration(e, indent = 0) {
  let ret = " ".repeat(indent * 2);
  const optional2 = e.optional ? "optional " : "";
  let what = `${optional2}${e.kind} ${e.name}`;
  if (e.index != void 0)
    what += `[${e.index}]`;
  if (e.type === "primitive") {
    if (e.expected === "null" || e.expected === "undefined") {
      ret += `Expected ${what} to be ${e.expected}, got ${e.actual}`;
    } else {
      ret += `Expected ${what} to be a ${e.expected}, got ${e.actual}`;
    }
  } else if (e.type === "literal") {
    ret += `Expected ${what} to be ${e.expected}, got ${e.actual}`;
  } else if (e.type === "union") {
    const allPrimitive = e.nested.every((n) => n.type === "primitive");
    if (allPrimitive || primivitiveTypes.has(e.actualType)) {
      ret += `Expected ${what} to be one of ${e.nested.map(getTypeName).join(" | ")}, got ${e.actual}`;
    } else {
      if (e.typeName) {
        ret += `${what} is not assignable to ${e.typeName}. Expected one of the following constraints to pass:`;
      } else {
        ret += `Expected ${what} to be one of ${getTypeName(e)}`;
      }
      for (const nested of e.nested) {
        if (nested.type === "primitive")
          continue;
        ret += "\n" + formatElaboration(addIndexToName(nested, e.index), indent + 1);
      }
    }
  } else if (e.type === "intersection") {
    if (e.nested.length > 1) {
      ret += `${what} is violating multiple constraints`;
      for (const nested of e.nested) {
        ret += "\n" + formatElaboration(addIndexToName(nested, e.index), indent + 1);
      }
    } else {
      ret = ret.slice(0, -2) + formatElaboration(addIndexToName(e.nested[0], e.index), indent);
    }
  } else if (e.type === "enum") {
    ret += `Expected ${what} to be a member of enum ${e.enum}, got ${e.actual}`;
  } else if (e.type === "date") {
    ret += `Expected ${what} to be a Date, got ${e.actual}`;
  } else if (e.type === "array") {
    if (e.nested) {
      ret += `${what} is not assignable to Array<${e.itemType}>`;
      for (const nested of e.nested) {
        ret += "\n" + formatElaboration(addIndexToName(nested, e.index), indent + 1);
      }
    } else {
      ret += `Expected ${what} to be an Array<${e.itemType}>, got ${e.actual}`;
    }
  } else if (e.type === "tuple") {
    if (e.nested) {
      ret += `${what} is not assignable to ${e.tupleType}`;
      for (const nested of e.nested) {
        ret += "\n" + formatElaboration(addIndexToName(nested, e.index), indent + 1);
      }
    } else {
      ret += `Expected ${what} to be of type ${e.tupleType}, got ${e.actual}`;
    }
  } else if (e.type === "object") {
    if (e.nested) {
      ret += `${what} is not assignable to ${e.objectType}`;
      for (const nested of e.nested) {
        if ("actual" in nested && nested.actual === "undefined") {
          ret += `
${" ".repeat((indent + 1) * 2)}required property ${nested.name} is missing`;
        } else {
          ret += "\n" + formatElaboration(addIndexToName(nested, e.index), indent + 1);
        }
      }
    } else {
      ret += `Expected ${what} to be of type ${e.objectType}, got ${e.actual}`;
    }
  } else if (e.type === "uint8array") {
    ret += `Expected ${what} to be a Uint8Array or BytesView, got ${e.actual}`;
  } else if (e.type === "missing") {
    ret += `ERROR: Missing validation for ${what}`;
  } else if (e.type === "class") {
    ret += `Expected ${what} to be an instance of class ${e.class}, got ${e.actual}`;
  } else {
    (0, import_helpers.assertNever)(e);
  }
  return ret;
}
__name(formatElaboration, "formatElaboration");
function formatActualType(value) {
  if (value === null)
    return "null";
  if (value === void 0)
    return "undefined";
  const type = typeof value;
  switch (type) {
    case "string":
    case "number":
    case "boolean":
    case "function":
      return type;
  }
  if (Array.isArray(value))
    return `array`;
  return "object";
}
__name(formatActualType, "formatActualType");
function formatActualValue(value) {
  if (value === null)
    return "null";
  if (value === void 0)
    return "undefined";
  switch (typeof value) {
    case "string":
      return `string "${value}"`;
    case "number":
    case "boolean":
      return value.toString();
    case "function":
      return `function ${value.name || "<anonymous>"}`;
  }
  if (Array.isArray(value)) {
    return `array [ ...${value.length} item${value.length !== 1 ? "s" : ""} ]`;
  }
  if (typeof value === "object")
    return "object";
  return String(value);
}
__name(formatActualValue, "formatActualValue");
const primitive = /* @__PURE__ */ __name((ctx, expected) => (value) => {
  if (typeof value === expected)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "primitive",
      expected,
      actual: formatActualValue(value)
    }
  };
}, "primitive");
const literal = /* @__PURE__ */ __name((ctx, expected) => (value) => {
  if (value === expected)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "literal",
      expected: formatActualValue(expected),
      actual: formatActualValue(value)
    }
  };
}, "literal");
const enm = /* @__PURE__ */ __name((ctx, name, values) => (value) => {
  if (typeof value === "number") {
    if (!values)
      return { success: true };
    if (values.includes(value))
      return { success: true };
  }
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "enum",
      enum: name,
      actual: formatActualValue(value)
    }
  };
}, "enm");
const undef = /* @__PURE__ */ __name((ctx) => (value) => {
  if (value === void 0)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "primitive",
      expected: "undefined",
      actual: formatActualValue(value)
    }
  };
}, "undef");
const nul = /* @__PURE__ */ __name((ctx) => (value) => {
  if (value === null)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "primitive",
      expected: "null",
      actual: formatActualValue(value)
    }
  };
}, "nul");
const date = /* @__PURE__ */ __name((ctx) => (value) => {
  if (value instanceof globalThis.Date)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "date",
      actual: formatActualValue(value)
    }
  };
}, "date");
const array = /* @__PURE__ */ __name((ctx, itemType, item) => (value) => {
  if (!Array.isArray(value)) {
    return {
      success: false,
      elaboration: {
        ...ctx,
        type: "array",
        itemType,
        actual: formatActualValue(value)
      }
    };
  }
  const results = value.map(item);
  results.forEach((r, index) => {
    if (!r.success)
      r.elaboration.index = index;
  });
  const failed = results.filter((r) => !r.success);
  if (failed.length === 0)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "array",
      itemType,
      actual: formatActualValue(value),
      nested: failed.map((r) => ({
        ...r.elaboration,
        kind: "item"
      }))
    }
  };
}, "array");
const tuple = /* @__PURE__ */ __name((ctx, tupleType, ...items) => (value) => {
  if (!Array.isArray(value)) {
    return {
      success: false,
      elaboration: {
        ...ctx,
        type: "tuple",
        tupleType,
        actual: formatActualValue(value)
      }
    };
  }
  if (value.length > items.length) {
    return {
      success: false,
      elaboration: {
        ...ctx,
        type: "tuple",
        tupleType,
        actual: `tuple with ${value.length} items`
      }
    };
  }
  const results = items.map((validator, index) => {
    const ret = validator(value[index]);
    if (!ret.success)
      ret.elaboration.index = index;
    return ret;
  });
  const failed = results.filter((r) => !r.success);
  if (failed.length === 0)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "tuple",
      tupleType,
      actual: `tuple with ${value.length} items`,
      nested: failed.map((r) => ({
        ...r.elaboration,
        kind: "item"
      }))
    }
  };
}, "tuple");
const object = /* @__PURE__ */ __name((ctx, objectType, properties) => (value) => {
  if (typeof value !== "object" || Array.isArray(value) || value === null) {
    return {
      success: false,
      elaboration: {
        ...ctx,
        type: "object",
        objectType,
        actual: formatActualValue(value)
      }
    };
  }
  const result = [];
  for (const [prop, validator] of Object.entries(properties)) {
    const ret = validator(value[prop]);
    if (!ret.success) {
      ret.elaboration.kind = "property";
      result.push(ret.elaboration);
    }
  }
  if (result.length === 0)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "object",
      objectType,
      actual: "",
      // not relevant
      nested: result
    }
  };
}, "object");
const klass = /* @__PURE__ */ __name((ctx, name, klass2) => (value) => {
  if (value instanceof klass2)
    return { success: true };
  if (typeof klass2[`is${name}`] === "function" && klass2[`is${name}`](value)) {
    return { success: true };
  }
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "class",
      class: name,
      actual: formatActualValue(value)
    }
  };
}, "klass");
const uint8array = /* @__PURE__ */ __name((ctx) => (value) => {
  if ((0, import_shared.isUint8Array)(value))
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "uint8array",
      actual: formatActualValue(value)
    }
  };
}, "uint8array");
const optional = /* @__PURE__ */ __name((ctx, otherwise) => (value) => {
  if (value === void 0)
    return { success: true };
  const result = otherwise(value);
  if (result.success)
    return result;
  return {
    success: false,
    elaboration: {
      ...result.elaboration,
      optional: true
    }
  };
}, "optional");
const oneOf = /* @__PURE__ */ __name((ctx, typeName, ...nested) => (value) => {
  if (!nested.length) {
    return {
      success: false,
      elaboration: {
        ...ctx,
        type: "missing"
      }
    };
  }
  const failed = [];
  for (const f of nested) {
    const result = f(value);
    if (result.success)
      return result;
    failed.push(result);
  }
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "union",
      typeName,
      actual: formatActualValue(value),
      actualType: formatActualType(value),
      nested: failed.map((r) => r.elaboration)
    }
  };
}, "oneOf");
const allOf = /* @__PURE__ */ __name((ctx, typeName, ...nested) => (value) => {
  if (!nested.length) {
    return {
      success: false,
      elaboration: {
        ...ctx,
        type: "missing"
      }
    };
  }
  const results = nested.map((f) => f(value));
  const failed = results.filter((r) => !r.success);
  if (failed.length === 0)
    return { success: true };
  return {
    success: false,
    elaboration: {
      ...ctx,
      type: "intersection",
      typeName,
      actual: formatActualValue(value),
      nested: failed.map((r) => r.elaboration)
    }
  };
}, "allOf");
function assert(...results) {
  const failed = results.filter((r) => !r.success);
  if (failed.length > 0) {
    const message = `Argument validation failed:
${failed.map((r) => formatElaboration(r.elaboration)).join("\n")}`;
    throw new import_index_browser.ZWaveError(message, import_index_browser.ZWaveErrorCodes.Argument_Invalid);
  }
}
__name(assert, "assert");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  allOf,
  array,
  assert,
  class: null,
  date,
  enum: null,
  literal,
  nul,
  null: null,
  object,
  oneOf,
  optional,
  primitive,
  tuple,
  uint8array,
  undef,
  undefined
});
//# sourceMappingURL=validation.js.map
