"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const mapper_1 = require("../../mapper");
const condition_expression_builder_1 = require("./condition-expression-builder");
const attribute_names_function_1 = require("./functions/attribute-names.function");
const unique_attribute_value_name_function_1 = require("./functions/unique-attribute-value-name.function");
/**
 * Will create a condition which can be added to a request using the param object.
 * It will create the expression statement and the attribute names and values.
 *
 * @param {string} attributePath
 * @param {ConditionOperator} operation
 * @param {any[]} values Depending on the operation the amount of values differs
 * @param {string[]} existingValueNames If provided the existing names are used to make sure we have a unique name for the current attributePath
 * @param {Metadata<any>} metadata If provided we use the metadata to define the attribute name and use it to map the given value(s) to attributeValue(s)
 * @returns {Expression}
 */
function buildUpdateExpression(attributePath, operation, values, existingValueNames, metadata) {
    // metadata get rid of undefined values
    values = condition_expression_builder_1.deepFilter(values, value => value !== undefined);
    // TODO LOW:VALIDATION check if provided values are valid for given operation
    // validateValues(operation, values)
    // load property metadata if model metadata was provided
    let propertyMetadata;
    if (metadata) {
        propertyMetadata = metadata.forProperty(attributePath);
    }
    /*
     * resolve placeholder and valuePlaceholder names (same as attributePath if it not already exists)
     * myProp -> #myProp for name placeholder and :myProp for value placeholder
     *
     * person[0] -> #person: person
     * person.list[0].age -> #person: person, #attr: attr, #age: age
     * person.age
     */
    const resolvedAttributeNames = attribute_names_function_1.resolveAttributeNames(attributePath, propertyMetadata);
    const valuePlaceholder = unique_attribute_value_name_function_1.uniqAttributeValueName(attributePath, existingValueNames);
    /*
     * build the statement
     */
    // const buildFilterFn: any = curryRight()
    return buildDefaultExpression(attributePath, resolvedAttributeNames.placeholder, valuePlaceholder, resolvedAttributeNames.attributeNames, values, existingValueNames, propertyMetadata, operation);
}
exports.buildUpdateExpression = buildUpdateExpression;
function buildDefaultExpression(attributePath, namePlaceholder, valuePlaceholder, attributeNames, values, existingValueNames, propertyMetadata, operator) {
    let statement;
    switch (operator.action) {
        case 'incrementBy':
            statement = `${namePlaceholder} = ${namePlaceholder} + ${valuePlaceholder}`;
            break;
        case 'decrementBy':
            statement = `${namePlaceholder} = ${namePlaceholder} - ${valuePlaceholder}`;
            break;
        case 'set':
            const ifNotExists = values.length > 1 && !!values[values.length - 1] || false;
            if (ifNotExists) {
                statement = `${namePlaceholder} = if_not_exists(${namePlaceholder}, ${valuePlaceholder})`;
            }
            else {
                statement = `${namePlaceholder} = ${valuePlaceholder}`;
            }
            break;
        case 'appendToList':
            const position = values.length > 1 ? values[values.length - 1] || 'END' : 'END';
            switch (position) {
                case 'END':
                    statement = `${namePlaceholder} = list_append(${namePlaceholder}, ${valuePlaceholder})`;
                    break;
                case 'START':
                    statement = `${namePlaceholder} = list_append(${valuePlaceholder}, ${namePlaceholder})`;
                    break;
                default:
                    throw new Error("make sure to provide either 'START' or 'END' as value for position argument");
            }
            break;
        case 'remove':
            statement = `${namePlaceholder}`;
            break;
        case 'removeFromListAt':
            statement = values.map(pos => `${namePlaceholder}[${pos}]`).join(', ');
            break;
        case 'add':
            // TODO LOW:VALIDATION add validation to make sure expressionAttributeValue to be N(umber) or S(et)
            statement = `${namePlaceholder} ${valuePlaceholder}`;
            // TODO LOW:VALIDATION won't work for numbers, is always gonna be mapped to a collection type
            if ((values.length === 1 && Array.isArray(values[0])) || mapper_1.isSet(values[0])) {
                // dealing with arr | set as single argument
            }
            else {
                // dealing with vararg
                values[0] = [...values];
            }
            break;
        case 'removeFromSet':
            // TODO LOW:VALIDATION add validation to make sure expressionAttributeValue to be S(et)
            statement = `${namePlaceholder} ${valuePlaceholder}`;
            if ((values.length === 1 && Array.isArray(values[0])) || mapper_1.isSet(values[0])) {
                // dealing with arr | set as single argument
            }
            else {
                // dealing with vararg
                values[0] = [...values];
            }
            break;
        default:
            throw new Error(`no implementation for action ${operator.action}`);
    }
    const hasValue = !isNoValueAction(operator.action);
    const attributes = {};
    if (hasValue) {
        const attribute = mapper_1.toDbOne(values[0], propertyMetadata);
        if (attribute) {
            attributes[valuePlaceholder] = attribute;
        }
    }
    return {
        type: operator.actionKeyword,
        statement,
        attributeNames,
        attributeValues: attributes,
    };
}
function isNoValueAction(action) {
    return (action === 'remove' ||
        // special cases: values are used in statement instead of expressionValues
        action === 'removeFromListAt');
}
//# sourceMappingURL=update-expression-builder.js.map