import { DriverUtils } from "../driver/DriverUtils";
import { TypeORMError } from "../error/TypeORMError";
import { FindOptionsUtils } from "../find-options/FindOptionsUtils";
import { TreeRepositoryUtils } from "../util/TreeRepositoryUtils";
import { Repository } from "./Repository";
/**
 * Repository with additional functions to work with trees.
 *
 * @see Repository
 */
export class TreeRepository extends Repository {
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    /**
     * Gets complete trees for all roots in the table.
     */
    async findTrees(options) {
        const roots = await this.findRoots(options);
        await Promise.all(roots.map((root) => this.findDescendantsTree(root, options)));
        return roots;
    }
    /**
     * Roots are entities that have no ancestors. Finds them all.
     */
    findRoots(options) {
        const escapeAlias = (alias) => this.manager.connection.driver.escape(alias);
        const escapeColumn = (column) => this.manager.connection.driver.escape(column);
        const joinColumn = this.metadata.treeParentRelation.joinColumns[0];
        const parentPropertyName = joinColumn.givenDatabaseName || joinColumn.databaseName;
        const qb = this.createQueryBuilder("treeEntity");
        FindOptionsUtils.applyOptionsToTreeQueryBuilder(qb, options);
        return qb
            .where(`${escapeAlias("treeEntity")}.${escapeColumn(parentPropertyName)} IS NULL`)
            .getMany();
    }
    /**
     * Gets all children (descendants) of the given entity. Returns them all in a flat array.
     */
    findDescendants(entity, options) {
        const qb = this.createDescendantsQueryBuilder("treeEntity", "treeClosure", entity);
        FindOptionsUtils.applyOptionsToTreeQueryBuilder(qb, options);
        return qb.getMany();
    }
    /**
     * Gets all children (descendants) of the given entity. Returns them in a tree - nested into each other.
     */
    async findDescendantsTree(entity, options) {
        // todo: throw exception if there is no column of this relation?
        const qb = this.createDescendantsQueryBuilder("treeEntity", "treeClosure", entity);
        FindOptionsUtils.applyOptionsToTreeQueryBuilder(qb, options);
        const entities = await qb.getRawAndEntities();
        const relationMaps = TreeRepositoryUtils.createRelationMaps(this.manager, this.metadata, "treeEntity", entities.raw);
        TreeRepositoryUtils.buildChildrenEntityTree(this.metadata, entity, entities.entities, relationMaps, {
            depth: -1,
            ...options,
        });
        return entity;
    }
    /**
     * Gets number of descendants of the entity.
     */
    countDescendants(entity) {
        return this.createDescendantsQueryBuilder("treeEntity", "treeClosure", entity).getCount();
    }
    /**
     * Creates a query builder used to get descendants of the entities in a tree.
     */
    createDescendantsQueryBuilder(alias, closureTableAlias, entity) {
        // create shortcuts for better readability
        const escape = (alias) => this.manager.connection.driver.escape(alias);
        if (this.metadata.treeType === "closure-table") {
            const joinCondition = this.metadata.closureJunctionTable.descendantColumns
                .map((column) => {
                return (escape(closureTableAlias) +
                    "." +
                    escape(column.propertyPath) +
                    " = " +
                    escape(alias) +
                    "." +
                    escape(column.referencedColumn.propertyPath));
            })
                .join(" AND ");
            const parameters = {};
            const whereCondition = this.metadata.closureJunctionTable.ancestorColumns
                .map((column) => {
                parameters[column.referencedColumn.propertyName] =
                    column.referencedColumn.getEntityValue(entity);
                return (escape(closureTableAlias) +
                    "." +
                    escape(column.propertyPath) +
                    " = :" +
                    column.referencedColumn.propertyName);
            })
                .join(" AND ");
            return this.createQueryBuilder(alias)
                .innerJoin(this.metadata.closureJunctionTable.tableName, closureTableAlias, joinCondition)
                .where(whereCondition)
                .setParameters(parameters);
        }
        else if (this.metadata.treeType === "nested-set") {
            const whereCondition = alias +
                "." +
                this.metadata.nestedSetLeftColumn.propertyPath +
                " BETWEEN " +
                "joined." +
                this.metadata.nestedSetLeftColumn.propertyPath +
                " AND joined." +
                this.metadata.nestedSetRightColumn.propertyPath;
            const parameters = {};
            const joinCondition = this.metadata
                .treeParentRelation.joinColumns.map((joinColumn) => {
                const parameterName = joinColumn.referencedColumn.propertyPath.replace(".", "_");
                parameters[parameterName] =
                    joinColumn.referencedColumn.getEntityValue(entity);
                return ("joined." +
                    joinColumn.referencedColumn.propertyPath +
                    " = :" +
                    parameterName);
            })
                .join(" AND ");
            return this.createQueryBuilder(alias)
                .innerJoin(this.metadata.targetName, "joined", whereCondition)
                .where(joinCondition, parameters);
        }
        else if (this.metadata.treeType === "materialized-path") {
            return this.createQueryBuilder(alias).where((qb) => {
                const subQuery = qb
                    .subQuery()
                    .select(`${this.metadata.targetName}.${this.metadata.materializedPathColumn.propertyPath}`, "path")
                    .from(this.metadata.target, this.metadata.targetName)
                    .whereInIds(this.metadata.getEntityIdMap(entity));
                if (DriverUtils.isSQLiteFamily(this.manager.connection.driver)) {
                    return `${alias}.${this.metadata.materializedPathColumn.propertyPath} LIKE ${subQuery.getQuery()} || '%'`;
                }
                else {
                    return `${alias}.${this.metadata.materializedPathColumn.propertyPath} LIKE NULLIF(CONCAT(${subQuery.getQuery()}, '%'), '%')`;
                }
            });
        }
        throw new TypeORMError(`Supported only in tree entities`);
    }
    /**
     * Gets all parents (ancestors) of the given entity. Returns them all in a flat array.
     */
    findAncestors(entity, options) {
        const qb = this.createAncestorsQueryBuilder("treeEntity", "treeClosure", entity);
        FindOptionsUtils.applyOptionsToTreeQueryBuilder(qb, options);
        return qb.getMany();
    }
    /**
     * Gets all parents (ancestors) of the given entity. Returns them in a tree - nested into each other.
     */
    async findAncestorsTree(entity, options) {
        // todo: throw exception if there is no column of this relation?
        const qb = this.createAncestorsQueryBuilder("treeEntity", "treeClosure", entity);
        FindOptionsUtils.applyOptionsToTreeQueryBuilder(qb, options);
        const entities = await qb.getRawAndEntities();
        const relationMaps = TreeRepositoryUtils.createRelationMaps(this.manager, this.metadata, "treeEntity", entities.raw);
        TreeRepositoryUtils.buildParentEntityTree(this.metadata, entity, entities.entities, relationMaps);
        return entity;
    }
    /**
     * Gets number of ancestors of the entity.
     */
    countAncestors(entity) {
        return this.createAncestorsQueryBuilder("treeEntity", "treeClosure", entity).getCount();
    }
    /**
     * Creates a query builder used to get ancestors of the entities in the tree.
     */
    createAncestorsQueryBuilder(alias, closureTableAlias, entity) {
        // create shortcuts for better readability
        // const escape = (alias: string) => this.manager.connection.driver.escape(alias);
        if (this.metadata.treeType === "closure-table") {
            const joinCondition = this.metadata.closureJunctionTable.ancestorColumns
                .map((column) => {
                return (closureTableAlias +
                    "." +
                    column.propertyPath +
                    " = " +
                    alias +
                    "." +
                    column.referencedColumn.propertyPath);
            })
                .join(" AND ");
            const parameters = {};
            const whereCondition = this.metadata.closureJunctionTable.descendantColumns
                .map((column) => {
                parameters[column.referencedColumn.propertyName] =
                    column.referencedColumn.getEntityValue(entity);
                return (closureTableAlias +
                    "." +
                    column.propertyPath +
                    " = :" +
                    column.referencedColumn.propertyName);
            })
                .join(" AND ");
            return this.createQueryBuilder(alias)
                .innerJoin(this.metadata.closureJunctionTable.tableName, closureTableAlias, joinCondition)
                .where(whereCondition)
                .setParameters(parameters);
        }
        else if (this.metadata.treeType === "nested-set") {
            const joinCondition = "joined." +
                this.metadata.nestedSetLeftColumn.propertyPath +
                " BETWEEN " +
                alias +
                "." +
                this.metadata.nestedSetLeftColumn.propertyPath +
                " AND " +
                alias +
                "." +
                this.metadata.nestedSetRightColumn.propertyPath;
            const parameters = {};
            const whereCondition = this.metadata
                .treeParentRelation.joinColumns.map((joinColumn) => {
                const parameterName = joinColumn.referencedColumn.propertyPath.replace(".", "_");
                parameters[parameterName] =
                    joinColumn.referencedColumn.getEntityValue(entity);
                return ("joined." +
                    joinColumn.referencedColumn.propertyPath +
                    " = :" +
                    parameterName);
            })
                .join(" AND ");
            return this.createQueryBuilder(alias)
                .innerJoin(this.metadata.targetName, "joined", joinCondition)
                .where(whereCondition, parameters);
        }
        else if (this.metadata.treeType === "materialized-path") {
            // example: SELECT * FROM category category WHERE (SELECT mpath FROM `category` WHERE id = 2) LIKE CONCAT(category.mpath, '%');
            return this.createQueryBuilder(alias).where((qb) => {
                const subQuery = qb
                    .subQuery()
                    .select(`${this.metadata.targetName}.${this.metadata.materializedPathColumn.propertyPath}`, "path")
                    .from(this.metadata.target, this.metadata.targetName)
                    .whereInIds(this.metadata.getEntityIdMap(entity));
                if (DriverUtils.isSQLiteFamily(this.manager.connection.driver)) {
                    return `${subQuery.getQuery()} LIKE ${alias}.${this.metadata.materializedPathColumn.propertyPath} || '%'`;
                }
                else {
                    return `${subQuery.getQuery()} LIKE CONCAT(${alias}.${this.metadata.materializedPathColumn.propertyPath}, '%')`;
                }
            });
        }
        throw new TypeORMError(`Supported only in tree entities`);
    }
}

//# sourceMappingURL=TreeRepository.js.map
