"use strict";
const zod = require("./zod-AYpgycup.cjs");
require("./index-Br7AzmuA.cjs");
const isBookingLimits = require("./isBookingLimits-4w4srzLF.cjs");
const index = require("./i18n-HHdcQbuJ.cjs");
require("react-awesome-query-builder");
const dateRanges = require("./date-ranges-Uz4z3i_9.cjs");
const dayjs = require("dayjs");
var RoutingFormFieldType = /* @__PURE__ */ ((RoutingFormFieldType2) => {
  RoutingFormFieldType2["TEXT"] = "text";
  RoutingFormFieldType2["NUMBER"] = "number";
  RoutingFormFieldType2["TEXTAREA"] = "textarea";
  RoutingFormFieldType2["SINGLE_SELECT"] = "select";
  RoutingFormFieldType2["MULTI_SELECT"] = "multiselect";
  RoutingFormFieldType2["PHONE"] = "phone";
  RoutingFormFieldType2["EMAIL"] = "email";
  return RoutingFormFieldType2;
})(RoutingFormFieldType || {});
const FieldTypes = [
  {
    label: "Short Text",
    value: "text"
    /* TEXT */
  },
  {
    label: "Number",
    value: "number"
    /* NUMBER */
  },
  {
    label: "Long Text",
    value: "textarea"
    /* TEXTAREA */
  },
  {
    label: "Single Selection",
    value: "select"
    /* SINGLE_SELECT */
  },
  {
    label: "Multiple Selection",
    value: "multiselect"
    /* MULTI_SELECT */
  },
  {
    label: "Phone",
    value: "phone"
    /* PHONE */
  },
  {
    label: "Email",
    value: "email"
    /* EMAIL */
  }
];
const conjunctions = {
  AND: {
    label: "And",
    jsonLogicConj: "and",
    reversedConj: "OR",
    formatConj: () => "",
    sqlFormatConj: () => "",
    spelFormatConj: () => "",
    mongoConj: ""
  },
  OR: {
    label: "Or",
    jsonLogicConj: "or",
    reversedConj: "AND",
    formatConj: () => "",
    sqlFormatConj: () => "",
    spelFormatConj: () => "",
    mongoConj: ""
  }
};
const operators = {
  equal: {
    label: "Equals",
    labelForFormat: "==",
    reversedOp: "not_equal",
    jsonLogic: "=="
  },
  not_equal: {
    isNotOp: true,
    label: "Does not equal",
    labelForFormat: "!=",
    reversedOp: "equal",
    jsonLogic: "!="
  },
  less: {
    label: "Less than",
    labelForFormat: "<",
    reversedOp: "greater_or_equal",
    jsonLogic: "<"
  },
  less_or_equal: {
    label: "Less than or equal to",
    labelForFormat: "<=",
    reversedOp: "greater",
    jsonLogic: "<="
  },
  greater: {
    label: "Greater than",
    labelForFormat: ">",
    reversedOp: "less_or_equal",
    jsonLogic: ">"
  },
  greater_or_equal: {
    label: "Greater than or equal to",
    labelForFormat: ">=",
    reversedOp: "less",
    jsonLogic: ">="
  },
  like: {
    label: "Contains",
    labelForFormat: "Contains",
    reversedOp: "not_like",
    jsonLogic: "in",
    _jsonLogicIsRevArgs: true,
    valueSources: ["value"]
  },
  not_like: {
    isNotOp: true,
    label: "Not contains",
    reversedOp: "like",
    labelForFormat: "Not Contains",
    valueSources: ["value"]
  },
  starts_with: {
    label: "Starts with",
    labelForFormat: "Starts with",
    jsonLogic: "starts_with",
    valueSources: ["value"]
  },
  //   ends_with: {
  //     label: "Ends with",
  //     labelForFormat: "Ends with",
  //     jsonLogic: undefined, // not supported
  //     valueSources: ["value"],
  //   },
  between: {
    label: "Between",
    labelForFormat: "BETWEEN",
    cardinality: 2,
    valueLabels: ["Value from", "Value to"],
    reversedOp: "not_between",
    jsonLogic: (field, op, vals) => {
      const min = parseInt(vals[0], 10);
      const max = parseInt(vals[1], 10);
      return {
        and: [{ ">=": [field, min] }, { "<=": [field, max] }]
      };
    }
  },
  not_between: {
    isNotOp: true,
    label: "Not between",
    labelForFormat: "NOT BETWEEN",
    cardinality: 2,
    valueLabels: ["Value from", "Value to"],
    reversedOp: "between",
    jsonLogic: (field, op, vals) => {
      const min = parseInt(vals[0], 10);
      const max = parseInt(vals[1], 10);
      return {
        or: [{ "<": [field, min] }, { ">": [field, max] }]
      };
    }
  },
  is_empty: {
    label: "Is empty",
    labelForFormat: "IS EMPTY",
    cardinality: 0,
    reversedOp: "is_not_empty",
    jsonLogic: "!"
  },
  is_not_empty: {
    isNotOp: true,
    label: "Is not empty",
    labelForFormat: "IS NOT EMPTY",
    cardinality: 0,
    reversedOp: "is_empty",
    jsonLogic: "!!"
  },
  /**
   * We don't need these operators
   */
  //   is_null: {
  //     label: "Is null",
  //     labelForFormat: "IS NULL",
  //     cardinality: 0,
  //     reversedOp: "is_not_null",
  //     jsonLogic: "==",
  //   },
  //   is_not_null: {
  //     label: "Is not null",
  //     labelForFormat: "IS NOT NULL",
  //     cardinality: 0,
  //     reversedOp: "is_null",
  //     jsonLogic: "!=",
  //   },
  select_equals: {
    label: "Equals",
    labelForFormat: "==",
    reversedOp: "select_not_equals",
    jsonLogic: "=="
  },
  select_not_equals: {
    isNotOp: true,
    label: "Does not equal",
    labelForFormat: "!=",
    reversedOp: "select_equals",
    jsonLogic: "!="
  },
  select_any_in: {
    label: "Any in",
    labelForFormat: "IN",
    reversedOp: "select_not_any_in",
    jsonLogic: "in"
  },
  select_not_any_in: {
    isNotOp: true,
    label: "Not any in",
    labelForFormat: "NOT IN",
    reversedOp: "select_any_in"
  },
  // We define this operator but use it conditionally for multiselect for Attributes only
  multiselect_some_in: {
    label: "Any in",
    jsonLogic: (field, operator, vals) => {
      return {
        // Tested in jsonLogic.test.ts
        some: [field, { in: [{ var: "" }, vals] }]
      };
    }
  },
  multiselect_not_some_in: {
    label: "Not any in",
    reversedOp: "multiselect_some_in"
  },
  multiselect_equals: {
    label: "All in",
    reversedOp: "multiselect_not_equals",
    // jsonLogic2: "all-in",
    jsonLogic: (field, op, vals, ...rest) => {
      return {
        // This is wrongly implemented as "includes". This isn't "equals". Because if field is ["a" ] and vals is ["a", "b"], it still matches. Expectation would probably be that it should be a strict match(["a", "b"] or ["b", "a"])
        all: [field, { in: [{ var: "" }, vals] }]
      };
    }
  },
  multiselect_not_equals: {
    isNotOp: true,
    label: "Not all in",
    reversedOp: "multiselect_equals"
  },
  some: {
    label: "Some",
    labelForFormat: "SOME",
    cardinality: 0,
    jsonLogic: "some"
  },
  all: {
    label: "All",
    labelForFormat: "ALL",
    cardinality: 0
  },
  none: {
    label: "None",
    labelForFormat: "NONE",
    cardinality: 0,
    jsonLogic: "none"
  }
};
const widgets = {
  text: {
    type: "text",
    jsType: "string",
    valueSrc: "value",
    valueLabel: "String",
    valuePlaceholder: "Enter string",
    toJS: (val) => val
  },
  textarea: {
    type: "text",
    jsType: "string",
    valueSrc: "value",
    valueLabel: "Text",
    valuePlaceholder: "Enter text",
    toJS: (val) => val
  },
  number: {
    type: "number",
    jsType: "number",
    valueSrc: "value",
    valueLabel: "Number",
    valuePlaceholder: "Enter number",
    toJS: (val) => val
  },
  select: {
    type: "select",
    jsType: "string",
    valueSrc: "value",
    valueLabel: "Value",
    valuePlaceholder: "Select value",
    toJS: (val) => val
  },
  multiselect: {
    type: "multiselect",
    jsType: "array",
    valueSrc: "value",
    valueLabel: "Values",
    valuePlaceholder: "Select values",
    toJS: (val) => val
  }
};
const types = {
  text: {
    defaultOperator: "equal",
    mainWidget: "text",
    widgets: {
      text: {
        // Note: any operator mentioned here but isn't defined in operators will be automatically excluded
        operators: [
          "equal",
          "not_equal",
          "like",
          "not_like",
          "starts_with",
          "ends_with",
          "proximity",
          "is_empty",
          "is_not_empty",
          "is_null",
          "is_not_null"
        ],
        widgetProps: {},
        opProps: {}
      },
      textarea: {
        operators: [
          "equal",
          "not_equal",
          "like",
          "not_like",
          "starts_with",
          "ends_with",
          "is_empty",
          "is_not_empty",
          "is_null",
          "is_not_null"
        ],
        widgetProps: {},
        opProps: {}
      }
    }
  },
  number: {
    defaultOperator: "equal",
    mainWidget: "number",
    widgets: {
      number: {
        operators: [
          "equal",
          "not_equal",
          "less",
          "less_or_equal",
          "greater",
          "greater_or_equal",
          "between",
          "not_between",
          // "is_empty",
          // "is_not_empty",
          "is_null",
          "is_not_null"
        ]
      }
      //   slider: {
      //     operators: [
      //       "equal",
      //       "not_equal",
      //       "less",
      //       "less_or_equal",
      //       "greater",
      //       "greater_or_equal",
      //       // "is_empty",
      //       // "is_not_empty",
      //       "is_null",
      //       "is_not_null",
      //     ],
      //   },
    }
  },
  select: {
    mainWidget: "select",
    defaultOperator: "select_equals",
    widgets: {
      select: {
        operators: ["select_equals", "select_not_equals"],
        widgetProps: {
          customProps: {
            showSearch: true
          }
        }
      },
      multiselect: {
        operators: ["select_any_in", "select_not_any_in"]
      }
    }
  },
  multiselect: {
    defaultOperator: "multiselect_equals",
    widgets: {
      multiselect: {
        operators: ["multiselect_equals", "multiselect_not_equals", "is_null", "is_not_null"]
      }
    }
  }
  //   "!group": {
  //     defaultOperator: "some",
  //     mainWidget: "number",
  //     widgets: {
  //       number: {
  //         widgetProps: {
  //           min: 0,
  //         },
  //         operators: [
  //           // w/o operand
  //           "some",
  //           "all",
  //           "none",
  //           // w/ operand - count
  //           "equal",
  //           "not_equal",
  //           "less",
  //           "less_or_equal",
  //           "greater",
  //           "greater_or_equal",
  //           "between",
  //           "not_between",
  //         ],
  //         opProps: {
  //           equal: {
  //             label: "Count ==",
  //           },
  //           not_equal: {
  //             label: "Count !=",
  //           },
  //           less: {
  //             label: "Count <",
  //           },
  //           less_or_equal: {
  //             label: "Count <=",
  //           },
  //           greater: {
  //             label: "Count >",
  //           },
  //           greater_or_equal: {
  //             label: "Count >=",
  //           },
  //           between: {
  //             label: "Count between",
  //           },
  //           not_between: {
  //             label: "Count not between",
  //           },
  //         },
  //       },
  //     },
  //   },
  //   case_value: {
  //     mainWidget: "case_value",
  //     widgets: {
  //       case_value: {},
  //     },
  //   },
};
const settings = {
  setOpOnChangeField: ["keep", "default"]
  // 'default' (default if present), 'keep' (keep prev from last field), 'first', 'none'
};
const basicConfig = {
  conjunctions,
  operators,
  widgets,
  types,
  settings
};
var ConfigFor = /* @__PURE__ */ ((ConfigFor2) => {
  ConfigFor2["FormFields"] = "FormFields";
  ConfigFor2["Attributes"] = "Attributes";
  return ConfigFor2;
})(ConfigFor || {});
function getWidgetsWithoutFactory(_configFor) {
  const widgetsWithoutFactory = {
    ...basicConfig.widgets,
    phone: {
      ...basicConfig.widgets.text
    },
    email: {
      ...basicConfig.widgets.text
    }
  };
  return widgetsWithoutFactory;
}
function getTypes(configFor) {
  const multiSelectOperators = basicConfig.types.multiselect.widgets.multiselect.operators || [];
  if (configFor === ConfigFor.Attributes) {
    multiSelectOperators.push("multiselect_some_in", "multiselect_not_some_in");
  }
  const types2 = {
    ...basicConfig.types,
    phone: {
      ...basicConfig.types.text,
      widgets: {
        ...basicConfig.types.text.widgets
      }
    },
    email: {
      ...basicConfig.types.text,
      widgets: {
        ...basicConfig.types.text.widgets
      }
    },
    multiselect: {
      ...basicConfig.types.multiselect,
      widgets: {
        ...basicConfig.types.multiselect.widgets,
        multiselect: {
          ...basicConfig.types.multiselect.widgets.multiselect,
          operators: [...multiSelectOperators]
        }
      }
    }
  };
  return types2;
}
function getOperators(_configFor) {
  const operators2 = {
    ...basicConfig.operators
  };
  return operators2;
}
function getConjunctions(_configFor) {
  return {
    ...basicConfig.conjunctions
  };
}
function getSettingsWithoutRenderFns() {
  return {
    ...basicConfig.settings,
    groupActionsPosition: "bottomCenter",
    // TODO: Test it and then enable it. It might allow us to show better error messages.
    // But it doesn't detect every kind of error like an operator gone missing e.g. what happened in https://github.com/calcom/cal.com/pull/17102
    showErrorMessage: true,
    // Disable groups
    maxNesting: 1
  };
}
const FormFieldsBaseConfig = {
  conjunctions: getConjunctions(ConfigFor.FormFields),
  operators: getOperators(ConfigFor.FormFields),
  types: getTypes(ConfigFor.FormFields),
  widgets: getWidgetsWithoutFactory(ConfigFor.FormFields),
  settings: getSettingsWithoutRenderFns()
};
const AttributesBaseConfig = {
  conjunctions: getConjunctions(ConfigFor.Attributes),
  operators: getOperators(ConfigFor.Attributes),
  types: getTypes(ConfigFor.Attributes),
  widgets: getWidgetsWithoutFactory(ConfigFor.Attributes),
  settings: getSettingsWithoutRenderFns()
};
const FormFieldsInitialConfig = FormFieldsBaseConfig;
const AttributesInitialConfig = AttributesBaseConfig;
const buildOptionsFromLegacySelectText = ({ legacySelectText }) => {
  return legacySelectText.trim().split("\n").map((fieldValue) => ({
    label: fieldValue,
    id: null
  }));
};
const getFieldWithOptions = (field) => {
  const legacySelectText = field.selectText;
  if (field.options) {
    return {
      ...field,
      options: field.options
    };
  } else if (legacySelectText) {
    const options = buildOptionsFromLegacySelectText({ legacySelectText });
    return {
      ...field,
      options
    };
  }
  return {
    ...field
  };
};
function areSelectOptionsInLegacyFormat(field) {
  const options = field.options || [];
  return !!options.find((option) => !option.id);
}
function getUIOptionsForSelect(field) {
  const fieldWithOptions = getFieldWithOptions(field);
  const options = fieldWithOptions.options || [];
  const areOptionsInLegacyFormat = areSelectOptionsInLegacyFormat(
    fieldWithOptions
  );
  const shouldUseLabelAsValue = areOptionsInLegacyFormat;
  return options.map((option) => {
    const value = shouldUseLabelAsValue ? option.label : option.id ?? option.label;
    return {
      value,
      title: option.label
    };
  });
}
const buildDynamicOperandFieldVariable = (fieldId) => {
  return `{field:${fieldId}}`;
};
const attributeTypesMap = /* @__PURE__ */ new Map([
  [index.AttributeType.SINGLE_SELECT, RoutingFormFieldType.SINGLE_SELECT],
  [index.AttributeType.MULTI_SELECT, RoutingFormFieldType.MULTI_SELECT],
  [index.AttributeType.TEXT, RoutingFormFieldType.TEXT],
  [index.AttributeType.NUMBER, RoutingFormFieldType.NUMBER]
]);
function getQueryBuilderConfigForFormFields(form, forReporting = false) {
  const fields = {};
  form.fields?.forEach((field) => {
    if ("routerField" in field) {
      field = field.routerField;
    }
    const fieldType = field.type;
    if (FieldTypes.map((f) => f.value).includes(fieldType)) {
      const options = getUIOptionsForSelect(field);
      const widget = FormFieldsInitialConfig.widgets[fieldType];
      const widgetType = widget.type;
      fields[field.id] = {
        label: field.label,
        type: widgetType,
        valueSources: ["value"],
        fieldSettings: {
          // IMPORTANT: listValues must be undefined for non-select/multiselect fields otherwise RAQB doesn't like it. It ends up considering all the text values as per the listValues too which could be empty as well making all values invalid
          listValues: fieldType === "select" || fieldType === "multiselect" ? options : void 0
        }
      };
    } else {
      throw new Error(`Unsupported field type:${field.type}`);
    }
  });
  const initialConfigCopy = {
    ...FormFieldsInitialConfig,
    operators: { ...FormFieldsInitialConfig.operators }
  };
  if (forReporting) {
    delete initialConfigCopy.operators.is_empty;
    delete initialConfigCopy.operators.is_not_empty;
    delete initialConfigCopy.operators.between;
    delete initialConfigCopy.operators.not_between;
    initialConfigCopy.operators.__calReporting = true;
  }
  const config = {
    ...initialConfigCopy,
    fields
  };
  return config;
}
function transformAttributesToCompatibleFormat(attributes) {
  return attributes.map((attribute) => {
    const mappedType = attributeTypesMap.get(attribute.type);
    if (!mappedType) {
      throw new Error(`Unsupported attribute type:${attribute.type}`);
    }
    return {
      label: attribute.name,
      id: attribute.id,
      type: mappedType,
      options: attribute.options.map((option) => ({
        title: option.value,
        // We have to use something that doesn't change often. ID of attribute never changes. Changing means a saved value will become invalid
        value: option.id
      }))
    };
  });
}
function getQueryBuilderConfigForAttributes({
  attributes,
  /**
   * It is the fields that makes up additional options to be matched with for the single select/multiselect attributes.
   * They are shown as 'Value of field <field-label>' in the dropdown.
   */
  dynamicOperandFields = []
}) {
  const transformedAttributes = transformAttributesToCompatibleFormat(attributes);
  const fields = {};
  transformedAttributes.forEach((attribute) => {
    const attributeType = attribute.type;
    if (FieldTypes.map((f) => f.value).includes(attributeType)) {
      const widget = FormFieldsInitialConfig.widgets[attributeType];
      const widgetType = widget.type;
      const valueOfFieldOptions = (() => {
        const formFieldsOptions = dynamicOperandFields.map((field) => ({
          title: `Value of field '${field.label}'`,
          value: buildDynamicOperandFieldVariable(field.id)
        }));
        return formFieldsOptions;
      })();
      const attributeOptions = [...valueOfFieldOptions, ...attribute.options];
      fields[attribute.id] = {
        label: attribute.label,
        type: widgetType,
        valueSources: ["value"],
        fieldSettings: {
          // IMPORTANT: listValues must be undefined for non-select/multiselect fields otherwise RAQB doesn't like it. It ends up considering all the text values as per the listValues too which could be empty as well making all values invalid
          listValues: attributeType === "select" || attributeType === "multiselect" ? attributeOptions : void 0
        }
      };
    } else {
      throw new Error(`Unsupported field type:${attribute.type}`);
    }
  });
  const initialConfigCopy = {
    ...AttributesInitialConfig,
    operators: { ...AttributesInitialConfig.operators }
  };
  return {
    ...initialConfigCopy,
    fields
  };
}
function caseInsensitive(stringOrStringArray) {
  return stringOrStringArray instanceof Array ? stringOrStringArray.map((string) => string.toLowerCase()) : stringOrStringArray.toLowerCase();
}
const moduleLogger = index.logger.getSubLogger({ prefix: ["raqb/resolveQueryValue"] });
const replaceAttributeOptionIdsWithOptionLabel = ({
  queryValue,
  attributes
}) => {
  const queryValueString = JSON.stringify(queryValue);
  let queryValueWithLabels = queryValueString;
  const allAttributesOptions = attributes.map((attribute) => attribute.options).flat();
  allAttributesOptions.forEach((attributeOption) => {
    const attributeOptionId = attributeOption.id;
    queryValueWithLabels = queryValueWithLabels.replace(
      new RegExp(`${attributeOptionId}`, "g"),
      caseInsensitive(attributeOption.value)
    );
  });
  return JSON.parse(queryValueWithLabels);
};
function getFieldResponseValueAsLabel({
  field,
  fieldResponseValue
}) {
  const nonNumberFieldResponseValue = typeof fieldResponseValue === "number" ? fieldResponseValue.toString() : fieldResponseValue;
  if (!field.options) {
    return nonNumberFieldResponseValue;
  }
  if (nonNumberFieldResponseValue instanceof Array) {
    return nonNumberFieldResponseValue.map(transformValue);
  }
  return transformValue(nonNumberFieldResponseValue);
  function transformValue(idOrLabel) {
    const foundOptionById = field.options?.find((option) => {
      return option.id === idOrLabel;
    });
    if (foundOptionById) {
      return foundOptionById.label;
    } else {
      return idOrLabel;
    }
  }
}
const resolveQueryValue = ({
  queryValue,
  dynamicFieldValueOperands,
  attributes
}) => {
  const queryValueWithLabels = replaceAttributeOptionIdsWithOptionLabel({
    queryValue,
    attributes
  });
  if (!dynamicFieldValueOperands) {
    return queryValueWithLabels;
  }
  const { fields, response } = dynamicFieldValueOperands;
  const isFieldTemplate = (str) => {
    const trimmedStr = str.trim();
    return trimmedStr.startsWith("{field:") && trimmedStr.endsWith("}");
  };
  const extractFieldId = (template) => {
    const trimmedTemplate = template.trim();
    if (!isFieldTemplate(trimmedTemplate))
      return null;
    return trimmedTemplate.slice(7, -1);
  };
  const resolveFieldId = (fieldId) => {
    const field = fields.find((f) => f.id === fieldId);
    if (!field || !response[fieldId]?.value) {
      return null;
    }
    const fieldResponseValue = response[fieldId].value;
    return getFieldResponseValueAsLabel({ field, fieldResponseValue });
  };
  const processStringValue = (value) => {
    const fieldId = extractFieldId(value);
    if (!fieldId) {
      return value;
    }
    const resolvedValue = resolveFieldId(fieldId);
    if (!resolvedValue) {
      return value;
    }
    return caseInsensitive(resolvedValue);
  };
  const processArray = (arr) => {
    const hasFieldTemplate = arr.some((item) => typeof item === "string" && isFieldTemplate(item));
    if (!hasFieldTemplate) {
      return arr.map((item) => processAnyValue(item));
    }
    const result = [];
    for (const item of arr) {
      if (typeof item === "string" && isFieldTemplate(item)) {
        const processed = processStringValue(item);
        if (Array.isArray(processed)) {
          result.push(...processed);
        } else {
          result.push(processed);
        }
      } else {
        result.push(processAnyValue(item));
      }
    }
    return result;
  };
  const processAnyValue = (value) => {
    if (value instanceof Array) {
      return processArray(value);
    }
    if (typeof value === "object" && value !== null) {
      const result = {};
      for (const key in value) {
        result[key] = processAnyValue(value[key]);
      }
      return result;
    }
    if (typeof value === "string") {
      const processedValue = processStringValue(value);
      return processedValue;
    }
    return value;
  };
  try {
    const processed = processAnyValue(queryValueWithLabels);
    return processed;
  } catch (e) {
    moduleLogger.error("Failed to resolve field templates", {
      error: e,
      queryValue: JSON.stringify(queryValueWithLabels),
      fieldsCount: fields?.length || 0,
      responseKeys: Object.keys(response || {}),
      dynamicFieldValueOperandsProvided: !!dynamicFieldValueOperands
    });
    throw new Error(
      `Failed to resolve field templates in query value: ${e instanceof Error ? e.message : String(e)}`
    );
  }
};
function ensureArray(value) {
  return typeof value === "string" ? [value] : value;
}
const raqbQueryValueUtils = {
  isQueryValueARuleGroup: function isQueryValueARuleGroup(queryValue) {
    return queryValue.type === "group";
  },
  isARule: function isARule(rule) {
    return rule.type === "rule";
  },
  getValueTypeFromAttributesQueryValueForRaqbField: function getValueTypeFromAttributesQueryValueForRaqbField({
    attributesQueryValue,
    raqbFieldId
  }) {
    if (!raqbQueryValueUtils.isQueryValueARuleGroup(attributesQueryValue)) {
      return null;
    }
    if (!attributesQueryValue.children1) {
      return null;
    }
    let raqbFieldValueType = null;
    Object.values(attributesQueryValue.children1).reduce((acc, rule) => {
      if (!raqbQueryValueUtils.isARule(rule)) {
        return acc;
      }
      const ruleProperties = rule?.properties;
      if (!ruleProperties) {
        return acc;
      }
      if (ruleProperties.field === raqbFieldId) {
        raqbFieldValueType = ruleProperties.valueType?.[0];
      }
      return acc;
    }, null);
    return raqbFieldValueType;
  },
  isQueryValueEmpty: function isQueryValueEmpty(queryValue) {
    if (!queryValue) {
      return true;
    }
    return !queryValue.children1;
  }
};
function getValueOfAttributeOption(attributeOptions) {
  if (!(attributeOptions instanceof Array)) {
    return transformAttributeOption(attributeOptions);
  }
  return attributeOptions.map(transformAttributeOption).flat().filter((value, index2, self) => self.indexOf(value) === index2);
  function transformAttributeOption(attributeOption) {
    if (attributeOption.isGroup) {
      const subOptions = attributeOption.contains.map((option) => option.value);
      console.log("A group option found. Using all its sub-options instead", index.safeStringify(subOptions));
      return subOptions;
    }
    return attributeOption.value;
  }
}
function getAttributesData({
  attributesData
}) {
  return Object.entries(attributesData).reduce(
    (acc, [attributeId, { type: attributeType, attributeOption }]) => {
      const compatibleValueForAttributeAndFormFieldMatching = caseInsensitive(
        getValueOfAttributeOption(attributeOption)
      );
      acc[attributeId] = // multiselect attribute's value must be an array as all the operators multiselect_some_in, multiselect_all_in and their respective not operators expect an array
      // If we add an operator that doesn't expect an array, we need to somehow make it operator based.
      attributeType === index.AttributeType.MULTI_SELECT ? ensureArray(compatibleValueForAttributeAndFormFieldMatching) : compatibleValueForAttributeAndFormFieldMatching;
      return acc;
    },
    {}
  );
}
function getAttributesQueryValue$1({
  attributesQueryValue,
  attributes,
  dynamicFieldValueOperands
}) {
  if (!attributesQueryValue) {
    return null;
  }
  const resolvedQueryValue = resolveQueryValue({
    queryValue: attributesQueryValue,
    attributes,
    dynamicFieldValueOperands
  });
  return resolvedQueryValue;
}
function getAttributesQueryBuilderConfigHavingListofLabels({
  dynamicFieldValueOperands,
  attributes
}) {
  const attributesQueryBuilderConfig = getQueryBuilderConfigForAttributes({
    attributes,
    dynamicOperandFields: dynamicFieldValueOperands?.fields
  });
  const attributesQueryBuilderConfigFieldsWithCompatibleListValues = Object.fromEntries(
    Object.entries(attributesQueryBuilderConfig.fields).map(([raqbFieldId, raqbField]) => {
      const raqbFieldType = raqbField.type;
      return [
        raqbFieldId,
        {
          ...raqbField,
          type: raqbFieldType,
          fieldSettings: {
            ...raqbField.fieldSettings,
            listValues: raqbField.fieldSettings.listValues?.map((option) => {
              return {
                ...option,
                // Use the title(which is the attributeOption.value) as the value of the raqb field so that it can be compatible for matching with the form field value
                value: caseInsensitive(option.title)
              };
            })
          }
        }
      ];
    })
  );
  const attributesQueryBuilderConfigWithCompatibleListValues = {
    ...attributesQueryBuilderConfig,
    fields: attributesQueryBuilderConfigFieldsWithCompatibleListValues
  };
  return attributesQueryBuilderConfigWithCompatibleListValues;
}
const acrossQueryValueCompatiblity = {
  getAttributesQueryBuilderConfigHavingListofLabels,
  getAttributesQueryValue: getAttributesQueryValue$1,
  getAttributesData,
  resolveQueryValue
};
const log = index.logger.getSubLogger({ prefix: ["getLuckyUser"] });
const { getAttributesQueryValue } = acrossQueryValueCompatiblity;
const startOfMonth = (date = /* @__PURE__ */ new Date()) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
const startOfDay = (date = /* @__PURE__ */ new Date()) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
const endOfDay = (date) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 23, 59, 59, 999));
const endOfMonth = (date) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0, 23, 59, 59, 999));
const getIntervalEndDate = ({
  interval,
  rrTimestampBasis,
  meetingStartTime
}) => {
  if (rrTimestampBasis === index.RRTimestampBasis.START_TIME) {
    if (!meetingStartTime) {
      throw new Error("Meeting start time is required");
    }
    if (interval === index.RRResetInterval.DAY) {
      return endOfDay(meetingStartTime);
    }
    return endOfMonth(meetingStartTime);
  }
  return /* @__PURE__ */ new Date();
};
const getIntervalStartDate = ({
  interval,
  rrTimestampBasis,
  meetingStartTime
}) => {
  if (rrTimestampBasis === index.RRTimestampBasis.START_TIME) {
    if (!meetingStartTime) {
      throw new Error("Meeting start time is required");
    }
    if (interval === index.RRResetInterval.DAY) {
      return startOfDay(meetingStartTime);
    }
    return startOfMonth(meetingStartTime);
  }
  if (interval === index.RRResetInterval.DAY) {
    return startOfDay();
  }
  return startOfMonth();
};
const isNonEmptyArray = (arr) => arr.length > 0;
function isFullDayEvent(date1, date2) {
  const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1e3;
  const difference = Math.abs(date1.getTime() - date2.getTime());
  if (difference % MILLISECONDS_IN_A_DAY === 0)
    return true;
}
class LuckyUserService {
  constructor(deps) {
    this.bookingRepository = deps.bookingRepository;
    this.hostRepository = deps.hostRepository;
    this.oooRepository = deps.oooRepository;
    this.userRepository = deps.userRepository;
    this.attributeRepository = deps.attributeRepository;
  }
  leastRecentlyBookedUser({
    availableUsers,
    bookingsOfAvailableUsers,
    organizersWithLastCreated
  }) {
    const organizerIdAndAtCreatedPair = organizersWithLastCreated.reduce(
      (keyValuePair, user) => {
        keyValuePair[user.id] = user.bookings[0]?.createdAt || /* @__PURE__ */ new Date(0);
        return keyValuePair;
      },
      {}
    );
    const attendeeUserIdAndAtCreatedPair = bookingsOfAvailableUsers.reduce(
      (aggregate, booking) => {
        availableUsers.forEach((user) => {
          if (aggregate[user.id])
            return;
          if (!booking.attendees.map((attendee) => attendee.email).includes(user.email))
            return;
          if (organizerIdAndAtCreatedPair[user.id] > booking.createdAt)
            return;
          aggregate[user.id] = booking.createdAt;
        });
        return aggregate;
      },
      {}
    );
    const userIdAndAtCreatedPair = {
      ...organizerIdAndAtCreatedPair,
      ...attendeeUserIdAndAtCreatedPair
    };
    log.info(
      "userIdAndAtCreatedPair",
      index.safeStringify({
        organizerIdAndAtCreatedPair,
        attendeeUserIdAndAtCreatedPair,
        userIdAndAtCreatedPair
      })
    );
    if (!userIdAndAtCreatedPair) {
      throw new Error("Unable to find users by availableUser ids.");
    }
    const leastRecentlyBookedUser = availableUsers.sort((a, b) => {
      if (userIdAndAtCreatedPair[a.id] > userIdAndAtCreatedPair[b.id])
        return 1;
      else if (userIdAndAtCreatedPair[a.id] < userIdAndAtCreatedPair[b.id])
        return -1;
      else
        return 0;
    })[0];
    return leastRecentlyBookedUser;
  }
  getHostsWithCalibration({
    hosts,
    allRRHostsBookingsOfInterval,
    allRRHostsCreatedInInterval,
    oooData
  }) {
    function calculateNewHostCalibration(newHost) {
      const existingBookingsBeforeAdded = existingBookings.filter(
        (booking) => booking.userId !== newHost.userId && booking.createdAt < newHost.createdAt
      );
      const hostsAddedBefore = hosts.filter(
        (host) => host.userId !== newHost.userId && host.createdAt < newHost.createdAt
      );
      const calibration = existingBookingsBeforeAdded.length && hostsAddedBefore.length ? existingBookingsBeforeAdded.length / hostsAddedBefore.length : 0;
      log.debug(
        "calculateNewHostCalibration",
        index.safeStringify({
          newHost,
          existingBookingsBeforeAdded: existingBookingsBeforeAdded.length,
          hostsAddedBefore: hostsAddedBefore.length,
          calibration
        })
      );
      return calibration;
    }
    const existingBookings = allRRHostsBookingsOfInterval;
    const oooCalibration = /* @__PURE__ */ new Map();
    oooData.forEach(({ userId, oooEntries }) => {
      let calibration = 0;
      oooEntries.forEach((oooEntry) => {
        const bookingsInTimeframe = existingBookings.filter(
          (booking) => booking.createdAt >= oooEntry.start && booking.createdAt <= oooEntry.end && booking.userId !== userId
        );
        calibration += bookingsInTimeframe.length / (hosts.length - 1);
      });
      oooCalibration.set(userId, calibration);
    });
    let newHostsWithCalibration = /* @__PURE__ */ new Map();
    if (allRRHostsCreatedInInterval.length && existingBookings.length) {
      newHostsWithCalibration = new Map(
        allRRHostsCreatedInInterval.map((newHost) => [
          newHost.userId,
          { ...newHost, calibration: calculateNewHostCalibration(newHost) }
        ])
      );
    }
    return hosts.map((host) => ({
      ...host,
      calibration: (newHostsWithCalibration.get(host.userId)?.calibration ?? 0) + (oooCalibration.get(host.userId) ?? 0)
    }));
  }
  getUsersWithHighestPriority({
    availableUsers
  }) {
    const highestPriority = Math.max(...availableUsers.map((user) => user.priority ?? 2));
    const usersWithHighestPriority = availableUsers.filter(
      (user) => user.priority === highestPriority || user.priority == null && highestPriority === 2
    );
    if (!isNonEmptyArray(usersWithHighestPriority)) {
      throw new Error("Internal Error: Highest Priority filter should never return length=0.");
    }
    log.info(
      "getUsersWithHighestPriority",
      index.safeStringify({
        highestPriorityUsers: usersWithHighestPriority.map((user) => user.id)
      })
    );
    return usersWithHighestPriority;
  }
  filterUsersBasedOnWeights({
    availableUsers,
    bookingsOfAvailableUsersOfInterval,
    bookingsOfNotAvailableUsersOfInterval,
    allRRHosts,
    allRRHostsBookingsOfInterval,
    allRRHostsCreatedInInterval,
    attributeWeights,
    oooData
  }) {
    const allBookings = bookingsOfAvailableUsersOfInterval.concat(bookingsOfNotAvailableUsersOfInterval);
    const allHostsWithCalibration = this.getHostsWithCalibration({
      hosts: allRRHosts.map((host) => {
        return { email: host.user.email, userId: host.user.id, createdAt: host.createdAt };
      }),
      allRRHostsBookingsOfInterval,
      allRRHostsCreatedInInterval,
      oooData
    });
    let totalWeight;
    if (attributeWeights && attributeWeights.length > 0) {
      totalWeight = attributeWeights.reduce((totalWeight2, userWeight) => {
        totalWeight2 += userWeight.weight ?? 100;
        return totalWeight2;
      }, 0);
    } else {
      totalWeight = allRRHosts.reduce((totalWeight2, host) => {
        totalWeight2 += host.weight ?? 100;
        return totalWeight2;
      }, 0);
    }
    const totalCalibration = allHostsWithCalibration.reduce((totalCalibration2, host) => {
      totalCalibration2 += host.calibration;
      return totalCalibration2;
    }, 0);
    const usersWithBookingShortfalls = availableUsers.map((user) => {
      let userWeight = user.weight ?? 100;
      if (attributeWeights) {
        userWeight = attributeWeights.find((userWeight2) => userWeight2.userId === user.id)?.weight ?? 100;
      }
      const targetPercentage = userWeight / totalWeight;
      const userBookings = bookingsOfAvailableUsersOfInterval.filter(
        (booking) => booking.userId === user.id || booking.attendees.some((attendee) => attendee.email === user.email)
      );
      const targetNumberOfBookings = (allBookings.length + totalCalibration) * targetPercentage;
      const userCalibration = allHostsWithCalibration.find((host) => host.userId === user.id)?.calibration ?? 0;
      const bookingShortfall = targetNumberOfBookings - (userBookings.length + userCalibration);
      return {
        ...user,
        calibration: userCalibration,
        weight: userWeight,
        targetNumberOfBookings,
        bookingShortfall,
        numBookings: userBookings.length
      };
    });
    const maxShortfall = Math.max(...usersWithBookingShortfalls.map((user) => user.bookingShortfall));
    const usersWithMaxShortfall = usersWithBookingShortfalls.filter(
      (user) => user.bookingShortfall === maxShortfall
    );
    const maxWeight = Math.max(...usersWithMaxShortfall.map((user) => user.weight ?? 100));
    const userIdsWithMaxShortfallAndWeight = new Set(
      usersWithMaxShortfall.filter((user) => {
        const weight = user.weight ?? 100;
        return weight === maxWeight;
      }).map((user) => user.id)
    );
    const remainingUsersAfterWeightFilter = availableUsers.filter(
      (user) => userIdsWithMaxShortfallAndWeight.has(user.id)
    );
    log.debug(
      "filterUsersBasedOnWeights",
      index.safeStringify({
        userIdsWithMaxShortfallAndWeight,
        usersWithMaxShortfall: usersWithMaxShortfall.map((user) => user.email),
        usersWithBookingShortfalls: usersWithBookingShortfalls.map((user) => ({
          calibration: user.calibration,
          bookingShortfall: user.bookingShortfall,
          email: user.email,
          targetNumberOfBookings: user.targetNumberOfBookings,
          weight: user.weight,
          numBookings: user.numBookings
        })),
        remainingUsersAfterWeightFilter: remainingUsersAfterWeightFilter.map((user) => user.email)
      })
    );
    if (!isNonEmptyArray(remainingUsersAfterWeightFilter)) {
      throw new Error("Internal Error: Weight filter should never return length=0.");
    }
    return {
      remainingUsersAfterWeightFilter,
      usersAndTheirBookingShortfalls: usersWithBookingShortfalls.map((user) => ({
        id: user.id,
        calibration: user.calibration,
        bookingShortfall: user.bookingShortfall,
        weight: user.weight
      }))
    };
  }
  getAverageAttributeWeights(allRRHosts, attributesQueryValueChild, attributeWithWeights) {
    let averageWeightsHosts = [];
    const fieldValueArray = Object.values(attributesQueryValueChild).map((child) => ({
      field: child.properties?.field,
      value: child.properties?.value
    }));
    fieldValueArray.map((obj) => {
      const attributeId = obj.field;
      const allRRHostsWeights = /* @__PURE__ */ new Map();
      if (attributeId === attributeWithWeights.id) {
        obj.value.forEach((arrayobj) => {
          arrayobj.forEach((attributeOption) => {
            const attributeOptionWithUsers = attributeWithWeights.options.find(
              (option) => option.value.toLowerCase() === attributeOption.toLowerCase()
            );
            allRRHosts.forEach((rrHost) => {
              const assignedUser = attributeOptionWithUsers?.assignedUsers.find(
                (assignedUser2) => rrHost.user.id === assignedUser2.member.userId
              );
              if (allRRHostsWeights.has(rrHost.user.id)) {
                allRRHostsWeights.get(rrHost.user.id)?.push(assignedUser?.weight ?? rrHost.weight ?? 100);
              } else {
                allRRHostsWeights.set(rrHost.user.id, [assignedUser?.weight ?? rrHost.weight ?? 100]);
              }
            });
          });
        });
        averageWeightsHosts = Array.from(allRRHostsWeights.entries()).map(([userId, weights]) => {
          const totalWeight = weights.reduce((acc, weight) => acc + weight, 0);
          const averageWeight = totalWeight / weights.length;
          return {
            userId,
            weight: averageWeight
          };
        });
      }
    });
    log.debug(
      "getAverageAttributeWeights",
      index.safeStringify({ allRRHosts, attributesQueryValueChild, attributeWithWeights, averageWeightsHosts })
    );
    return averageWeightsHosts;
  }
  getAttributesForVirtualQueues(response, attributesQueryValueChild, attributeWithWeights) {
    let selectionOptions;
    const fieldValueArray = Object.values(attributesQueryValueChild).map((child) => ({
      field: child.properties?.field,
      value: child.properties?.value
    }));
    fieldValueArray.some((obj) => {
      const attributeId = obj.field;
      if (attributeId === attributeWithWeights.id) {
        obj.value.some((arrayobj) => {
          arrayobj.some((attributeOptionId) => {
            const content = attributeOptionId.slice(1, -1);
            const routingFormFieldId = content.includes("field:") ? content.split("field:")[1] : null;
            if (routingFormFieldId) {
              const fieldResponse = response[routingFormFieldId];
              selectionOptions = { fieldId: routingFormFieldId, selectedOptionIds: fieldResponse.value };
              return true;
            }
          });
        });
      }
    });
    return selectionOptions;
  }
  async getQueueAndAttributeWeightData(allRRHosts, routingFormResponse, attributeWithWeights) {
    let averageWeightsHosts = [];
    const chosenRouteId = routingFormResponse?.chosenRouteId ?? void 0;
    if (!chosenRouteId)
      return;
    let fieldOptionData;
    const routingForm = routingFormResponse?.form;
    if (routingForm && routingFormResponse) {
      const response = routingFormResponse.response;
      const routes = zod.zodRoutes.parse(routingForm.routes);
      const chosenRoute = routes?.find((route) => route.id === routingFormResponse.chosenRouteId);
      if (chosenRoute && "attributesQueryValue" in chosenRoute) {
        const parsedAttributesQueryValue = index.raqbQueryValueSchema.parse(chosenRoute.attributesQueryValue);
        const attributesQueryValueWithLabel = getAttributesQueryValue({
          attributesQueryValue: chosenRoute.attributesQueryValue,
          attributes: [attributeWithWeights],
          dynamicFieldValueOperands: {
            fields: routingFormResponse.form.fields || [],
            response
          }
        });
        const parsedAttributesQueryValueWithLabel = index.raqbQueryValueSchema.parse(attributesQueryValueWithLabel);
        if (parsedAttributesQueryValueWithLabel && parsedAttributesQueryValueWithLabel.children1) {
          averageWeightsHosts = this.getAverageAttributeWeights(
            allRRHosts,
            parsedAttributesQueryValueWithLabel.children1,
            attributeWithWeights
          );
        }
        if (parsedAttributesQueryValue && parsedAttributesQueryValue.children1) {
          fieldOptionData = this.getAttributesForVirtualQueues(
            response,
            parsedAttributesQueryValue.children1,
            attributeWithWeights
          );
        }
      }
    }
    if (fieldOptionData) {
      return { averageWeightsHosts, virtualQueuesData: { chosenRouteId, fieldOptionData } };
    }
    return;
  }
  async getCalendarBusyTimesOfInterval(usersWithCredentials, interval, rrTimestampBasis, meetingStartTime) {
    const usersBusyTimesQuery = await Promise.all(
      usersWithCredentials.map(
        (user) => isBookingLimits.getBusyCalendarTimes(
          user.credentials,
          getIntervalStartDate({ interval, rrTimestampBasis, meetingStartTime }).toISOString(),
          getIntervalEndDate({ interval, rrTimestampBasis, meetingStartTime }).toISOString(),
          user.userLevelSelectedCalendars,
          true,
          true
        )
      )
    );
    return usersBusyTimesQuery.reduce((usersBusyTime, userBusyTimeQuery, index2) => {
      if (userBusyTimeQuery.success) {
        usersBusyTime.push({
          userId: usersWithCredentials[index2].id,
          busyTimes: userBusyTimeQuery.data
        });
      }
      return usersBusyTime;
    }, []);
  }
  async getBookingsOfInterval({
    eventTypeId,
    users,
    virtualQueuesData,
    interval,
    includeNoShowInRRCalculation,
    rrTimestampBasis,
    meetingStartTime
  }) {
    return await this.bookingRepository.getAllBookingsForRoundRobin({
      eventTypeId,
      users,
      startDate: getIntervalStartDate({ interval, rrTimestampBasis, meetingStartTime }),
      endDate: getIntervalEndDate({ interval, rrTimestampBasis, meetingStartTime }),
      virtualQueuesData,
      includeNoShowInRRCalculation,
      rrTimestampBasis
    });
  }
  async fetchAllDataNeededForCalculations(getLuckyUserParams) {
    const startTime = performance.now();
    const { availableUsers, allRRHosts, eventType, meetingStartTime } = getLuckyUserParams;
    const notAvailableHosts = function getNotAvailableHosts() {
      const availableUserIds = new Set(availableUsers.map((user) => user.id));
      return allRRHosts.reduce(
        (acc, host) => {
          if (!availableUserIds.has(host.user.id)) {
            acc.push({
              id: host.user.id,
              email: host.user.email
            });
          }
          return acc;
        },
        []
      );
    }();
    const { attributeWeights, virtualQueuesData } = await this.prepareQueuesAndAttributesData(
      getLuckyUserParams
    );
    const interval = eventType.isRRWeightsEnabled && getLuckyUserParams.eventType.team?.rrResetInterval ? getLuckyUserParams.eventType.team?.rrResetInterval : index.RRResetInterval.MONTH;
    const rrTimestampBasis = eventType.isRRWeightsEnabled && getLuckyUserParams.eventType.team?.rrTimestampBasis ? getLuckyUserParams.eventType.team.rrTimestampBasis : index.RRTimestampBasis.CREATED_AT;
    const intervalStartDate = getIntervalStartDate({ interval, rrTimestampBasis, meetingStartTime });
    const intervalEndDate = getIntervalEndDate({ interval, rrTimestampBasis, meetingStartTime });
    const [
      userBusyTimesOfInterval,
      bookingsOfAvailableUsersOfInterval,
      bookingsOfNotAvailableUsersOfInterval,
      allRRHostsBookingsOfInterval,
      allRRHostsCreatedInInterval,
      organizersWithLastCreated
    ] = await Promise.all([
      this.getCalendarBusyTimesOfInterval(
        allRRHosts.map((host) => host.user),
        interval,
        rrTimestampBasis,
        meetingStartTime
      ),
      this.getBookingsOfInterval({
        eventTypeId: eventType.id,
        users: availableUsers.map((user) => {
          return { id: user.id, email: user.email };
        }),
        virtualQueuesData: virtualQueuesData ?? null,
        interval,
        includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation,
        rrTimestampBasis,
        meetingStartTime
      }),
      this.getBookingsOfInterval({
        eventTypeId: eventType.id,
        users: notAvailableHosts,
        virtualQueuesData: virtualQueuesData ?? null,
        interval,
        includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation,
        rrTimestampBasis,
        meetingStartTime
      }),
      this.getBookingsOfInterval({
        eventTypeId: eventType.id,
        users: allRRHosts.map((host) => {
          return { id: host.user.id, email: host.user.email };
        }),
        virtualQueuesData: virtualQueuesData ?? null,
        interval,
        includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation,
        rrTimestampBasis,
        meetingStartTime
      }),
      this.hostRepository.findHostsCreatedInInterval({
        eventTypeId: eventType.id,
        userIds: allRRHosts.map((host) => host.user.id),
        startDate: intervalStartDate
      }),
      this.userRepository.findUsersWithLastBooking({
        userIds: availableUsers.map((user) => user.id),
        eventTypeId: eventType.id
      })
    ]);
    const userFullDayBusyTimes = /* @__PURE__ */ new Map();
    userBusyTimesOfInterval.forEach((userBusyTime) => {
      const fullDayBusyTimes = userBusyTime.busyTimes.filter((busyTime) => {
        if (!busyTime.timeZone)
          return false;
        const timezoneOffset = dayjs(busyTime.start).tz(busyTime.timeZone).utcOffset() * 6e4;
        let start = new Date(new Date(busyTime.start).getTime() + timezoneOffset);
        const end = new Date(new Date(busyTime.end).getTime() + timezoneOffset);
        const earliestStartTime = new Date(Date.UTC((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1));
        if (start < earliestStartTime)
          start = earliestStartTime;
        return end.getTime() < (/* @__PURE__ */ new Date()).getTime() && isFullDayEvent(start, end);
      }).map((busyTime) => ({ start: new Date(busyTime.start), end: new Date(busyTime.end) }));
      userFullDayBusyTimes.set(userBusyTime.userId, fullDayBusyTimes);
    });
    const oooEntries = await this.oooRepository.findOOOEntriesInInterval({
      userIds: allRRHosts.map((host) => host.user.id),
      startDate: intervalStartDate,
      endDate: intervalEndDate
    });
    const oooEntriesGroupedByUserId = /* @__PURE__ */ new Map();
    oooEntries.forEach((entry) => {
      if (!oooEntriesGroupedByUserId.has(entry.userId)) {
        oooEntriesGroupedByUserId.set(entry.userId, []);
      }
      oooEntriesGroupedByUserId.get(entry.userId).push({ start: entry.start, end: entry.end });
    });
    const oooData = [];
    userFullDayBusyTimes.forEach((fullDayBusyTimes, userId) => {
      const oooEntriesForUser = oooEntriesGroupedByUserId.get(userId) || [];
      const combinedEntries = [...oooEntriesForUser, ...fullDayBusyTimes];
      const oooEntries2 = dateRanges.mergeOverlappingRanges(combinedEntries);
      oooData.push({
        userId,
        oooEntries: oooEntries2
      });
    });
    const endTime = performance.now();
    log.info(`fetchAllDataNeededForCalculations took ${endTime - startTime}ms`);
    log.debug(
      "fetchAllDataNeededForCalculations",
      index.safeStringify({
        bookingsOfAvailableUsersOfInterval: bookingsOfAvailableUsersOfInterval.length,
        bookingsOfNotAvailableUsersOfInterval: bookingsOfNotAvailableUsersOfInterval.length,
        allRRHostsBookingsOfInterval: allRRHostsBookingsOfInterval.length,
        allRRHostsCreatedInInterval: allRRHostsCreatedInInterval.length,
        virtualQueuesData,
        attributeWeights,
        oooData
      })
    );
    return {
      bookingsOfAvailableUsersOfInterval,
      bookingsOfNotAvailableUsersOfInterval,
      allRRHostsBookingsOfInterval,
      allRRHostsCreatedInInterval,
      organizersWithLastCreated,
      attributeWeights,
      virtualQueuesData,
      oooData
    };
  }
  // === Public methods exposed by the service ===
  async getLuckyUser(getLuckyUserParams) {
    const fetchedData = await this.fetchAllDataNeededForCalculations(getLuckyUserParams);
    const { luckyUser } = this.getLuckyUser_requiresDataToBePreFetched({
      ...getLuckyUserParams,
      ...fetchedData
    });
    return luckyUser;
  }
  getLuckyUser_requiresDataToBePreFetched({ availableUsers, ...getLuckyUserParams }) {
    const {
      eventType,
      bookingsOfAvailableUsersOfInterval,
      bookingsOfNotAvailableUsersOfInterval,
      allRRHostsBookingsOfInterval,
      allRRHostsCreatedInInterval,
      organizersWithLastCreated,
      oooData
    } = getLuckyUserParams;
    if (availableUsers.length === 1) {
      return { luckyUser: availableUsers[0], usersAndTheirBookingShortfalls: [] };
    }
    let usersAndTheirBookingShortfalls = [];
    if (eventType.isRRWeightsEnabled) {
      const {
        remainingUsersAfterWeightFilter,
        usersAndTheirBookingShortfalls: _usersAndTheirBookingShortfalls
      } = this.filterUsersBasedOnWeights({
        ...getLuckyUserParams,
        availableUsers,
        bookingsOfAvailableUsersOfInterval,
        bookingsOfNotAvailableUsersOfInterval,
        allRRHostsBookingsOfInterval,
        allRRHostsCreatedInInterval,
        oooData
      });
      availableUsers = remainingUsersAfterWeightFilter;
      usersAndTheirBookingShortfalls = _usersAndTheirBookingShortfalls;
    }
    const highestPriorityUsers = this.getUsersWithHighestPriority({ availableUsers });
    if (highestPriorityUsers.length === 1) {
      return {
        luckyUser: highestPriorityUsers[0],
        usersAndTheirBookingShortfalls
      };
    }
    return {
      luckyUser: this.leastRecentlyBookedUser({
        ...getLuckyUserParams,
        availableUsers: highestPriorityUsers,
        bookingsOfAvailableUsers: bookingsOfAvailableUsersOfInterval,
        organizersWithLastCreated
      }),
      usersAndTheirBookingShortfalls
    };
  }
  async prepareQueuesAndAttributesData({
    eventType,
    routingFormResponse,
    allRRHosts
  }) {
    let attributeWeights;
    let virtualQueuesData;
    const organizationId = eventType.team?.parentId;
    log.debug("prepareQueuesAndAttributesData", index.safeStringify({ routingFormResponse, organizationId }));
    if (routingFormResponse && organizationId) {
      const routingForm = routingFormResponse?.form;
      const routes = zod.zodRoutes.parse(routingForm.routes);
      const chosenRoute = routes?.find((route) => route.id === routingFormResponse.chosenRouteId);
      if (chosenRoute && "attributeIdForWeights" in chosenRoute) {
        const attributeIdForWeights = chosenRoute.attributeIdForWeights;
        if (attributeIdForWeights) {
          const attributeWithEnabledWeights = await this.attributeRepository.findUniqueWithWeights({
            teamId: organizationId,
            attributeId: attributeIdForWeights,
            isWeightsEnabled: true
          });
          if (attributeWithEnabledWeights) {
            const queueAndAtributeWeightData = await this.getQueueAndAttributeWeightData(
              allRRHosts,
              routingFormResponse,
              attributeWithEnabledWeights
            );
            if (queueAndAtributeWeightData?.averageWeightsHosts && queueAndAtributeWeightData?.virtualQueuesData) {
              attributeWeights = queueAndAtributeWeightData?.averageWeightsHosts;
              virtualQueuesData = queueAndAtributeWeightData?.virtualQueuesData;
            }
          }
        }
      }
    }
    return { attributeWeights, virtualQueuesData };
  }
  async getOrderedListOfLuckyUsers(getLuckyUserParams) {
    const { availableUsers, eventType } = getLuckyUserParams;
    const {
      bookingsOfAvailableUsersOfInterval,
      bookingsOfNotAvailableUsersOfInterval,
      allRRHostsBookingsOfInterval,
      allRRHostsCreatedInInterval,
      organizersWithLastCreated,
      attributeWeights,
      virtualQueuesData,
      oooData
    } = await this.fetchAllDataNeededForCalculations(getLuckyUserParams);
    log.info(
      "getOrderedListOfLuckyUsers",
      index.safeStringify({
        availableUsers: availableUsers.map((user) => {
          return { id: user.id, email: user.email, priority: user.priority, weight: user.weight };
        }),
        bookingsOfAvailableUsersOfInterval,
        bookingsOfNotAvailableUsersOfInterval,
        allRRHostsBookingsOfInterval,
        allRRHostsCreatedInInterval,
        organizersWithLastCreated
      })
    );
    let remainingAvailableUsers = [...availableUsers];
    let bookingsOfRemainingAvailableUsersOfInterval = [...bookingsOfAvailableUsersOfInterval];
    const orderedUsersSet = /* @__PURE__ */ new Set();
    const perUserBookingsCount = {};
    const startTime = performance.now();
    let usersAndTheirBookingShortfalls = [];
    while (remainingAvailableUsers.length > 0) {
      const { luckyUser, usersAndTheirBookingShortfalls: _usersAndTheirBookingShortfalls } = this.getLuckyUser_requiresDataToBePreFetched({
        ...getLuckyUserParams,
        eventType,
        availableUsers: remainingAvailableUsers,
        bookingsOfAvailableUsersOfInterval: bookingsOfRemainingAvailableUsersOfInterval,
        bookingsOfNotAvailableUsersOfInterval,
        allRRHostsBookingsOfInterval,
        allRRHostsCreatedInInterval,
        organizersWithLastCreated,
        attributeWeights,
        virtualQueuesData,
        oooData
      });
      if (!usersAndTheirBookingShortfalls.length) {
        usersAndTheirBookingShortfalls = _usersAndTheirBookingShortfalls;
      }
      if (orderedUsersSet.has(luckyUser)) {
        throw new Error(
          `Error building ordered list of lucky users. The lucky user ${luckyUser.email} is already in the set.`
        );
      }
      orderedUsersSet.add(luckyUser);
      perUserBookingsCount[luckyUser.id] = bookingsOfAvailableUsersOfInterval.filter(
        (booking) => booking.userId === luckyUser.id
      ).length;
      remainingAvailableUsers = remainingAvailableUsers.filter((user) => user.id !== luckyUser.id);
      bookingsOfRemainingAvailableUsersOfInterval = bookingsOfRemainingAvailableUsersOfInterval.filter(
        (booking) => remainingAvailableUsers.map((user) => user.id).includes(booking.userId ?? 0)
      );
    }
    const endTime = performance.now();
    log.info(`getOrderedListOfLuckyUsers took ${endTime - startTime}ms`);
    const bookingShortfalls = {};
    const calibrations = {};
    const weights = {};
    usersAndTheirBookingShortfalls.forEach((user) => {
      bookingShortfalls[user.id] = parseFloat(user.bookingShortfall.toFixed(2));
      calibrations[user.id] = parseFloat(user.calibration.toFixed(2));
      weights[user.id] = user.weight;
    });
    return {
      users: Array.from(orderedUsersSet),
      isUsingAttributeWeights: !!attributeWeights && !!virtualQueuesData,
      perUserData: {
        bookingsCount: perUserBookingsCount,
        bookingShortfalls: eventType.isRRWeightsEnabled ? bookingShortfalls : null,
        calibrations: eventType.isRRWeightsEnabled ? calibrations : null,
        weights: eventType.isRRWeightsEnabled ? weights : null
      }
    };
  }
}
exports.LuckyUserService = LuckyUserService;
exports.acrossQueryValueCompatiblity = acrossQueryValueCompatiblity;
exports.areSelectOptionsInLegacyFormat = areSelectOptionsInLegacyFormat;
exports.getFieldWithOptions = getFieldWithOptions;
exports.getQueryBuilderConfigForFormFields = getQueryBuilderConfigForFormFields;
exports.raqbQueryValueUtils = raqbQueryValueUtils;
