"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InClauseUnderConjunction = void 0;
const head = require("lodash.head");
const keys = require("lodash.keys");
const get = require("lodash.get");
const isEmpty = require("lodash.isempty");
const startsWith = require("lodash.startswith");
const includes = require("lodash.includes");
const compact = require("lodash.compact");
const ddfcsv_error_1 = require("../ddfcsv-error");
const WHERE_KEYWORD = 'where';
const JOIN_KEYWORD = 'join';
const KEY_IN = '$in';
const KEY_NIN = '$nin';
const KEY_AND = '$and';
const KEY_OR = '$or';
class InClauseUnderConjunction {
    constructor(parent, queryParam, options) {
        this.parent = parent;
        this.options = options;
        this.flow = {};
        this.fileReader = options.fileReader;
        this.datasetPath = options.basePath;
        this.query = queryParam;
        this.datapackage = options.datapackage;
        this.conceptsLookup = options.conceptsLookup;
    }
    isMatched() {
        this.flow.joinObject = get(this.query, JOIN_KEYWORD);
        return this.query.from === "datapoints" && this.flow.joinObject;
    }
    async getRecommendedFilesSet() {
        const { debug, error, warning } = this.options.diagnostic.prepareDiagnosticFor('getRecommendedFilesSet');
        if (this.isMatched()) {
            debug('plugin matched');
            let result;
            try {
                this.flow.processableClauses = await this.collectProcessableClauses();
                if (!this.flow.processableClauses)
                    return [];
                this.collectEntityFilesNames();
                const data = await this.collectEntities();
                this.fillEntityValuesHash(data);
                this.getFilesGroupsQueryClause();
                result = this.getOptimalFilesGroup();
            }
            catch (err) {
                error('wrong data processing', err);
                return [];
            }
            debug('recommended files found', result);
            return result;
        }
        else {
            const message = `Plugin "InClauseUnderConjunction" is not matched!`;
            warning(message);
            throw new ddfcsv_error_1.DdfCsvError(message, 'InClauseUnderConjunction plugin');
        }
    }
    collectProcessableClauses() {
        const joinKeys = keys(this.flow.joinObject).filter(key => { var _a; return ["entity_domain", "entity_set"].includes((_a = this.options.conceptsLookup.get(key.slice(1))) === null || _a === void 0 ? void 0 : _a.concept_type); });
        if (!joinKeys.length)
            return Promise.resolve(false);
        return Promise.all(joinKeys.map(joinKey => {
            const key = this.flow.joinObject[joinKey].key;
            const where = get(this.flow.joinObject, `${joinKey}.${WHERE_KEYWORD}`, {});
            return this.parent.queryData({
                select: { key: [key] },
                where,
                from: this.options.conceptsLookup.has(key) ? 'entities' : 'concepts'
            }, Object.assign({ joinID: joinKey }, this.options))
                .then(result => ({
                key,
                entities: new Set(result.map(row => row[key]))
            }));
        }));
    }
    collectEntityFilesNames() {
        this.flow.entityFilesNames = new Set();
        this.flow.entityResources = new Set();
        this.flow.fileNameToPrimaryKeyHash = new Map();
        for (const schemaResourceRecord of this.datapackage.ddfSchema.entities) {
            for (const clause of this.flow.processableClauses) {
                const key = clause.key;
                if (head(schemaResourceRecord.primaryKey) === key) {
                    for (const resourceName of schemaResourceRecord.resources) {
                        const resource = this.options.resourcesLookup.get(resourceName);
                        this.flow.entityResources.add(resource);
                        this.flow.entityFilesNames.add(resource.path);
                        this.flow.fileNameToPrimaryKeyHash.set(resource.path, key);
                    }
                }
            }
        }
        return this;
    }
    collectEntities() {
        const self = this;
        const actions = [...self.flow.entityResources].map(resource => {
            return (resource.data || (resource.data = self.parent.loadFile(resource.path, self.options)))
                .then(data => ({ result: data, file: resource.path }));
        });
        return Promise.all(actions);
    }
    fillEntityValuesHash(entitiesData) {
        const getSubdomainsFromRecord = record => compact(keys(record)
            .filter(key => startsWith(key, 'is--') && (record[key] === true))
            .map(key => key.replace(/^is--/, '')));
        this.flow.entityValueToFileHash = new Map();
        this.flow.entityValueToDomainHash = new Map();
        for (const entityFileDescriptor of entitiesData) {
            for (const entityRecord of entityFileDescriptor.result.data) {
                const primaryKeyForThisFile = this.flow.fileNameToPrimaryKeyHash.get(entityFileDescriptor.file);
                const domainsForCurrentRecord = [...getSubdomainsFromRecord(entityRecord)];
                if (isEmpty(domainsForCurrentRecord)) {
                    domainsForCurrentRecord.push(primaryKeyForThisFile);
                }
                const primaryKeyCellValue = entityRecord[primaryKeyForThisFile] || entityRecord[domainsForCurrentRecord[0]];
                this.flow.entityValueToDomainHash.set(primaryKeyCellValue, domainsForCurrentRecord);
                this.flow.entityValueToFileHash.set(primaryKeyCellValue, entityFileDescriptor.file);
            }
        }
        return this;
    }
    getFilesGroupsQueryClause() {
        var _a;
        const filesGroupsByClause = new Map();
        for (const clause of this.flow.processableClauses) {
            const filesGroupByClause = {
                datapoints: new Set()
            };
            for (const entityValueFromClause of clause.entities) {
                const entitiesByQuery = this.flow.entityValueToDomainHash.get(entityValueFromClause);
                for (const entityByQuery of entitiesByQuery) {
                    for (const schemaResourceRecord of this.datapackage.ddfSchema.datapoints) {
                        for (const resourceName of schemaResourceRecord.resources) {
                            if (includes(schemaResourceRecord.primaryKey, entityByQuery)) {
                                const resource = this.options.resourcesLookup.get(resourceName);
                                const constraint = (_a = resource.constraints) === null || _a === void 0 ? void 0 : _a[entityByQuery];
                                if (constraint) {
                                    if (constraint.includes(entityValueFromClause)) {
                                        filesGroupByClause.datapoints.add(resource.path);
                                    }
                                }
                                else {
                                    filesGroupByClause.datapoints.add(resource.path);
                                }
                            }
                        }
                    }
                }
            }
            filesGroupsByClause.set(clause.key, filesGroupByClause);
        }
        this.flow.filesGroupsByClause = filesGroupsByClause;
        return this;
    }
    getOptimalFilesGroup() {
        const entities = this.flow.entityFilesNames;
        const concepts = new Set();
        for (const schemaResourceRecord of this.datapackage.ddfSchema.concepts) {
            for (const resourceName of schemaResourceRecord.resources) {
                concepts.add(this.options.resourcesLookup.get(resourceName).path);
            }
        }
        const clauseKeys = this.flow.filesGroupsByClause.keys();
        let datapoints = Array.from(this.flow.filesGroupsByClause.get(clauseKeys.next().value).datapoints);
        for (const key of clauseKeys) {
            datapoints = this.intersectArray(datapoints, Array.from(this.flow.filesGroupsByClause.get(key).datapoints));
        }
        return [...Array.from(concepts), ...Array.from(entities)].concat(datapoints);
    }
    intersectArray(array1, array2) {
        return array1.filter(value => array2.includes(value));
    }
}
exports.InClauseUnderConjunction = InClauseUnderConjunction;
//# sourceMappingURL=in-clause-under-conjunction.js.map