import { TypeORMError } from "../error";
import { ObjectUtils } from "../util/ObjectUtils";
/**
 * Allows to work with entity relations and perform specific operations with those relations.
 *
 * todo: add transactions everywhere
 */
export class RelationUpdater {
    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------
    constructor(queryBuilder, expressionMap) {
        this.queryBuilder = queryBuilder;
        this.expressionMap = expressionMap;
    }
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    /**
     * Performs set or add operation on a relation.
     */
    async update(value) {
        const relation = this.expressionMap.relationMetadata;
        if (relation.isManyToOne || relation.isOneToOneOwner) {
            const updateSet = relation.joinColumns.reduce((updateSet, joinColumn) => {
                const relationValue = ObjectUtils.isObject(value)
                    ? joinColumn.referencedColumn.getEntityValue(value)
                    : value;
                joinColumn.setEntityValue(updateSet, relationValue);
                return updateSet;
            }, {});
            if (!this.expressionMap.of ||
                (Array.isArray(this.expressionMap.of) &&
                    !this.expressionMap.of.length))
                return;
            await this.queryBuilder
                .createQueryBuilder()
                .update(relation.entityMetadata.target)
                .set(updateSet)
                .whereInIds(this.expressionMap.of)
                .execute();
        }
        else if ((relation.isOneToOneNotOwner || relation.isOneToMany) &&
            value === null) {
            // we handle null a bit different way
            const updateSet = {};
            relation.inverseRelation.joinColumns.forEach((column) => {
                updateSet[column.propertyName] = null;
            });
            const ofs = Array.isArray(this.expressionMap.of)
                ? this.expressionMap.of
                : [this.expressionMap.of];
            const parameters = {};
            const conditions = [];
            ofs.forEach((of, ofIndex) => {
                relation.inverseRelation.joinColumns.map((column, columnIndex) => {
                    const parameterName = "joinColumn_" + ofIndex + "_" + columnIndex;
                    parameters[parameterName] = ObjectUtils.isObject(of)
                        ? column.referencedColumn.getEntityValue(of)
                        : of;
                    conditions.push(`${column.propertyPath} = :${parameterName}`);
                });
            });
            const condition = conditions
                .map((str) => "(" + str + ")")
                .join(" OR ");
            if (!condition)
                return;
            await this.queryBuilder
                .createQueryBuilder()
                .update(relation.inverseEntityMetadata.target)
                .set(updateSet)
                .where(condition)
                .setParameters(parameters)
                .execute();
        }
        else if (relation.isOneToOneNotOwner || relation.isOneToMany) {
            if (Array.isArray(this.expressionMap.of))
                throw new TypeORMError(`You cannot update relations of multiple entities with the same related object. Provide a single entity into .of method.`);
            const of = this.expressionMap.of;
            const updateSet = relation.inverseRelation.joinColumns.reduce((updateSet, joinColumn) => {
                const relationValue = ObjectUtils.isObject(of)
                    ? joinColumn.referencedColumn.getEntityValue(of)
                    : of;
                joinColumn.setEntityValue(updateSet, relationValue);
                return updateSet;
            }, {});
            if (!value || (Array.isArray(value) && !value.length))
                return;
            await this.queryBuilder
                .createQueryBuilder()
                .update(relation.inverseEntityMetadata.target)
                .set(updateSet)
                .whereInIds(value)
                .execute();
        }
        else {
            // many to many
            const junctionMetadata = relation.junctionEntityMetadata;
            const ofs = Array.isArray(this.expressionMap.of)
                ? this.expressionMap.of
                : [this.expressionMap.of];
            const values = Array.isArray(value) ? value : [value];
            const firstColumnValues = relation.isManyToManyOwner ? ofs : values;
            const secondColumnValues = relation.isManyToManyOwner ? values : ofs;
            const bulkInserted = [];
            firstColumnValues.forEach((firstColumnVal) => {
                secondColumnValues.forEach((secondColumnVal) => {
                    const inserted = {};
                    junctionMetadata.ownerColumns.forEach((column) => {
                        inserted[column.databaseName] = ObjectUtils.isObject(firstColumnVal)
                            ? column.referencedColumn.getEntityValue(firstColumnVal)
                            : firstColumnVal;
                    });
                    junctionMetadata.inverseColumns.forEach((column) => {
                        inserted[column.databaseName] = ObjectUtils.isObject(secondColumnVal)
                            ? column.referencedColumn.getEntityValue(secondColumnVal)
                            : secondColumnVal;
                    });
                    bulkInserted.push(inserted);
                });
            });
            if (!bulkInserted.length)
                return;
            if (this.queryBuilder.connection.driver.options.type === "oracle" ||
                this.queryBuilder.connection.driver.options.type === "sap") {
                await Promise.all(bulkInserted.map((value) => {
                    return this.queryBuilder
                        .createQueryBuilder()
                        .insert()
                        .into(junctionMetadata.tableName)
                        .values(value)
                        .execute();
                }));
            }
            else {
                await this.queryBuilder
                    .createQueryBuilder()
                    .insert()
                    .into(junctionMetadata.tableName)
                    .values(bulkInserted)
                    .execute();
            }
        }
    }
}

//# sourceMappingURL=RelationUpdater.js.map
