"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 fhir () {
        return fhir;
    },
    get fhirCommand () {
        return fhirCommand;
    }
});
const _commander = require("commander");
const _sequelize = require("sequelize");
const _yup = /*#__PURE__*/ _interop_require_wildcard(require("yup"));
const _constants = require("@tamanu/constants");
const _logging = require("@tamanu/shared/services/logging");
const _resources = require("@tamanu/shared/utils/fhir/resources");
const _ApplicationContext = require("../ApplicationContext");
const _prepareQuery = require("../utils/prepareQuery");
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {
        __proto__: null
    };
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
async function showStatus() {
    const app = await new _ApplicationContext.ApplicationContext().init();
    const materialisableResources = (0, _resources.resourcesThatCanDo)(app.store.models, _constants.FHIR_INTERACTIONS.INTERNAL.MATERIALISE);
    for (const Resource of materialisableResources){
        const count = await Resource.count();
        const latest = (await Resource.findOne({
            order: [
                [
                    'lastUpdated',
                    'DESC'
                ]
            ]
        }))?.lastUpdated?.toISOString() || 'never';
        let upstreamCount = 0;
        let upstreamLatest = '';
        for (const UpstreamModel of Resource.UpstreamModels){
            upstreamCount += await UpstreamModel.count();
            const currentUpstreamLatest = (await UpstreamModel.findOne({
                order: [
                    [
                        'updatedAt',
                        'DESC'
                    ]
                ]
            }))?.updatedAt?.toISOString();
            if (currentUpstreamLatest > upstreamLatest) upstreamLatest = currentUpstreamLatest;
        }
        _logging.log.info(`${Resource.name}: ${count}/${upstreamCount} records/upstream, last updated ${latest}/${upstreamLatest || 'never'}`);
    }
    await app.close();
}
async function doRefresh(resource, { existing, missing, since }) {
    const app = await new _ApplicationContext.ApplicationContext().init();
    const materialisableResources = (0, _resources.resourcesThatCanDo)(app.store.models, _constants.FHIR_INTERACTIONS.INTERNAL.MATERIALISE);
    if (resource.toLowerCase() === 'all') {
        for (const Resource of materialisableResources){
            if (!Resource?.UpstreamModels || Resource.UpstreamModels.length === 0) continue;
            await doRefresh(Resource.fhirName, {
                existing,
                missing,
                since
            });
        }
        return;
    }
    const Resource = materialisableResources.find((r)=>r.fhirName.toLowerCase() === resource.toLowerCase());
    if (!Resource) throw new Error(`No such FHIR Resource: ${resource}`);
    const sql = (await Promise.all(Resource.UpstreamModels.map(async (upstreamModel)=>{
        const queryToFilterUpstream = await Resource.queryToFilterUpstream(upstreamModel.tableName);
        const sqlToFilterUpstream = await (0, _prepareQuery.prepareQuery)(upstreamModel, {
            attributes: [
                'id',
                'deleted_at',
                'updated_at'
            ],
            ...queryToFilterUpstream
        });
        return sqlToFilterUpstream.replace(/;$/, '');
    }))).join(' UNION ');
    const recordsToDo = (await Resource.sequelize.query(`
      WITH upstream AS (${sql})
      SELECT upstream.id AS id
      FROM upstream
      LEFT JOIN fhir.${Resource.tableName} downstream ON downstream.upstream_id = upstream.id
      WHERE true
        AND upstream.deleted_at IS NULL
        ${missing ? 'AND downstream.id IS NULL' : ''}
        ${existing ? 'AND downstream.id IS NOT NULL' : ''}
        ${since ? `AND upstream.updated_at > $1` : ''}
    `, {
        type: _sequelize.QueryTypes.SELECT,
        bind: since ? [
            since
        ] : []
    })).map(({ id })=>id);
    let done = 0;
    _logging.log.info(`Going to refresh ${recordsToDo.length} records...`);
    for (const upstreamId of recordsToDo){
        await Resource.materialiseFromUpstream(upstreamId);
        done += 1;
        if (done % 100 === 0) _logging.log.info(`Refreshed ${done} out of ${recordsToDo.length}`);
    }
    _logging.log.info('Resolving upstream references...');
    await Resource.resolveUpstreams();
    _logging.log.info(`Done refreshing ${done} ${Resource.fhirName} records`);
    await app.close();
}
const fhir = async ({ status, refresh, existing, missing, since })=>{
    if (status || !refresh) return showStatus();
    return doRefresh(refresh, _yup.object({
        existing: _yup.boolean().default(false),
        missing: _yup.boolean().default(false),
        since: _yup.date().nullable().default(null)
    }).validateSync({
        existing,
        missing,
        since
    }));
};
const fhirCommand = new _commander.Command('fhir').description('FHIR integration utilities').option('--status', 'show status (default)').option('--refresh <Resource>', 'refresh a FHIR Resource (use `all` to do refresh all resources)').option('--existing', 'only refresh already-materialised resources, not missing ones').option('--missing', 'only materialise missing resources, leave existing ones alone').option('--since <date>', 'filter to source tables that have been updated since that date only').action(fhir);

//# sourceMappingURL=fhir.js.map