"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProcessMutation = void 0;
const tslib_1 = require("tslib");
const type_graphql_1 = require("type-graphql");
const typeorm_1 = require("typeorm");
const GraphQLUpload_js_1 = tslib_1.__importDefault(require("graphql-upload/GraphQLUpload.js"));
const shell_1 = require("@things-factory/shell");
const thumbnail_1 = require("../../controllers/thumbnail");
const process_group_1 = require("../process-group/process-group");
const process_1 = require("./process");
const process_history_1 = require("./process-history");
const process_type_1 = require("./process-type");
async function parseJSONFile(uploadedFile) {
    var { createReadStream } = await uploadedFile;
    return new Promise((resolve, reject) => {
        const chunks = [];
        createReadStream()
            .on('data', (chunk) => {
            chunks.push(chunk);
        })
            .on('end', () => {
            try {
                const fileContents = Buffer.concat(chunks).toString('utf-8');
                const jsonData = JSON.parse(fileContents);
                resolve(jsonData);
            }
            catch (error) {
                reject(error);
            }
        })
            .on('error', (error) => {
            reject(error);
        });
    });
}
let ProcessMutation = class ProcessMutation {
    async createProcess(process, context) {
        const { domain, user, notify, tx } = context.state;
        const repository = tx.getRepository(process_1.Process);
        const groupRepository = tx.getRepository(process_group_1.ProcessGroup);
        const oldProcess = await repository.findOneBy({
            name: process.name,
            domain: { id: domain.id }
        });
        if (oldProcess) {
            throw new Error(context.t('error.process name is already taken', { name: process.name }));
        }
        const newProcess = Object.assign({}, process);
        newProcess.thumbnail || (newProcess.thumbnail = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); /* empty thumbnail */
        if (process.groupId) {
            newProcess.group = await groupRepository.findOneBy({
                id: process.groupId
            });
        }
        const created = await repository.save(Object.assign(Object.assign({ domain }, newProcess), { state: 'draft', creator: user, updater: user }));
        notify &&
            notify({
                mode: 'in-app',
                title: `Process '${created.name}' created`,
                body: `Process '${created.name}' created by ${user.name}\n${created.description}`,
                url: (0, shell_1.getRedirectSubdomainPath)(context, domain.subdomain, `/process-viewer/${created.id}`)
            });
        return created;
    }
    async cloneProcess(id, patch, targetSubdomain, targetGroupId, context) {
        const { domain, user, notify, tx } = context.state;
        const { t } = context;
        const repository = tx.getRepository(process_1.Process);
        const process = await repository.findOneBy({ domain: { id: (0, typeorm_1.In)([domain.id, domain.parentId].filter(Boolean)) }, id });
        if (!patch.name || (patch.name == process.name && targetSubdomain == domain.subdomain)) {
            throw t('error.name must be unique from the original process', { name: patch.name });
        }
        if (targetSubdomain != domain.subdomain) {
            if ((await repository.count({ where: { domain: { subdomain: targetSubdomain }, name: patch.name } })) > 0) {
                throw t('error.name must be unique from the original process', { name: patch.name });
            }
        }
        var targetDomain = domain;
        if (targetDomain && domain.subdomain != targetSubdomain) {
            targetDomain = await tx.getRepository(shell_1.Domain).findOneBy({ subdomain: targetSubdomain });
            if (!targetDomain) {
                throw `given subdomain(${targetSubdomain}) not found`;
            }
        }
        var targetProcessGroup = null;
        if (targetGroupId) {
            targetProcessGroup = await tx
                .getRepository(process_group_1.ProcessGroup)
                .findOneBy({ domain: { id: targetDomain.id }, id: targetGroupId });
            if (!targetProcessGroup) {
                throw `given group(${targetGroupId}) in domain(${targetSubdomain}) not found`;
            }
        }
        const { id: excluded } = process, clone = tslib_1.__rest(process, ["id"]);
        const cloned = await repository.save(Object.assign(Object.assign(Object.assign({ domain: targetDomain }, clone), patch), { group: targetProcessGroup, version: 0, state: 'draft', updater: user, creator: user }));
        notify &&
            notify({
                mode: 'in-app',
                title: `Process '${cloned.name}' cloned`,
                body: `Process '${cloned.name}' cloned by ${user.name}\n${cloned.description}`,
                image: (0, shell_1.getRedirectSubdomainPath)(context, targetSubdomain, `/thumbnail/${cloned.id}`),
                url: (0, shell_1.getRedirectSubdomainPath)(context, targetSubdomain, `/process-viewer/${cloned.id}`)
            });
        return cloned;
    }
    async updateProcess(id, patch, context) {
        const { domain, user, notify, tx } = context.state;
        const repository = tx.getRepository(process_1.Process);
        const process = await repository.findOne({
            where: { domain: { id: domain.id }, id },
            relations: ['creator']
        });
        if (patch.model) {
            const thumbnailPromise = (0, thumbnail_1.thumbnail)({
                model: patch.model,
                context
            });
            try {
                const thumbnailBase64 = await Promise.race([
                    thumbnailPromise,
                    new Promise((_, reject) => setTimeout(() => reject(new Error('5 seconds timeout')), 5000))
                ]);
                patch.thumbnail = 'data:image/png;base64,' + thumbnailBase64.toString('base64');
            }
            catch (e) {
                console.warn(`Failed to get thumbnail for '${process.name}' in first 5 seconds`);
                // 5초 안에 썸네일이 생성되지 않았으므로 모델만 저장합니다.
                patch.thumbnail =
                    'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; /* empty thumbnail */
                Promise.race([
                    thumbnailPromise,
                    new Promise((_, reject) => setTimeout(() => reject(new Error('Thumbnail extended timeout')), 5000))
                ])
                    .then(async (thumbnailBase64) => {
                    /* use new resource manager */
                    await (0, shell_1.getDataSource)().transaction(async (tx) => {
                        await tx.getRepository(process_1.Process).save({
                            id: updated.id,
                            thumbnail: 'data:image/png;base64,' + thumbnailBase64.toString('base64')
                        });
                    });
                })
                    .catch(error => {
                    console.error(`Failed to save thumbnail for '${process.name}' even after extended time:`, error);
                });
            }
        }
        const { groupId } = patch, patched = tslib_1.__rest(patch, ["groupId"]);
        if (groupId !== undefined) {
            const groupRepository = tx.getRepository(process_group_1.ProcessGroup);
            process.group = groupId
                ? (await groupRepository.findOneBy({
                    domain: { id: domain.id },
                    id: groupId
                })) || null
                : null;
        }
        const updated = await repository.save(Object.assign(Object.assign(Object.assign({ domain }, process), patched), { state: 'draft', updater: user }));
        notify &&
            notify({
                mode: 'in-app',
                title: `Process '${updated.name}' updated`,
                body: `Process '${updated.name}' updated by ${user.name}\n${updated.description}`,
                image: (0, shell_1.getRedirectSubdomainPath)(context, domain.subdomain, `/thumbnail/${updated.id}`),
                url: (0, shell_1.getRedirectSubdomainPath)(context, domain.subdomain, `/process-viewer/${updated.id}`)
            });
        return updated;
    }
    async releaseProcess(id, context) {
        const { domain, user, notify, tx } = context.state;
        const repository = tx.getRepository(process_1.Process);
        const process = await repository.findOne({
            where: { domain: { id: domain.id }, id },
            relations: ['creator']
        });
        if (!process) {
            throw `Process given id(${id}) is not found`;
        }
        if (process.state == 'released') {
            throw `Process given id(${id}) is already released`;
        }
        const updated = await repository.save(Object.assign(Object.assign({ domain }, process), { version: (process.version || 0) + 1, state: 'released', updater: user }));
        notify &&
            notify({
                mode: 'in-app',
                title: `Process '${updated.name}' released`,
                body: `Process '${updated.name}' released by ${user.name}\n${updated.description}`,
                image: (0, shell_1.getRedirectSubdomainPath)(context, domain.subdomain, `/thumbnail/${updated.id}`),
                url: (0, shell_1.getRedirectSubdomainPath)(context, domain.subdomain, `/process-viewer/${updated.id}`)
            });
        return updated;
    }
    async revertProcessVersion(id, version, context) {
        const { domain, user, notify, tx } = context.state;
        const repository = tx.getRepository(process_1.Process);
        const process = await repository.findOne({
            where: { domain: { id: domain.id }, id },
            relations: ['creator']
        });
        if (!process) {
            throw `Process with id(${id}) is not found`;
        }
        const historyRepository = tx.getRepository(process_history_1.ProcessHistory);
        const processHistory = await historyRepository.findOne({
            where: { domain: { id: domain.id }, originalId: id, version },
            order: { version: 'DESC' }
        });
        if (!processHistory) {
            throw `Process with id:version(${id}:${version}) is not found`;
        }
        const updated = await repository.save(Object.assign(Object.assign({ domain }, process), { model: processHistory.model, thumbnail: processHistory.thumbnail, state: 'draft', updater: user }));
        notify &&
            notify({
                mode: 'in-app',
                title: `Process '${updated.name}' updated`,
                body: `Process '${updated.name}' updated by ${user.name}\n${updated.description}`,
                image: (0, shell_1.getRedirectSubdomainPath)(context, domain.subdomain, `/thumbnail/${updated.id}`),
                url: (0, shell_1.getRedirectSubdomainPath)(context, domain.subdomain, `/process-viewer/${updated.id}`)
            });
        return updated;
    }
    async deleteProcess(id, context) {
        const { domain, user, notify, tx } = context.state;
        const repository = tx.getRepository(process_1.Process);
        const process = await repository.findOneBy({ domain: { id: domain.id }, id });
        const deleted = await repository.softDelete(id);
        notify &&
            notify({
                mode: 'in-app',
                title: `Process '${process.name}' deleted`,
                body: `Process '${process.name}' deleted by ${user.name}\n${process.description}`
            });
        return true;
    }
    async importProcesss(groupId, files, overwrite, context) {
        const { domain, user, notify, tx } = context.state;
        const groupRepository = tx.getRepository(process_group_1.ProcessGroup);
        const processRepository = tx.getRepository(process_1.Process);
        const group = await groupRepository.findOneBy({ domain: { id: domain.id }, id: groupId });
        if (!group) {
            throw `ProcessGroup with id(${groupId}) is not found`;
        }
        const processs = [];
        for (const file of files) {
            const { id, name, description, model, thumbnail } = await parseJSONFile(file);
            var sameNameProcess = await processRepository.findOneBy({ domain: { id: domain.id }, name });
            var sameIdProcess = await processRepository.findOneBy({ id });
            if (overwrite) {
                var process = {};
                if (sameIdProcess) {
                    if (overwrite && sameIdProcess.domainId != domain.id) {
                        throw `Process with id(${id}) is already taken in another domain`;
                    }
                    process = Object.assign(Object.assign({}, sameIdProcess), { name });
                    if (sameNameProcess && sameIdProcess.id != sameNameProcess.id) {
                        /* 이름 충돌 회피 */
                        process.name = `${process.name}(${Date.now()})`;
                    }
                }
                else {
                    process = {
                        id,
                        name: sameNameProcess ? `${name}(${Date.now()})` : name,
                        version: 0,
                        creator: user
                    };
                }
            }
            else {
                process = {
                    name,
                    version: 0,
                    creator: user
                };
                /* ID가 없으면, 사용해도 됨 */
                if (!sameIdProcess) {
                    process.id = id;
                }
                /* 이름 충돌 회피 */
                if (sameNameProcess) {
                    process.name = `${process.name}(${Date.now()})`;
                }
            }
            processs.push(await processRepository.save(Object.assign(Object.assign({}, process), { domain,
                description, model: typeof model != 'string' ? JSON.stringify(model) : model, thumbnail,
                group, state: 'draft', updater: user })));
        }
        notify &&
            notify({
                mode: 'in-app',
                title: `${processs.length} Process(s) are imported`,
                body: `${processs.length} Process(s) are imported into group ${group.name} by ${user.name}`
            });
        return processs;
    }
};
tslib_1.__decorate([
    (0, type_graphql_1.Directive)('@transaction'),
    (0, type_graphql_1.Directive)('@privilege(category: "process", privilege: "mutation", domainOwnerGranted: true)'),
    (0, type_graphql_1.Mutation)(returns => process_1.Process, { description: 'To create new Process' }),
    tslib_1.__param(0, (0, type_graphql_1.Arg)('process')),
    tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [process_type_1.NewProcess, Object]),
    tslib_1.__metadata("design:returntype", Promise)
], ProcessMutation.prototype, "createProcess", null);
tslib_1.__decorate([
    (0, type_graphql_1.Directive)('@transaction'),
    (0, type_graphql_1.Directive)('@privilege(category: "process", privilege: "mutation", domainOwnerGranted: true)'),
    (0, type_graphql_1.Mutation)(returns => process_1.Process, { description: 'To clone a Process from existing Process' }),
    tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
    tslib_1.__param(1, (0, type_graphql_1.Arg)('patch')),
    tslib_1.__param(2, (0, type_graphql_1.Arg)('targetSubdomain')),
    tslib_1.__param(3, (0, type_graphql_1.Arg)('targetGroupId', { nullable: true })),
    tslib_1.__param(4, (0, type_graphql_1.Ctx)()),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [String, process_type_1.ProcessPatch, String, String, Object]),
    tslib_1.__metadata("design:returntype", Promise)
], ProcessMutation.prototype, "cloneProcess", null);
tslib_1.__decorate([
    (0, type_graphql_1.Directive)('@transaction'),
    (0, type_graphql_1.Directive)('@privilege(category: "process", privilege: "mutation", domainOwnerGranted: true)'),
    (0, type_graphql_1.Mutation)(returns => process_1.Process, { description: 'To modify Process information' }),
    tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
    tslib_1.__param(1, (0, type_graphql_1.Arg)('patch')),
    tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [String, process_type_1.ProcessPatch, Object]),
    tslib_1.__metadata("design:returntype", Promise)
], ProcessMutation.prototype, "updateProcess", null);
tslib_1.__decorate([
    (0, type_graphql_1.Directive)('@transaction'),
    (0, type_graphql_1.Directive)('@privilege(category: "process", privilege: "mutation", domainOwnerGranted: true)'),
    (0, type_graphql_1.Mutation)(returns => process_1.Process, { description: 'To release a Process' }),
    tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
    tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [String, Object]),
    tslib_1.__metadata("design:returntype", Promise)
], ProcessMutation.prototype, "releaseProcess", null);
tslib_1.__decorate([
    (0, type_graphql_1.Directive)('@transaction'),
    (0, type_graphql_1.Directive)('@privilege(category: "process", privilege: "mutation", domainOwnerGranted: true)'),
    (0, type_graphql_1.Mutation)(returns => process_1.Process, { description: 'To revert Process version' }),
    tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
    tslib_1.__param(1, (0, type_graphql_1.Arg)('version')),
    tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [String, Number, Object]),
    tslib_1.__metadata("design:returntype", Promise)
], ProcessMutation.prototype, "revertProcessVersion", null);
tslib_1.__decorate([
    (0, type_graphql_1.Directive)('@transaction'),
    (0, type_graphql_1.Directive)('@privilege(category: "process", privilege: "mutation", domainOwnerGranted: true)'),
    (0, type_graphql_1.Mutation)(returns => Boolean, { description: 'To delete Process' }),
    tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
    tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [String, Object]),
    tslib_1.__metadata("design:returntype", Promise)
], ProcessMutation.prototype, "deleteProcess", null);
tslib_1.__decorate([
    (0, type_graphql_1.Directive)('@transaction'),
    (0, type_graphql_1.Directive)('@privilege(category: "process", privilege: "mutation", domainOwnerGranted: true)'),
    (0, type_graphql_1.Mutation)(returns => [process_1.Process], { description: 'To import some Processs' }),
    tslib_1.__param(0, (0, type_graphql_1.Arg)('groupId')),
    tslib_1.__param(1, (0, type_graphql_1.Arg)('files', () => [GraphQLUpload_js_1.default])),
    tslib_1.__param(2, (0, type_graphql_1.Arg)('overwrite')),
    tslib_1.__param(3, (0, type_graphql_1.Ctx)()),
    tslib_1.__metadata("design:type", Function),
    tslib_1.__metadata("design:paramtypes", [String, Array, Boolean, Object]),
    tslib_1.__metadata("design:returntype", Promise)
], ProcessMutation.prototype, "importProcesss", null);
ProcessMutation = tslib_1.__decorate([
    (0, type_graphql_1.Resolver)(process_1.Process)
], ProcessMutation);
exports.ProcessMutation = ProcessMutation;
//# sourceMappingURL=process-mutation.js.map