import { validateProps } from "../../src/types/utils";
import { UsedProperties, SegmentProperties, subConditionProperties, SegmentProfileCountProperties } from "../../src/types/segments";
import { PropertiesDefault, PropertiesPossible, PropertiesSegmentCondition } from "../../src/types/parameterValues";
import { segmentObjectProperties, parameterValuesObjectProperties, segmentCountObjectProperties } from "../../src/types/validation";
import { generateUuid, findAllIndicesOfCharacter, areArraysEqual, allSameType, getProfilePropertyComparisonOperators, getProfileSegmentMatchTypes } from "../utils/helpers";
import { sdkResponse } from "../../src/types/sdkResponse";

/**
 * @function validatePropertyName
 * @param {Array<any>} parameterValuesObject
 */
export function validatePropertyName(parameterValuesObject: PropertiesDefault): PropertiesDefault { // validate property name in subcondition

    if (!parameterValuesObject.propertyName.startsWith("properties.")) { // check if "properties." is missing
        parameterValuesObject.propertyName = "properties." + parameterValuesObject.propertyName; // add "properties." in front of property name
    }

    return parameterValuesObject; // return validated object
}

export function formatPropertyName(subCondition: subConditionProperties): subConditionProperties { // format property name in subcondition

    if (subCondition.parameterValues.propertyName.startsWith("properties.")) { // check if "properties." is not missing
        subCondition.parameterValues.propertyName = subCondition.parameterValues.propertyName.replace("properties.", ""); // remove it from property name
    }

    return subCondition; // return formatted subcondition
}

export function checkExpectedValue(propertyName: string): string { // check what types work with profile property in subcondition
    // note: CDP front-end sends all profile property values values as strings (except booleans), but some profile properties
    //       like "age" only work with integers so they have to be changed to the right type to be used in unomi segments

    let noPrefixProperties = [ // default profile properties
        "age", // integer
        "firstVisit", // date
        "lastVisit", // date
        "nbOfVisits", // integer
        "previousVisit" //date
    ];

    let words = propertyName.split("."); // split property name on dot

    // eg: properties.capture_properties.event.eventData.target.size.do__height
    // will become: [ 'properties', 'capture_properties', 'event', 'eventData', 'target', 'size', 'do__height' ]

    let property = words[words.length - 1]; // get property name

    let expectedType = "string"; // default expected type

    if (noPrefixProperties.includes(property)) { // check if profile property is a default property of an unomi profile

        switch (property) { // check which property it is

            // these properties have integer values
            case "age":
            case "nbOfVisits":
                expectedType = "number"; // expect integer/float value
                break;

            // these properties have date values
            case "firstVisit":
            case "lastVisit":
            case "previousVisit":
                expectedType = "string"; // expect string value
                break;
        }

    } else { // it's a profile property with a prefix
        let prefix = property.substring(0, 2); // get prefix in property name

        switch (prefix) { // check which prefix

            case "te": // text (saved as string in unomi)
            case "lt": // long text (saved as string in unomi)
            case "da": // date (saved as string in unomi)
            case "bo": // boolean (saved as string in unomi)
                expectedType = "string"; // expect string value
                break;

            case "lo": // long (saved as integer in unomi)
            case "do": // double (saved as float in unomi)
                expectedType = "number"; // expect integer/float value
                break;
        }
    }

    return expectedType; // return expected type for property
}

export function setExpectedValue(expectedType: string, propertyValue: any): any {

    if (typeof (propertyValue) === "object") { // check if array, since array is treated as object by typeof
        // note: CDP front-end sends all profile property values values as strings (except booleans), but some profile properties
        //       like "age" only work with integers so they have to be changed to the right type to be used in unomi segments

        // note for Number(): https://gomakethings.com/converting-strings-to-numbers-with-vanilla-javascript/

        if (allSameType(propertyValue)) { // check if all values in array are of the same type

            switch (expectedType) { // change type based on expected value check
                case "string":
                    for (let index = 0; index < propertyValue.length; index++) { // go through array
                        propertyValue[index] = propertyValue[index].toString(); // convert all values to string
                    }
                    break;
                case "number":
                    for (let index = 0; index < propertyValue.length; index++) { // go through array
                        propertyValue[index] = Number(propertyValue[index]); // convert all values to integer/float
                    }
                    break;
            }
        }

    } else { // value is not an array

        switch (expectedType) { // change type based on expected value check
            case "string":
                propertyValue = propertyValue.toString(); // convert value to string
                break;
            case "number":
                propertyValue = Number(propertyValue); // convert value to integer/float
                break;
        }
    }

    return propertyValue; // return property value in expected type
}

/**
 * @function validatePropertyValue
 * @param {PropertiesDefault} parameterValuesObject
 */
export function validatePropertyValue(parameterValuesObject: PropertiesDefault): parameterValuesObjectProperties { // validate property value in subcondition

    let isValidPropertyValueUsage = false; // default validation status

    let validatedParameterValuesObject = { // object for validated subcondition
        propertyName: parameterValuesObject.propertyName,
        comparisonOperator: parameterValuesObject.comparisonOperator,
    };

    let validationError = "";

    let indicesDate = [4, 7]; // indices for the '-' character in yyyy-mm-dd (works with ISO format too, eg: 2020-03-20T14:28:23.382748)

    let indices; // array for storing indeces

    let expectedType = checkExpectedValue(parameterValuesObject.propertyName); // get expected type for profile property in current subcondition

    parameterValuesObject.propertyValue = setExpectedValue(expectedType, parameterValuesObject.propertyValue); // convert property value to expected type

    switch (typeof (parameterValuesObject.propertyValue)) { // check type of given propertyValue
        case "boolean":

            Object.assign(validatedParameterValuesObject, { propertyValue: parameterValuesObject.propertyValue.toString() }); // add string property value to validated object

            isValidPropertyValueUsage = true; // set subcondition as valid

            break;

        case "number":

            Object.assign(validatedParameterValuesObject, { propertyValueInteger: parameterValuesObject.propertyValue }); // add integer/float property value to validated object

            isValidPropertyValueUsage = true; // set subcondition as valid

            break;

        case "string":

            indices = findAllIndicesOfCharacter(parameterValuesObject.propertyValue, "-"); // find all instances of "-" in property value

            if (areArraysEqual(indicesDate, indices)) { // if indeces in arrays are equal, then property value is a date

                Object.assign(validatedParameterValuesObject, { propertyValueDate: parameterValuesObject.propertyValue }); // add date property value to validated object

                isValidPropertyValueUsage = true; // set subcondition as valid
            }

            else {

                if (parameterValuesObject.propertyValue.toString().startsWith("dateExpr::")) { // if value starts with 'dateExpr::', then property is a date expression

                    let isValidDateExpr = isValidDateExpression(parameterValuesObject.propertyValue.replace("dateExpr::", ""));

                    if (isValidDateExpr) {
                        Object.assign(validatedParameterValuesObject, { propertyValueDateExpr: parameterValuesObject.propertyValue.replace("dateExpr::", "") }); // add date expression property value to validated object

                        isValidPropertyValueUsage = true; // set subcondition as valid
                    }

                    else {
                        isValidPropertyValueUsage = false; // set subcondition as invalid
                        validationError = "Date expression " + parameterValuesObject.propertyValue.replace("dateExpr::", "") + " is invalid."; // set error message
                    }

                }

                else {

                    Object.assign(validatedParameterValuesObject, { propertyValue: parameterValuesObject.propertyValue }); // add string property value to validated object

                    isValidPropertyValueUsage = true; // set subcondition as valid
                }

            }

            break;

        case "object":

            if (parameterValuesObject.propertyValue == undefined || parameterValuesObject.propertyValue == null) { // check if null or undefined, since null is treated as object by typeof
                isValidPropertyValueUsage = true; // set subcondition as valid
            }

            if (parameterValuesObject.propertyValue instanceof Array) { // check if array, since array is treated as object by typeof

                if (allSameType(parameterValuesObject.propertyValue)) { // check if all array values are of the same type

                    switch (typeof (parameterValuesObject.propertyValue[0])) { // check type of first element in array (rest of elements should be same value!)

                        case "number":

                            Object.assign(validatedParameterValuesObject, { propertyValuesInteger: parameterValuesObject.propertyValue }); // add integer/float property values to validated object

                            isValidPropertyValueUsage = true; // set subcondition as valid

                            break;

                        case "string":

                            indices = findAllIndicesOfCharacter(parameterValuesObject.propertyValue[0], "-"); // find all instances of "-" in property value

                            if (areArraysEqual(indicesDate, indices)) { // if indeces in arrays are equal, then property value is a date

                                Object.assign(validatedParameterValuesObject, { propertyValuesDate: parameterValuesObject.propertyValue }); // add date property value to validated object

                                isValidPropertyValueUsage = true; // set subcondition as valid
                            }

                            else {

                                if (parameterValuesObject.propertyValue[0].toString().startsWith("dateExpr::")) { // if value starts with 'dateExpr::', then property is a date expression

                                    let values = [];

                                    for (let index = 0; index < parameterValuesObject.propertyValue.length; index++) { // go through property values
                                        values.push(parameterValuesObject.propertyValue[index].replace("dateExpr::", "")); // remove 'dateExpr::' prefix from property value and add to new list
                                    }

                                    Object.assign(validatedParameterValuesObject, { propertyValuesDateExpr: values }); // add date expression property value to validated object

                                    isValidPropertyValueUsage = true; // set subcondition as valid
                                }

                                else {
                                    Object.assign(validatedParameterValuesObject, { propertyValues: parameterValuesObject.propertyValue }); // add string property value to validated object

                                    isValidPropertyValueUsage = true; // set subcondition as valid
                                }

                            }

                            break;
                    }
                }
            }

            break;
    }

    return { // return validated subcondition and validation status (valid or not valid)
        isValidPropertyValueUsage: isValidPropertyValueUsage,
        validatedParameterValuesObject: validatedParameterValuesObject,
        validationError: validationError
    };
}

/**
 * @function validateComparisonOperatorUsage
 * @param {PropertiesPossible} parameterValuesObject
 */
export function validateComparisonOperatorUsage(parameterValuesObject: PropertiesPossible): boolean { // validate comparison operator choice

    let isValidComparisonOperatorUsage = false; // default validation status

    const comparisonOperators = getProfilePropertyComparisonOperators(); // get an array of comparison operators

    const comparisonOperatorExists = comparisonOperators.includes(parameterValuesObject.comparisonOperator); // check if comparison operator in subcondition exists

    if (comparisonOperatorExists) { // check if comparison operator exists
        const parameterValuesObjectKeys = Object.keys(parameterValuesObject); // get an array with the names of the subcondition properties

        const propertyNameExists = parameterValuesObjectKeys.includes("propertyName"); // check if property name exists

        // check which property value keys exist
        const propertyValueExists = parameterValuesObjectKeys.includes("propertyValue");
        const propertyValueIntegerExists = parameterValuesObjectKeys.includes("propertyValueInteger");
        const propertyValueDateExists = parameterValuesObjectKeys.includes("propertyValueDate");
        const propertyValueDateExprExists = parameterValuesObjectKeys.includes("propertyValueDateExpr");
        const propertyValuesExists = parameterValuesObjectKeys.includes("propertyValues");
        const propertyValuesIntegerExists = parameterValuesObjectKeys.includes("propertyValuesInteger");
        const propertyValuesDateExists = parameterValuesObjectKeys.includes("propertyValuesDate");
        const propertyValuesDateExprExists = parameterValuesObjectKeys.includes("propertyValuesDateExpr");

        switch (parameterValuesObject.comparisonOperator) { // check which comparison operator

            // these comparisonOperators do not accept arrays as propertyValue
            case "equals":
            case "notEquals":
            case "greaterThan":
            case "greaterThanOrEqualTo":
            case "lessThan":
            case "lessThanOrEqualTo":
                if (propertyValueExists || propertyValueIntegerExists || propertyValueDateExists || propertyValueDateExprExists) {
                    isValidComparisonOperatorUsage = true; // set comparison operator choice as valid
                }

                break;

            // these comparisonOperators only accept a single string as propertyValue
            case "contains":
            case "notContains":
            case "startsWith":
            case "endsWith":
                if (propertyValueExists) {
                    isValidComparisonOperatorUsage = true; // set comparison operator choice as valid
                }

                break;

            // these comparisonOperators do not need a propertyValue
            case "exists":
            case "missing":
                if (parameterValuesObjectKeys.length === 2 && comparisonOperatorExists && propertyNameExists) {
                    isValidComparisonOperatorUsage = true; // set comparison operator choice as valid
                }

                break;

            // these comparisonOperators do not accept single values as propertyValue
            case "between":
            case "in":
            case "notIn":
            case "all":
            case "hasSomeOf":
            case "hasNoneOf":
                if (propertyValuesExists || propertyValuesIntegerExists || propertyValuesDateExists || propertyValuesDateExprExists) {
                    isValidComparisonOperatorUsage = true; // set comparison operator choice as valid
                }

                break;
        }
    }

    return isValidComparisonOperatorUsage; // return validation status of comparison operator
}

/**
 * @function validateMatchType
 * @param {PropertiesDefault} subConditionObject
 */
export function validateMatchType(subConditionObject: PropertiesDefault): boolean { // validate match type choice

    let isValidMatchType = false; // default validation status;

    const matchTypes = getProfileSegmentMatchTypes(); // get an array of match types

    const matchTypeExists = matchTypes.includes(subConditionObject.matchType); // check if match type in subcondition exists

    if (matchTypeExists) { // check if comparison operator exists
        isValidMatchType = true;
    }

    return isValidMatchType; // return validation status of comparison operator
}

/**
 * @function validateSegmentsProperty
 * @param {PropertiesDefault} subConditionObject
 */
export function validateSegmentsProperty(subConditionObject: PropertiesDefault): [boolean, Array<string>] { // validate segments property
    let isValidSegments = true; // default validation status;

    let errors = new Array; // array to store validation errors

    const regex = /[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}/ // regex to validate the segment uuid against: https://stackoverflow.com/a/18516125
    
    for (let index = 0; index < subConditionObject.segments.length; index++) { // validate every segment uuid

        const segment = subConditionObject.segments[index];

        if (!regex.test(segment)) { // if the segment uuid is not a valid UUID4
            isValidSegments = false; // segemnt uuid is not valid
            errors.push({
                value: "Segment " + segment + " is not a valid segment uuid. Segment uuids have to comply with the UUID4 specifications."
            })
        }
    }
    
    return [isValidSegments, errors]; // return validation status & errors
}

/**
 * @function validateSegmentObject
 * @param {UsedProperties} params
 * @param {boolean} segmentUpdate
 */
export function validateSegmentObject(params: UsedProperties, segmentUpdate: boolean): segmentObjectProperties { // validate segment
    let subConditions = new Array; // array for storing subconditions

    let id; // segment (uu)id

    let errors = new Array; // array to store errors

    let areSubConditionsValid = true; // default validation status

    for (let index = 0; index < params.subConditions.length; index++) { // go through subconditions

        const subCondition = params.subConditions[index]; // get current subcondition

        // right now we only support 2 conditionTypes: profilePropertyCondition & profileSegmentCondition
        if (subCondition.conditionType == "profilePropertyCondition") {

            const validationPropertyNameResponse = validatePropertyName(Object.assign({}, subCondition)); // use deepcopy of subCondition for rest of validation

            const validationPropertyValueResponse = validatePropertyValue(validationPropertyNameResponse); // validate property value of subcondition
    
            if (validationPropertyValueResponse.isValidPropertyValueUsage) { // check if valid property value
    
                const isValidComparisonOperatorUsage = validateComparisonOperatorUsage(validationPropertyValueResponse.validatedParameterValuesObject); // validate comparison operator usage in subcondition
    
                if (!isValidComparisonOperatorUsage) { // used propertyValue(s) and comparisonOperator combination is not valid
    
                    areSubConditionsValid = false; // set validation status as invalid
    
                    if (validationPropertyValueResponse.validationError == "") { // if no specific error
                        errors.push({ // add error to array
                            value: "comparisonOperator and propertyValue do not work together for property " + subCondition.propertyName
                        });
                    }
    
                    else { // if there is a specific error
                        errors.push({ // add error to array
                            value: validationPropertyValueResponse.validationError
                        });
                    }
                }
            }

            else { // used propertyValue(s) is not valid

                areSubConditionsValid = false; // set validation status as invalid
    
                if (validationPropertyValueResponse.validationError == "") { // if no specific error
                    errors.push({ // add error to array
                        value: "propertyValue is not allowed for property " + subCondition.propertyName
                    });
                }
    
                else { // if there is a specific error
                    errors.push({ // add error to array
                        value: validationPropertyValueResponse.validationError
                    });
                }
            }

            subConditions.push({ // add subcondition to array
                type: subCondition.conditionType,
                parameterValues: validationPropertyValueResponse.validatedParameterValuesObject
            });

        } else if (subCondition.conditionType == "profileSegmentCondition") {

            let validatedSubCondition = <PropertiesSegmentCondition>{};

            const isValidMatchType = validateMatchType(Object.assign({}, subCondition)); // use deepcopy of subCondition

            if (isValidMatchType) { // check if valid matchType
                validatedSubCondition.matchType = subCondition.matchType;
            } else { // used matchType is not valid
                areSubConditionsValid = false;
                errors.push({ // add error to array
                    value: "The matchType is not valid: " + subCondition.matchType
                });
            }

            const [isValidSegments, segmentvalidationerrors] = validateSegmentsProperty(Object.assign({}, subCondition)); // use deepcopy of subCondition

            if (isValidSegments) { // check if valid matchType
                validatedSubCondition.segments = subCondition.segments;
            } else { // used matchType is not valid
                areSubConditionsValid = false;
                errors.push({ // add error to array
                    value: "The segments are not valid: " + subCondition.segments
                });
                if (segmentvalidationerrors.length > 0) { // if there is at least 1 error
                    for (let index in segmentvalidationerrors) {
                        errors.push(segmentvalidationerrors[index]); // add validation errors to array
                    }
                }
            }

            subConditions.push({ // add subcondition to array
                type: subCondition.conditionType,
                parameterValues: validatedSubCondition
            });
        } else { // if there's a conditionType we don't support yet
            areSubConditionsValid = false;
            errors.push({
                value: "The selected condition type is not supported: " + subCondition.conditionType
            });
        }
    };

    // if not valid subconditions, then return the user's original subconditions so the mistake(s) can be seen and corrected
    if (!areSubConditionsValid) subConditions = params.subConditions;

    if (segmentUpdate) id = params.id; // check if segment update function
    else id = generateUuid(); // create segment function

    const segmentObject = { // segment structure
        metadata: {
            id: id,
            name: params.name,
            description: params.description,
            scope: params.scope,
            tags: params.tags,
            systemTags: params.systemTags
        },
        condition: {
            type: "booleanCondition",
            parameterValues: {
                operator: params.operator,
                subConditions: subConditions
            }
        }
    };

    const validatedSegmentObject = { // validated segment object structure
        segmentObject: segmentObject,
        errors: errors,
        areSubConditionsValid: areSubConditionsValid
    }

    return validatedSegmentObject; // return validated segment
}

/**
 * @function validateRequiredProps
 * @param {string[]} required
 * @param {{[key: string]: any}} props
 */
export function validateRequiredProps(required: string[], props: { [key: string]: any }): validateProps { // 

    let missing = [];

    for (const prop of required) {

        if (!Object.keys(props).includes(prop) || props[prop] === null || props[prop] === undefined) {

            missing.push(prop);
        }
    };

    return {
        valid: !missing.length,
        missing,
    };
}

export function formatSubConditions(subConditions: subConditionProperties[]): any { // format subconditions of a segment
    let formattedSubConditions = new Array; // array to store formatted subconditions

    for (let index = 0; index < subConditions.length; index++) { // go through all subconditions
        let subCondition = subConditions[index]; // get current subcondition

        if (subCondition.type == "booleanCondition") { // check if current subcondition is a nested subcondition
            let _formattedSubConditions = formatSubConditions(subCondition.parameterValues.subConditions); // summon this function again to loop over nested subconditions

            formattedSubConditions.push({ // add formatted nested subconditions to array
                "operator": subCondition.parameterValues.operator,
                "subConditions": _formattedSubConditions
            });
        }

        else { // current subcondition is a normal subcondition
            // right now we only support 2 conditionTypes: profilePropertyCondition & profileSegmentCondition
            if (subCondition.type === "profilePropertyCondition") {
                subCondition = formatPropertyName(subCondition); // format subcondition
                formattedSubConditions.push(formatProfilePropertySubCondition(subCondition)); // add formatted subcondition to array
            } else if (subCondition.type === "profileSegmentCondition") {
                formattedSubConditions.push(formatProfileSegmentSubCondition(subCondition)); // add formatted subcondition to array
            }
        }
    }

    return formattedSubConditions // return formatted subcondition
}

export function formatProfilePropertySubCondition(subCondition: subConditionProperties): any { // format subcondition of segment
    // note: CDP front-end expects all subcondition property values (except booleans) as type string,
    //       because it sends them as string too and the validation happens in the unomi-sdk

    const parameterValuesObjectKeys = Object.keys(subCondition.parameterValues); // get an array with the names of the subcondition properties

    // check which property value keys exist
    const propertyValueExists = parameterValuesObjectKeys.includes("propertyValue");
    const propertyValueIntegerExists = parameterValuesObjectKeys.includes("propertyValueInteger");
    const propertyValueDateExists = parameterValuesObjectKeys.includes("propertyValueDate");
    const propertyValueDateExprExists = parameterValuesObjectKeys.includes("propertyValueDateExpr");
    const propertyValuesExists = parameterValuesObjectKeys.includes("propertyValues");
    const propertyValuesIntegerExists = parameterValuesObjectKeys.includes("propertyValuesInteger");
    const propertyValuesDateExists = parameterValuesObjectKeys.includes("propertyValuesDate");
    const propertyValuesDateExprExists = parameterValuesObjectKeys.includes("propertyValuesDateExpr");

    let formattedSubCondition = { // object for formatted subcondition
        propertyName: subCondition.parameterValues.propertyName,
        comparisonOperator: subCondition.parameterValues.comparisonOperator,
        conditionType: "profilePropertyCondition"
    };

    if (propertyValueExists) { // check if string property value in subcondition
        if (subCondition.parameterValues.propertyValue === "true" || subCondition.parameterValues.propertyValue === "false") { // check if originally boolean
            subCondition.parameterValues.propertyValue = JSON.parse(subCondition.parameterValues.propertyValue.toLowerCase()); // convert to boolean
        }

        Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValue }); // add formatted property value
    }

    else if (propertyValueIntegerExists) { // check if integer/float property value in subcondition
        Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValueInteger.toString() }); // convert to string and add formatted property value
    }

    else if (propertyValueDateExists) { // check if date property value in subcondition
        Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValueDate.toString() }); // convert to string and add formatted property value
    }

    else if (propertyValueDateExprExists) { // check if date expression property value in subcondition
        Object.assign(formattedSubCondition, { propertyValue: "dateExpr::" + subCondition.parameterValues.propertyValueDateExpr.toString() }); // convert to string and add formatted property value
    }

    else if (propertyValuesExists) { // check if string property values in subcondition

        for (let index = 0; index < subCondition.parameterValues.propertyValues.length; index++) { // go through each element in property values
            if (subCondition.parameterValues.propertyValues[index] === "true" || subCondition.parameterValues.propertyValues[index] === "false") { // check if originally boolean
                subCondition.parameterValues.propertyValues[index] = JSON.parse(subCondition.parameterValues.propertyValues[index].toLowerCase()); // convert to boolean
            }

            else { // not originally boolean
                subCondition.parameterValues.propertyValues[index] = subCondition.parameterValues.propertyValues[index].toString(); // convert to string
            }
        }

        Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValues }); // add formatted property value
    }

    else if (propertyValuesIntegerExists) { // check if integer/float property values in subcondition

        for (let index = 0; index < subCondition.parameterValues.propertyValuesInteger.length; index++) { // go through each element in property values
            subCondition.parameterValues.propertyValuesInteger[index] = subCondition.parameterValues.propertyValuesInteger[index].toString(); // convert to string
        }

        Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValuesInteger }); // add formatted property value
    }

    else if (propertyValuesDateExists) { // check if date property values in subcondition

        for (let index = 0; index < subCondition.parameterValues.propertyValuesDate.length; index++) { // go through each element in property values
            subCondition.parameterValues.propertyValuesDate[index] = subCondition.parameterValues.propertyValuesDate[index].toString(); // convert to string
        }

        Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValuesDate }); // add formatted property value
    }

    else if (propertyValuesDateExprExists) { // check if date expression property values in subcondition

        for (let index = 0; index < subCondition.parameterValues.propertyValuesDateExpr.length; index++) { // go through each element in property values
            subCondition.parameterValues.propertyValuesDateExpr[index] = "dateExpr::" + subCondition.parameterValues.propertyValuesDateExpr[index].toString(); // convert to string
        }

        Object.assign(formattedSubCondition, { propertyValue: subCondition.parameterValues.propertyValuesDateExpr }); // add formatted property value | add dateExpr prefix so front-end knows it's a date expression and not a timestamp
    }

    else {
        Object.assign(formattedSubCondition, { propertyValue: null }); // add formatted property value
    }

    return formattedSubCondition // return formatted subcondition
}

export function formatProfileSegmentSubCondition(subCondition: subConditionProperties): any { // format profileSegmentCondition subcondition of segment

    let formattedSubCondition = { // object for formatted subcondition
        matchType: subCondition.parameterValues.matchType,
        segments: subCondition.parameterValues.segments,
        conditionType: "profileSegmentCondition"
    };

    return formattedSubCondition // return formatted subcondition

}

export function reformatSegment(sdkObject: sdkResponse, segment: SegmentProperties): sdkResponse { // reformat segment

    // Sometimes segments exist where the condition is different from what is by default expected
    // example of a normal segment condition:
    //     "condition": {
    //         "type": "booleanCondition",
    //         "parameterValues": {
    //             "operator": "or",
    //             "subConditions": [
    //                 {
    //                     "parameterValues": {
    //                         "matchType": "in",
    //                         "segments": ["9a3066ac-9259-42c9-b289-89d68fe12827"]
    //                     },
    //                     "type": "profileSegmentCondition"
    //                 },
    //                 {
    //                     "parameterValues": {
    //                         "propertyName": "properties.capture_properties.te__domain",
    //                         "comparisonOperator": "equals",
    //                         "propertyValue": "example.com"
    //                     },
    //                     "type": "profilePropertyCondition"
    //                 }
    //             ]
    //         }
    //     }
    // example of the different segment condition:
    //     "condition": {
    //     "parameterValues": {
    //         "propertyName": "properties.capture_properties.te__domain",
    //         "comparisonOperator": "equals",
    //         "propertyValue": "example.com"
    //     },
    //     "type": "profilePropertyCondition"
    // }
    // This happens when the condition had a profileSegmentCondition on a segment (let's say segment-001), but then that segment (segment-001) got delete
    // When that segment (segment-001) gets deleted, the profileSegmentCondition on that segment also gets deleted
    // then if after that, the original segment only has 1 subCondition left, then you get the 2nd format (even though that's not how you're supposed to make segment in Unomi, so seems like a Unomi bug somewhere, maybe)
    
    // so, just because it can happen, we need to be able to also show these segments in the frontend instead of getting an error during the formatting of the segment for the frontend
    
    // so we first check if either propertyName or matchType exists in the condition object, to see if it's a normal condition, or the specific case described above
    // if it's the 2nd case, then we build our own object for the condition, the way it is supposed to be, and then our object gets validated and sent to the frontend
    if ("propertyName" in segment.condition.parameterValues) {
        segment.condition = {
            "type": "booleanCondition",
            "parameterValues": {
                "operator": "and",
                "subConditions": [
                    {
                        "parameterValues": {
                            "propertyName": segment.condition.parameterValues.propertyName,
                            "propertyValue": segment.condition.parameterValues.propertyValue,
                            "comparisonOperator": segment.condition.parameterValues.comparisonOperator
                        },
                        "type": segment.condition.type
                    }
                ]
            }
        }
    }
    if ("matchType" in segment.condition.parameterValues) {
        segment.condition = {
            "type": "booleanCondition",
            "parameterValues": {
                "operator": "and",
                "subConditions": [
                    {
                        "parameterValues": {
                            "matchType": segment.condition.parameterValues.matchType,
                            "segments": segment.condition.parameterValues.segments,
                        },
                        "type": segment.condition.type
                    }
                ]
            }
        }
    }

    let formattedSubConditions = formatSubConditions(segment.condition.parameterValues.subConditions); // format subconditions in segment

    const segmentObject = { // segment object structure
        id: segment.metadata.id,
        name: segment.metadata.name,
        description: segment.metadata.description,
        scope: segment.metadata.scope,
        tags: segment.metadata.tags,
        systemTags: segment.metadata.systemTags,
        operator: segment.condition.parameterValues.operator,
        subConditions: formattedSubConditions
    };

    sdkObject.responseData = segmentObject; // set object as response data

    return sdkObject; // return unomi-sdk response object
}

/**
 * @function isValidDateExpression
 * @param {string} dateExpr
 */
export function isValidDateExpression(dateExpr: string): boolean { // validate the date expression with the regex

    // examples of what date expressions this regex allows
    // * now+1y
    // * now-22M
    // * now
    // * now+1y/y
    // * now+2d+1w
    // * now/d
    // * now+1y+2M+3w+4d+5H+6m+7s
    // * nowww
    // * now+1y
    // * now/d
    //
    // examples of what the regex does NOT allow
    // * now+7d/d+89w/M/H/s
    // * now+7lekkerkoek
    // * nowww
    // * no

    const regex = /^now(|\/[yMwdhHms]|([+-][0-9]+[yMwdhHms](\/[yMwdhHms])?)+)$/;    // regex to check valid date expressions

    const found = dateExpr.match(regex);    // check if the dateExpr meets the regex conditions (null if it doesn't, a list with the groups if it does)

    var isValid = false;    // default value

    if (found != null) {
        isValid = true; // if the dateExpr meets the regex conditions, then it is a valid date expression
    }

    return isValid  // return whether the dateExpr is valid or not
}

/**
 * @function validateSegmentCountObject
 * @param {SegmentProfileCountProperties} params
 */
export function validateSegmentCountObject(params: SegmentProfileCountProperties): segmentCountObjectProperties { // validate segment count input
    let segments = new Array; // array for storing validated segments

    let validatedOperator;  // to store the validated operator in the end

    let errors = new Array; // array to store validation errors

    let isValid = true; // valid by default

    const operator = params.operator;   // get the operator from the object to validate

    const regex = /[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}/    // regex to validate the segment uuid against: https://stackoverflow.com/a/18516125

    for (let index = 0; index < params.segments.length; index++) {  // validate every segment uuid
        const segment = params.segments[index];
        if (regex.test(segment)) { // if the segment uuid is a valid UUID4
            segments.push(segment);
        } else { // if the segment uuid is not a valid UUID4
            isValid = false;
            errors.push({
                value: "Segment " + segment + " is not a valid segment uuid. Segment uuids have to comply with the UUID4 specifications."
            });
        }
    }

    if (operator.toUpperCase() === "OR" || operator.toUpperCase() === "AND") {  // validate the operator, options we support are 'OR' and 'AND'
        validatedOperator = operator.toUpperCase();
    } else {
        isValid = false;
        errors.push({
            value: "Operator " + operator + " is not a valid operator. Valid operators are 'OR' & 'AND'."
        });
    }

    const validatedSegmentCountObject = { // validated segment count object structure
        segments: segments,
        operator: validatedOperator,
        isValid: isValid,
        errors: errors
    }

    return validatedSegmentCountObject; // return validated object
}