"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
const key_property_const_1 = require("../property/key-property.const");
const key_model_const_1 = require("./key-model.const");
function Model(opts = {}) {
    // tslint:disable-next-line:ban-types
    return (constructor) => {
        const type = constructor;
        // get all the properties with @Property() annotation (or @PartitionKey(),...)
        // if given class has own properties, all inherited properties are already set and we can get the properties with 'getOwnMetadata'.
        // otherwise when the given class does not have own properties, there's no 'ownMetadata' but we need to get them from the class it extends with 'getMetadata'
        const properties = Reflect.hasOwnMetadata(key_property_const_1.KEY_PROPERTY, constructor)
            ? Reflect.getOwnMetadata(key_property_const_1.KEY_PROPERTY, constructor)
            : Reflect.getMetadata(key_property_const_1.KEY_PROPERTY, constructor);
        // get partition key
        const partitionKeys = properties
            ? properties.filter(property => property.key && property.key.type === 'HASH')
            : null;
        const partitionKeyName = partitionKeys && partitionKeys.length ? partitionKeys[0].nameDb : null;
        /*
         * get the local and global secondary indexes
         */
        const globalSecondaryIndexes = getGlobalSecondaryIndexes(properties) || [];
        const localSecondaryIndexes = getLocalSecondaryIndexes(partitionKeyName, properties) || [];
        const indexes = new Map([...globalSecondaryIndexes, ...localSecondaryIndexes]);
        const transientProperties = properties && properties.length
            ? properties.filter(property => property.transient === true).map(property => property.name)
            : [];
        const metaData = Object.assign({ clazz: constructor, clazzName: type.name, tableName: `${lodash_1.kebabCase(type.name)}s`, properties,
            transientProperties,
            indexes }, opts);
        // console.log(`Decorating: ${metaData.clazzName}`, metaData);
        Reflect.defineMetadata(key_model_const_1.KEY_MODEL, metaData, constructor);
    };
}
exports.Model = Model;
function testForGSI(property) {
    return !!(property.keyForGSI && Object.keys(property.keyForGSI).length);
}
function testForLSI(property) {
    return !!(property.sortKeyForLSI && property.sortKeyForLSI.length);
}
function getGlobalSecondaryIndexes(properties) {
    if (properties && properties.length) {
        return properties.filter(testForGSI).reduce((map, property) => {
            let gsi;
            Object.keys(property.keyForGSI).forEach(indexName => {
                if (map.has(indexName)) {
                    gsi = map.get(indexName);
                }
                else {
                    gsi = {};
                }
                switch (property.keyForGSI[indexName]) {
                    case 'HASH':
                        if (gsi.partitionKey) {
                            throw new Error(`there is already a partition key defined for global secondary index ${indexName} (property name: ${property.nameDb})`);
                        }
                        gsi.partitionKey = property.nameDb;
                        break;
                    case 'RANGE':
                        if (gsi.sortKey) {
                            throw new Error(`there is already a sort key defined for global secondary index ${indexName} (property name: ${property.nameDb})`);
                        }
                        gsi.sortKey = property.nameDb;
                        break;
                }
                if (map.has(indexName)) {
                    map.set(indexName, gsi);
                }
                else {
                    if (map.size < 5) {
                        map.set(indexName, gsi);
                    }
                    else {
                        // a maximum of 5 global secondary indexes can be defined per table
                        throw new Error('make sure to define no more than 5 global secondary indexes per model');
                    }
                }
            });
            return map;
        }, new Map());
    }
    else {
        return null;
    }
}
function getLocalSecondaryIndexes(basePartitionKey, properties) {
    if (properties && properties.length) {
        return properties.filter(testForLSI).reduce((map, property) => {
            let lsi;
            property.sortKeyForLSI.forEach(indexName => {
                if (map.has(indexName)) {
                    throw new Error(`only one sort key can be defined for the same local secondary index, ${property.nameDb} is already defined as sort key for index ${indexName}`);
                }
                if (!basePartitionKey) {
                    throw new Error('a local secondary index requires the partition key to be defined, use the @PartitionKey decorator');
                }
                lsi = {
                    partitionKey: basePartitionKey,
                    sortKey: property.nameDb,
                };
                map.set(indexName, lsi);
            });
            return map;
        }, new Map());
    }
    else {
        return null;
    }
}
//# sourceMappingURL=model.decorator.js.map