"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Ajv = require("ajv");
var ajvKeywords = require("ajv-keywords");
var metaSchema6 = require("ajv/lib/refs/json-schema-draft-06.json");
var cloneDeep = require("lodash/cloneDeep");
var defaults = require("lodash/defaults");
var every = require("lodash/every");
var findIndex = require("lodash/findIndex");
var findKey = require("lodash/findKey");
var isArray = require("lodash/isArray");
var isBoolean = require("lodash/isBoolean");
var map = require("lodash/map");
var pickBy = require("lodash/pickBy");
var reduce = require("lodash/reduce");
var startsWith = require("lodash/startsWith");
var trimStart = require("lodash/trimStart");
var utils = require("../../utils");
var DataTypes_1 = require("../DataTypes");
var ajv = new Ajv();
ajvKeywords(ajv, ['regexp', 'formatMaximum', 'formatMinimum']);
ajv.addMetaSchema(metaSchema6);
exports.FULL_TEXT_SLUG = 'full_text_search';
var DEFAULT_DELIMITER = '___';
exports.filter = function (filters, collection) {
    ajv.removeSchema(/^.*$/);
    var validators = isArray(filters)
        ? filters.map(function (s) { return ajv.compile(s); })
        : [ajv.compile(filters)];
    if (isArray(collection)) {
        return collection.filter(function (m) { return every(validators, function (v) { return v(m); }); });
    }
    return pickBy(collection, function (m) { return every(validators, function (v) { return v(m); }); });
};
exports.createFullTextSearchFilter = function (schema, term) {
    var stringKeys = reduce(schema.properties, function (carry, item, key) {
        if (item.type === 'string') {
            carry.push(key);
        }
        return carry;
    }, []);
    var filter = {
        $id: utils.randomString(),
        title: exports.FULL_TEXT_SLUG,
        anyOf: [
            {
                description: "Any field contains " + term,
                anyOf: stringKeys.map(function (key) {
                    return ({
                        properties: (_a = {},
                            _a[key] = {
                                type: 'string',
                                regexp: {
                                    pattern: utils.regexEscape(term),
                                    flags: 'i',
                                },
                            },
                            _a),
                        required: [key],
                    });
                    var _a;
                }),
            },
        ],
    };
    return filter;
};
exports.upsertFullTextSearch = function (schema, filters, term) {
    if (filters === void 0) { filters = []; }
    var searchFilter = exports.createFullTextSearchFilter(schema, term);
    var existingSearchIndex = findIndex(filters, { title: exports.FULL_TEXT_SLUG });
    if (existingSearchIndex > -1) {
        filters.splice(existingSearchIndex, 1, searchFilter);
    }
    else {
        filters.push(searchFilter);
    }
    return filters;
};
exports.removeFullTextSearch = function (filters) {
    return filters.filter(function (f) { return f.title !== exports.FULL_TEXT_SLUG; });
};
exports.createFilter = function (schema, signatures) {
    var anyOf = signatures.map(function (_a) {
        var field = _a.field, operator = _a.operator, value = _a.value;
        var subSchema = schema.properties[field];
        var model = DataTypes_1.getDataModel(subSchema);
        if (!model || !model.operators.hasOwnProperty(operator)) {
            return {};
        }
        return model.createFilter(field, operator, value, subSchema);
    });
    return {
        $id: utils.randomString(),
        anyOf: anyOf,
    };
};
exports.decodeFilter = function (schema, filter) {
    var signatures = filter.anyOf.map(function (f) {
        if (!f.properties &&
            (!f.anyOf || !f.anyOf.length || !f.anyOf[0].properties)) {
            return null;
        }
        var schemaField = f.anyOf
            ? Object.keys(f.anyOf[0].properties).shift()
            : Object.keys(f.properties).shift();
        var subSchema = schema.properties[schemaField];
        var model = DataTypes_1.getDataModel(subSchema);
        if (!model) {
            return {};
        }
        return model.decodeFilter(f);
    });
    return signatures.filter(function (s) { return s !== null; });
};
exports.getOperators = function (schema, field) {
    var subSchema = schema.properties[field];
    var model = DataTypes_1.getDataModel(subSchema);
    if (!model) {
        return [];
    }
    return map(model.operators, function (value, key) { return ({
        slug: key,
        label: value.getLabel(subSchema),
    }); });
};
var flattenAccumulator = function (schema, delimiter, parentKey, accumulator) {
    if (parentKey === void 0) { parentKey = ''; }
    if (!accumulator) {
        accumulator = cloneDeep(schema);
        if (schema.properties) {
            accumulator.properties = {};
        }
    }
    if (schema.anyOf && isArray(schema.anyOf)) {
        accumulator.anyOf = schema.anyOf.map(function (subSchema) {
            return flattenAccumulator(subSchema, delimiter, parentKey);
        });
    }
    return reduce(schema.properties, function (result, value, key) {
        if (isBoolean(value)) {
            return accumulator;
        }
        var newKey = parentKey + delimiter + key;
        if (value.type !== 'object' ||
            (findKey(value.properties, { description: 'key' }) &&
                findKey(value.properties, { description: 'value' }))) {
            if (parentKey) {
                result.properties[newKey] = defaults(value, { title: key });
                return result;
            }
            result.properties[key] = value;
            return result;
        }
        return flattenAccumulator(value, delimiter, newKey, result);
    }, accumulator);
};
exports.flattenSchema = function (schema, delimiter) {
    if (delimiter === void 0) { delimiter = DEFAULT_DELIMITER; }
    return flattenAccumulator(schema, delimiter);
};
exports.unflattenSchema = function (schema, delimiter) {
    if (delimiter === void 0) { delimiter = DEFAULT_DELIMITER; }
    var base = cloneDeep(schema);
    if (schema.properties) {
        base.properties = {};
    }
    var unflattenedSchema = reduce(schema.properties, function (result, value, field) {
        if (!startsWith(field, delimiter)) {
            result.properties[field] = value;
            return result;
        }
        var keys = trimStart(field, delimiter).split(delimiter);
        var lastKey = keys.pop();
        var head = result;
        for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {
            var key = keys_1[_i];
            if (!head.properties[key]) {
                head.properties[key] = {
                    type: 'object',
                    properties: {},
                };
            }
            head = head.properties[key];
        }
        head.properties[lastKey] = value;
        return result;
    }, base);
    if (unflattenedSchema.anyOf) {
        unflattenedSchema.anyOf = unflattenedSchema.anyOf.map(function (subSchema) {
            return exports.unflattenSchema(subSchema, delimiter);
        });
    }
    if (unflattenedSchema.required) {
        var requiredFields = unflattenedSchema.required;
        unflattenedSchema.required = [];
        requiredFields.forEach(function (requiredField) {
            var fields = trimStart(requiredField, delimiter).split(delimiter);
            var head = unflattenedSchema;
            for (var _i = 0, fields_1 = fields; _i < fields_1.length; _i++) {
                var field = fields_1[_i];
                if (!head.required) {
                    head.required = [];
                }
                head.required.push(field);
                head = head.properties[field];
            }
        });
    }
    return unflattenedSchema;
};
