var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import format from "string-format";
import { RelationBase } from "../contracts/base/relation";
import { RelationCardinality } from "../contracts/base/relation-cardinality";
import { RelationRole } from "../contracts/base/relation-role";
import { ChildToManyParentsRelation } from "../contracts/base/relations/child-to-many-parents-relation";
import { ChildToOneParentRelation } from "../contracts/base/relations/child-to-one-parent-relation";
import { ParentToManyChildrenRelation } from "../contracts/base/relations/parent-to-many-children-relation";
import { ParentToOneChildRelation } from "../contracts/base/relations/parent-to-one-child-relation";
import { MinimalSchema } from "../contracts/querying/minimal-schema";
import { RelationData } from "../contracts/querying/relation-data";
import { EntityLink } from "../entity-link";
import ErrorMessages from "../error-messages";
import { InternalError } from "../errors/internal-error";
import { NotSupportedError } from "../errors/not-supported-error";
import Guard from "../guard";
import { RelationResource } from "../models/relation-resource";
export class RelationMapper {
    constructor(client) {
        Guard.notNullOrUndefined(client);
        this._client = client;
    }
    mapRelationsAsync(resource, schema) {
        return __awaiter(this, void 0, void 0, function* () {
            Guard.notNullOrUndefined(resource);
            const list = [];
            // If no minimal schema is available, build it from the definition.
            if (schema == null) {
                const definitionName = yield this._client.linkHelper.nameFromDefinitionAsync(resource.entityDefinition);
                if (definitionName == null) {
                    throw new InternalError(`Could not resolve definition name from entity resource.`);
                }
                const definition = yield this._client.entityDefinitions.getCachedAsync(definitionName);
                if (definition == null) {
                    throw new InternalError(`Could not load definition with name '${definitionName}.`);
                }
                schema = new MinimalSchema(definitionName);
                const loadedRelations = Object.keys(resource.relations);
                schema.relations = definition
                    .getRelationDefinitions()
                    .filter(relationDefinition => loadedRelations.includes(relationDefinition.name))
                    .map(relationDefinition => new RelationData({
                    name: relationDefinition.name,
                    cardinality: relationDefinition.cardinality,
                    role: relationDefinition.role,
                }));
            }
            for (const relation of schema.relations) {
                const name = relation.name;
                const cardinality = relation.cardinality;
                const role = relation.role;
                // Try to get the relation, it is on the schema, so it should be there.
                const relationObject = resource.relations[name];
                if (!relationObject) {
                    const msg = `[Internal] Relation with name '${relation.name}' was on schema, but not on the entity resource.`;
                    this._client.logger.warn(msg);
                    continue;
                }
                const isRelationResource = relationObject instanceof RelationResource;
                const relationResource = relationObject;
                // The relation should be nested, so we can use it as a RelationResource
                if (!isRelationResource) {
                    const msg = `[Internal] Relation with name '${relation.name}' was on the entity resource, but the relation was not nested.`;
                    this._client.logger.warn(msg);
                    continue;
                }
                let result = null;
                if (role == RelationRole.Parent) {
                    if (cardinality == RelationCardinality.ManyToMany || cardinality == RelationCardinality.OneToMany) {
                        let subRelation;
                        if (relationResource.children != null) {
                            const ids = [];
                            const properties = {};
                            for (const r of relationResource.children) {
                                const id = yield this._client.linkHelper.idFromEntityAsync(r);
                                ids.push(id);
                                properties[id] = r.properties;
                            }
                            subRelation = new ParentToManyChildrenRelation(name, properties, this._client, relationResource.childTotal);
                            subRelation.setIds(ids);
                        }
                        else {
                            subRelation = new ParentToManyChildrenRelation(name, null, this._client, relationResource.childTotal);
                        }
                        result = subRelation;
                    }
                    else if (cardinality == RelationCardinality.OneToOne) {
                        let childId = null;
                        const properties = {};
                        if (relationResource.child != null) {
                            childId = yield this._client.linkHelper.idFromEntityAsync(relationResource.child);
                            properties[childId] = relationResource.child.properties;
                        }
                        else if (relationResource.children instanceof Array && relationResource.children.length) {
                            childId = yield this._client.linkHelper.idFromEntityAsync(relationResource.children[0]);
                            properties[childId] = relationResource.children[0].properties;
                        }
                        const subRelation = new ParentToOneChildRelation(name, properties, this._client);
                        subRelation.child = childId;
                        result = subRelation;
                    }
                }
                else if (role == RelationRole.Child) {
                    if (cardinality == RelationCardinality.ManyToMany) {
                        let subRelation;
                        if (relationResource.parents != null) {
                            const ids = [];
                            const properties = {};
                            for (const r of relationResource.parents) {
                                const id = yield this._client.linkHelper.idFromEntityAsync(r);
                                ids.push(id);
                                properties[id] = r.properties;
                            }
                            subRelation = new ChildToManyParentsRelation(name, properties, this._client, relationResource.parentTotal);
                            subRelation.setIds(ids);
                        }
                        else {
                            subRelation = new ChildToManyParentsRelation(name, null, this._client, relationResource.parentTotal);
                        }
                        result = subRelation;
                    }
                    else if (cardinality == RelationCardinality.OneToOne ||
                        cardinality == RelationCardinality.OneToMany) {
                        let parentId = null;
                        const properties = {};
                        if (relationResource.parent != null) {
                            parentId = yield this._client.linkHelper.idFromEntityAsync(relationResource.parent);
                            properties[parentId] = relationResource.parent.properties;
                        }
                        else if (relationResource.parents instanceof Array && relationResource.parents.length) {
                            parentId = yield this._client.linkHelper.idFromEntityAsync(relationResource.parents[0]);
                            properties[parentId] = relationResource.parents[0].properties;
                        }
                        const subRelation = new ChildToOneParentRelation(name, properties, this._client);
                        subRelation.parent = parentId;
                        result = subRelation;
                    }
                }
                if (result == null) {
                    const msg = format(ErrorMessages.UnsupportedRelation, RelationCardinality[relation.cardinality], RelationRole[relation.role]);
                    throw new Error(msg);
                }
                list.push(result);
            }
            return list;
        });
    }
    mapToRelationResourcesAsync(entity) {
        return __awaiter(this, void 0, void 0, function* () {
            Guard.notNullOrUndefined(entity);
            return yield this.mapRelationsToResourcesAsync([...entity.relations], entity.id || 0);
        });
    }
    mapRelationToResourceAsync(relation, id) {
        return __awaiter(this, void 0, void 0, function* () {
            Guard.notNullOrUndefined(relation);
            const resource = new RelationResource();
            if (RelationBase.isChildRelation(relation)) {
                resource.inheritsSecurity = relation.inheritsSecurity;
            }
            if (RelationBase.isChildToManyParentsRelation(relation)) {
                const parentLinks = yield this._client.linkHelper.entitiesToLinksAsync(relation.parents);
                resource.parents = parentLinks.map((link, index) => {
                    const parent = new EntityLink(link.href);
                    parent.properties = relation.properties[relation.parents[index]];
                    return parent;
                });
            }
            else if (RelationBase.isChildToOneParentRelation(relation)) {
                if (relation.parent) {
                    const link = yield this._client.linkHelper.entityToLinkAsync(relation.parent);
                    resource.parent = new EntityLink(link.href);
                    resource.parent.properties = relation.properties[relation.parent];
                }
            }
            else if (RelationBase.isParentToManyChildrenRelation(relation)) {
                const childLinks = yield this._client.linkHelper.entitiesToLinksAsync(relation.children);
                resource.children = childLinks.map(link => new EntityLink(link.href));
                resource.children = childLinks.map((link, index) => {
                    const child = new EntityLink(link.href);
                    child.properties = relation.properties[relation.children[index]];
                    return child;
                });
            }
            else if (RelationBase.isParentToOneChildRelation(relation)) {
                if (relation.child) {
                    const link = yield this._client.linkHelper.entityToLinkAsync(relation.child);
                    resource.child = new EntityLink(link.href);
                    resource.child.properties = relation.properties[relation.child];
                }
            }
            else {
                throw new NotSupportedError("Unknown relation type.");
            }
            const relationLink = yield this._client.linkHelper.relationToLinkAsync(id, relation.name);
            resource.self = relationLink;
            return resource;
        });
    }
    mapDirtyRelationsAsync(entity) {
        return __awaiter(this, void 0, void 0, function* () {
            Guard.notNullOrUndefined(entity);
            const relationNames = entity.relations.reduce((a, r) => (r.isDirty && a.push(r.name), a), []);
            const relations = entity.relations.filter(relation => relationNames.includes(relation.name));
            const dict = yield this.mapRelationsToResourcesAsync(relations, entity.id || 0);
            return dict;
        });
    }
    mapRelationsToResourcesAsync(relations, id) {
        return __awaiter(this, void 0, void 0, function* () {
            Guard.notNullOrUndefined(relations);
            const dict = {};
            for (const relation of relations) {
                const mappedRelation = yield this.mapRelationToResourceAsync(relation, id);
                if (dict[relation.name]) {
                    const existing = dict[relation.name];
                    const merged = RelationResource.merge(existing, mappedRelation);
                    dict[relation.name] = merged;
                }
                else {
                    dict[relation.name] = mappedRelation;
                }
            }
            return dict;
        });
    }
}
//# sourceMappingURL=relation-mapper.js.map