/**
 * Copyright (c) 2020-present, Goldman Sachs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { assertTrue, filterByType, findLast, generateEnumerableNameFromToken, guaranteeNonNullable, uniq, UnsupportedOperationError, } from '@finos/legend-shared';
import { AggregationAwareSetImplementation } from '../metamodel/pure/packageableElements/mapping/aggregationAware/AggregationAwareSetImplementation.js';
import { RootRelationalInstanceSetImplementation } from '../metamodel/pure/packageableElements/store/relational/mapping/RootRelationalInstanceSetImplementation.js';
import { InstanceSetImplementation } from '../metamodel/pure/packageableElements/mapping/InstanceSetImplementation.js';
import { LakehouseRuntime, } from '../metamodel/pure/packageableElements/runtime/Runtime.js';
import { OperationSetImplementation } from '../metamodel/pure/packageableElements/mapping/OperationSetImplementation.js';
import { ObjectInputType } from '../metamodel/pure/packageableElements/mapping/DEPRECATED__MappingTest.js';
// ----------------------------------------- Mapping -----------------------------------------
/**
 * Get all included mappings, accounted for loop and duplication (which should be caught by compiler)
 */
export const getAllIncludedMappings = (mapping) => {
    const visited = new Set();
    visited.add(mapping);
    const resolveIncludes = (_mapping) => {
        _mapping.includes.forEach((incMapping) => {
            if (!visited.has(incMapping.included.value)) {
                visited.add(incMapping.included.value);
                resolveIncludes(incMapping.included.value);
            }
        });
    };
    resolveIncludes(mapping);
    visited.delete(mapping);
    return Array.from(visited);
};
export const getAllClassMappings = (mapping) => uniq(mapping.classMappings.concat(getAllIncludedMappings(mapping)
    .map((e) => e.classMappings)
    .flat()));
export const getAllEnumerationMappings = (mapping) => uniq(mapping.enumerationMappings.concat(getAllIncludedMappings(mapping)
    .map((e) => e.enumerationMappings)
    .flat()));
export const extractClassMappingsFromAggregationAwareClassMappings = (mapping) => {
    const aggregationAwareClassMappings = mapping.classMappings.filter(filterByType(AggregationAwareSetImplementation));
    return [
        ...aggregationAwareClassMappings.map((aggregate) => aggregate.mainSetImplementation),
        ...aggregationAwareClassMappings
            .map((aggregate) => aggregate.aggregateSetImplementations.map((setImpl) => setImpl.setImplementation))
            .flat(),
    ];
};
export const getOwnClassMappingById = (mapping, id) => guaranteeNonNullable([
    ...mapping.classMappings,
    ...extractClassMappingsFromAggregationAwareClassMappings(mapping),
].find((classMapping) => classMapping.id.value === id), `Can't find class mapping with ID '${id}' in mapping '${mapping.path}'`);
export const getClassMappingById = (mapping, id) => guaranteeNonNullable([
    ...getAllClassMappings(mapping),
    ...extractClassMappingsFromAggregationAwareClassMappings(mapping),
].find((classMapping) => classMapping.id.value === id), `Can't find class mapping with ID '${id}' in mapping '${mapping.path}'`);
export const getOwnClassMappingsByClass = (mapping, _class) => 
// TODO: Add association property Mapping to class mappings, AggregationAwareSetImplementation, mappingClass
// NOTE: Add in the proper order so find root can resolve properly down the line
mapping.classMappings.filter((classMapping) => classMapping.class.value === _class);
export const getClassMappingsByClass = (mapping, _class) => 
// TODO: Add association property Mapping to class mappings, AggregationAwareSetImplementation, mappingClass
// NOTE: Add in the proper order so find root can resolve properly down the line
getAllClassMappings(mapping).filter((classMapping) => classMapping.class.value === _class);
export const getEnumerationMappingsByEnumeration = (mapping, enumeration) => getAllEnumerationMappings(mapping).filter((enumerationMapping) => enumerationMapping.enumeration.value === enumeration);
export const getAllSuperSetImplementations = (currentSetImpl) => {
    if (currentSetImpl instanceof RootRelationalInstanceSetImplementation &&
        currentSetImpl.superSetImplementationId) {
        const superSetImpl = [
            getClassMappingById(currentSetImpl._PARENT, currentSetImpl.superSetImplementationId),
        ];
        const superSetImplFromParents = superSetImpl
            .map((s) => getAllSuperSetImplementations(s))
            .flat();
        return superSetImpl.concat(superSetImplFromParents);
    }
    else {
        return [];
    }
};
export const findRootSetImplementation = (classMappingsWithSimilarTarget) => {
    // NOTE: we use find last so that we can be sure we're picking the root set implementation of the current mapping first,
    // not the root set implementation of one of the included mappings, in case there's no root, we would prefer the root
    // of one of the included mappings (whichever comes later),
    // if there is not root set, and only one set implementation is found, we assume that is the root
    if (classMappingsWithSimilarTarget.length === 1 &&
        classMappingsWithSimilarTarget[0]?.root.value === false) {
        return classMappingsWithSimilarTarget[0];
    }
    return findLast(classMappingsWithSimilarTarget, (setImp) => setImp.root.value);
};
export const getRootSetImplementation = (mapping, _class) => findRootSetImplementation(getClassMappingsByClass(mapping, _class));
export const findPropertyMapping = (instanceSetImplementation, propertyName, targetId) => {
    let properties = undefined;
    properties = instanceSetImplementation.propertyMappings.filter((propertyMapping) => propertyMapping.property.value.name === propertyName);
    if (targetId === undefined || properties.length === 1) {
        return properties[0];
    }
    return properties.find((propertyMapping) => propertyMapping.targetSetImplementation &&
        propertyMapping.targetSetImplementation.value.id.value === targetId);
};
/**
 * Get all child set implementation of an operation set implementation (including itself).
 * This takes into account loops and duplication.
 */
export const getAllChildSetImplementations = (operationSetImplementation) => {
    const visitedOperations = new Set();
    visitedOperations.add(operationSetImplementation);
    const _leaves = new Set();
    const resolveLeaves = (_opSetImpl) => {
        _opSetImpl.parameters.forEach((p) => {
            const setImp = p.setImplementation.value;
            if (setImp instanceof OperationSetImplementation &&
                !visitedOperations.has(setImp)) {
                visitedOperations.add(setImp);
                resolveLeaves(setImp);
            }
            else {
                _leaves.add(setImp);
            }
        });
    };
    resolveLeaves(operationSetImplementation);
    visitedOperations.delete(operationSetImplementation);
    return Array.from(_leaves).concat(Array.from(visitedOperations));
};
/**
 * Get all leaf set implementations (i.e. no operation) of an operation set implementation
 * This takes into account loops and duplication.
 */
export const getLeafSetImplementations = (operationSetImplementation) => getAllChildSetImplementations(operationSetImplementation).filter((child) => !(child instanceof OperationSetImplementation));
export const getObjectInputType = (type) => {
    switch (type) {
        case ObjectInputType.JSON:
            return ObjectInputType.JSON;
        case ObjectInputType.XML:
            return ObjectInputType.XML;
        default:
            throw new UnsupportedOperationError(`Encountered unsupported object input type '${type}'`);
    }
};
export const getMappingCompatibleClasses = (mapping, classes) => {
    const mappedClasses = new Set();
    getAllClassMappings(mapping).forEach((cm) => mappedClasses.add(cm.class.value));
    return classes.filter((c) => mappedClasses.has(c));
};
export const getClassCompatibleMappings = (_class, mappings) => {
    const mappingsWithClassMapped = mappings.filter((mapping) => mapping.classMappings.some((cm) => cm.class.value === _class));
    const resolvedMappingIncludes = mappings.filter((mapping) => getAllIncludedMappings(mapping).some((e) => mappingsWithClassMapped.includes(e)));
    return uniq([...mappingsWithClassMapped, ...resolvedMappingIncludes]);
};
export const findMappingLocalProperty = (mappings, propertyName) => {
    let newProperty;
    mappings.forEach((mapping) => {
        mapping.classMappings.forEach((setImplementation) => {
            if (setImplementation instanceof InstanceSetImplementation) {
                setImplementation.propertyMappings.forEach((propertyMapping) => {
                    if (propertyMapping.localMappingProperty &&
                        propertyMapping.property.value.name === propertyName) {
                        newProperty = propertyMapping.property.value;
                    }
                });
            }
        });
    });
    return newProperty;
};
// ----------------------------------------- Runtime -----------------------------------------
export const getAllIdentifiedConnections = (runtime) => runtime.connections.flatMap((storeConnections) => storeConnections.storeConnections);
export const generateIdentifiedConnectionId = (runtime) => {
    const generatedId = generateEnumerableNameFromToken(getAllIdentifiedConnections(runtime).map((identifiedConnection) => identifiedConnection.id), 'connection');
    assertTrue(!getAllIdentifiedConnections(runtime).find((identifiedConnection) => identifiedConnection.id === generatedId), `Can't auto-generate connection ID with value '${generatedId}'`);
    return generatedId;
};
const isLakehouseRuntime = (runtimeValue) => {
    return runtimeValue instanceof LakehouseRuntime;
};
export const getMappingCompatibleRuntimes = (mapping, runtimes) => 
// If the runtime claims to cover some mappings which include the specified mapping,
// then we deem the runtime to be compatible with the such mapping
runtimes.filter((runtime) => runtime.runtimeValue.mappings
    .flatMap((mappingReference) => [
    mappingReference.value,
    ...getAllIncludedMappings(mappingReference.value),
])
    .includes(mapping) ||
    // TODO: remove once mappings are added to the protocol
    // include lakehouse runtime as for now they have no reference to mappings within their protocol
    isLakehouseRuntime(runtime.runtimeValue));
//# sourceMappingURL=DSL_Mapping_Helper.js.map