/* eslint-disable no-unused-vars */ "use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "FhirResource", {
    enumerable: true,
    get: function() {
        return FhirResource;
    }
});
const _lodash = require("lodash");
const _sequelize = require("sequelize");
const _datefns = require("date-fns");
const _constants = require("@tamanu/constants");
const _fhir = require("@tamanu/shared/utils/fhir");
const _utils = require("../../utils/fhir/utils");
const _Model = require("../Model");
function _define_property(obj, key, value) {
    if (key in obj) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
    } else {
        obj[key] = value;
    }
    return obj;
}
let FhirResource = class FhirResource extends _Model.Model {
    static initResource(attributes, options) {
        super.init({
            id: {
                type: _sequelize.DataTypes.UUID,
                allowNull: false,
                defaultValue: _sequelize.Sequelize.fn('gen_random_uuid'),
                primaryKey: true
            },
            versionId: {
                type: _sequelize.DataTypes.UUID,
                allowNull: false,
                defaultValue: _sequelize.Sequelize.fn('gen_random_uuid')
            },
            upstreamId: {
                type: this.UPSTREAM_UUID ? _sequelize.DataTypes.UUID : _sequelize.DataTypes.STRING,
                allowNull: false
            },
            lastUpdated: {
                type: _sequelize.DataTypes.TIMESTAMP,
                allowNull: false,
                defaultValue: _sequelize.DataTypes.NOW,
                set (utcDate) {
                    // Sequelize converts TIMESTAMP into UTC, so we convert it back to local time
                    if (!(utcDate instanceof Date)) {
                        return utcDate;
                    }
                    const localOffsetMinutes = new Date().getTimezoneOffset();
                    this.setDataValue('lastUpdated', (0, _datefns.subMinutes)(utcDate, localOffsetMinutes));
                    return this.lastUpdated;
                }
            },
            isLive: {
                type: _sequelize.DataTypes.BOOLEAN,
                allowNull: false,
                defaultValue: true
            },
            resolved: {
                type: _sequelize.DataTypes.BOOLEAN,
                allowNull: false,
                defaultValue: false
            },
            ...attributes
        }, {
            tableName: (0, _lodash.snakeCase)(_sequelize.Utils.pluralize(this.fhirName)),
            ...options,
            syncDirection: _constants.SYNC_DIRECTIONS.DO_NOT_SYNC,
            schema: 'fhir',
            timestamps: false
        });
    }
    // name in FHIR
    static get fhirName() {
        return this.name.replace(/^Fhir/, '');
    }
    // Resource specific logic to find referenced resources in the bundle
    // and using them to hydrate any missing fields in the raw resource
    static hydrateRawResourceFromBundle(_bundle, rawResource) {
        return rawResource;
    }
    // set upstream_id, call updateMaterialisation
    // do not set relatedToId when calling this, it's for internal use only.
    static async materialiseFromUpstream(id, relatedToId = null) {
        let resource = await this.findOne({
            where: {
                upstreamId: id
            }
        });
        if (!resource) {
            resource = this.build({
                id: _sequelize.Sequelize.fn('gen_random_uuid'),
                versionId: _sequelize.Sequelize.fn('gen_random_uuid'),
                upstreamId: id
            });
        }
        const currentIsLive = resource.isLive;
        await resource.updateIsLive();
        const newIsLive = resource.isLive;
        if (resource.resolved && // We can only skip rematerialisation if the resource is resolved
        !currentIsLive && !newIsLive && !await resource.shouldForceRematerialise()) {
            // Skipping rematerialisation
            return resource;
        }
        await resource.updateMaterialisation();
        await resource.save();
        // don't look up related records if we're already in the process of doing so
        // this may miss records that are transitively related, but it avoids infinite
        // loops: to make sure nothing is missed, write the getRelatedUpstreamIds()
        // to traverse the entire graph or tree upfront as needed.
        if (!relatedToId) {
            for (const relatedId of (await resource.getRelatedUpstreamIds())){
                if (relatedId === id) continue;
                if (relatedId === relatedToId) continue;
                await this.materialiseFromUpstream(relatedId, id);
            }
        }
        return resource;
    }
    // fetch upstream and necessary includes, diff and update
    async updateMaterialisation() {
        throw new Error('must be overridden');
    }
    async updateIsLive() {
        this.isLive = true; // Override this to have custom logic at the model level
    }
    async shouldForceRematerialise() {
        return false; // By default we don't force rematerialise, but can be overridden
    }
    // return the IDs of upstream records that are not this one's upstream, but
    // which should be re-materialised when this one is, so that the view of FHIR
    // data is up to date and consistent.
    async getRelatedUpstreamIds() {
        return [];
    }
    // call updateMat, don't save, output bool
    async isUpToDate() {
        const resource = await this.constructor.findByPk(this.id);
        if (!resource) return false;
        await resource.updateMaterialisation();
        return !resource.changed();
    }
    // fetch (single) upstream with query options (e.g. includes)
    // this implies that the PK on every upstream table is unique across all!
    async getUpstream(queryOptions = {}) {
        let upstream;
        for (const UpstreamModel of this.constructor.UpstreamModels){
            const upstreamQueryOptions = queryOptions[UpstreamModel.tableName] || {};
            upstream = await UpstreamModel.findByPk(this.upstreamId, {
                ...upstreamQueryOptions,
                paranoid: false
            });
            if (upstream) break;
        }
        return upstream;
    }
    static async resolveUpstreams() {
        const unresolvedResources = await this.findAll({
            where: {
                resolved: false
            }
        });
        for (const unresolvedResource of unresolvedResources){
            try {
                await this.materialiseFromUpstream(unresolvedResource.upstreamId);
            } catch (error) {
                if (error instanceof Error) {
                    // Rethrowing like this to preserve stacktrace while logging which resource failed to resolve
                    const errorMessage = `Error resolving upstreams for ${this.fhirName}/${unresolvedResource.id}: ${error.message ?? error.toString() ?? ''}`;
                    const rethrownError = new Error(errorMessage);
                    rethrownError.stack = `${errorMessage}\n${error.stack}`;
                    throw rethrownError;
                } else {
                    throw error;
                }
            }
        }
    }
    // take a FhirResource and save it into Tamanu
    async pushUpstream(..._args) {
        throw new Error('must be overridden');
    }
    static async queryToFindUpstreamIdsFromTable(_upstreamTable, _table, _id, _deletedRow = null) {
        return null;
    }
    static async queryToFilterUpstream(_upstreamTable) {
        return null;
    }
    formatFieldsAsFhir(fields) {
        return (0, _utils.objectAsFhir)(fields);
    }
    asFhir() {
        const fields = {};
        for (const name of Object.keys(this.constructor.getAttributes())){
            if ([
                'id',
                'versionId',
                'upstreamId',
                'lastUpdated',
                'isLive',
                'resolved'
            ].includes(name)) continue;
            fields[name] = this.get(name);
        }
        return {
            resourceType: this.constructor.fhirName,
            id: this.id,
            meta: {
                // TODO: uncomment when we support versioning
                // versionId: this.versionId,
                lastUpdated: (0, _fhir.formatFhirDate)(this.lastUpdated, _constants.FHIR_DATETIME_PRECISION.SECONDS_WITH_TIMEZONE)
            },
            ...this.formatFieldsAsFhir(fields)
        };
    }
    /**
   * FHIR search parameter configuration for the Resource.
   */ static searchParameters() {
        return {
            _id: {
                type: _constants.FHIR_SEARCH_PARAMETERS.TOKEN,
                path: [
                    [
                        'id'
                    ]
                ],
                tokenType: _constants.FHIR_SEARCH_TOKEN_TYPES.STRING
            },
            _lastUpdated: {
                type: _constants.FHIR_SEARCH_PARAMETERS.DATE,
                path: [
                    [
                        'lastUpdated'
                    ]
                ]
            },
            // selecting fields to return:
            // _elements: {},
            // whole record search:
            // _text: {},
            // _content: {},
            // lists:
            // _list: {},
            // reverse chaining:
            // _has: {},
            // multi-type search:
            // _type: {},
            // advanced search:
            // _query: {},
            // _filter: {},
            // meta fields:
            // _tag: {},
            // _profile: {},
            // _security: {},
            // _source: {},
            // legacy for mSupply support
            'subject:identifier': {
                type: _constants.FHIR_SEARCH_PARAMETERS.TOKEN,
                path: [
                    [
                        'identifier',
                        '[]'
                    ]
                ],
                tokenType: _constants.FHIR_SEARCH_TOKEN_TYPES.VALUE
            },
            status: {
                type: _constants.FHIR_SEARCH_PARAMETERS.STRING,
                path: []
            },
            after: {
                type: _constants.FHIR_SEARCH_PARAMETERS.DATE,
                path: [
                    [
                        'lastUpdated'
                    ]
                ]
            },
            issued: {
                type: _constants.FHIR_SEARCH_PARAMETERS.DATE,
                path: [
                    [
                        'lastUpdated'
                    ]
                ]
            }
        };
    }
};
// API interactions enabled for the resource
// see FHIR_INTERACTIONS constant
_define_property(FhirResource, "CAN_DO", new Set());
// main Tamanu models this resource is based on
_define_property(FhirResource, "UpstreamModels", void 0);
// list of Tamanu models that are used to materialise this resource
_define_property(FhirResource, "upstreams", []);
// list of FHIR resources that are referenced by this resource
_define_property(FhirResource, "referencedResources", []);
// switch to true if the upstream's ID is the UUID pg type
_define_property(FhirResource, "UPSTREAM_UUID", false);
// yup schema for validating incoming resource
// TODO: derive from the sequelize attributes by default
_define_property(FhirResource, "INTAKE_SCHEMA", void 0);

//# sourceMappingURL=Resource.js.map