"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const numericRegex = /^\s*[+-]?(\d+|\d*\.\d+|\d+\.\d*)([Ee][+-]?\d+)?\s*$/;
const trimIfString = (val) => typeof val === "string" ? val.trim() : val;
const toArray = (v) => (Array.isArray(v) ? v : typeof v === "string" ? v.split(",").filter((s) => !/^\s*$/.test(s)) : []).map(trimIfString);
const mongoOperators = {
  "=": "$eq",
  "!=": "$ne",
  "<": "$lt",
  "<=": "$lte",
  ">": "$gt",
  ">=": "$gte",
  in: "$in",
  notIn: "$nin"
};
const isValidValue = (v) => typeof v === "string" && v.length > 0 || typeof v === "number" && !isNaN(v) || typeof v !== "string" && typeof v !== "number";
const shouldRenderAsNumber = (v, parseNumbers) => !!parseNumbers && (typeof v === "number" || typeof v === "bigint" || typeof v === "string" && numericRegex.test(v));
const defaultValueProcessorByRule = ({ operator, value, valueSource }, { escapeQuotes, parseNumbers } = {}) => {
  const escapeSingleQuotes = (v) => typeof v !== "string" || !escapeQuotes ? v : v.replaceAll(`'`, `''`);
  const valueIsField = valueSource === "field";
  const operatorLowerCase = operator.toLowerCase();
  if (operatorLowerCase === "null" || operatorLowerCase === "notnull") {
    return "";
  } else if (operatorLowerCase === "in" || operatorLowerCase === "notin") {
    const valArray = toArray(value);
    if (valArray.length > 0) {
      return `(${valArray.map(
        (v) => valueIsField || shouldRenderAsNumber(v, parseNumbers) ? `${trimIfString(v)}` : `'${escapeSingleQuotes(v)}'`
      ).join(", ")})`;
    }
    return "";
  } else if (operatorLowerCase === "between" || operatorLowerCase === "notbetween") {
    const valArray = toArray(value);
    if (valArray.length >= 2 && isValidValue(valArray[0]) && isValidValue(valArray[1])) {
      const [first, second] = valArray;
      return valueIsField || shouldRenderAsNumber(first, parseNumbers) && shouldRenderAsNumber(second, parseNumbers) ? `${trimIfString(first)} and ${trimIfString(second)}` : `'${escapeSingleQuotes(first)}' and '${escapeSingleQuotes(second)}'`;
    }
    return "";
  } else if (operatorLowerCase === "contains" || operatorLowerCase === "doesnotcontain") {
    return valueIsField ? `'%' || ${value} || '%'` : `'%${escapeSingleQuotes(value)}%'`;
  } else if (operatorLowerCase === "beginswith" || operatorLowerCase === "doesnotbeginwith") {
    return valueIsField ? `${value} || '%'` : `'${escapeSingleQuotes(value)}%'`;
  } else if (operatorLowerCase === "endswith" || operatorLowerCase === "doesnotendwith") {
    return valueIsField ? `'%' || ${value}` : `'%${escapeSingleQuotes(value)}'`;
  } else if (typeof value === "boolean") {
    return `${value}`.toUpperCase();
  }
  return valueIsField || shouldRenderAsNumber(value, parseNumbers) ? `${trimIfString(value)}` : `'${escapeSingleQuotes(value)}'`;
};
const shouldNegate$1 = (op) => /^(does)?not/i.test(op);
const defaultValueProcessorCELByRule = ({ field, operator, value, valueSource }, { escapeQuotes, parseNumbers } = {}) => {
  const escapeDoubleQuotes = (v) => typeof v !== "string" || !escapeQuotes ? v : v.replaceAll(`"`, `\\"`);
  const valueIsField = valueSource === "field";
  const operatorTL = operator.replace(/^=$/, "==");
  const useBareValue = typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || shouldRenderAsNumber(value, parseNumbers);
  if (operatorTL === "<" || operatorTL === "<=" || operatorTL === "==" || operatorTL === "!=" || operatorTL === ">" || operatorTL === ">=") {
    return `${field} ${operatorTL} ${valueIsField || useBareValue ? trimIfString(value) : `"${escapeDoubleQuotes(value)}"`}`;
  } else if (operatorTL === "contains" || operatorTL === "doesNotContain") {
    const negate = shouldNegate$1(operatorTL) ? "!" : "";
    return `${negate}${field}.contains(${valueIsField ? trimIfString(value) : `"${escapeDoubleQuotes(value)}"`})`;
  } else if (operatorTL === "beginsWith" || operatorTL === "doesNotBeginWith") {
    const negate = shouldNegate$1(operatorTL) ? "!" : "";
    return `${negate}${field}.startsWith(${valueIsField ? trimIfString(value) : `"${escapeDoubleQuotes(value)}"`})`;
  } else if (operatorTL === "endsWith" || operatorTL === "doesNotEndWith") {
    const negate = shouldNegate$1(operatorTL) ? "!" : "";
    return `${negate}${field}.endsWith(${valueIsField ? trimIfString(value) : `"${escapeDoubleQuotes(value)}"`})`;
  } else if (operatorTL === "null") {
    return `${field} == null`;
  } else if (operatorTL === "notNull") {
    return `${field} != null`;
  } else if (operatorTL === "in" || operatorTL === "notIn") {
    const negate = shouldNegate$1(operatorTL);
    const valArray = toArray(value);
    if (valArray.length > 0) {
      return `${negate ? "!(" : ""}${field} in [${valArray.map(
        (val) => valueIsField || shouldRenderAsNumber(val, parseNumbers) ? `${trimIfString(val)}` : `"${escapeDoubleQuotes(val)}"`
      ).join(", ")}]${negate ? ")" : ""}`;
    } else {
      return "";
    }
  } else if (operatorTL === "between" || operatorTL === "notBetween") {
    const valArray = toArray(value);
    if (valArray.length >= 2 && !!valArray[0] && !!valArray[1]) {
      const [first, second] = valArray;
      const firstNum = shouldRenderAsNumber(first, true) ? parseFloat(first) : NaN;
      const secondNum = shouldRenderAsNumber(second, true) ? parseFloat(second) : NaN;
      let firstValue = isNaN(firstNum) ? valueIsField ? `${first}` : `"${escapeDoubleQuotes(first)}"` : firstNum;
      let secondValue = isNaN(secondNum) ? valueIsField ? `${second}` : `"${escapeDoubleQuotes(second)}"` : secondNum;
      if (firstValue === firstNum && secondValue === secondNum && secondNum < firstNum) {
        const tempNum = secondNum;
        secondValue = firstNum;
        firstValue = tempNum;
      }
      if (operator === "between") {
        return `(${field} >= ${firstValue} && ${field} <= ${secondValue})`;
      } else {
        return `(${field} < ${firstValue} || ${field} > ${secondValue})`;
      }
    } else {
      return "";
    }
  }
  return "";
};
const defaultValueProcessorMongoDBByRule = ({ field, operator, value, valueSource, fieldType, disabled }, { escapeQuotes, parseNumbers } = {}) => {
  if (disabled)
    return "";
  const escapeDoubleQuotes = (v) => typeof v !== "string" || !escapeQuotes ? v : v.replaceAll(`"`, `\\"`);
  const convertSpecial = (v, type) => typeof v !== "string" ? v : type === "date" ? `ISODate("${v}")` : type === "objectId" ? `ObjectId("${v}")` : type === "regex" ? `/${v}/i` : type === "object" ? `${v}` : type === "array" ? `${v}` : `"${v}"`;
  const valueIsField = valueSource === "field";
  const useBareValue = typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || shouldRenderAsNumber(value, parseNumbers);
  if (operator === "<" || operator === "<=" || operator === "!=" || operator === ">" || operator === ">=") {
    const mongoOperator = mongoOperators[operator];
    return `"${field}":{"${mongoOperator}":${useBareValue ? trimIfString(value) : convertSpecial(value, fieldType)}}`;
  } else if (operator === "=") {
    return `"${field}":${useBareValue ? trimIfString(value) : convertSpecial(value, fieldType)}`;
  } else if (operator === "contains") {
    return valueIsField ? `"$where":"this.${field}.includes(this.${value})"` : `"${field}":{"$regex":/${escapeDoubleQuotes(value)}/i}`;
  } else if (operator === "beginsWith") {
    return valueIsField ? `"$where":"this.${field}.startsWith(this.${value})"` : `"${field}":{"$regex":/^${escapeDoubleQuotes(value)}/i}`;
  } else if (operator === "endsWith") {
    return valueIsField ? `"$where":"this.${field}.endsWith(this.${value})"` : `"${field}":{"$regex":/${escapeDoubleQuotes(value)}$/i}`;
  } else if (operator === "doesNotContain") {
    return valueIsField ? `"$where":"!this.${field}.includes(this.${value})"` : `"${field}":{"$not":{"$regex":/${escapeDoubleQuotes(value)}/i}}`;
  } else if (operator === "doesNotBeginWith") {
    return valueIsField ? `"$where":"!this.${field}.startsWith(this.${value})"` : `"${field}":{"$not":{"$regex":/^${escapeDoubleQuotes(value)}/i}}`;
  } else if (operator === "doesNotEndWith") {
    return valueIsField ? `"$where":"!this.${field}.endsWith(this.${value})"` : `"${field}":{"$not":{"$regex":/${escapeDoubleQuotes(value)}$/i}}`;
  } else if (operator === "null") {
    return `"${field}":null`;
  } else if (operator === "exist") {
    return `"${field}":{$exists:true}`;
  } else if (operator === "notExist") {
    return `"${field}":{$exists:false}`;
  } else if (operator === "notNull") {
    return `"${field}":{"$ne":null}`;
  } else if (operator === "in" || operator === "notIn") {
    const valArray = toArray(value);
    return `"${field}":{"${mongoOperators[operator]}":[${valArray.map(
      (val) => shouldRenderAsNumber(val, parseNumbers) ? `${trimIfString(val)}` : `"${escapeDoubleQuotes(val)}"`
    ).join(",")}]}`;
  } else if (operator === "between" || operator === "notBetween") {
    const valArray = toArray(value);
    if (isValidValue(valArray[0]) && isValidValue(valArray[1])) {
      const [first, second] = valArray;
      const firstNum = shouldRenderAsNumber(first, true) ? parseFloat(first) : NaN;
      const secondNum = shouldRenderAsNumber(second, true) ? parseFloat(second) : NaN;
      const firstValue = !isNaN(firstNum) ? `${first}` : first instanceof Object ? JSON.stringify(first) : `"${escapeDoubleQuotes(first) ?? ""}"`;
      const secondValue = !isNaN(secondNum) ? `${second}` : second instanceof Object ? JSON.stringify(second) : `"${escapeDoubleQuotes(second) ?? ""}"`;
      if (operator === "between") {
        return `"${field}":{"$gte":${firstValue},"$lte":${secondValue}}`;
      } else {
        return `"${field}":{"$lte":${firstValue},"$gte":${secondValue}}`;
      }
    } else {
      return "";
    }
  }
  return "";
};
const shouldNegate = (op) => /^(does)?not/i.test(op);
const wrapInNegation = (clause, negate) => `${negate ? "!(" : ""}${clause}${negate ? ")" : ""}`;
const defaultValueProcessorSpELByRule = ({ field, operator, value, valueSource }, { escapeQuotes, parseNumbers } = {}) => {
  const escapeSingleQuotes = (v) => typeof v !== "string" || !escapeQuotes ? v : v.replaceAll(`'`, `\\'`);
  const valueIsField = valueSource === "field";
  const operatorTL = operator.replace(/^=$/, "==");
  const useBareValue = typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || shouldRenderAsNumber(value, parseNumbers);
  if (operatorTL === "<" || operatorTL === "<=" || operatorTL === "==" || operatorTL === "!=" || operatorTL === ">" || operatorTL === ">=") {
    return `${field} ${operatorTL} ${valueIsField || useBareValue ? trimIfString(value) : `'${escapeSingleQuotes(value)}'`}`;
  } else if (operatorTL === "contains" || operatorTL === "doesNotContain") {
    return wrapInNegation(
      `${field} matches ${valueIsField || useBareValue ? trimIfString(value) : `'${escapeSingleQuotes(value)}'`}`,
      shouldNegate(operatorTL)
    );
  } else if (operatorTL === "beginsWith" || operatorTL === "doesNotBeginWith") {
    const valueTL = valueIsField ? `'^'.concat(${trimIfString(value)})` : `'${typeof value === "string" && !value.startsWith("^") || useBareValue ? "^" : ""}${escapeSingleQuotes(value)}'`;
    return wrapInNegation(`${field} matches ${valueTL}`, shouldNegate(operatorTL));
  } else if (operatorTL === "endsWith" || operatorTL === "doesNotEndWith") {
    const valueTL = valueIsField ? `${trimIfString(value)}.concat('$')` : `'${escapeSingleQuotes(value)}${typeof value === "string" && !value.endsWith("$") || useBareValue ? "$" : ""}'`;
    return wrapInNegation(`${field} matches ${valueTL}`, shouldNegate(operatorTL));
  } else if (operatorTL === "null") {
    return `${field} == null`;
  } else if (operatorTL === "notNull") {
    return `${field} != null`;
  } else if (operatorTL === "in" || operatorTL === "notIn") {
    const negate = shouldNegate(operatorTL) ? "!" : "";
    const valArray = toArray(value);
    if (valArray.length > 0) {
      return `${negate}(${valArray.map(
        (val) => `${field} == ${valueIsField || shouldRenderAsNumber(val, parseNumbers) ? `${trimIfString(val)}` : `'${escapeSingleQuotes(val)}'`}`
      ).join(" or ")})`;
    } else {
      return "";
    }
  } else if (operatorTL === "between" || operatorTL === "notBetween") {
    const valArray = toArray(value);
    if (valArray.length >= 2 && !!valArray[0] && !!valArray[1]) {
      const [first, second] = valArray;
      const firstNum = shouldRenderAsNumber(first, true) ? parseFloat(first) : NaN;
      const secondNum = shouldRenderAsNumber(second, true) ? parseFloat(second) : NaN;
      let firstValue = isNaN(firstNum) ? valueIsField ? `${first}` : `'${escapeSingleQuotes(first)}'` : firstNum;
      let secondValue = isNaN(secondNum) ? valueIsField ? `${second}` : `'${escapeSingleQuotes(second)}'` : secondNum;
      if (firstValue === firstNum && secondValue === secondNum && secondNum < firstNum) {
        const tempNum = secondNum;
        secondValue = firstNum;
        firstValue = tempNum;
      }
      if (operator === "between") {
        return `(${field} >= ${firstValue} and ${field} <= ${secondValue})`;
      } else {
        return `(${field} < ${firstValue} or ${field} > ${secondValue})`;
      }
    } else {
      return "";
    }
  }
  return "";
};
const placeholderName = "~";
const defaultPlaceholderFieldName = placeholderName;
const defaultPlaceholderOperatorName = placeholderName;
const uniqByName = (originalArray) => {
  const names = /* @__PURE__ */ new Set();
  const newArray = [];
  originalArray.forEach((el) => {
    if (!names.has(el.name)) {
      names.add(el.name);
      newArray.push(el);
    }
  });
  return newArray;
};
const processRuleOrStringOrRuleGroupIC = (r) => typeof r === "object" && "rules" in r ? generateRuleGroupICWithConsistentCombinators(r) : r;
const generateRuleGroupICWithConsistentCombinators = (rg) => {
  const returnArray = [];
  const push = (r) => returnArray.push(processRuleOrStringOrRuleGroupIC(r));
  let startIndex = 0;
  for (let i = 0; i < rg.rules.length; i += 2) {
    if (rg.rules.length === 1) {
      push(rg.rules[0]);
    } else if (rg.rules[i + 1] === "and") {
      startIndex = i;
      let j = 1;
      while (rg.rules[startIndex + j] === "and") {
        i += 2;
        j += 2;
      }
      returnArray.push({
        // TODO: @ts-expect-error once we don't support TS@<4.5
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore TS can't keep track of odd/even indexes here
        rules: rg.rules.slice(startIndex, i + 1).map(processRuleOrStringOrRuleGroupIC)
      });
      i -= 2;
    } else if (rg.rules[i + 1] === "or") {
      if (i === 0 || i === rg.rules.length - 3) {
        if (i === 0 || rg.rules[i - 1] === "or") {
          push(rg.rules[i]);
        }
        push(rg.rules[i + 1]);
        if (i === rg.rules.length - 3) {
          push(rg.rules[i + 2]);
        }
      } else {
        if (rg.rules[i - 1] === "and") {
          push(rg.rules[i + 1]);
        } else {
          push(rg.rules[i]);
          push(rg.rules[i + 1]);
        }
      }
    }
  }
  if (
    // TODO: @ts-expect-error once we don't support TS@<4.5
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore TS still thinks returnArray has length 0
    returnArray.length === 1 && typeof returnArray[0] === "object" && "rules" in returnArray[0]
  ) {
    return { ...rg, ...returnArray[0] };
  }
  return { ...rg, rules: returnArray };
};
const convertFromIC = (rg) => {
  const processedRG = generateRuleGroupICWithConsistentCombinators(rg);
  const rulesAsMixedList = processedRG.rules.map(
    (r) => typeof r === "string" || !("rules" in r) ? r : convertFromIC(r)
  );
  const combinator = rulesAsMixedList.length < 2 ? "and" : rulesAsMixedList[1];
  const rules = rulesAsMixedList.filter((r) => typeof r !== "string");
  return { ...processedRG, combinator, rules };
};
const isValidationResult = (vr) => typeof vr === "object" && vr !== null && typeof vr.valid === "boolean";
const isRuleOrGroupValid = (rg, validationResult, validator) => {
  if (typeof validationResult === "boolean") {
    return validationResult;
  }
  if (isValidationResult(validationResult)) {
    return validationResult.valid;
  }
  if (typeof validator === "function" && !("rules" in rg)) {
    const vr = validator(rg);
    if (typeof vr === "boolean") {
      return vr;
    }
    if (isValidationResult(vr)) {
      return vr.valid;
    }
  }
  return true;
};
function formatQuery(ruleGroup, options = "mongodb") {
  let format = "json";
  let valueProcessorInternal = defaultValueProcessorByRule;
  let validator = () => true;
  let fields = [];
  let validationMap = {};
  let fallbackExpression = "";
  let parseNumbers = false;
  let placeholderFieldName = defaultPlaceholderFieldName;
  let placeholderOperatorName = defaultPlaceholderOperatorName;
  if (typeof options === "string") {
    format = options.toLowerCase();
    valueProcessorInternal = defaultValueProcessorMongoDBByRule;
  } else {
    format = (options.format ?? "json").toLowerCase();
    valueProcessorInternal = defaultValueProcessorMongoDBByRule;
    validator = options.validator ?? (() => true);
    fields = options.fields ?? [];
    fallbackExpression = options.fallbackExpression ?? "";
    parseNumbers = !!options.parseNumbers;
    placeholderFieldName = options.placeholderFieldName ?? defaultPlaceholderFieldName;
    placeholderOperatorName = options.placeholderOperatorName ?? defaultPlaceholderOperatorName;
  }
  if (!fallbackExpression) {
    fallbackExpression = "";
  }
  if (typeof validator === "function") {
    const validationResult = validator(ruleGroup);
    if (typeof validationResult === "boolean") {
      if (validationResult === false) {
        return format === "mongodb" ? `{${fallbackExpression}}` : fallbackExpression;
      }
    } else {
      validationMap = validationResult;
    }
  }
  const validatorMap = {};
  const uniqueFields = uniqByName(fields);
  uniqueFields.forEach((f) => {
    if (typeof f.validator === "function") {
      validatorMap[f.name] = f.validator;
    }
  });
  const validateRule = (rule) => {
    let validationResult = void 0;
    let fieldValidator = void 0;
    if (rule.id) {
      validationResult = validationMap[rule.id];
    }
    if (fields.length) {
      const fieldArr = fields.filter((f) => f.name === rule.field);
      if (fieldArr.length) {
        const field = fieldArr[0];
        if (typeof field.validator === "function") {
          fieldValidator = field.validator;
        }
      }
    }
    return [validationResult, fieldValidator];
  };
  const processRuleGroup = ({
    rg,
    outermost = false,
    parentCombinator,
    parentSurrounded = false
  }) => {
    if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* istanbul ignore next */
    ""])) {
      return outermost ? fallbackExpression : "";
    }
    const combinator = `"$${rg.combinator}"`;
    let sameFieldInMultipleRules = false;
    rg.rules.reduce(
      (accFields, rule) => {
        if (rule.field) {
          if (accFields.indexOf(rule.field) > -1) {
            sameFieldInMultipleRules = true;
          }
        }
        accFields.push(rule.field);
        return accFields;
      },
      []
    );
    const expression = rg.disabled ? "" : rg.rules.map((rule) => {
      if (rule.disabled)
        return "";
      if ("rules" in rule) {
        const processedRuleGroup = processRuleGroup({
          rg: rule,
          parentCombinator: rg.combinator,
          parentSurrounded: sameFieldInMultipleRules
        });
        return processedRuleGroup ? `${processedRuleGroup}` : "";
      }
      const [validationResult, fieldValidator] = validateRule(rule);
      if (!isRuleOrGroupValid(rule, validationResult, fieldValidator) || rule.field === placeholderFieldName || rule.operator === placeholderOperatorName) {
        return "";
      }
      const condition = valueProcessorInternal(rule, {
        parseNumbers,
        escapeQuotes: (rule.valueSource ?? "value") === "value"
      });
      if (!outermost || rg.combinator === "or" || sameFieldInMultipleRules) {
        return `{${condition}}`;
      }
      return condition;
    }).filter(Boolean).join(",");
    if (expression) {
      if (parentSurrounded || parentCombinator == "or") {
        return `{${combinator}:[${expression}]}`;
      } else if (outermost && rg.combinator === "and" && !sameFieldInMultipleRules) {
        return `${expression}`;
      } else {
        return `${combinator}:[${expression}]`;
      }
    } else {
      return fallbackExpression;
    }
  };
  const rgStandard = "combinator" in ruleGroup ? ruleGroup : convertFromIC(ruleGroup);
  return `{${processRuleGroup({ rg: rgStandard, outermost: true })}}`;
}
const internalValueProcessors = {
  mongodb: defaultValueProcessorMongoDBByRule
};
const generateValueProcessor = (format) => (field, operator, value, valueSource, fieldType) => internalValueProcessors[format](
  { field, operator, value, valueSource, fieldType },
  { parseNumbers: false }
);
const defaultMongoDBValueProcessor = generateValueProcessor("mongodb");
exports.defaultMongoDBValueProcessor = defaultMongoDBValueProcessor;
exports.defaultValueProcessorByRule = defaultValueProcessorByRule;
exports.defaultValueProcessorCELByRule = defaultValueProcessorCELByRule;
exports.defaultValueProcessorMongoDBByRule = defaultValueProcessorMongoDBByRule;
exports.defaultValueProcessorSpELByRule = defaultValueProcessorSpELByRule;
exports.formatQuery = formatQuery;
