"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get resolveIncludes () {
        return resolveIncludes;
    },
    get retrieveIncludes () {
        return retrieveIncludes;
    }
});
const _constants = require("@tamanu/constants");
const _reference = require("@tamanu/shared/services/fhirTypes/reference");
const _fhir = require("@tamanu/shared/utils/fhir");
const _resources = require("@tamanu/shared/utils/fhir/resources");
function resolveIncludes(models, query, parameters, FhirResource) {
    const includes = new Map();
    const referenceParameters = invertReferenceParameters(parameters);
    const materialisedResources = (0, _resources.resourcesThatCanDo)(models, _constants.FHIR_INTERACTIONS.INTERNAL.MATERIALISE);
    for (const { modifier, resource, parameter, targetType } of flattenIncludes(query.get('_include'))){
        if (modifier) {
            throw new _fhir.Unsupported(`_include modifiers are not yet supported`);
        }
        if (resource === '*') {
            throw new _fhir.Unsupported(`_include=* is not yet supported`);
        }
        if (!referenceParameters.has(resource)) {
            throw new _fhir.Unsupported(`_include=${resource} is not supported on ${FhirResource.fhirName}`);
        }
        if (!materialisedResources.some(({ fhirName })=>fhirName === resource)) {
            throw new _fhir.Unsupported(`_include=${resource} is not supported (not indexed)`);
        // "indexed" is FHIR-specese for what we call "materialised"
        }
        if (parameter === '*') {
            throw new _fhir.Unsupported(`_include=${resource}:* is not yet supported`);
        }
        const searchableReferencesForResource = referenceParameters.get(resource);
        if (!searchableReferencesForResource.has(parameter)) {
            throw new _fhir.Unsupported(`_include=${resource}:${parameter} is not supported on ${FhirResource.fhirName}`);
        }
        mapmapPush(includes, resource, parameter, {
            ...searchableReferencesForResource.get(parameter),
            targetType
        });
    }
    return includes;
}
async function retrieveIncludes(models, records, includes, FhirResource) {
    if (!includes?.size) return {
        included: [],
        errors: []
    };
    const [toFetch, errors] = findIncludesToFetch(records, includes, FhirResource);
    const materialisedResources = (0, _resources.resourcesThatCanDo)(models, _constants.FHIR_INTERACTIONS.INTERNAL.MATERIALISE);
    let included = [];
    for (const [resource, ids] of toFetch.entries()){
        try {
            const ChildResource = materialisedResources.find(({ fhirName })=>fhirName === resource);
            const includedRecords = await ChildResource.findAll({
                where: {
                    id: [
                        ...ids
                    ]
                }
            });
            included = included.concat(includedRecords);
        } catch (err) {
            errors.push(new _fhir.Processing(`Failed to retrieve included ${resource}(s)`, {
                diagnostics: err.stack
            }));
        }
    }
    return {
        included,
        errors
    };
}
function findIncludesToFetch(records, includes, FhirResource) {
    const errors = [];
    const toFetch = new Map();
    for (const [resource, includeParams] of includes.entries()){
        for (const [parameter, { path, targetType }] of includeParams.entries()){
            const ids = new Set();
            const referenceTypes = new Set();
            if (targetType) {
                referenceTypes.add(targetType);
            }
            for (const record of records){
                for (const route of path){
                    for (const ref of routeInto(record, route)){
                        // failing to retrieve a reference is not an error
                        try {
                            const typeId = new _reference.FhirReference(ref).fhirTypeAndId();
                            if (!typeId) {
                                throw new _fhir.Unsupported(`Can't _include=${resource}:${parameter} on ${FhirResource.fhirName}#${record.id} because the reference is not explicit`);
                            }
                            const { type, id } = typeId;
                            if (targetType && type !== targetType) {
                                continue;
                            }
                            ids.add(id);
                            referenceTypes.add(type);
                        } catch (err) {
                            if (err instanceof _fhir.FhirError) {
                                // we collect errors to be nice, even though we're not required to
                                errors.push(err);
                            } else {
                                throw err;
                            }
                        }
                    }
                }
            }
            if (ids.size > 0) {
                for (const referenceType of referenceTypes){
                    toFetch.set(referenceType, ids);
                }
            }
        }
    }
    return [
        toFetch,
        errors
    ];
}
function invertReferenceParameters(parameters) {
    const inverted = new Map();
    for (const [searchField, params] of parameters){
        if (params.type !== _constants.FHIR_SEARCH_PARAMETERS.REFERENCE) continue;
        const { referenceTypes } = params;
        for (const referenceType of referenceTypes){
            mapmapPush(inverted, referenceType, searchField, params);
        }
    }
    return inverted;
}
function mapmapPush(map, key, innerKey, value) {
    const inner = map.get(key) ?? new Map();
    inner.set(innerKey, value);
    map.set(key, inner);
}
function* flattenIncludes(includes) {
    for (const { modifier, value } of includes){
        for (const vals of value){
            yield {
                ...vals,
                modifier
            };
        }
    }
}
function* routeInto(record, route) {
    const [field, ...rest] = route;
    const value = record[field];
    if (field === '[]' && rest.length > 0) {
        // iterate over array
        for (const item of record){
            yield* routeInto(item, rest);
        }
    } else if (field === '[]' && rest.length === 0) {
        for (const item of record){
            yield item;
        }
    } else if (value === undefined) {
    // yield nothing and return
    } else if (value === null) {
    // yield nothing and return
    // in OUR particular case, we don't care about nulls, but in general we might!
    // if copying or exporting/using this code elsewhere, check what you want to do
    } else if (rest.length > 0) {
        // more path to go, recurse
        yield* routeInto(value, rest);
    } else {
        // reached the end of the path
        yield value;
    }
}

//# sourceMappingURL=include.js.map