"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 validate_exports = {};
__export(validate_exports, {
  validate: () => validate
});
module.exports = __toCommonJS(validate_exports);
var import_schema = require("./schema.js");
class MaxDepthExceededError extends Error {
}
class MaxErrorsReachedError extends Error {
}
function validate(schema, instance, config) {
  const formats = schema.metadata?.formats;
  const validateFormat = createFormatValidator(formats);
  const state = {
    errors: [],
    instanceTokens: [],
    schemaTokens: [[]],
    root: schema,
    config: {
      maxDepth: config?.maxDepth ?? 0,
      maxErrors: config?.maxErrors ?? 0,
      features: config?.features
    }
  };
  try {
    validateWithState(state, schema, instance, { format: validateFormat });
  } catch (err) {
    if (err instanceof MaxErrorsReachedError) {
    } else {
      throw err;
    }
  }
  return state.errors;
}
function validateWithState(state, schema, instance, nonStandardValidations, parentTag, parentInstance) {
  if (schema.nullable && instance === null) {
    return;
  }
  if (state.config.features && schema.metadata?.features && !schema.metadata.features.some((f) => state.config.features?.includes(f)) && instance !== null && !isEmptyObject(instance)) {
    pushSchemaToken(state, "metadata");
    pushSchemaToken(state, "features");
    pushError(
      state,
      schema.metadata.features.length > 1 ? `Features [${schema.metadata.features.join(", ")}] are not supported` : `Feature [${schema.metadata.features[0]}] is not supported`
    );
    popSchemaToken(state);
    popSchemaToken(state);
  }
  if ((0, import_schema.isRefForm)(schema)) {
    if (state.schemaTokens.length === state.config.maxDepth) {
      throw new MaxDepthExceededError();
    }
    state.schemaTokens.push(["definitions", schema.ref]);
    validateWithState(
      state,
      state.root.definitions[schema.ref],
      instance,
      nonStandardValidations,
      void 0,
      parentInstance
    );
    state.schemaTokens.pop();
  } else if ((0, import_schema.isTypeForm)(schema)) {
    pushSchemaToken(state, "type");
    switch (schema.type) {
      case "boolean":
        if (typeof instance !== "boolean") {
          pushError(state, "Expected a boolean");
        }
        break;
      case "float32":
      case "float64":
        if (typeof instance !== "number") {
          pushError(state, "Expected a number");
        }
        break;
      case "int8":
        validateInt(state, instance, schema, -128, 127);
        break;
      case "uint8":
        validateInt(state, instance, schema, 0, 255);
        break;
      case "int16":
        validateInt(state, instance, schema, -32768, 32767);
        break;
      case "uint16":
        validateInt(state, instance, schema, 0, 65535);
        break;
      case "int32":
        validateInt(state, instance, schema, -2147483648, 2147483647);
        break;
      case "uint32":
        validateInt(state, instance, schema, 0, 4294967295);
        break;
      case "string":
        if (typeof instance !== "string") {
          pushError(state, "Expected a string");
        }
        if (schema.metadata?.validation?.minLength && instance.length < schema.metadata.validation.minLength) {
          pushSchemaToken(state, ["metadata", "validation", "minLength"]);
          pushError(state, `Expected at least ${schema.metadata.validation.minLength} characters`);
          popSchemaToken(state, 3);
        }
        if (schema.metadata?.validation?.maxLength && instance.length > schema.metadata.validation.maxLength) {
          pushSchemaToken(state, ["metadata", "validation", "maxLength"]);
          pushError(state, `Expected at most ${schema.metadata.validation.minLength} characters`);
          popSchemaToken(state, 3);
        }
        break;
      case "timestamp":
        if (typeof instance !== "string") {
          pushError(state, "Expected a timestamp");
        }
        break;
    }
    const formatError = nonStandardValidations.format(schema, instance);
    if (formatError) {
      pushError(state, formatError);
    }
    popSchemaToken(state);
  } else if ((0, import_schema.isEnumForm)(schema)) {
    pushSchemaToken(state, "enum");
    if (typeof instance !== "string" || !schema.enum.includes(instance)) {
      pushError(state, `Expected one of: ${schema.enum.join(", ")}`);
    }
    popSchemaToken(state);
  } else if ((0, import_schema.isElementsForm)(schema)) {
    pushSchemaToken(state, "elements");
    if (Array.isArray(instance)) {
      for (const [index, subInstance] of instance.entries()) {
        pushInstanceToken(state, index.toString());
        validateWithState(
          state,
          schema.elements,
          subInstance,
          nonStandardValidations,
          void 0,
          parentInstance
        );
        popInstanceToken(state);
      }
      if (schema.metadata?.pattern) {
        const pattern = schema.metadata.pattern;
        const validationResult = validatePattern(pattern.value, instance);
        if (!validationResult.valid) {
          pushSchemaToken(state, ["metadata", "pattern"]);
          pushError(state, validationResult.message);
          popSchemaToken(state, 2);
        }
      }
      if (schema.metadata?.validation?.maxSize && instance.length > schema.metadata.validation.maxSize) {
        pushSchemaToken(state, ["metadata", "validation", "maxSize"]);
        pushError(state, `Expected at most ${schema.metadata.validation.maxSize} items`);
        popSchemaToken(state, 3);
      }
      if (schema.metadata?.validation?.minSize && instance.length < schema.metadata.validation.minSize) {
        pushSchemaToken(state, ["metadata", "validation", "minSize"]);
        pushError(state, `Expected at least ${schema.metadata.validation.minSize} items`);
        popSchemaToken(state, 3);
      }
      if (schema.metadata?.validation?.uniqueTags) {
        const repeating = instance.map((i) => i?.type).filter((item, index, arr) => arr.indexOf(item) !== index);
        if (repeating.length) {
          pushSchemaToken(state, ["metadata", "validation", "uniqueTags"]);
          pushError(state, `Expected unique types, but [${repeating.join(", ")}] are repeated`);
          popSchemaToken(state, 3);
        }
      }
    } else {
      pushError(state, "Expected an array");
    }
    popSchemaToken(state);
  } else if ((0, import_schema.isPropertiesForm)(schema)) {
    if (typeof instance === "object" && instance !== null && !Array.isArray(instance)) {
      if (schema.properties !== void 0) {
        pushSchemaToken(state, "properties");
        for (const [name, subSchema] of Object.entries(schema.properties)) {
          pushSchemaToken(state, name);
          if (instance.hasOwnProperty(name)) {
            pushInstanceToken(state, name);
            validateWithState(
              state,
              subSchema,
              instance[name],
              nonStandardValidations,
              void 0,
              instance
            );
            popInstanceToken(state);
          } else {
            pushError(state, `Missing property: ${name}`);
          }
          popSchemaToken(state);
        }
        popSchemaToken(state);
      }
      if (schema.optionalProperties !== void 0) {
        pushSchemaToken(state, "optionalProperties");
        for (const [name, subSchema] of Object.entries(schema.optionalProperties)) {
          pushSchemaToken(state, name);
          if (instance.hasOwnProperty(name)) {
            const value = instance[name];
            if (value !== void 0) {
              pushInstanceToken(state, name);
              validateWithState(
                state,
                subSchema,
                value,
                nonStandardValidations,
                void 0,
                instance
              );
              popInstanceToken(state);
            }
          }
          popSchemaToken(state);
        }
        popSchemaToken(state);
      }
      if (schema.additionalProperties !== true) {
        for (const name of Object.keys(instance)) {
          const inRequired = schema.properties && name in schema.properties;
          const inOptional = schema.optionalProperties && name in schema.optionalProperties;
          const value = instance[name];
          if (!inRequired && !inOptional && name !== parentTag && value !== void 0) {
            pushInstanceToken(state, name);
            pushError(state, "Unexpected property");
            popInstanceToken(state);
          }
        }
      }
      if (schema.metadata?.validation?.matchFieldWithParent && parentInstance) {
        const { parent: parentPath, self: selfPath } = schema.metadata.validation.matchFieldWithParent;
        const parentValue = getNestedValue(parentInstance, parentPath);
        const selfValue = getNestedValue(instance, selfPath);
        if (parentValue !== null && parentValue !== void 0 && selfValue !== null && selfValue !== void 0 && parentValue !== selfValue) {
          pushSchemaToken(state, ["metadata", "validation", "matchFieldWithParent"]);
          pushError(
            state,
            `Expected ${selfPath.join(".")} to match parent ${parentPath.join(".")}: "${parentValue}", got "${selfValue}"`
          );
          popSchemaToken(state, 3);
        }
      }
      if (schema.metadata?.oneOf) {
        const oneOfProperties = schema.metadata?.oneOf.properties;
        const countOfInitializedOneOfProperties = oneOfProperties.filter((p) => {
          return Object.hasOwn(instance, p) && (instance[p] !== null || instance[p] !== void 0);
        }).length;
        if (countOfInitializedOneOfProperties > 1) {
          pushSchemaToken(state, ["metadata", "oneOf"]);
          pushError(
            state,
            `Only one of the properties [${oneOfProperties.join(", ")}] can be initialized`
          );
          popSchemaToken(state, 2);
        }
        if (schema.metadata.oneOf.strict && countOfInitializedOneOfProperties < 1) {
          pushSchemaToken(state, ["metadata", "oneOf"]);
          pushError(
            state,
            `One property in oneOf group [${oneOfProperties.join(",")}] must be initialized`
          );
          popSchemaToken(state, 2);
        }
      }
      if (schema.metadata?.validation?.matchFieldWithParent && parentInstance) {
        const { parent: parentPath, self: selfPath } = schema.metadata.validation.matchFieldWithParent;
        const parentValue = getNestedValue(parentInstance, parentPath);
        const selfValue = getNestedValue(instance, selfPath);
        if (parentValue !== null && parentValue !== void 0 && selfValue !== null && selfValue !== void 0 && parentValue !== selfValue) {
          pushSchemaToken(state, ["metadata", "validation", "matchFieldWithParent"]);
          pushError(
            state,
            `Expected ${selfPath.join(".")} to match parent ${parentPath.join(".")}: "${parentValue}", got "${selfValue}"`
          );
          popSchemaToken(state, 3);
        }
      }
    } else {
      if (schema.properties !== void 0) {
        pushSchemaToken(state, "properties");
      } else {
        pushSchemaToken(state, "optionalProperties");
      }
      pushError(state, "Expected an object");
      popSchemaToken(state);
    }
  } else if ((0, import_schema.isValuesForm)(schema)) {
    pushSchemaToken(state, "values");
    if (typeof instance === "object" && instance !== null && !Array.isArray(instance)) {
      for (const [name, subInstance] of Object.entries(instance)) {
        pushInstanceToken(state, name);
        validateWithState(
          state,
          schema.values,
          subInstance,
          nonStandardValidations,
          void 0,
          parentInstance
        );
        popInstanceToken(state);
      }
    } else {
      pushError(state, "Expected an object in map form");
    }
    popSchemaToken(state);
  } else if ((0, import_schema.isDiscriminatorForm)(schema)) {
    if (typeof instance === "object" && instance !== null && !Array.isArray(instance)) {
      if (instance.hasOwnProperty(schema.discriminator)) {
        const tag = instance[schema.discriminator];
        if (typeof tag === "string") {
          if (tag in schema.mapping) {
            pushSchemaToken(state, "mapping");
            pushSchemaToken(state, tag);
            validateWithState(
              state,
              schema.mapping[tag],
              instance,
              nonStandardValidations,
              schema.discriminator,
              parentInstance
            );
            popSchemaToken(state);
            popSchemaToken(state);
          } else {
            pushSchemaToken(state, "mapping");
            pushInstanceToken(state, schema.discriminator);
            pushError(state, `Allowed values: ${Object.keys(schema.mapping).join(", ")}`);
            popInstanceToken(state);
            popSchemaToken(state);
          }
        } else {
          pushSchemaToken(state, "discriminator");
          pushInstanceToken(state, schema.discriminator);
          pushError(state, "Expected a string");
          popInstanceToken(state);
          popSchemaToken(state);
        }
      } else {
        pushSchemaToken(state, "discriminator");
        pushError(state, `Missing tag: ${schema.discriminator}`);
        popSchemaToken(state);
      }
    } else {
      pushSchemaToken(state, "discriminator");
      pushError(state, "Expected an object");
      popSchemaToken(state);
    }
  }
}
function validateInt(state, instance, schema, min, max) {
  if (typeof instance !== "number" || !Number.isInteger(instance) || instance < min || instance > max) {
    pushError(state, `Expected an integer between ${min} and ${max}`);
  }
  if (schema.metadata?.validation?.min && Number(instance) < schema.metadata.validation.min) {
    pushSchemaToken(state, ["metadata", "validation", "min"]);
    pushError(state, `Expected min value of ${schema.metadata.validation.min}`);
    popSchemaToken(state, 3);
  }
  if (schema.metadata?.validation?.max && Number(instance) > schema.metadata.validation.max) {
    pushSchemaToken(state, ["metadata", "validation", "max"]);
    pushError(state, `Expected max value of ${schema.metadata.validation.max}`);
    popSchemaToken(state, 3);
  }
}
function pushInstanceToken(state, token) {
  state.instanceTokens.push(token);
}
function popInstanceToken(state) {
  state.instanceTokens.pop();
}
function pushSchemaToken(state, token) {
  state.schemaTokens[state.schemaTokens.length - 1].push(
    ...Array.isArray(token) ? token : [token]
  );
}
function popSchemaToken(state, count = 1) {
  for (let i = 0; i < count; i++) {
    state.schemaTokens[state.schemaTokens.length - 1].pop();
  }
}
function pushError(state, message) {
  state.errors.push({
    instancePath: [...state.instanceTokens],
    schemaPath: [...state.schemaTokens[state.schemaTokens.length - 1]],
    message
  });
  if (state.errors.length === state.config.maxErrors) {
    throw new MaxErrorsReachedError();
  }
}
function createFormatValidator(formats) {
  if (!formats) {
    return () => void 0;
  }
  return (schema, instance) => {
    if (!schema.metadata?.format || !("type" in schema)) {
      return void 0;
    }
    if (schema.type !== "string") {
      throw new Error(
        `Invalid schema: Format validation can only be applied to string type form (got ${schema.type})`
      );
    }
    if (!instance) {
      return void 0;
    }
    const format = formats?.[schema.metadata.format];
    if (format?.validation.type === "regex") {
      const regex = new RegExp(format.validation.regex);
      if (regex.test(instance)) {
        return void 0;
      }
      return `Incorrect format (expected: /${format.validation.regex})/ ${format.description})`;
    }
    if (format?.validation.type === "web_url") {
      try {
        new URL(instance);
      } catch (e) {
        return "Incorrect format (expected: web_url): " + e.message;
      }
      const url = new URL(instance);
      if (url.protocol !== "http:" && url.protocol !== "https:") {
        return "Incorrect format (expected: web_url): URL must start with http:// or https://";
      }
    }
    return void 0;
  };
}
const validatePattern = (pattern, instance) => {
  const nodeArrangement = instance.map((o) => o?.type ?? "UNDEFINED").join("");
  const isValid = !!nodeArrangement.match(`^${pattern.replaceAll(" ", "")}$`);
  if (isValid) {
    return { valid: true };
  }
  return {
    valid: false,
    message: `Incorrect node order [${instance.map((o) => o?.type ?? "UNDEFINED").join(" ")}] does not match required pattern [${pattern}]`
  };
};
const isEmptyObject = (obj) => obj && typeof obj === "object" && !Object.keys(obj).length;
const getNestedValue = (obj, path) => path.reduce((acc, key) => acc?.[key], obj);
//# sourceMappingURL=validate.js.map
