"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.traitToPropertyMap = void 0;
const internal_1 = require("../internal");
const traitToListOfProperties = new Map([
    ['is.CDM.entityVersion', ['version']],
    ['is.CDM.attributeGroup', ['cdmSchemas']],
    ['is.CDM.sourceNamed', ['sourceName']],
    ['is.localized.displayedAs', ['displayName']],
    ['is.localized.describedAs', ['description']],
    ['is.CDS.ordered', ['sourceOrdering']],
    ['is.readOnly', ['isReadOnly']],
    ['is.nullable', ['isNullable']],
    ['is.constrainedList', ['valueConstrainedToList']],
    ['is.constrained', ['maximumValue', 'minimumValue', 'maximumLength']]
]);
const dataFormatTraitNames = [
    'is.dataFormat.integer',
    'is.dataFormat.small',
    'is.dataFormat.big',
    'is.dataFormat.floatingPoint',
    'is.dataFormat.guid',
    'is.dataFormat.character',
    'is.dataFormat.array',
    'is.dataFormat.byte',
    'is.dataFormat.time',
    'is.dataFormat.date',
    'is.dataFormat.timeOffset',
    'is.dataFormat.boolean',
    'is.dataFormat.numeric.shaped',
    'means.content.text.JSON'
];
/**
 * @internal
 * attribute and entity traits that are represented as properties
 * this entire class is gross. it is a different abstraction level than all of the rest of this om.
 * however, it does make it easier to work with the consumption object model so ... i will hold my nose.
 */
class traitToPropertyMap {
    constructor(host) {
        this.TAG = traitToPropertyMap.name;
        this.host = host;
    }
    get ctx() {
        return this.host.ctx;
    }
    get traits() {
        if ('appliedTraits' in this.host) {
            return this.host.appliedTraits;
        }
        if ('exhibitsTraits' in this.host) {
            return this.host.exhibitsTraits;
        }
    }
    /**
     * @internal
     */
    updatePropertyValue(propertyName, newValue) {
        const traitName = this.mapTraitName(propertyName);
        const listOfProps = traitToListOfProperties.get(traitName);
        const multipleProperties = listOfProps && listOfProps.length > 1;
        if (newValue === undefined && !multipleProperties) {
            this.removeTrait(traitName);
        }
        else {
            switch (propertyName) {
                case 'version':
                    this.updateTraitArgument('is.CDM.entityVersion', 'versionNumber', newValue);
                    break;
                case 'cdmSchemas':
                    this.updateSingleAttributeTraitTable('is.CDM.attributeGroup', 'groupList', 'attributeGroupSet', newValue);
                    break;
                case 'sourceName':
                    this.updateTraitArgument('is.CDS.sourceNamed', 'name', newValue);
                    break;
                case 'displayName':
                    this.constructLocalizedTraitTable('is.localized.displayedAs', newValue);
                    break;
                case 'description':
                    this.constructLocalizedTraitTable('is.localized.describedAs', newValue);
                    break;
                case 'sourceOrdering':
                    this.updateTraitArgument('is.CDS.ordered', 'ordinal', newValue.toString());
                    break;
                case 'isPrimaryKey':
                    this.updateTraitArgument('is.identifiedBy', '', newValue);
                    break;
                case 'isReadOnly':
                    this.mapBooleanTrait('is.readOnly', newValue);
                    break;
                case 'isNullable':
                    this.mapBooleanTrait('is.nullable', newValue);
                    break;
                case 'valueConstrainedToList':
                    this.mapBooleanTrait('is.constrainedList', newValue);
                    break;
                case 'maximumValue':
                    this.updateTraitArgument('is.constrained', 'maximumValue', newValue);
                    break;
                case 'minimumValue':
                    this.updateTraitArgument('is.constrained', 'minimumValue', newValue);
                    break;
                case 'maximumLength':
                    this.updateTraitArgument('is.constrained', 'maximumLength', newValue !== undefined ? newValue.toString() : undefined);
                    break;
                case 'dataFormat':
                    this.dataFormatToTraits(newValue);
                    break;
                case 'defaultValue':
                    this.updateDefaultValue(newValue);
                    break;
                default:
            }
        }
    }
    /**
     * @internal
     */
    fetchPropertyValue(propertyName, fromProperty = false) {
        var _a;
        switch (propertyName) {
            case 'version':
                return this.fetchTraitReferenceArgumentValue(this.fetchTraitReference('is.CDM.entityVersion', fromProperty), 'versionNumber');
            case 'sourceName':
                return this.fetchTraitReferenceArgumentValue(this.fetchTraitReference('is.CDS.sourceNamed', fromProperty), 'name');
            case 'displayName':
                return this.fetchLocalizedTraitTable('is.localized.displayedAs', fromProperty);
            case 'description':
                return this.fetchLocalizedTraitTable('is.localized.describedAs', fromProperty);
            case 'cdmSchemas':
                return this.fetchSingleAttTraittable('is.CDM.attributeGroup', 'groupList', fromProperty);
            case 'sourceOrdering':
                return parseInt(this.fetchTraitReferenceArgumentValue(this.fetchTraitReference('is.CDS.ordered', fromProperty), 'ordinal'), 10);
            case 'isPrimaryKey':
                if (this.host instanceof internal_1.CdmTypeAttributeDefinition) {
                    const typeAttribute = this.host;
                    if (!fromProperty && typeAttribute.purpose && typeAttribute.purpose.namedReference === 'identifiedBy') {
                        return true;
                    }
                }
                return this.fetchTraitReference('is.identifiedBy', fromProperty) !== undefined;
            case 'isNullable':
                return this.fetchTraitReference('is.nullable', fromProperty) !== undefined;
            case 'isReadOnly':
                return this.fetchTraitReference('is.readOnly', fromProperty) !== undefined;
            case 'isResolved':
                const trait = this.fetchTraitReference('has.entitySchemaAbstractionLevel', fromProperty);
                return ((_a = trait === null || trait === void 0 ? void 0 : trait.arguments) === null || _a === void 0 ? void 0 : _a.fetchValue('level')) === 'resolved';
            case 'valueConstrainedToList':
                return this.fetchTraitReference('is.constrainedList', fromProperty) !== undefined;
            case 'maximumValue':
                return this.fetchTraitReferenceArgumentValue(this.fetchTraitReference('is.constrained', fromProperty), 'maximumValue');
            case 'minimumValue':
                return this.fetchTraitReferenceArgumentValue(this.fetchTraitReference('is.constrained', fromProperty), 'minimumValue');
            case 'maximumLength':
                const temp = this.fetchTraitReferenceArgumentValue(this.fetchTraitReference('is.constrained', fromProperty), 'maximumLength');
                if (temp !== undefined) {
                    return parseInt(temp, 10);
                }
                break;
            case 'dataFormat':
                return this.traitsToDataFormat(fromProperty);
            case 'primaryKey':
                const attRef = this.fetchTraitReferenceArgumentValue(this.fetchTraitReference('is.identifiedBy', fromProperty), 'attribute');
                if (attRef) {
                    if (typeof attRef === 'string' || attRef instanceof String) {
                        return attRef;
                    }
                    return attRef.fetchObjectDefinitionName();
                }
                break;
            case 'defaultValue':
                return this.fetchDefaultValue(fromProperty);
            case 'isIncremental':
                return this.fetchTraitReference(internal_1.constants.INCREMENTAL_TRAIT_NAME) !== undefined;
            default:
        }
    }
    /**
     * @internal
     */
    fetchTraitReference(traitName, fromProperty = false) {
        const traitIndex = this.traits === undefined ? -1 : this.traits.indexOf(traitName, fromProperty);
        return traitIndex !== -1 ? this.traits.allItems[traitIndex] : undefined;
    }
    /**
     * @internal
     */
    removeTrait(traitName) {
        this.traits.remove(traitName, true);
    }
    /**
     * @internal
     */
    mapBooleanTrait(traitName, value) {
        if (value === true) {
            this.fetchOrCreateTrait(traitName, true);
        }
        else {
            this.removeTrait(traitName);
        }
    }
    /**
     * @internal
     */
    dataFormatToTraits(dataFormat) {
        // reset the current dataFormat
        for (const traitName of dataFormatTraitNames) {
            this.removeTrait(traitName);
        }
        switch (dataFormat) {
            case internal_1.cdmDataFormat.int16:
                this.fetchOrCreateTrait('is.dataFormat.integer', true);
                this.fetchOrCreateTrait('is.dataFormat.small', true);
                break;
            case internal_1.cdmDataFormat.int32:
                this.fetchOrCreateTrait('is.dataFormat.integer', true);
                break;
            case internal_1.cdmDataFormat.int64:
                this.fetchOrCreateTrait('is.dataFormat.integer', true);
                this.fetchOrCreateTrait('is.dataFormat.big', true);
                break;
            case internal_1.cdmDataFormat.float:
                this.fetchOrCreateTrait('is.dataFormat.floatingPoint', true);
                break;
            case internal_1.cdmDataFormat.double:
                this.fetchOrCreateTrait('is.dataFormat.floatingPoint', true);
                this.fetchOrCreateTrait('is.dataFormat.big', true);
                break;
            case internal_1.cdmDataFormat.guid:
                this.fetchOrCreateTrait('is.dataFormat.guid', true);
                this.fetchOrCreateTrait('is.dataFormat.character', true);
                this.fetchOrCreateTrait('is.dataFormat.array', true);
                break;
            case internal_1.cdmDataFormat.string:
                this.fetchOrCreateTrait('is.dataFormat.character', true);
                this.fetchOrCreateTrait('is.dataFormat.array', true);
                break;
            case internal_1.cdmDataFormat.char:
                this.fetchOrCreateTrait('is.dataFormat.character', true);
                this.fetchOrCreateTrait('is.dataFormat.big', true);
                break;
            case internal_1.cdmDataFormat.byte:
                this.fetchOrCreateTrait('is.dataFormat.byte', true);
                break;
            case internal_1.cdmDataFormat.binary:
                this.fetchOrCreateTrait('is.dataFormat.byte', true);
                this.fetchOrCreateTrait('is.dataFormat.array', true);
                break;
            case internal_1.cdmDataFormat.time:
                this.fetchOrCreateTrait('is.dataFormat.time', true);
                break;
            case internal_1.cdmDataFormat.date:
                this.fetchOrCreateTrait('is.dataFormat.date', true);
                break;
            case internal_1.cdmDataFormat.dateTime:
                this.fetchOrCreateTrait('is.dataFormat.time', true);
                this.fetchOrCreateTrait('is.dataFormat.date', true);
                break;
            case internal_1.cdmDataFormat.dateTimeOffset:
                this.fetchOrCreateTrait('is.dataFormat.time', true);
                this.fetchOrCreateTrait('is.dataFormat.date', true);
                this.fetchOrCreateTrait('is.dataFormat.timeOffset', true);
                break;
            case internal_1.cdmDataFormat.boolean:
                this.fetchOrCreateTrait('is.dataFormat.boolean', true);
                break;
            case internal_1.cdmDataFormat.decimal:
                this.fetchOrCreateTrait('is.dataFormat.numeric.shaped', true);
                break;
            case internal_1.cdmDataFormat.json:
                this.fetchOrCreateTrait('is.dataFormat.array', true);
                this.fetchOrCreateTrait('means.content.text.JSON', true);
                break;
            default:
        }
    }
    /**
     * @internal
     */
    mapTraitName(propertyName) {
        switch (propertyName) {
            case 'version':
                return 'is.CDM.entityVersion';
            case 'cdmSchemas':
                return 'is.CDM.attributeGroup';
            case 'sourceName':
                return 'is.CDS.sourceNamed';
            case 'displayName':
                return 'is.localized.displayedAs';
            case 'description':
                return 'is.localized.describedAs';
            case 'sourceOrdering':
                return 'is.CDS.ordered';
            case 'isPrimaryKey':
                return 'is.identifiedBy';
            case 'isReadOnly':
                return 'is.readOnly';
            case 'isNullable':
                return 'is.nullable';
            case 'valueConstrainedToList':
                return 'is.constrainedList';
            case 'maximumValue':
            case 'minimumValue':
            case 'maximumLength':
                return 'is.constrained';
            default:
                return propertyName;
        }
    }
    /**
     * @internal
     */
    traitsToDataFormat(onlyFromProperty = false) {
        let isArray = false;
        let isBig = false;
        let isSmall = false;
        let isInteger = false;
        let probablyJson = false;
        if (!this.traits) {
            return undefined;
        }
        let baseType = 0;
        for (const trait of this.traits) {
            if (onlyFromProperty &&
                (trait instanceof internal_1.CdmTraitGroupReference || !trait.isFromProperty)) {
                continue;
            }
            const traitName = trait.fetchObjectDefinitionName();
            // tslint:disable:switch-default
            switch (traitName) {
                case 'is.dataFormat.array':
                    isArray = true;
                    break;
                case 'is.dataFormat.big':
                    isBig = true;
                    break;
                case 'is.dataFormat.small':
                    isSmall = true;
                    break;
                case 'is.dataFormat.integer':
                    isInteger = true;
                    break;
                case 'is.dataFormat.floatingPoint':
                    baseType = internal_1.cdmDataFormat.float;
                    break;
                case 'is.dataFormat.character':
                    baseType = baseType !== internal_1.cdmDataFormat.guid ? internal_1.cdmDataFormat.char : baseType;
                    break;
                case 'is.dataFormat.byte':
                    baseType = internal_1.cdmDataFormat.byte;
                    break;
                case 'is.dataFormat.date':
                    baseType = baseType === internal_1.cdmDataFormat.time ? internal_1.cdmDataFormat.dateTime : internal_1.cdmDataFormat.date;
                    break;
                case 'is.dataFormat.time':
                    baseType = baseType === internal_1.cdmDataFormat.date ? internal_1.cdmDataFormat.dateTime : internal_1.cdmDataFormat.time;
                    break;
                case 'is.dataFormat.timeOffset':
                    baseType = baseType === internal_1.cdmDataFormat.dateTime ? internal_1.cdmDataFormat.dateTimeOffset : baseType;
                    break;
                case 'is.dataFormat.boolean':
                    baseType = internal_1.cdmDataFormat.boolean;
                    break;
                case 'is.dataFormat.numeric.shaped':
                    baseType = internal_1.cdmDataFormat.decimal;
                    break;
                case 'is.dataFormat.guid':
                    baseType = internal_1.cdmDataFormat.guid;
                    break;
                case 'means.content.text.JSON':
                    baseType = isArray ? internal_1.cdmDataFormat.json : internal_1.cdmDataFormat.unknown;
                    probablyJson = true;
            }
        }
        if (isArray) {
            if (probablyJson) {
                baseType = internal_1.cdmDataFormat.json;
            }
            else if (baseType === internal_1.cdmDataFormat.char) {
                baseType = internal_1.cdmDataFormat.string;
            }
            else if (baseType === internal_1.cdmDataFormat.byte) {
                baseType = internal_1.cdmDataFormat.binary;
            }
            else if (baseType !== internal_1.cdmDataFormat.guid) {
                baseType = internal_1.cdmDataFormat.unknown;
            }
        }
        if (baseType === internal_1.cdmDataFormat.float && isBig) {
            baseType = internal_1.cdmDataFormat.double;
        }
        if (isInteger && isBig) {
            baseType = internal_1.cdmDataFormat.int64;
        }
        else if (isInteger && isSmall) {
            baseType = internal_1.cdmDataFormat.int16;
        }
        else if (isInteger) {
            baseType = internal_1.cdmDataFormat.int32;
        }
        return baseType;
    }
    /**
     * @internal
     */
    fetchOrCreateTrait(traitName, simpleRef = false) {
        let trait = this.fetchTraitReference(traitName, true);
        if (!trait) {
            trait = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.traitRef, traitName);
            this.traits.push(trait);
            trait.isFromProperty = true;
        }
        return trait;
    }
    /**
     * @internal
     */
    updateTraitArgument(traitName, argName, value) {
        const trait = this.fetchOrCreateTrait(traitName, false);
        const args = trait.arguments;
        if (!args || !args.length) {
            if (value !== undefined) {
                trait.arguments.push(argName, value);
                return;
            }
            else {
                this.removeTrait(traitName);
            }
        }
        else {
            for (const arg of args) {
                if (arg.getName() === argName) {
                    if (!value) {
                        args.remove(arg);
                        if (trait.arguments && trait.arguments.length === 0) {
                            this.removeTrait(traitName);
                        }
                    }
                    else {
                        arg.setValue(value);
                    }
                    return;
                }
            }
        }
        if (value !== undefined) {
            trait.arguments.push(argName, value);
        }
    }
    /**
     * @internal
     */
    updateTraitTable(traitName, argName, entityName, action) {
        const resultantTrait = this.fetchOrCreateTrait(traitName, false);
        if (!resultantTrait.arguments || !resultantTrait.arguments.length) {
            // make the argument nothing but a ref to a constant entity
            // safe since there is only one param for the trait and it looks cleaner
            const cEnt = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.constantEntityDef);
            cEnt.setEntityShape(this.ctx.corpus.MakeRef(internal_1.cdmObjectType.entityRef, entityName, true));
            action(cEnt, true);
            resultantTrait.arguments.push(argName, this.ctx.corpus.MakeRef(internal_1.cdmObjectType.entityRef, cEnt, false));
        }
        else {
            const locEntRef = this.fetchTraitReferenceArgumentValue(resultantTrait, argName);
            if (locEntRef) {
                const locEnt = locEntRef.fetchObjectDefinition(undefined);
                if (locEnt) {
                    action(locEnt, false);
                }
            }
        }
    }
    /**
     * @internal
     */
    fetchTraitTable(traitName, argName, fromProperty) {
        let trait;
        const traitIndex = this.traits.indexOf(traitName, fromProperty);
        trait = traitIndex !== -1 ? this.traits.allItems[traitIndex] : undefined;
        const locEntRef = this.fetchTraitReferenceArgumentValue(trait, argName);
        if (locEntRef) {
            return locEntRef.fetchObjectDefinition(undefined);
        }
    }
    /**
     * @internal
     */
    constructLocalizedTraitTable(traitName, sourceText) {
        this.updateTraitTable(traitName, 'localizedDisplayText', 'localizedTable', (cEnt, created) => {
            if (created) {
                cEnt.setConstantValues([['en', sourceText]]);
            }
            else {
                /**
                 * search for a match
                 * -1 or order gets us last row that matches. needed because inheritence
                 * chain with different descriptions stacks these up
                 * need to use ordinals because no binding done yet
                 */
                cEnt.updateConstantValue(undefined, 1, sourceText, 0, 'en', -1);
            } // need to use ordinals because no binding done yet
        });
    }
    /**
     * @internal
     */
    fetchLocalizedTraitTable(traitName, fromProperty) {
        const cEnt = this.fetchTraitTable(traitName, 'localizedDisplayText', fromProperty);
        if (cEnt) {
            /**
             * search for a match
             * -1 or order gets us last row that matches. needed because inheritence
             * chain with different descriptions stacks these up
             * need to use ordinals because no binding done yet
             */
            return cEnt.fetchConstantValue(undefined, 1, 0, 'en', -1);
        }
    }
    /**
     * @internal
     */
    updateSingleAttributeTraitTable(traitName, argName, entityName, sourceText) {
        this.updateTraitTable(traitName, argName, entityName, (cEnt, created) => {
            // turn array of strings into array of array of strings;
            const vals = [];
            sourceText.forEach((v) => { const r = []; r.push(v); vals.push(r); });
            cEnt.setConstantValues(vals);
        });
    }
    /**
     * @internal
     */
    fetchSingleAttTraittable(traitName, argName, fromProperty) {
        const cEnt = this.fetchTraitTable(traitName, argName, fromProperty);
        if (cEnt) {
            // turn array of arrays into single array of strings
            const result = [];
            cEnt.getConstantValues()
                .forEach((v) => { result.push(v[0]); });
            return result;
        }
    }
    /**
     * @internal
     */
    fetchDefaultValue(fromProperty) {
        const trait = this.fetchTraitReference('does.haveDefault', fromProperty);
        if (trait) {
            let defVal = this.fetchTraitReferenceArgumentValue(trait, 'default');
            if (defVal !== undefined) {
                if (typeof (defVal) === 'string') {
                    return defVal;
                }
                if (defVal.getObjectType() === internal_1.cdmObjectType.entityRef) {
                    // no doc or directives should work ?
                    const cEnt = defVal.fetchObjectDefinition();
                    if (cEnt) {
                        const esName = cEnt.getEntityShape()
                            .fetchObjectDefinitionName();
                        const corr = esName === 'listLookupCorrelatedValues';
                        const lookup = esName === 'listLookupValues';
                        if (esName === 'localizedTable' || lookup || corr) {
                            const result = [];
                            const rawValues = cEnt.getConstantValues();
                            if (rawValues) {
                                const l = rawValues.length;
                                for (let i = 0; i < l; i++) {
                                    const row = {};
                                    const rawRow = rawValues[i];
                                    if (rawRow.length === 2 || (lookup && rawRow.length === 4) || (corr && rawRow.length === 5)) {
                                        row.languageTag = rawRow[0] !== undefined ? rawRow[0] : null;
                                        row.displayText = rawRow[1] !== undefined ? rawRow[1] : null;
                                        if (lookup || corr) {
                                            row.attributeValue = rawRow[2] !== undefined ? rawRow[2] : null;
                                            row.displayOrder = rawRow[3] !== undefined ? rawRow[3] : null;
                                            if (corr) {
                                                row.correlatedValue = rawRow[4] !== undefined ? rawRow[4] : null;
                                            }
                                        }
                                    }
                                    result.push(row);
                                }
                                return result;
                            }
                        }
                        else {
                            // an unknown entity shape. only thing to do is serialize the object
                            defVal = defVal.copyData(undefined, undefined);
                        }
                    }
                }
                else {
                    // is it a cdm object?
                    if (defVal.getObjectType !== undefined) {
                        defVal = defVal.copyData(undefined, undefined);
                    }
                }
            }
            return defVal;
        }
    }
    /**
     * @internal
     */
    updateDefaultValue(newDefault) {
        if (newDefault instanceof Array) {
            const a = newDefault;
            const l = a.length;
            if (l && a[0].languageTag !== undefined && a[0].displayText !== undefined) {
                // looks like something we understand
                const tab = [];
                const corr = (a[0].correlatedValue !== undefined);
                const lookup = (a[0].displayOrder !== undefined && a[0].attributeValue !== undefined);
                for (let i = 0; i < l; i++) {
                    const row = [];
                    row.push(a[i].languageTag);
                    row.push(a[i].displayText);
                    if (lookup || corr) {
                        row.push(a[i].attributeValue);
                        row.push(a[i].displayOrder);
                        if (corr) {
                            row.push(a[i].correlatedValue);
                        }
                    }
                    tab.push(row);
                }
                const cEnt = this.ctx.corpus.MakeObject(internal_1.cdmObjectType.constantEntityDef);
                cEnt.setEntityShape(this.ctx.corpus.MakeRef(internal_1.cdmObjectType.entityRef, corr ? 'listLookupCorrelatedValues' : 'listLookupValues', true));
                cEnt.setConstantValues(tab);
                newDefault = this.ctx.corpus.MakeRef(internal_1.cdmObjectType.entityRef, cEnt, false);
                this.updateTraitArgument('does.haveDefault', 'default', newDefault);
            }
            else {
                internal_1.Logger.error(this.host.ctx, this.TAG, this.updateDefaultValue.name, null, internal_1.cdmLogCode.ErrValdnMissingLanguageTag);
            }
        }
        else {
            internal_1.Logger.error(this.host.ctx, this.TAG, this.updateDefaultValue.name, null, internal_1.cdmLogCode.ErrUnsupportedType);
        }
    }
    fetchTraitReferenceArgumentValue(tr, argName) {
        {
            if (tr) {
                let av;
                if (tr.parameterValues) {
                    av = tr.parameterValues.fetchParameterValueByName(argName).value;
                }
                else {
                    av = tr.arguments.fetchValue(argName);
                }
                return av;
            }
        }
    }
}
exports.traitToPropertyMap = traitToPropertyMap;
//# sourceMappingURL=traitToPropertyMap.js.map