import * as tslib_1 from "tslib";
import { kebabCase } from 'lodash';
import { KEY_PROPERTY } from '../property/key-property.const';
import { KEY_MODEL } from './key-model.const';
export function Model(opts) {
    if (opts === void 0) { opts = {}; }
    // tslint:disable-next-line:ban-types
    return function (constructor) {
        // logger.debug('defining metadata for thing')
        // Make sure everything is valid
        // const classType = getMetadataType(constructor)
        var 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'
        var properties = Reflect.hasOwnMetadata(KEY_PROPERTY, constructor)
            ? Reflect.getOwnMetadata(KEY_PROPERTY, constructor)
            : Reflect.getMetadata(KEY_PROPERTY, constructor);
        // get partition key
        var partitionKeys = properties
            ? properties.filter(function (property) { return property.key && property.key.type === 'HASH'; })
            : null;
        var partitionKeyName = partitionKeys && partitionKeys.length ? partitionKeys[0].nameDb : null;
        /*
         * get the local and global secondary indexes
         */
        var globalSecondaryIndexes = getGlobalSecondaryIndexes(properties) || [];
        var localSecondaryIndexes = getLocalSecondaryIndexes(partitionKeyName, properties) || [];
        var indexes = new Map(tslib_1.__spread(globalSecondaryIndexes, localSecondaryIndexes));
        var transientProperties = properties && properties.length
            ? properties.filter(function (property) { return property.transient === true; }).map(function (property) { return property.name; })
            : [];
        var metaData = tslib_1.__assign({ clazz: constructor, clazzName: type.name, tableName: kebabCase(type.name) + "s", properties: properties,
            transientProperties: transientProperties,
            indexes: indexes }, opts);
        // console.log(`Decorating: ${metaData.clazzName}`, metaData);
        Reflect.defineMetadata(KEY_MODEL, metaData, constructor);
    };
}
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(function (map, property) {
            var gsi;
            Object.keys(property.keyForGSI).forEach(function (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(function (map, property) {
            var lsi;
            property.sortKeyForLSI.forEach(function (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