/**
 * 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 { LogEvent, assertNonNullable, assertNonEmptyString, guaranteeType, guaranteeNonNullable, returnUndefOnError, UnsupportedOperationError, } from '@finos/legend-shared';
import { GRAPH_MANAGER_EVENT } from '../../../../../../../__lib__/GraphManagerEvent.js';
import { PurePropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/modelToModel/mapping/PurePropertyMapping.js';
import { RelationalPropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/RelationalPropertyMapping.js';
import { EmbeddedFlatDataPropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/flatData/mapping/EmbeddedFlatDataPropertyMapping.js';
import { FlatDataPropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/flatData/mapping/FlatDataPropertyMapping.js';
import { Class } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Class.js';
import { InferableMappingElementIdExplicitValue } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/InferableMappingElementId.js';
import { PackageableElementImplicitReference, } from '../../../../../../../graph/metamodel/pure/packageableElements/PackageableElementReference.js';
import { PropertyImplicitReference, } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/PropertyReference.js';
import { RootRelationalInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/RootRelationalInstanceSetImplementation.js';
import { OtherwiseEmbeddedRelationalInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/OtherwiseEmbeddedRelationalInstanceSetImplementation.js';
import { EmbeddedRelationalInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/EmbeddedRelationalInstanceSetImplementation.js';
import { InlineEmbeddedRelationalInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/InlineEmbeddedRelationalInstanceSetImplementation.js';
import { AssociationImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/AssociationImplementation.js';
import { V1_buildRelationalOperationElement } from './helpers/V1_DatabaseBuilderHelper.js';
import { V1_buildEmbeddedRelationalMappingProperty } from './helpers/V1_RelationalPropertyMappingBuilder.js';
import { XStorePropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/xStore/XStorePropertyMapping.js';
import { Property } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Property.js';
import { MappingClass } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/MappingClass.js';
import { LocalMappingPropertyInfo } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/LocalMappingPropertyInfo.js';
import { AggregationAwarePropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/aggregationAware/AggregationAwarePropertyMapping.js';
import { V1_buildRawLambdaWithResolvedPaths } from './helpers/V1_ValueSpecificationPathResolver.js';
import { V1_deserializeRelationalOperationElement, V1_serializeRelationalOperationElement, } from '../../pureProtocol/serializationHelpers/V1_DatabaseSerializationHelper.js';
import { V1_transformRelationalOperationElement } from '../from/V1_DatabaseTransformer.js';
import { V1_GraphTransformerContextBuilder } from '../from/V1_GraphTransformerContext.js';
import { getAllEnumerationMappings, getAllIncludedMappings, getClassMappingById, getClassMappingsByClass, } from '../../../../../../../graph/helpers/DSL_Mapping_Helper.js';
import { GraphBuilderError } from '../../../../../../../graph-manager/GraphManagerUtils.js';
import { BindingTransformer } from '../../../../../../../graph/metamodel/pure/packageableElements/externalFormat/store/DSL_ExternalFormat_BindingTransformer.js';
import { V1_resolveBinding } from './V1_DSL_ExternalFormat_GraphBuilderHelper.js';
import { INTERNAL__UnresolvedSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/INTERNAL__UnresolvedSetImplementation.js';
import { getAssociatedPropertyClass, getOwnProperty, getClassProperty, } from '../../../../../../../graph/helpers/DomainHelper.js';
import { SetImplementationImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/SetImplementationReference.js';
import { EnumerationMappingExplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/EnumerationMappingReference.js';
import { FlatDataAssociationPropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/flatData/mapping/FlatDataAssociationPropertyMapping.js';
import { INTERNAL__UnknownPropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/INTERNAL__UnknownPropertyMapping.js';
import { INTERNAL__PseudoMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/INTERNAL__PseudoMapping.js';
import { RelationFunctionPropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/relationFunction/RelationFunctionPropertyMapping.js';
import { RelationColumn } from '../../../../../../../graph/metamodel/pure/packageableElements/relation/RelationType.js';
const TEMPORARY__resolveSetImplementationByID = (mapping, id, context) => {
    const classMapping = returnUndefOnError(() => getClassMappingById(mapping, id));
    const isMappingWithUnknownMappingIncludes = getAllIncludedMappings(mapping).includes(INTERNAL__PseudoMapping.INSTANCE);
    if (!classMapping) {
        const message = `Can't find class mapping with ID '${id}' in mapping '${mapping.path}'`;
        /**
         * If the mapping has unknown mapping includes, this kind of problems
         * might be due to the fact that the class mapping is not reachable due to the system
         * not knowing how to analyze the unknown mapping include, as such, we will not throw
         * errors, otherwise, we should in strict-mode
         *
         * See https://github.com/finos/legend-studio/issues/880
         * See https://github.com/finos/legend-studio/issues/941
         *
         * @discrepancy graph-building
         */
        if (context.options?.strict && !isMappingWithUnknownMappingIncludes) {
            throw new GraphBuilderError(message);
        }
        context.logService.warn(LogEvent.create(message));
        return new INTERNAL__UnresolvedSetImplementation(id, mapping);
    }
    return classMapping;
};
const resolvePropertyMappingSourceImplementation = (immediateParent, value, topParent, context) => {
    if (immediateParent instanceof AssociationImplementation) {
        if (value.source) {
            return TEMPORARY__resolveSetImplementationByID(immediateParent._PARENT, value.source, context);
        }
        const property = getOwnProperty(immediateParent.association.value, value.property.property);
        const _class = getAssociatedPropertyClass(immediateParent.association.value, property);
        const setImpls = getClassMappingsByClass(immediateParent._PARENT, _class);
        return setImpls.find((r) => r.root.value) ?? setImpls[0];
    }
    return topParent;
};
export class V1_PropertyMappingBuilder {
    context;
    immediateParent; // either root instance set implementation or the immediate embedded parent property mapping (needed for processing embedded property mapping)
    topParent;
    allEnumerationMappings = [];
    tableAliasIndex;
    allClassMappings;
    xStoreParent;
    aggregationAwareParent;
    constructor(context, immediateParent, topParent, allEnumerationMappings, tableAliasMap, allClassMappings, xStoreParent, aggregationAwareParent) {
        this.context = context;
        this.immediateParent = immediateParent;
        this.topParent = topParent;
        this.allEnumerationMappings = allEnumerationMappings;
        this.tableAliasIndex = tableAliasMap ?? new Map();
        this.allClassMappings = allClassMappings ?? [];
        this.xStoreParent = xStoreParent;
        this.aggregationAwareParent = aggregationAwareParent;
    }
    visit_PropertyMapping(propertyMapping) {
        const extraPropertyMappingBuilders = this.context.extensions.plugins.flatMap((plugin) => plugin.V1_getExtraPropertyMappingBuilders?.() ?? []);
        for (const builder of extraPropertyMappingBuilders) {
            const extraPropertyMapping = builder(propertyMapping, this.context);
            if (extraPropertyMapping) {
                return extraPropertyMapping;
            }
        }
        throw new UnsupportedOperationError(`Can't build property mapping: no compatible builder available from plugins`, propertyMapping);
    }
    visit_INTERNAL__UnknownPropertyMapping(propertyMapping) {
        const metamodel = new INTERNAL__UnknownPropertyMapping();
        metamodel.content = propertyMapping.content;
        return metamodel;
    }
    visit_PurePropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Pure instance property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Pure instance property mapping 'property.property' field is missing or empty`);
        assertNonNullable(protocol.transform, `Pure instance property mapping 'transform' field is missing`);
        // NOTE: mapping for derived property is not supported
        let property;
        let localMapping;
        if (protocol.localMappingProperty) {
            const localMappingProperty = protocol.localMappingProperty;
            const mappingClass = new MappingClass(`${this.topParent?._PARENT.path}_${this.topParent?.id}${protocol.property.property}`);
            const _multiplicity = this.context.graph.getMultiplicity(localMappingProperty.multiplicity.lowerBound, localMappingProperty.multiplicity.upperBound);
            const _property = new Property(protocol.property.property, _multiplicity, this.context.resolveGenericType(localMappingProperty.type), mappingClass);
            property = PropertyImplicitReference.create(PackageableElementImplicitReference.create(mappingClass, protocol.property.class), _property);
            localMapping = new LocalMappingPropertyInfo();
            localMapping.localMappingProperty = true;
            localMapping.localMappingPropertyMultiplicity = _multiplicity;
            localMapping.localMappingPropertyType = this.context.resolveType(localMappingProperty.type);
        }
        else {
            assertNonEmptyString(protocol.property.class, `Pure instance property mapping 'property.class' field is missing or empty`);
            property = this.context.resolveProperty(protocol.property);
        }
        const propertyType = property.value.genericType.value.rawType;
        let targetSetImplementation;
        const topParent = guaranteeNonNullable(this.topParent);
        if (propertyType instanceof Class) {
            if (protocol.target) {
                targetSetImplementation = TEMPORARY__resolveSetImplementationByID(topParent._PARENT, protocol.target, this.context);
            }
            else {
                // NOTE: if no there is one non-root class mapping, auto-nominate that as the target set implementation
                targetSetImplementation = getClassMappingsByClass(topParent._PARENT, guaranteeType(propertyType, Class))[0];
            }
        }
        const sourceSetImplementation = protocol.source
            ? TEMPORARY__resolveSetImplementationByID(topParent._PARENT, protocol.source, this.context)
            : undefined;
        const purePropertyMapping = new PurePropertyMapping(topParent, property, V1_buildRawLambdaWithResolvedPaths([], protocol.transform.body, this.context), SetImplementationImplicitReference.create(sourceSetImplementation ?? topParent, protocol.source), targetSetImplementation
            ? SetImplementationImplicitReference.create(targetSetImplementation, protocol.target)
            : undefined, protocol.explodeProperty);
        if (protocol.enumMappingId) {
            const enumerationMapping = this.allEnumerationMappings.find((em) => em.id.value === protocol.enumMappingId);
            if (!enumerationMapping) {
                // TODO: Since we don't support includedMappings, this will throw errors, but right now we can just make it undefined.
                this.context.logService.debug(LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE), `Can't find enumeration mapping with ID '${protocol.enumMappingId}' in mapping '${topParent._PARENT.path}' (perhaps because we haven't supported included mappings)`);
            }
            purePropertyMapping.transformer = enumerationMapping
                ? EnumerationMappingExplicitReference.create(enumerationMapping)
                : undefined;
        }
        purePropertyMapping.localMappingProperty = localMapping;
        return purePropertyMapping;
    }
    visit_FlatDataPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Flat-data property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Flat-data property mapping 'property.property' field is missing or empty`);
        assertNonNullable(protocol.transform, `Flat-data property mapping 'transform' field is missing`);
        // NOTE: there are cases that property pointer class might be missing, such as when we transform grammar to JSON
        // since we do not do look up, due to nesting structure introudced by embedded mappings, we might not have the class information
        // as such, here we have to resolve the class being mapped depending on where the property mapping is in the class mapping
        let propertyOwnerClass;
        if (protocol.property.class) {
            propertyOwnerClass = this.context.resolveClass(protocol.property.class).value;
        }
        else if (this.immediateParent instanceof EmbeddedFlatDataPropertyMapping) {
            propertyOwnerClass = this.immediateParent.class.value;
        }
        else {
            throw new GraphBuilderError(`Can't find property owner class for property '${protocol.property.property}'`);
        }
        // NOTE: mapping for derived property is not supported
        const property = getClassProperty(propertyOwnerClass, protocol.property.property);
        const sourceSetImplementation = guaranteeNonNullable(this.immediateParent instanceof EmbeddedFlatDataPropertyMapping
            ? this.immediateParent
            : this.topParent);
        // target
        let targetSetImplementation;
        const propertyType = property.genericType.value.rawType;
        if (propertyType instanceof Class && protocol.target) {
            targetSetImplementation = this.topParent
                ? TEMPORARY__resolveSetImplementationByID(this.topParent._PARENT, protocol.target, this.context)
                : undefined;
        }
        const flatDataPropertyMapping = new FlatDataPropertyMapping(this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(propertyOwnerClass, protocol.property.class ?? ''), property), V1_buildRawLambdaWithResolvedPaths([], protocol.transform.body, this.context), SetImplementationImplicitReference.create(sourceSetImplementation, protocol.source), targetSetImplementation
            ? SetImplementationImplicitReference.create(targetSetImplementation, protocol.target)
            : undefined);
        if (protocol.enumMappingId) {
            const enumerationMapping = this.allEnumerationMappings.find((em) => em.id.value === protocol.enumMappingId);
            if (!enumerationMapping) {
                // TODO: Since we don't support includedMappings, this will throw errors, but right now we can just make it undefined.
                this.context.logService.debug(LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE), `Can't find enumeration mapping with ID '${protocol.enumMappingId}' in mapping '${this.topParent?._PARENT.path} (perhaps because we haven't supported included mappings)`);
            }
            flatDataPropertyMapping.transformer = enumerationMapping
                ? EnumerationMappingExplicitReference.create(enumerationMapping)
                : undefined;
        }
        return flatDataPropertyMapping;
    }
    visit_EmbeddedFlatDataPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Embedded flat-data property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Embedded flat-data property mapping property 'property.property' field is missing or empty`);
        // NOTE: there are cases that property pointer class might be missing, such as when we transform grammar to JSON
        // since we do not do look up, due to nesting structure introudced by embedded mappings, we might not have the class information
        // as such, here we have to resolve the class being mapped depending on where the property mapping is in the class mapping
        let propertyOwnerClass;
        if (protocol.property.class) {
            propertyOwnerClass = this.context.resolveClass(protocol.property.class).value;
        }
        else if (this.immediateParent instanceof EmbeddedFlatDataPropertyMapping) {
            propertyOwnerClass = this.immediateParent.class.value;
        }
        else {
            throw new GraphBuilderError(`Can't find property owner class for property '${protocol.property.property}'`);
        }
        const property = getClassProperty(propertyOwnerClass, protocol.property.property);
        let _class;
        if (protocol.class) {
            _class = this.context.resolveClass(protocol.class);
        }
        else {
            const propertyType = property.genericType.value.rawType;
            const complexClass = guaranteeType(propertyType, Class, 'Only complex classes can be the target of an embedded property mapping');
            _class = PackageableElementImplicitReference.create(complexClass, '');
        }
        const sourceSetImplementation = guaranteeNonNullable(this.immediateParent instanceof EmbeddedFlatDataPropertyMapping
            ? this.immediateParent
            : this.topParent);
        const embeddedPropertyMapping = new EmbeddedFlatDataPropertyMapping(this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(propertyOwnerClass, protocol.property.class ?? ''), property), guaranteeNonNullable(this.topParent), SetImplementationImplicitReference.create(sourceSetImplementation, protocol.source), _class, InferableMappingElementIdExplicitValue.create(`${sourceSetImplementation.id.value}.${property.name}`, ''), undefined);
        embeddedPropertyMapping.targetSetImplementation = protocol.target
            ? SetImplementationImplicitReference.create(embeddedPropertyMapping, protocol.target)
            : undefined;
        embeddedPropertyMapping.propertyMappings = protocol.propertyMappings.map((propertyMapping) => propertyMapping.accept_PropertyMappingVisitor(new V1_PropertyMappingBuilder(this.context, embeddedPropertyMapping, this.topParent, this.allEnumerationMappings)));
        return embeddedPropertyMapping;
    }
    visit_RelationalPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Relational property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Relational property mapping 'property.property' field is missing or empty`);
        assertNonNullable(protocol.relationalOperation, `Relational property mapping 'relationalOperation' field is missing`);
        // NOTE: mapping for derived property is not supported
        let propertyOwner;
        let property;
        let localMapping;
        if (protocol.localMappingProperty) {
            const localMappingProperty = protocol.localMappingProperty;
            const mappingClass = new MappingClass(`${this.topParent?._PARENT.path}_${this.topParent?.id}${protocol.property.property}`);
            const _multiplicity = this.context.graph.getMultiplicity(localMappingProperty.multiplicity.lowerBound, localMappingProperty.multiplicity.upperBound);
            const _property = new Property(protocol.property.property, _multiplicity, this.context.resolveGenericType(localMappingProperty.type), mappingClass);
            property = PropertyImplicitReference.create(PackageableElementImplicitReference.create(mappingClass, protocol.property.class), _property).value;
            localMapping = new LocalMappingPropertyInfo();
            localMapping.localMappingProperty = true;
            localMapping.localMappingPropertyMultiplicity = _multiplicity;
            localMapping.localMappingPropertyType = this.context.resolveType(localMappingProperty.type);
            propertyOwner = property._OWNER;
        }
        else {
            if (this.immediateParent instanceof AssociationImplementation) {
                propertyOwner = this.immediateParent.association.value;
            }
            else if (protocol.property.class) {
                propertyOwner = this.context.resolveClass(protocol.property.class).value;
            }
            else if (this.immediateParent instanceof
                EmbeddedRelationalInstanceSetImplementation) {
                propertyOwner =
                    this.immediateParent._OWNER instanceof
                        EmbeddedRelationalInstanceSetImplementation &&
                        this.immediateParent.property.value.name ===
                            protocol.property.property
                        ? this.immediateParent.property.ownerReference.value
                        : this.immediateParent.class.value;
            }
            else {
                throw new GraphBuilderError(`Can't find property owner class for property '${protocol.property.property}'`);
            }
            property =
                propertyOwner instanceof Class
                    ? getClassProperty(propertyOwner, protocol.property.property)
                    : getOwnProperty(propertyOwner, protocol.property.property);
        }
        // NOTE: mapping for derived property is not supported
        // since we are not doing embedded property mappings yet, the target must have already been added to the mapping
        const propertyType = property.genericType.value.rawType;
        let targetSetImplementation;
        if (propertyType instanceof Class) {
            let parentMapping = this.topParent?._PARENT;
            if (!parentMapping &&
                this.immediateParent instanceof AssociationImplementation) {
                parentMapping = this.immediateParent._PARENT;
            }
            if (protocol.target) {
                targetSetImplementation = parentMapping
                    ? TEMPORARY__resolveSetImplementationByID(parentMapping, protocol.target, this.context)
                    : undefined;
            }
            else {
                targetSetImplementation = parentMapping
                    ? getClassMappingsByClass(parentMapping, guaranteeType(propertyType, Class))[0]
                    : undefined;
            }
        }
        const sourceSetImplementation = guaranteeNonNullable(resolvePropertyMappingSourceImplementation(this.immediateParent, protocol, this.topParent, this.context));
        const relationalPropertyMapping = new RelationalPropertyMapping(this.topParent ?? this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(propertyOwner, protocol.property.class ?? ''), property), SetImplementationImplicitReference.create(sourceSetImplementation, protocol.source), targetSetImplementation
            ? SetImplementationImplicitReference.create(targetSetImplementation, protocol.target)
            : undefined);
        if (protocol.bindingTransformer?.binding) {
            const bindingTransformer = new BindingTransformer();
            const binding = V1_resolveBinding(protocol.bindingTransformer.binding, this.context);
            bindingTransformer.binding = binding;
            relationalPropertyMapping.bindingTransformer = bindingTransformer;
        }
        // NOTE: we only need to use the raw form of the operation for the editor
        // but we need to process it anyway so we can:
        // 1. do analytics on table alias map
        // 2. and to resolve paths (similar to lambda).
        // As such, we will need to do a full round-trip processing for the operation
        // See https://github.com/finos/legend-studio/pull/173
        try {
            relationalPropertyMapping.relationalOperation =
                V1_serializeRelationalOperationElement(V1_transformRelationalOperationElement(V1_buildRelationalOperationElement(V1_deserializeRelationalOperationElement(protocol.relationalOperation), this.context, this.tableAliasIndex, [], undefined, true), new V1_GraphTransformerContextBuilder(this.context.extensions.plugins).build(), {
                    // NOTE: here we will always resolve paths found in the operation to full
                    // since right now we still delete the section index, which will cause
                    // these paths to be potentially become corrupted.
                    TEMPORARY__resolveToFullPath: true,
                }));
        }
        catch {
            relationalPropertyMapping.relationalOperation =
                protocol.relationalOperation;
        }
        if (protocol.enumMappingId) {
            const enumerationMapping = this.allEnumerationMappings.find((em) => em.id.value === protocol.enumMappingId);
            if (!enumerationMapping) {
                // TODO: Since we don't support includedMappings, this will throw errors, but right now we can just make it undefined.
                this.context.logService.debug(LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE), `Can't find enumeration mapping with ID '${protocol.enumMappingId}' in mapping '${this.topParent?._PARENT.path}' (perhaps because we haven't supported included mappings)`);
            }
            relationalPropertyMapping.transformer = enumerationMapping
                ? EnumerationMappingExplicitReference.create(enumerationMapping)
                : undefined;
        }
        relationalPropertyMapping.localMappingProperty = localMapping;
        return relationalPropertyMapping;
    }
    visit_FlatDataAssociationPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `FlatData property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `FlatData property mapping 'property.property' field is missing or empty`);
        assertNonNullable(protocol.flatData, `FlatData property mapping 'flatdata' field is missing`);
        assertNonNullable(protocol.sectionName, `FlatData property mapping 'sectionName' field is missing`);
        // NOTE: mapping for derived property is not supported
        let propertyOwner;
        if (this.immediateParent instanceof AssociationImplementation) {
            propertyOwner = this.immediateParent.association.value;
        }
        else {
            throw new GraphBuilderError(`Can't find property owner class for property '${protocol.property.property}'`);
        }
        const property = getOwnProperty(propertyOwner, protocol.property.property);
        // NOTE: mapping for derived property is not supported
        // since we are not doing embedded property mappings yet, the target must have already been added to the mapping
        const propertyType = property.genericType.value.rawType;
        let targetSetImplementation;
        if (propertyType instanceof Class) {
            const parentMapping = this.immediateParent._PARENT;
            if (protocol.target) {
                targetSetImplementation = TEMPORARY__resolveSetImplementationByID(parentMapping, protocol.target, this.context);
            }
            else {
                targetSetImplementation = getClassMappingsByClass(parentMapping, guaranteeType(propertyType, Class))[0];
            }
        }
        const sourceSetImplementation = guaranteeNonNullable(resolvePropertyMappingSourceImplementation(this.immediateParent, protocol, this.topParent, this.context));
        const flatDataAssociationPropertyMapping = new FlatDataAssociationPropertyMapping(this.topParent ?? this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(propertyOwner, protocol.property.class ?? ''), property), SetImplementationImplicitReference.create(sourceSetImplementation, protocol.source), targetSetImplementation
            ? SetImplementationImplicitReference.create(targetSetImplementation, protocol.target)
            : undefined);
        flatDataAssociationPropertyMapping.flatData = protocol.flatData;
        flatDataAssociationPropertyMapping.sectionName = protocol.sectionName;
        return flatDataAssociationPropertyMapping;
    }
    visit_InlineEmbeddedPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Inline embedded property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Inline embedded property mapping 'property.property' field is missing or empty`);
        let propertyOwnerClass;
        if (protocol.property.class) {
            propertyOwnerClass = this.context.resolveClass(protocol.property.class).value;
        }
        else if (this.immediateParent instanceof RootRelationalInstanceSetImplementation ||
            this.immediateParent instanceof
                EmbeddedRelationalInstanceSetImplementation) {
            propertyOwnerClass = this.immediateParent.class.value;
        }
        else {
            throw new GraphBuilderError(`Can't find property owner class for property '${protocol.property.property}'`);
        }
        const property = getClassProperty(propertyOwnerClass, protocol.property.property);
        const propertyType = property.genericType.value.rawType;
        const complexClass = guaranteeType(propertyType, Class, 'Only complex classes can be the target of an embedded property mapping');
        const _class = PackageableElementImplicitReference.create(complexClass, protocol.property.class ?? '');
        const id = `${this.immediateParent.id.value}_${property.name}`;
        const topParent = guaranteeNonNullable(this.topParent);
        const sourceSetImplementation = this.immediateParent instanceof RootRelationalInstanceSetImplementation
            ? this.immediateParent
            : topParent;
        const inline = new InlineEmbeddedRelationalInstanceSetImplementation(this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(propertyOwnerClass, protocol.property.class ?? ''), property), guaranteeType(this.topParent, RootRelationalInstanceSetImplementation), SetImplementationImplicitReference.create(sourceSetImplementation, protocol.source), _class, InferableMappingElementIdExplicitValue.create(id, ''), undefined);
        inline.inlineSetImplementation = TEMPORARY__resolveSetImplementationByID(topParent._PARENT, protocol.setImplementationId, this.context);
        return inline;
    }
    visit_EmbeddedRelationalPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Embedded relational property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Embedded relational property mapping 'property.property' field is missing or empty`);
        const property = V1_buildEmbeddedRelationalMappingProperty(protocol, this.immediateParent, guaranteeNonNullable(this.topParent), this.context);
        const embedded = new EmbeddedRelationalInstanceSetImplementation(this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(property.propertyOwnerClass, protocol.property.class ?? ''), property.property), guaranteeType(this.topParent, RootRelationalInstanceSetImplementation), SetImplementationImplicitReference.create(property.sourceSetImplementation, protocol.source), property._class, InferableMappingElementIdExplicitValue.create(`${property.id.value}`, ''), undefined);
        embedded.primaryKey = protocol.classMapping.primaryKey.map((key) => V1_buildRelationalOperationElement(key, this.context, this.tableAliasIndex, [], undefined, true));
        embedded.propertyMappings = protocol.classMapping.propertyMappings.map((propertyMapping) => propertyMapping.accept_PropertyMappingVisitor(new V1_PropertyMappingBuilder(this.context, embedded, this.topParent, this.topParent?._PARENT
            ? getAllEnumerationMappings(this.topParent._PARENT)
            : [], this.tableAliasIndex)));
        return embedded;
    }
    visit_OtherwiseEmbeddedRelationalPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Otherwise embedded relational property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Otherwise embedded relational property mapping 'property.property' field is missing or empty`);
        const property = V1_buildEmbeddedRelationalMappingProperty(protocol, this.immediateParent, guaranteeNonNullable(this.topParent), this.context);
        const otherwiseEmbedded = new OtherwiseEmbeddedRelationalInstanceSetImplementation(this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(property.propertyOwnerClass, protocol.property.class ?? ''), property.property), guaranteeType(this.topParent, RootRelationalInstanceSetImplementation), SetImplementationImplicitReference.create(property.sourceSetImplementation, protocol.source), property._class, InferableMappingElementIdExplicitValue.create(`${property.id.value}`, ''), undefined);
        otherwiseEmbedded.primaryKey = protocol.classMapping.primaryKey.map((key) => V1_buildRelationalOperationElement(key, this.context, this.tableAliasIndex, [], undefined, true));
        otherwiseEmbedded.propertyMappings =
            protocol.classMapping.propertyMappings.map((propertyMapping) => propertyMapping.accept_PropertyMappingVisitor(new V1_PropertyMappingBuilder(this.context, otherwiseEmbedded, this.topParent, this.topParent?._PARENT
                ? getAllEnumerationMappings(this.topParent._PARENT)
                : [], this.tableAliasIndex)));
        otherwiseEmbedded.otherwisePropertyMapping = guaranteeType(protocol.otherwisePropertyMapping.accept_PropertyMappingVisitor(new V1_PropertyMappingBuilder(this.context, otherwiseEmbedded, this.topParent, this.topParent?._PARENT
            ? getAllEnumerationMappings(this.topParent._PARENT)
            : [], this.tableAliasIndex)), RelationalPropertyMapping);
        return otherwiseEmbedded;
    }
    visit_XStorePropertyMapping(protocol) {
        assertNonNullable(protocol.property, `XStore property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.class, `XStore property mapping 'property.class' field is missing or empty`);
        assertNonEmptyString(protocol.property.property, `XStore property mapping 'property.property' field is missing or empty`);
        assertNonNullable(protocol.crossExpression, `XStore property mapping 'crossExpression' field is missing`);
        const xStoreParent = guaranteeNonNullable(this.xStoreParent, `XStore property 'xStoreParent' field is missing`);
        const _association = xStoreParent.association.value;
        const property = getOwnProperty(_association, protocol.property.property);
        const sourceSetImplementation = guaranteeNonNullable(resolvePropertyMappingSourceImplementation(this.immediateParent, protocol, this.topParent, this.context));
        let targetSetImplementation;
        const propertyType = property.genericType.value.rawType;
        if (propertyType instanceof Class) {
            const parentMapping = this.immediateParent._PARENT;
            if (protocol.target) {
                targetSetImplementation = TEMPORARY__resolveSetImplementationByID(parentMapping, protocol.target, this.context);
            }
            else {
                targetSetImplementation = getClassMappingsByClass(parentMapping, guaranteeType(propertyType, Class))[0];
            }
        }
        const xStorePropertyMapping = new XStorePropertyMapping(xStoreParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(_association, protocol.property.class), property), SetImplementationImplicitReference.create(sourceSetImplementation, protocol.source), targetSetImplementation
            ? SetImplementationImplicitReference.create(targetSetImplementation, protocol.target)
            : undefined);
        xStorePropertyMapping.crossExpression = V1_buildRawLambdaWithResolvedPaths(protocol.crossExpression.parameters, protocol.crossExpression.body, this.context);
        return xStorePropertyMapping;
    }
    visit_AggregationAwarePropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Aggregation-aware property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.class, `Aggregation-aware property mapping 'property.class' field is missing or empty`);
        assertNonEmptyString(protocol.property.property, `Aggregation-aware property mapping 'property.property' field is missing or empty`);
        const aggregationAwareParent = guaranteeNonNullable(this.aggregationAwareParent, `Aggregation-aware property 'aggregationAwareParent' field is missing`);
        const propertyMapping = aggregationAwareParent.mainSetImplementation.propertyMappings.find((p) => p.property.value.name === protocol.property.property);
        const property = propertyMapping?.property ??
            this.context.resolveProperty(protocol.property);
        const sourceSetImplementation = guaranteeNonNullable(resolvePropertyMappingSourceImplementation(this.immediateParent, protocol, this.topParent, this.context));
        let targetSetImplementation;
        const propertyType = property.value.genericType.value.rawType;
        if (propertyType instanceof Class) {
            const parentMapping = this.immediateParent._PARENT;
            if (protocol.target) {
                targetSetImplementation = TEMPORARY__resolveSetImplementationByID(parentMapping, protocol.target, this.context);
            }
            else {
                targetSetImplementation = getClassMappingsByClass(parentMapping, guaranteeType(propertyType, Class))[0];
            }
        }
        const aggregationAwarePropertyMapping = new AggregationAwarePropertyMapping(this.topParent ?? this.immediateParent, property, SetImplementationImplicitReference.create(guaranteeNonNullable(sourceSetImplementation), protocol.source), targetSetImplementation
            ? SetImplementationImplicitReference.create(targetSetImplementation, protocol.target)
            : undefined);
        if (protocol.localMappingProperty &&
            propertyMapping?.localMappingProperty) {
            aggregationAwarePropertyMapping.localMappingProperty =
                guaranteeNonNullable(propertyMapping.localMappingProperty);
        }
        return aggregationAwarePropertyMapping;
    }
    visit_RelationFunctionPropertyMapping(protocol) {
        assertNonNullable(protocol.property, `Relation Function property mapping 'property' field is missing`);
        assertNonEmptyString(protocol.property.property, `Relation Function property mapping 'property.property' field is missing or empty`);
        let propertyOwner;
        let property;
        let localMapping;
        if (protocol.localMappingProperty) {
            const localMappingProperty = protocol.localMappingProperty;
            const mappingClass = new MappingClass(`${this.topParent?._PARENT.path}_${this.topParent?.id}${protocol.property.property}`);
            const _multiplicity = this.context.graph.getMultiplicity(localMappingProperty.multiplicity.lowerBound, localMappingProperty.multiplicity.upperBound);
            const _property = new Property(protocol.property.property, _multiplicity, this.context.resolveGenericType(localMappingProperty.type), mappingClass);
            property = PropertyImplicitReference.create(PackageableElementImplicitReference.create(mappingClass, protocol.property.class), _property).value;
            localMapping = new LocalMappingPropertyInfo();
            localMapping.localMappingProperty = true;
            localMapping.localMappingPropertyMultiplicity = _multiplicity;
            localMapping.localMappingPropertyType = this.context.resolveType(localMappingProperty.type);
            propertyOwner = property._OWNER;
        }
        else {
            if (protocol.property.class) {
                propertyOwner = this.context.resolveClass(protocol.property.class).value;
            }
            else {
                throw new GraphBuilderError(`Can't find property owner class for property '${protocol.property.property}'`);
            }
            property =
                propertyOwner instanceof Class
                    ? getClassProperty(propertyOwner, protocol.property.property)
                    : getOwnProperty(propertyOwner, protocol.property.property);
        }
        const sourceSetImplementation = guaranteeNonNullable(resolvePropertyMappingSourceImplementation(this.immediateParent, protocol, this.topParent, this.context));
        const propertyMapping = new RelationFunctionPropertyMapping(this.topParent ?? this.immediateParent, PropertyImplicitReference.create(PackageableElementImplicitReference.create(propertyOwner, protocol.property.class ?? ''), property), SetImplementationImplicitReference.create(sourceSetImplementation, protocol.source), undefined, new RelationColumn(protocol.column, property.genericType));
        propertyMapping.localMappingProperty = localMapping;
        if (protocol.bindingTransformer?.binding) {
            const bindingTransformer = new BindingTransformer();
            const binding = V1_resolveBinding(protocol.bindingTransformer.binding, this.context);
            bindingTransformer.binding = binding;
            propertyMapping.bindingTransformer = bindingTransformer;
        }
        return propertyMapping;
    }
}
//# sourceMappingURL=V1_PropertyMappingBuilder.js.map