"use strict";
const TOKEN_MAP = {
    EQUALS: "===",
    NOT_EQUALS: "!==",
    LT: "<",
    LTE: "<=",
    MT: ">",
    MTE: ">=",
    CONTAINS: "includes",
    AND: "&&",
    OR: "||",
};
const CONDITIONS = {
    EMPTY: "EMPTY",
    NOT_EMPTY: "NOT_EMPTY",
    CONTAINS: "CONTAINS",
};
const isEmptyExpression = key => {
    return `(
      doc["${key}"] === undefined ||
      doc["${key}"] === null ||
      doc["${key}"] === "" ||
      (Array.isArray(doc["${key}"]) && doc["${key}"].length === 0)
  )`;
};
const GROUP_PROPERTY = {
    group: {
        type: "string",
    },
};
const FIELD_PROPERTY = {
    field: {
        type: "string",
    },
};
const SCHEMA_MAP = {
    sum: {
        field: "string",
        value: "number",
    },
    count: {
        field: "string",
        value: "number",
    },
    stats: {
        sum: {
            type: "number",
        },
        min: {
            type: "number",
        },
        max: {
            type: "number",
        },
        count: {
            type: "number",
        },
        sumsqr: {
            type: "number",
        },
        avg: {
            type: "number",
        },
    },
};
/**
 * Iterates through the array of filters to create a JS
 * expression that gets used in a CouchDB view.
 * @param {Array} filters - an array of filter objects
 * @returns {String} JS Expression
 */
function parseFilterExpression(filters) {
    const expression = [];
    let first = true;
    for (let filter of filters) {
        if (!first && filter.conjunction) {
            expression.push(TOKEN_MAP[filter.conjunction]);
        }
        if (filter.condition === CONDITIONS.CONTAINS) {
            expression.push(`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")`);
        }
        else if (filter.condition === CONDITIONS.EMPTY) {
            expression.push(isEmptyExpression(filter.key));
        }
        else if (filter.condition === CONDITIONS.NOT_EMPTY) {
            expression.push(`!${isEmptyExpression(filter.key)}`);
        }
        else {
            const value = typeof filter.value == "string" ? `"${filter.value}"` : filter.value;
            expression.push(`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} ${value}`);
        }
        first = false;
    }
    return expression.join(" ");
}
/**
 * Returns a CouchDB compliant emit() expression that is used to emit the
 * correct key/value pairs for custom views.
 * @param {String?} field - field to use for calculations, if any
 * @param {String?} groupBy - field to group calculation results on, if any
 */
function parseEmitExpression(field, groupBy) {
    return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`;
}
/**
 * Return a fully parsed CouchDB compliant view definition
 * that will be stored in the design document in the database.
 *
 * @param {Object} viewDefinition - the JSON definition for a custom view.
 * field: field that calculations will be performed on
 * tableId: tableId of the table this view was created from
 * groupBy: field that calculations will be grouped by. Field must be present for this to be useful
 * filters: Array of filter objects containing predicates that are parsed into a JS expression
 * calculation: an optional calculation to be performed over the view data.
 */
function viewTemplate({ field, tableId, groupBy, filters = [], calculation }) {
    // first filter can't have a conjunction
    if (filters && filters.length > 0 && filters[0].conjunction) {
        delete filters[0].conjunction;
    }
    let schema = null, statFilter = null;
    if (calculation) {
        schema = Object.assign(Object.assign({}, (groupBy ? GROUP_PROPERTY : FIELD_PROPERTY)), SCHEMA_MAP[calculation]);
        if (!filters.find(filter => filter.key === field && filter.condition === CONDITIONS.NOT_EMPTY)) {
            statFilter = parseFilterExpression([
                { key: field, condition: CONDITIONS.NOT_EMPTY },
            ]);
        }
    }
    const parsedFilters = parseFilterExpression(filters);
    const filterExpression = parsedFilters ? `&& (${parsedFilters})` : "";
    const emitExpression = parseEmitExpression(field, groupBy);
    const tableExpression = `doc.tableId === "${tableId}"`;
    const coreExpression = statFilter
        ? `(${tableExpression} && ${statFilter})`
        : tableExpression;
    const reduction = field && calculation ? { reduce: `_${calculation}` } : {};
    return Object.assign({ meta: {
            field,
            tableId,
            groupBy,
            filters,
            schema,
            calculation,
        }, map: `function (doc) {
      if (${coreExpression} ${filterExpression}) {
        ${emitExpression}
      }
    }` }, reduction);
}
module.exports = viewTemplate;
