import { MetadataHelper } from '../decorator/metadata/metadata-helper';
import { BooleanMapper } from './for-type/boolean.mapper';
import { CollectionMapper } from './for-type/collection.mapper';
import { DateMapper } from './for-type/date.mapper';
import { EnumMapper } from './for-type/enum.mapper';
import { MomentMapper } from './for-type/moment.mapper';
import { NullMapper } from './for-type/null.mapper';
import { NumberMapper } from './for-type/number.mapper';
import { ObjectMapper } from './for-type/object.mapper';
import { StringMapper } from './for-type/string.mapper';
import { Binary } from './type/binary.type';
import { EnumType } from './type/enum.type';
import { MomentType } from './type/moment.type';
import { NullType } from './type/null.type';
import { UndefinedType } from './type/undefined.type';
import { Util } from './util';
// import debug from 'debug';
/**
 *
 */
var Mapper = /** @class */ (function () {
    function Mapper() {
    }
    // static logger = debug('Mapper');
    Mapper.toDb = function (item, modelConstructor) {
        var mapped = {};
        if (modelConstructor) {
            var metadata = MetadataHelper.get(modelConstructor);
            /*
             * initialize possible properties with auto generated uuid
             */
            if (metadata) {
                metadata.getKeysWithUUID().forEach(function (propertyMetadata) {
                    if (!Reflect.get(item, propertyMetadata.name)) {
                        Reflect.set(item, propertyMetadata.name, Util.uuidv4());
                    }
                });
            }
        }
        var propertyNames = Object.getOwnPropertyNames(item) || [];
        propertyNames.forEach(function (propertyKey) {
            /*
             * 1) get the value of the property
             */
            var propertyDescriptor = Object.getOwnPropertyDescriptor(item, propertyKey);
            // use get accessor if available otherwise use value property of descriptor
            var propertyValue;
            if (propertyDescriptor) {
                if (propertyDescriptor.get) {
                    propertyValue = propertyDescriptor.get();
                }
                else {
                    propertyValue = propertyDescriptor.value;
                }
            }
            else {
                throw new Error('there is no property descriptor for item ' + JSON.stringify(item) + ' and property key ' + propertyKey);
            }
            var attributeValue;
            // TODO concept maybe make this configurable how to map undefined & null values
            if (propertyValue === undefined || propertyValue === null) {
                // noop ignore because we can't map it
            }
            else {
                /*
                 * 2) decide how to map the property depending on type or value
                 */
                var propertyMetadata = void 0;
                if (modelConstructor) {
                    propertyMetadata = MetadataHelper.forProperty(modelConstructor, propertyKey);
                }
                if (propertyMetadata) {
                    if (propertyMetadata.transient) {
                        // skip transient property
                        // Mapper.logger.log('transient property -> skip')
                    }
                    else {
                        /*
                         * 3a) property metadata is defined
                         */
                        attributeValue = Mapper.toDbOne(propertyValue, propertyMetadata);
                    }
                }
                else {
                    /*
                     * 3b) no metadata found
                     */
                    // let typeByConvention = Util.typeByConvention(propertyKey);
                    // if (typeByConvention) {
                    /*
                       * 4a) matches a convention
                       */
                    // attributeValue = Mapper.mapperForConvention(typeByConvention).toDb(propertyValue);
                    // } else {
                    //   /*
                    //    * 4b) no naming convention matches
                    //    */
                    attributeValue = Mapper.toDbOne(propertyValue);
                    // }
                }
                if (attributeValue === undefined) {
                    // no-op transient field, just ignore it
                }
                else if (attributeValue === null) {
                    // empty values (string, set, list) will be ignored too
                }
                else {
                    mapped[propertyMetadata ? propertyMetadata.nameDb : propertyKey] = attributeValue;
                }
            }
        });
        return mapped;
    };
    Mapper.toDbOne = function (propertyValue, propertyMetadata) {
        var explicitType = propertyMetadata && propertyMetadata.typeInfo && propertyMetadata.typeInfo.isCustom
            ? propertyMetadata.typeInfo.type
            : null;
        var type = explicitType || Util.typeOf(propertyValue);
        // some basic validation
        if (propertyMetadata &&
            propertyMetadata.key &&
            propertyMetadata.key.type === 'HASH' &&
            !propertyMetadata.mapper &&
            type !== String &&
            type !== Number &&
            type !== Binary) {
            throw new Error("make sure to define a custom mapper which returns a string or number value for partition key, type " + type + " cannot be used as partition key, value = " + JSON.stringify(propertyValue));
        }
        if (propertyMetadata && propertyMetadata.mapper) {
            // custom mapper
            if (explicitType) {
                return new propertyMetadata.mapper().toDb(propertyValue, propertyMetadata);
            }
            else {
                return new propertyMetadata.mapper().toDb(propertyValue);
            }
        }
        else {
            // mapper by type
            if (explicitType) {
                return Mapper.forType(type).toDb(propertyValue, propertyMetadata);
            }
            else {
                return Mapper.forType(type).toDb(propertyValue);
            }
        }
    };
    Mapper.fromDb = function (attributeMap, modelClass) {
        var model = {};
        Object.getOwnPropertyNames(attributeMap).forEach(function (attributeName) {
            /*
             * 1) get the value of the property
             */
            var attributeValue = attributeMap[attributeName];
            /*
             * 2) decide how to map the property depending on type or value
             */
            var modelValue;
            var propertyMetadata;
            if (modelClass) {
                propertyMetadata = MetadataHelper.forProperty(modelClass, attributeName);
            }
            if (propertyMetadata) {
                if (propertyMetadata.transient) {
                    // skip transient property
                    // Mapper.logger.log('transient property -> skip')
                }
                else {
                    /*
                     * 3a) property metadata is defined
                     */
                    if (propertyMetadata && propertyMetadata.mapper) {
                        // custom mapper
                        modelValue = new propertyMetadata.mapper().fromDb(attributeValue, propertyMetadata);
                    }
                    else {
                        modelValue = Mapper.fromDbOne(attributeValue, propertyMetadata);
                    }
                }
            }
            else {
                /*
                 * 3b) no metadata found
                 */
                // let typeByConvention = Util.typeByConvention(propertyKey);
                // if (typeByConvention) {
                //   /*
                //    * 4a) matches a convention
                //    */
                //   modelValue = Mapper.mapperForConvention(typeByConvention).fromDb(attributeValue);
                // } else {
                /*
                   * 4b) no naming convention matches
                   */
                modelValue = Mapper.fromDbOne(attributeValue);
                // }
            }
            Reflect.set(model, propertyMetadata ? propertyMetadata.name : attributeName, modelValue);
            // throw new Error('don\'t know how to map without model class');
        });
        return model;
    };
    Mapper.fromDbOne = function (attributeValue, propertyMetadata) {
        var explicitType = propertyMetadata && propertyMetadata.typeInfo && propertyMetadata.typeInfo.isCustom
            ? propertyMetadata.typeInfo.type
            : null;
        var type = explicitType || Util.typeOfFromDb(attributeValue);
        // Mapper.logger.log(`mapFromDbOne for type ${type}`)
        if (explicitType) {
            return Mapper.forType(type).fromDb(attributeValue, propertyMetadata);
        }
        else {
            return Mapper.forType(type).fromDb(attributeValue);
        }
    };
    Mapper.forType = function (type) {
        // FIXME HIGH review this, we now use toString to compare because we had issues with ng client for moment when
        // using a GSI on creationDate (MomentType) was a different MomentType than for lastUpdatedDate
        if (!Mapper.mapperForType.has(type.toString())) {
            var mapperForType = void 0;
            switch (type.toString()) {
                case String.toString():
                    mapperForType = new StringMapper();
                    break;
                case Number.toString():
                    mapperForType = new NumberMapper();
                    break;
                case Boolean.toString():
                    mapperForType = new BooleanMapper();
                    break;
                case MomentType.toString():
                    mapperForType = new MomentMapper();
                    break;
                case Date.toString():
                    mapperForType = new DateMapper();
                    break;
                case EnumType.toString():
                    mapperForType = new EnumMapper();
                    break;
                case Map.toString():
                    // Maps support complex types as keys, we only support String & Number as Keys, otherwise a .toString() method should be implemented,
                    // so we now how to save a  key
                    // mapperForType = new MapMapper()
                    throw new Error('Map is not supported to be mapped for now');
                case Array.toString():
                    mapperForType = new CollectionMapper();
                    break;
                case Set.toString():
                    mapperForType = new CollectionMapper();
                    break;
                case Object.toString():
                    mapperForType = new ObjectMapper();
                    break;
                case NullType.toString():
                    mapperForType = new NullMapper();
                    break;
                case Binary.toString():
                    // TODO LOW:BINARY add binary mapper
                    throw new Error('no mapper for binary type implemented yet');
                case UndefinedType.toString():
                    mapperForType = new ObjectMapper();
                    break;
                default:
                    throw new Error('no mapper defined for type ' + JSON.stringify(type));
                // mapperForType = new ObjectMapper()
            }
            this.mapperForType.set(type.toString(), mapperForType);
        }
        return this.mapperForType.get(type.toString());
    };
    Mapper.mapperForType = new Map();
    return Mapper;
}());
export { Mapper };
//# sourceMappingURL=mapper.js.map