"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.ParameterValue = void 0;
const util_1 = require("util");
const internal_1 = require("../internal");
/**
 * @internal
 */
class ParameterValue {
    constructor(ctx, param, value) {
        // const bodyCode = () =>
        {
            this.parameter = param;
            this.value = value;
            this.ctx = ctx;
        }
        // return p.measure(bodyCode);
    }
    get name() {
        // const bodyCode = () =>
        {
            return this.parameter.name;
        }
        // return p.measure(bodyCode);
    }
    static fetchReplacementValue(resOpt, oldValue, newValue, wasSet) {
        // const bodyCode = () =>
        {
            if (!oldValue) {
                return newValue;
            }
            if (!wasSet) {
                // must explicitly set a value to override
                // if a new value is not set, then newValue holds nothing or the default.
                // in this case, if there was already a value in this argument then just keep using it.
                return oldValue;
            }
            if (typeof oldValue === 'string' || typeof oldValue === 'number' || (typeof oldValue === 'object' && !('getObjectType' in oldValue))) {
                return newValue;
            }
            const ov = oldValue;
            const nv = newValue;
            // replace an old table with a new table? actually just mash them together
            if (ov &&
                ov.getObjectType() === internal_1.cdmObjectType.entityRef &&
                nv &&
                typeof nv !== 'string' &&
                nv.getObjectType() === internal_1.cdmObjectType.entityRef) {
                const oldEnt = ov.fetchObjectDefinition(resOpt);
                const newEnt = nv.fetchObjectDefinition(resOpt);
                // check that the entities are the same shape
                if (!newEnt) {
                    return ov;
                }
                // BUG
                const entDefShape = oldEnt.getEntityShape().fetchObjectDefinition(resOpt);
                if (!oldEnt || entDefShape !== newEnt.getEntityShape().fetchObjectDefinition(resOpt)) {
                    return nv;
                }
                const oldCv = oldEnt.getConstantValues();
                const newCv = newEnt.getConstantValues();
                // rows in old?
                if (!oldCv || oldCv.length === 0) {
                    return nv;
                }
                // rows in new?
                if (!newCv || newCv.length === 0) {
                    return ov;
                }
                // make a set of rows in the old one and add the new ones. this will union the two
                // find rows in the new one that are not in the old one. slow, but these are small usually
                const unionedRows = new Map();
                // see if any of the entity atts are the primary key, meaning, the only thing that causes us to merge dups unique.
                // i know this makes you think about a snake eating its own tail, but fetch the resolved attributes of the constant shape
                let pkAtt = -1;
                if (entDefShape) {
                    var resOptShape = new internal_1.resolveOptions(entDefShape.inDocument);
                    var resAttsShape = entDefShape.fetchResolvedAttributes(resOptShape);
                    if (resAttsShape) {
                        pkAtt = resAttsShape.set.findIndex((ra) => ra.resolvedTraits.find(resOptShape, 'is.identifiedBy') !== undefined);
                    }
                }
                for (let i = 0; i < oldCv.length; i++) {
                    const row = oldCv[i];
                    let key;
                    // the entity might have a PK, if so, only look at that values as the key
                    if (pkAtt !== -1) {
                        key = row[pkAtt];
                    }
                    else {
                        key = row.join('::');
                    }
                    unionedRows.set(key, row);
                }
                for (let i = 0; i < newCv.length; i++) {
                    const row = newCv[i];
                    let key;
                    // the entity might have a PK, if so, only look at that values as the key
                    if (pkAtt !== -1) {
                        key = row[pkAtt];
                    }
                    else {
                        key = row.join('::');
                    }
                    unionedRows.set(key, row);
                }
                if (unionedRows.size === oldCv.length) {
                    return ov;
                }
                const allRows = Array.from(unionedRows.values());
                const replacementEnt = oldEnt.copy(resOpt);
                replacementEnt.setConstantValues(allRows);
                return resOpt.wrtDoc.ctx.corpus.MakeRef(internal_1.cdmObjectType.entityRef, replacementEnt, false);
            }
            return newValue;
        }
        // return p.measure(bodyCode);
    }
    fetchValueString(resOpt) {
        // const bodyCode = () =>
        {
            if (typeof this.value === 'string' || typeof this.value === 'number') {
                return this.value.toString();
            }
            else if (typeof this.value === 'object' && !('getObjectType' in this.value)) {
                return this.serializeFieldsInAlphabeticalOrder(this.value);
            }
            const value = this.value;
            if (value) {
                // if this is a constant table, then expand into an html table
                const def = value.fetchObjectDefinition(resOpt);
                if (value.getObjectType() === internal_1.cdmObjectType.entityRef && def && def.getObjectType() === internal_1.cdmObjectType.constantEntityDef) {
                    const entShape = def.getEntityShape();
                    const entValues = def.getConstantValues();
                    if (!entValues || entValues.length === 0) {
                        return '';
                    }
                    const rows = [];
                    const shapeAtts = entShape.fetchResolvedAttributes(resOpt);
                    if (shapeAtts && shapeAtts.set && shapeAtts.set.length > 0) {
                        for (const entVal of entValues) {
                            const rowData = entVal;
                            if (rowData && rowData.length) {
                                const row = {};
                                for (let c = 0; c < rowData.length; c++) {
                                    const tvalue = rowData[c];
                                    let colAtt = shapeAtts.set[c];
                                    if (colAtt !== undefined) {
                                        row[colAtt.resolvedName] = tvalue;
                                    }
                                }
                                rows.push(row);
                            }
                        }
                    }
                    return this.serializeFieldsInAlphabeticalOrder(rows);
                }
                // should be a reference to an object
                const data = value.copyData(resOpt, { stringRefs: false });
                if (typeof data === 'string') {
                    return data;
                }
                return this.serializeFieldsInAlphabeticalOrder(data);
            }
            return '';
        }
        // return p.measure(bodyCode);
    }
    setValue(resOpt, newValue) {
        // const bodyCode = () =>
        {
            this.value = ParameterValue.fetchReplacementValue(resOpt, this.value, newValue, true);
        }
        // return p.measure(bodyCode);
    }
    spew(resOpt, to, indent) {
        // const bodyCode = () =>
        {
            to.spewLine(`${indent}${this.name}:${this.fetchValueString(resOpt)}`);
        }
        // return p.measure(bodyCode);
    }
    /**
     * Generates a set of all the fields of the passed parameter. And all its children's fields.
     * @param data The object to retrieve the fields from.
     */
    // tslint:disable-next-line: no-any
    getFieldsNames(data) {
        const fieldNames = new Set();
        if ((0, util_1.isObject)(data)) {
            Object.keys(data)
                .forEach((field) => {
                fieldNames.add(field);
                if ((0, util_1.isObject)(data[field])) {
                    this.getFieldsNames(data[field])
                        .forEach((fieldName) => fieldNames.add(fieldName));
                }
            });
        }
        if ((0, util_1.isArray)(data)) {
            // tslint:disable-next-line: no-any
            data.forEach((element) => {
                this.getFieldsNames(element)
                    .forEach((fieldName) => fieldNames.add(fieldName));
            });
        }
        return fieldNames;
    }
    /**
     * Serializes parameter 'data' writing fields in alphabetical order.
     * @param data Object to be serialized
     * @returns String representing the serialized parameter
     */
    // tslint:disable-next-line: no-any
    serializeFieldsInAlphabeticalOrder(data) {
        const fieldNamesList = Array.from(this.getFieldsNames(data)
            .values());
        const sortedFieldNames = fieldNamesList.sort();
        return JSON.stringify(data, sortedFieldNames);
    }
}
exports.ParameterValue = ParameterValue;
//# sourceMappingURL=ParameterValue.js.map