"use strict";
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());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerPlanRoutes = registerPlanRoutes;
var zod_1 = require("zod");
var error_log_service_js_1 = require("../services/error-log-service.js");
var plan_notification_service_js_1 = require("../services/plan-notification-service.js");
var plan_notification_policy_js_1 = require("../services/plan-notification-policy.js");
var plan_service_js_1 = require("../services/plan-service.js");
var client_js_1 = require("../db/client.js");
var env_js_1 = require("../config/env.js");
var git_service_js_1 = require("../services/git-service.js");
var error_log_plan_registration_service_js_1 = require("../services/error-log-plan-registration-service.js");
var plan_schedule_service_js_1 = require("../services/plan-schedule-service.js");
var visitor_history_service_js_1 = require("../services/visitor-history-service.js");
var board_service_js_1 = require("../services/board-service.js");
var completeActionSchema = zod_1.z.object({
    note: zod_1.z.string().trim().min(1).optional(),
});
var actionNoteSchema = zod_1.z.object({
    actionNote: zod_1.z.string().trim().min(1),
    actionType: zod_1.z.string().trim().min(1).optional(),
});
var createSourceWorkSchema = zod_1.z.object({
    summary: zod_1.z.string().trim().min(1),
    branchName: zod_1.z.string().trim().min(1),
    commitHash: zod_1.z.string().trim().min(1).nullable().optional(),
    previewUrl: zod_1.z.string().trim().url().nullable().optional(),
    changedFiles: zod_1.z.array(zod_1.z.string()).default([]),
    commandLog: zod_1.z.string().nullable().optional(),
    diffText: zod_1.z.string().nullable().optional(),
    sourceFiles: zod_1.z
        .array(zod_1.z.object({
        path: zod_1.z.string().trim().min(1),
        previousPath: zod_1.z.string().trim().min(1).nullable().optional(),
        status: zod_1.z.enum(['added', 'modified', 'deleted', 'renamed', 'binary', 'unknown']),
        language: zod_1.z.string().trim().min(1),
        content: zod_1.z.string(),
    }))
        .default([]),
});
function registerPlanRoutes(app) {
    return __awaiter(this, void 0, void 0, function () {
        function getRequestTraceContext(request) {
            return {
                ip: request.ip,
                remoteAddress: request.raw.socket.remoteAddress,
                host: request.headers.host,
                origin: request.headers.origin,
                referer: request.headers.referer,
                userAgent: request.headers['user-agent'],
                clientId: request.headers['x-client-id'],
            };
        }
        function isLoopbackAddress(value) {
            var normalizedValue = String(value !== null && value !== void 0 ? value : '').trim();
            return normalizedValue === '127.0.0.1' || normalizedValue === '::1' || normalizedValue === '::ffff:127.0.0.1';
        }
        function hasPlanAccessToken(accessToken) {
            return (0, error_log_service_js_1.hasErrorLogViewAccessToken)(accessToken);
        }
        function hasPlanAccess(request) {
            var _a, _b, _c;
            if (hasPlanAccessToken(request.headers['x-access-token'])) {
                return true;
            }
            return isLoopbackAddress(request.ip) || isLoopbackAddress((_b = (_a = request.raw) === null || _a === void 0 ? void 0 : _a.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress) || isLoopbackAddress((_c = request.socket) === null || _c === void 0 ? void 0 : _c.remoteAddress);
        }
        function requirePlanAccessToken(request, reply) {
            if (hasPlanAccess(request)) {
                return true;
            }
            void reply.code(403).send({
                message: '권한 토큰이 등록된 사용자만 수정할 수 있습니다.',
            });
            return false;
        }
        function handleListPlanScheduledTasks(request, reply) {
            return __awaiter(this, void 0, void 0, function () {
                var rows;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!hasPlanAccess(request)) {
                                return [2 /*return*/, reply.code(403).send({
                                        message: '권한 토큰이 등록된 사용자만 스케줄을 사용할 수 있습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.listPlanScheduledTasks)()];
                        case 1:
                            rows = _a.sent();
                            return [2 /*return*/, {
                                    items: rows.map(plan_schedule_service_js_1.mapPlanScheduledTaskRow),
                                }];
                    }
                });
            });
        }
        function handleCreatePlanScheduledTask(request, reply) {
            return __awaiter(this, void 0, void 0, function () {
                var payload, row, ignoreScheduleDueForImmediateRegistration, immediateRegistration, _a, _b, latestRow, _c;
                var _d;
                var _e, _f;
                return __generator(this, function (_g) {
                    switch (_g.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            payload = plan_schedule_service_js_1.createPlanScheduledTaskSchema.parse((_e = request.body) !== null && _e !== void 0 ? _e : {});
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.createPlanScheduledTask)(payload)];
                        case 1:
                            row = _g.sent();
                            ignoreScheduleDueForImmediateRegistration = payload.repeatWindows.length === 0
                                && payload.scheduleWeekdays.length === 0
                                && payload.scheduleDateRanges.length === 0;
                            if (!(payload.executionMode === 'managed-service' && payload.recreateManagedServiceOnNextSave)) return [3 /*break*/, 3];
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.registerPlanScheduledTaskNow)(Number(row.id), new Date(), {
                                    forceManagedServiceGeneration: true,
                                })];
                        case 2:
                            _a = _g.sent();
                            return [3 /*break*/, 7];
                        case 3:
                            if (!(payload.enabled && payload.immediateRunEnabled)) return [3 /*break*/, 5];
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.registerPlanScheduledTaskNow)(Number(row.id), new Date(), {
                                    ignoreScheduleDue: ignoreScheduleDueForImmediateRegistration,
                                })];
                        case 4:
                            _b = _g.sent();
                            return [3 /*break*/, 6];
                        case 5:
                            _b = null;
                            _g.label = 6;
                        case 6:
                            _a = _b;
                            _g.label = 7;
                        case 7:
                            immediateRegistration = _a;
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.getPlanScheduledTaskById)(Number(row.id))];
                        case 8:
                            latestRow = _g.sent();
                            _d = {
                                ok: true,
                                item: (0, plan_schedule_service_js_1.mapPlanScheduledTaskRow)(latestRow !== null && latestRow !== void 0 ? latestRow : row)
                            };
                            if (!(immediateRegistration === null || immediateRegistration === void 0 ? void 0 : immediateRegistration.createdPlan)) return [3 /*break*/, 10];
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(Number(immediateRegistration.createdPlan.id))];
                        case 9:
                            _c = _g.sent();
                            return [3 /*break*/, 11];
                        case 10:
                            _c = null;
                            _g.label = 11;
                        case 11: return [2 /*return*/, (_d.registeredPlan = _c,
                                _d.registeredBoardPosts = (_f = immediateRegistration === null || immediateRegistration === void 0 ? void 0 : immediateRegistration.createdBoardPosts) !== null && _f !== void 0 ? _f : [],
                                _d)];
                    }
                });
            });
        }
        function handleGetPlanScheduledTask(request, reply) {
            return __awaiter(this, void 0, void 0, function () {
                var id, row;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!hasPlanAccess(request)) {
                                return [2 /*return*/, reply.code(403).send({
                                        message: '권한 토큰이 등록된 사용자만 스케줄을 사용할 수 있습니다.',
                                    })];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.getPlanScheduledTaskById)(id)];
                        case 1:
                            row = _a.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '스케줄을 찾을 수 없습니다.',
                                    })];
                            }
                            return [2 /*return*/, {
                                    item: (0, plan_schedule_service_js_1.mapPlanScheduledTaskRow)(row),
                                }];
                    }
                });
            });
        }
        function handleUpdatePlanScheduledTask(request, reply) {
            return __awaiter(this, void 0, void 0, function () {
                var id, payload, row, shouldTriggerImmediateRegistration, effectiveRepeatWindows, effectiveScheduleDateRanges, effectiveScheduleWeekdays, immediateRegistration, _a, latestRow, _b;
                var _c;
                var _d, _e, _f, _g, _h;
                return __generator(this, function (_j) {
                    switch (_j.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            payload = plan_schedule_service_js_1.updatePlanScheduledTaskSchema.parse((_d = request.body) !== null && _d !== void 0 ? _d : {});
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.updatePlanScheduledTask)(id, payload)];
                        case 1:
                            row = _j.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '수정할 스케줄을 찾을 수 없습니다.',
                                    })];
                            }
                            shouldTriggerImmediateRegistration = (row
                                && String((_e = row.execution_mode) !== null && _e !== void 0 ? _e : '') === 'managed-service'
                                && payload.recreateManagedServiceOnNextSave === true) || (row
                                && Boolean((_f = row.enabled) !== null && _f !== void 0 ? _f : true)
                                && Boolean((_g = row.immediate_run_enabled) !== null && _g !== void 0 ? _g : true)
                                && payload.enabled !== false);
                            effectiveRepeatWindows = payload.repeatWindows !== undefined
                                ? payload.repeatWindows
                                : (row ? (0, plan_schedule_service_js_1.mapPlanScheduledTaskRow)(row).repeatWindows : []);
                            effectiveScheduleDateRanges = payload.scheduleDateRanges !== undefined
                                ? payload.scheduleDateRanges
                                : (row ? (0, plan_schedule_service_js_1.mapPlanScheduledTaskRow)(row).scheduleDateRanges : []);
                            effectiveScheduleWeekdays = payload.scheduleWeekdays !== undefined
                                ? payload.scheduleWeekdays
                                : (row ? (0, plan_schedule_service_js_1.mapPlanScheduledTaskRow)(row).scheduleWeekdays : []);
                            if (!shouldTriggerImmediateRegistration) return [3 /*break*/, 3];
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.registerPlanScheduledTaskNow)(id, new Date(), {
                                    ignoreScheduleDue: payload.recreateManagedServiceOnNextSave === true
                                        ? false
                                        : effectiveRepeatWindows.length === 0
                                            && effectiveScheduleWeekdays.length === 0
                                            && effectiveScheduleDateRanges.length === 0,
                                    forceManagedServiceGeneration: payload.recreateManagedServiceOnNextSave === true,
                                })];
                        case 2:
                            _a = _j.sent();
                            return [3 /*break*/, 4];
                        case 3:
                            _a = null;
                            _j.label = 4;
                        case 4:
                            immediateRegistration = _a;
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.getPlanScheduledTaskById)(Number(id))];
                        case 5:
                            latestRow = _j.sent();
                            _c = {
                                ok: true,
                                item: (0, plan_schedule_service_js_1.mapPlanScheduledTaskRow)(latestRow !== null && latestRow !== void 0 ? latestRow : row)
                            };
                            if (!(immediateRegistration === null || immediateRegistration === void 0 ? void 0 : immediateRegistration.createdPlan)) return [3 /*break*/, 7];
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(Number(immediateRegistration.createdPlan.id))];
                        case 6:
                            _b = _j.sent();
                            return [3 /*break*/, 8];
                        case 7:
                            _b = null;
                            _j.label = 8;
                        case 8: return [2 /*return*/, (_c.registeredPlan = _b,
                                _c.registeredBoardPosts = (_h = immediateRegistration === null || immediateRegistration === void 0 ? void 0 : immediateRegistration.createdBoardPosts) !== null && _h !== void 0 ? _h : [],
                                _c)];
                    }
                });
            });
        }
        function handleDeletePlanScheduledTask(request, reply) {
            return __awaiter(this, void 0, void 0, function () {
                var id, row;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.deletePlanScheduledTask)(id)];
                        case 1:
                            row = _a.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '삭제할 스케줄을 찾을 수 없습니다.',
                                    })];
                            }
                            return [2 /*return*/, {
                                    ok: true,
                                    id: id,
                                }];
                    }
                });
            });
        }
        var _this = this;
        return __generator(this, function (_a) {
            app.post('/api/plan/registrations/error-logs', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var payload, result;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            payload = zod_1.z.object({
                                rangeStart: zod_1.z.coerce.date().optional(),
                                rangeEnd: zod_1.z.coerce.date().optional(),
                                maxGroups: zod_1.z.coerce.number().int().min(1).max(24).optional(),
                            }).parse((_a = request.body) !== null && _a !== void 0 ? _a : {});
                            return [4 /*yield*/, (0, error_log_plan_registration_service_js_1.registerErrorLogBoardPosts)(payload)];
                        case 1:
                            result = _b.sent();
                            return [2 /*return*/, {
                                    ok: true,
                                    rangeStart: result.rangeStart.toISOString(),
                                    rangeEnd: result.rangeEnd.toISOString(),
                                    recentLogCount: result.recentLogs.length,
                                    candidateCount: result.candidates.length,
                                    rawCandidateCount: result.rawCandidates.length,
                                    createdBoardPosts: result.createdPosts,
                                    skippedBoardPosts: result.skippedPosts,
                                }];
                    }
                });
            }); });
            app.get('/api/plan/statuses', function () { return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_a) {
                    return [2 /*return*/, ({
                            items: plan_service_js_1.planStatuses,
                        })];
                });
            }); });
            app.post('/api/plan/setup', function (request) { return __awaiter(_this, void 0, void 0, function () {
                var payload;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            payload = plan_service_js_1.setupSchema.parse((_a = request.body) !== null && _a !== void 0 ? _a : {});
                            if (!payload.recreate) return [3 /*break*/, 6];
                            return [4 /*yield*/, client_js_1.db.schema.dropTableIfExists(plan_schedule_service_js_1.PLAN_SCHEDULED_TASK_TABLE)];
                        case 1:
                            _b.sent();
                            return [4 /*yield*/, client_js_1.db.schema.dropTableIfExists(plan_service_js_1.PLAN_ACTION_TABLE)];
                        case 2:
                            _b.sent();
                            return [4 /*yield*/, client_js_1.db.schema.dropTableIfExists(plan_service_js_1.PLAN_ISSUE_TABLE)];
                        case 3:
                            _b.sent();
                            return [4 /*yield*/, client_js_1.db.schema.dropTableIfExists(plan_service_js_1.PLAN_SOURCE_WORK_TABLE)];
                        case 4:
                            _b.sent();
                            return [4 /*yield*/, client_js_1.db.schema.dropTableIfExists(plan_service_js_1.PLAN_TABLE)];
                        case 5:
                            _b.sent();
                            _b.label = 6;
                        case 6: return [4 /*yield*/, (0, plan_service_js_1.ensurePlanTable)()];
                        case 7:
                            _b.sent();
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.ensurePlanScheduledTaskTable)()];
                        case 8:
                            _b.sent();
                            return [2 /*return*/, {
                                    ok: true,
                                    table: plan_service_js_1.PLAN_TABLE,
                                    scheduleTable: plan_schedule_service_js_1.PLAN_SCHEDULED_TASK_TABLE,
                                    releaseReviewTable: plan_service_js_1.PLAN_RELEASE_REVIEW_TABLE,
                                    statuses: plan_service_js_1.planStatuses,
                                }];
                    }
                });
            }); });
            app.get('/api/plan/release-reviews', function (request) { return __awaiter(_this, void 0, void 0, function () {
                var items;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0: return [4 /*yield*/, (0, plan_service_js_1.listPlanReleaseReviewBoardItems)({
                                maskNote: !hasPlanAccess(request),
                            })];
                        case 1:
                            items = _a.sent();
                            return [2 /*return*/, {
                                    items: items,
                                }];
                    }
                });
            }); });
            app.patch('/api/plan/release-reviews/:planItemId', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var planItemId, payload, clientId, visitor, _a, review;
                var _b, _c, _d;
                return __generator(this, function (_e) {
                    switch (_e.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            planItemId = zod_1.z.coerce.number().int().positive().parse(request.params.planItemId);
                            payload = plan_service_js_1.updatePlanReleaseReviewSchema.parse((_b = request.body) !== null && _b !== void 0 ? _b : {});
                            clientId = String((_c = request.headers['x-client-id']) !== null && _c !== void 0 ? _c : '').trim();
                            if (!clientId) return [3 /*break*/, 2];
                            return [4 /*yield*/, (0, visitor_history_service_js_1.getVisitorClientByClientId)(clientId)];
                        case 1:
                            _a = _e.sent();
                            return [3 /*break*/, 3];
                        case 2:
                            _a = null;
                            _e.label = 3;
                        case 3:
                            visitor = _a;
                            return [4 /*yield*/, (0, plan_service_js_1.upsertPlanReleaseReview)(planItemId, payload, {
                                    clientId: clientId || null,
                                    nickname: (_d = visitor === null || visitor === void 0 ? void 0 : visitor.nickname) !== null && _d !== void 0 ? _d : null,
                                })];
                        case 4:
                            review = _e.sent();
                            if (!review) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '검수 대상을 찾을 수 없습니다.',
                                    })];
                            }
                            return [2 /*return*/, {
                                    ok: true,
                                    item: review,
                                }];
                    }
                });
            }); });
            app.get('/api/plan/scheduled-tasks', handleListPlanScheduledTasks);
            app.get('/api/plan/schedule/tasks', handleListPlanScheduledTasks);
            app.get('/api/plan/schedule', handleListPlanScheduledTasks);
            app.get('/api/plan/schedules', handleListPlanScheduledTasks);
            app.get('/api/plans/scheduled-tasks', handleListPlanScheduledTasks);
            app.get('/api/plans/schedule/tasks', handleListPlanScheduledTasks);
            app.get('/api/plans/schedule', handleListPlanScheduledTasks);
            app.get('/api/plans/schedules', handleListPlanScheduledTasks);
            app.get('/api/plan/scheduled-tasks/:id', handleGetPlanScheduledTask);
            app.get('/api/plan/schedule/tasks/:id', handleGetPlanScheduledTask);
            app.get('/api/plan/schedule/:id', handleGetPlanScheduledTask);
            app.get('/api/plan/schedules/:id', handleGetPlanScheduledTask);
            app.get('/api/plans/scheduled-tasks/:id', handleGetPlanScheduledTask);
            app.get('/api/plans/schedule/tasks/:id', handleGetPlanScheduledTask);
            app.get('/api/plans/schedule/:id', handleGetPlanScheduledTask);
            app.get('/api/plans/schedules/:id', handleGetPlanScheduledTask);
            app.post('/api/plan/scheduled-tasks', handleCreatePlanScheduledTask);
            app.post('/api/plan/schedule/tasks', handleCreatePlanScheduledTask);
            app.post('/api/plan/schedule', handleCreatePlanScheduledTask);
            app.post('/api/plan/schedules', handleCreatePlanScheduledTask);
            app.post('/api/plans/scheduled-tasks', handleCreatePlanScheduledTask);
            app.post('/api/plans/schedule/tasks', handleCreatePlanScheduledTask);
            app.post('/api/plans/schedule', handleCreatePlanScheduledTask);
            app.post('/api/plans/schedules', handleCreatePlanScheduledTask);
            app.patch('/api/plan/scheduled-tasks/:id', handleUpdatePlanScheduledTask);
            app.patch('/api/plan/schedule/tasks/:id', handleUpdatePlanScheduledTask);
            app.patch('/api/plan/schedule/:id', handleUpdatePlanScheduledTask);
            app.patch('/api/plan/schedules/:id', handleUpdatePlanScheduledTask);
            app.patch('/api/plans/scheduled-tasks/:id', handleUpdatePlanScheduledTask);
            app.patch('/api/plans/schedule/tasks/:id', handleUpdatePlanScheduledTask);
            app.patch('/api/plans/schedule/:id', handleUpdatePlanScheduledTask);
            app.patch('/api/plans/schedules/:id', handleUpdatePlanScheduledTask);
            app.delete('/api/plan/scheduled-tasks/:id', handleDeletePlanScheduledTask);
            app.delete('/api/plan/schedule/tasks/:id', handleDeletePlanScheduledTask);
            app.delete('/api/plan/schedule/:id', handleDeletePlanScheduledTask);
            app.delete('/api/plan/schedules/:id', handleDeletePlanScheduledTask);
            app.delete('/api/plans/scheduled-tasks/:id', handleDeletePlanScheduledTask);
            app.delete('/api/plans/schedule/tasks/:id', handleDeletePlanScheduledTask);
            app.delete('/api/plans/schedule/:id', handleDeletePlanScheduledTask);
            app.delete('/api/plans/schedules/:id', handleDeletePlanScheduledTask);
            app.get('/api/plan/items', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var parsedQuery, query, items;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            parsedQuery = plan_service_js_1.listPlanQuerySchema.safeParse((_a = request.query) !== null && _a !== void 0 ? _a : {});
                            if (!parsedQuery.success) {
                                return [2 /*return*/, reply.code(400).send({
                                        message: '유효하지 않은 status 쿼리입니다.',
                                    })];
                            }
                            query = parsedQuery.data;
                            return [4 /*yield*/, (0, plan_service_js_1.listPlanItems)(query.status, {
                                    maskNote: !hasPlanAccess(request),
                                })];
                        case 1:
                            items = _b.sent();
                            return [2 /*return*/, {
                                    items: items,
                                }];
                    }
                });
            }); });
            app.get('/api/plan/items/:id', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var hasAccess, id, row;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            hasAccess = hasPlanAccess(request);
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id, {
                                    maskNote: !hasAccess,
                                })];
                        case 1:
                            row = _a.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [2 /*return*/, {
                                    item: row,
                                }];
                    }
                });
            }); });
            app.post('/api/plan/items', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var payload, createdRow, row, error_1;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            _b.label = 1;
                        case 1:
                            _b.trys.push([1, 4, , 5]);
                            payload = plan_service_js_1.createPlanSchema.parse((_a = request.body) !== null && _a !== void 0 ? _a : {});
                            return [4 /*yield*/, (0, plan_service_js_1.createPlanItem)(payload)];
                        case 2:
                            createdRow = _b.sent();
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(Number(createdRow.id))];
                        case 3:
                            row = _b.sent();
                            return [2 /*return*/, {
                                    ok: true,
                                    item: row,
                                }];
                        case 4:
                            error_1 = _b.sent();
                            return [2 /*return*/, reply.code(409).send({
                                    message: error_1 instanceof Error ? error_1.message : '작업 항목 등록에 실패했습니다.',
                                })];
                        case 5: return [2 /*return*/];
                    }
                });
            }); });
            app.patch('/api/plan/items/:id', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, payload, updatedRow, row, error_2;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            _b.label = 1;
                        case 1:
                            _b.trys.push([1, 4, , 5]);
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            payload = plan_service_js_1.updatePlanSchema.parse((_a = request.body) !== null && _a !== void 0 ? _a : {});
                            return [4 /*yield*/, (0, plan_service_js_1.updatePlanItem)(id, payload)];
                        case 2:
                            updatedRow = _b.sent();
                            if (!updatedRow) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '수정할 작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 3:
                            row = _b.sent();
                            return [2 /*return*/, {
                                    ok: true,
                                    item: row,
                                }];
                        case 4:
                            error_2 = _b.sent();
                            return [2 /*return*/, reply.code(409).send({
                                    message: error_2 instanceof Error ? error_2.message : '작업 항목 수정에 실패했습니다.',
                                })];
                        case 5: return [2 /*return*/];
                    }
                });
            }); });
            app.patch('/api/plan/items/:id/jangsing-processing', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, payload, updatedRow, error_3;
                var _a;
                var _b;
                return __generator(this, function (_c) {
                    switch (_c.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            _c.label = 1;
                        case 1:
                            _c.trys.push([1, 4, , 5]);
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            payload = plan_service_js_1.updatePlanJangsingProcessingSchema.parse((_b = request.body) !== null && _b !== void 0 ? _b : {});
                            return [4 /*yield*/, (0, plan_service_js_1.updatePlanItemJangsingProcessingRequired)(id, payload.jangsingProcessingRequired)];
                        case 2:
                            updatedRow = _c.sent();
                            if (!updatedRow) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '수정할 작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            _a = {
                                ok: true
                            };
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 3: return [2 /*return*/, (_a.item = _c.sent(),
                                _a)];
                        case 4:
                            error_3 = _c.sent();
                            return [2 /*return*/, reply.code(409).send({
                                    message: error_3 instanceof Error ? error_3.message : '기능동작확인 수정에 실패했습니다.',
                                })];
                        case 5: return [2 /*return*/];
                    }
                });
            }); });
            app.delete('/api/plan/items/:id', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, linkedBoardPost, row;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            request.log.warn({
                                planItemId: id,
                                trace: getRequestTraceContext(request),
                            }, 'Plan item delete requested');
                            return [4 /*yield*/, (0, plan_service_js_1.getBoardPostLinkedToPlanItem)(id)];
                        case 1:
                            linkedBoardPost = _a.sent();
                            if (linkedBoardPost) {
                                request.log.warn({
                                    planItemId: id,
                                    boardPostId: linkedBoardPost.id,
                                    boardPostTitle: linkedBoardPost.title,
                                    trace: getRequestTraceContext(request),
                                }, 'Plan item delete blocked because it is linked to a board post');
                                return [2 /*return*/, reply.code(409).send({
                                        message: "\uC790\uB3D9\uD654 \uC811\uC218\uB41C \uD56D\uBAA9\uC740 \uC0AD\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC5F0\uACB0 \uAC8C\uC2DC\uAE00 #".concat(linkedBoardPost.id),
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_service_js_1.deletePlanItem)(id)];
                        case 2:
                            row = _a.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '삭제할 작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [2 /*return*/, {
                                    ok: true,
                                    id: id,
                                }];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/complete-development', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, row, planLabel;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.markPlanAsDevelopmentComplete)(id)];
                        case 1:
                            row = _b.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.syncManagedServiceGenerationCompletion)(id)];
                        case 2:
                            _b.sent();
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(row.work_id), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] release \uBC18\uC601 \uB300\uAE30"), '수동 작업완료로 release 반영 대기 상태가 되었습니다.', 'development-completed')];
                        case 3:
                            _b.sent();
                            return [4 /*yield*/, (0, board_service_js_1.progressBoardPostAutomationByPlanResult)(id, 'completed')];
                        case 4:
                            _b.sent();
                            _a = {
                                ok: true
                            };
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 5: return [2 /*return*/, (_a.item = _b.sent(),
                                _a)];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/complete', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, payload, row, planLabel;
                var _a;
                var _b, _c;
                return __generator(this, function (_d) {
                    switch (_d.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            payload = completeActionSchema.parse((_b = request.body) !== null && _b !== void 0 ? _b : {});
                            return [4 /*yield*/, (0, plan_service_js_1.markPlanAsCompleted)(id, payload.note)];
                        case 1:
                            row = _d.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_schedule_service_js_1.syncManagedServiceGenerationCompletion)(id)];
                        case 2:
                            _d.sent();
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(row.work_id), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC644\uB8CC \uCC98\uB9AC"), (_c = payload.note) !== null && _c !== void 0 ? _c : '작업이 완료 처리되었습니다.', 'plan-completed')];
                        case 3:
                            _d.sent();
                            return [4 /*yield*/, (0, board_service_js_1.progressBoardPostAutomationByPlanResult)(id, 'completed')];
                        case 4:
                            _d.sent();
                            _a = {
                                ok: true
                            };
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 5: return [2 /*return*/, (_a.item = _d.sent(),
                                _a)];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/start-work', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, row, planLabel;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.markPlanAsStarted)(id)];
                        case 1:
                            row = _b.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(row.work_id), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC791\uC5C5\uC2DC\uC791"), '작업이 시작되었습니다.', 'work-started')];
                        case 2:
                            _b.sent();
                            _a = {
                                ok: true
                            };
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 3: return [2 /*return*/, (_a.item = _b.sent(),
                                _a)];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/retry-branch', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, row, planLabel;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.retryPlanBranch)(id)];
                        case 1:
                            row = _b.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(row.work_id), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC791\uC5C5 \uC7AC\uC2DC\uC791"), '브랜치 재시도를 요청했습니다.', 'plan-restarted')];
                        case 2:
                            _b.sent();
                            _a = {
                                ok: true
                            };
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 3: return [2 /*return*/, (_a.item = _b.sent(),
                                _a)];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/retry-work', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, row, planLabel;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.retryPlanWork)(id)];
                        case 1:
                            row = _b.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(row.work_id), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC791\uC5C5 \uC7AC\uC2DC\uC791"), '자동 작업 재처리를 요청했습니다.', 'plan-restarted')];
                        case 2:
                            _b.sent();
                            _a = {
                                ok: true
                            };
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 3: return [2 /*return*/, (_a.item = _b.sent(),
                                _a)];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/retry-merge', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, row, planLabel;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.retryPlanMerge)(id)];
                        case 1:
                            row = _b.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(row.work_id), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC791\uC5C5 \uC7AC\uC2DC\uC791"), row.worker_status === 'main반영대기' ? 'main 반영 재시도를 요청했습니다.' : 'release 반영 재시도를 요청했습니다.', 'plan-restarted')];
                        case 2:
                            _b.sent();
                            _a = {
                                ok: true
                            };
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 3: return [2 /*return*/, (_a.item = _b.sent(),
                                _a)];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/cancel-release', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, item, env, isReleaseMergeFailure, sourceWorkCount, requiresRollbackBeforeCancel, result, _a, error_4;
                var _b;
                var _c, _d, _e, _f;
                return __generator(this, function (_g) {
                    switch (_g.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 1:
                            item = _g.sent();
                            if (!item) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            _g.label = 2;
                        case 2:
                            _g.trys.push([2, 9, , 10]);
                            env = (0, env_js_1.getEnv)();
                            isReleaseMergeFailure = item.status === '작업완료' && item.workerStatus === 'release반영실패';
                            sourceWorkCount = Math.max(0, Number((_d = (_c = item.usageSnapshot) === null || _c === void 0 ? void 0 : _c.sourceWorkCount) !== null && _d !== void 0 ? _d : 0) || 0);
                            requiresRollbackBeforeCancel = sourceWorkCount > 0 &&
                                (item.status === '릴리즈완료' || item.workerStatus === 'main반영실패');
                            if (!(!isReleaseMergeFailure && requiresRollbackBeforeCancel)) return [3 /*break*/, 4];
                            return [4 /*yield*/, (0, git_service_js_1.recreateReleaseBranchFromMain)({
                                    repoPath: env.PLAN_GIT_REPO_PATH,
                                    releaseBranch: env.PLAN_RELEASE_BRANCH,
                                    mainBranch: env.PLAN_MAIN_BRANCH,
                                }, String((_e = item.releaseTarget) !== null && _e !== void 0 ? _e : env.PLAN_RELEASE_BRANCH))];
                        case 3:
                            _g.sent();
                            _g.label = 4;
                        case 4: return [4 /*yield*/, (0, plan_service_js_1.cancelPlanRelease)(id)];
                        case 5:
                            result = _g.sent();
                            _b = {
                                ok: true
                            };
                            if (!((_f = result === null || result === void 0 ? void 0 : result.item) !== null && _f !== void 0)) return [3 /*break*/, 6];
                            _a = _f;
                            return [3 /*break*/, 8];
                        case 6: return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 7:
                            _a = (_g.sent());
                            _g.label = 8;
                        case 8: return [2 /*return*/, (_b.item = _a,
                                _b.message = result === null || result === void 0 ? void 0 : result.message,
                                _b)];
                        case 9:
                            error_4 = _g.sent();
                            return [2 /*return*/, reply.code(409).send({
                                    message: error_4 instanceof Error ? error_4.message : 'release 작업취소 처리에 실패했습니다.',
                                })];
                        case 10: return [2 /*return*/];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/request-main-merge', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, result;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.requestPlanMainMerge)(id)];
                        case 1:
                            result = _a.sent();
                            if (!result) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [2 /*return*/, {
                                    ok: true,
                                    item: result.item,
                                    message: result.message,
                                }];
                    }
                });
            }); });
            app.get('/api/plan/items/:id/issues', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, item, rows;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!hasPlanAccess(request)) {
                                return [2 /*return*/, reply.code(403).send({
                                        message: '상세 조회 권한이 없습니다.',
                                    })];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 1:
                            item = _a.sent();
                            if (!item) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_service_js_1.listPlanIssueHistories)(id)];
                        case 2:
                            rows = _a.sent();
                            return [2 /*return*/, {
                                    items: rows.map(plan_service_js_1.mapPlanIssueRow),
                                }];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/issues/action', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, payload, item, row, retryResult, planLabel, planLabel, _a, error_5;
                var _b;
                var _c, _d, _e;
                return __generator(this, function (_f) {
                    switch (_f.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            payload = plan_service_js_1.issueActionSchema.parse((_c = request.body) !== null && _c !== void 0 ? _c : {});
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 1:
                            item = _f.sent();
                            if (!item) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            _f.label = 2;
                        case 2:
                            _f.trys.push([2, 12, , 13]);
                            return [4 /*yield*/, (0, plan_service_js_1.appendLatestIssueAction)(id, payload.actionNote, payload.resolve)];
                        case 3:
                            row = _f.sent();
                            return [4 /*yield*/, (0, plan_service_js_1.queuePlanRetryFromIssueAction)(id, payload.actionNote, payload.retry)];
                        case 4:
                            retryResult = _f.sent();
                            if (!payload.resolve) return [3 /*break*/, 6];
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(item.workId), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC774\uC288 \uD574\uACB0 \uCC98\uB9AC"), "".concat(row.issue_tag, " \uC774\uC288\uAC00 \uD574\uACB0 \uCC98\uB9AC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."), 'issue-resolved')];
                        case 5:
                            _f.sent();
                            _f.label = 6;
                        case 6:
                            if (!(0, plan_notification_policy_js_1.shouldNotifyPlanRestart)(retryResult)) return [3 /*break*/, 8];
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(item.workId), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC791\uC5C5 \uC7AC\uC2DC\uC791"), (_d = retryResult === null || retryResult === void 0 ? void 0 : retryResult.message) !== null && _d !== void 0 ? _d : '작업 재시작을 요청했습니다.', 'plan-restarted')];
                        case 7:
                            _f.sent();
                            _f.label = 8;
                        case 8:
                            _b = {
                                ok: true,
                                item: (0, plan_service_js_1.mapPlanIssueRow)(row)
                            };
                            if (!((_e = retryResult === null || retryResult === void 0 ? void 0 : retryResult.item) !== null && _e !== void 0)) return [3 /*break*/, 9];
                            _a = _e;
                            return [3 /*break*/, 11];
                        case 9: return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 10:
                            _a = (_f.sent());
                            _f.label = 11;
                        case 11: return [2 /*return*/, (_b.planItem = _a,
                                _b.message = retryResult === null || retryResult === void 0 ? void 0 : retryResult.message,
                                _b)];
                        case 12:
                            error_5 = _f.sent();
                            return [2 /*return*/, reply.code(409).send({
                                    message: error_5 instanceof Error ? error_5.message : '이슈 조치 기록 저장에 실패했습니다.',
                                })];
                        case 13: return [2 /*return*/];
                    }
                });
            }); });
            app.get('/api/plan/items/:id/actions', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, item, rows;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!hasPlanAccess(request)) {
                                return [2 /*return*/, reply.code(403).send({
                                        message: '상세 조회 권한이 없습니다.',
                                    })];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 1:
                            item = _a.sent();
                            if (!item) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_service_js_1.listPlanActionHistories)(id)];
                        case 2:
                            rows = _a.sent();
                            return [2 /*return*/, {
                                    items: rows.map(plan_service_js_1.mapPlanActionRow),
                                }];
                    }
                });
            }); });
            app.get('/api/plan/items/:id/source-works', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, item, rows;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!hasPlanAccess(request)) {
                                return [2 /*return*/, reply.code(403).send({
                                        message: '상세 조회 권한이 없습니다.',
                                    })];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 1:
                            item = _a.sent();
                            if (!item) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_service_js_1.listPlanSourceWorkHistories)(id)];
                        case 2:
                            rows = _a.sent();
                            return [2 /*return*/, {
                                    items: rows.map(plan_service_js_1.mapPlanSourceWorkRow),
                                }];
                    }
                });
            }); });
            app.get('/api/plan/items/:id/source-works/:sourceWorkId', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var params, row;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            if (!hasPlanAccess(request)) {
                                return [2 /*return*/, reply.code(403).send({
                                        message: '상세 조회 권한이 없습니다.',
                                    })];
                            }
                            params = zod_1.z.object({
                                id: zod_1.z.coerce.number().int().positive(),
                                sourceWorkId: zod_1.z.coerce.number().int().positive(),
                            }).parse(request.params);
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanSourceWorkHistory)(params.id, params.sourceWorkId)];
                        case 1:
                            row = _a.sent();
                            if (!row) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '소스 작업 이력을 찾을 수 없습니다.',
                                    })];
                            }
                            return [2 /*return*/, {
                                    item: (0, plan_service_js_1.mapPlanSourceWorkRow)(row),
                                }];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/source-works', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, payload, item, row;
                var _a;
                return __generator(this, function (_b) {
                    switch (_b.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            payload = createSourceWorkSchema.parse((_a = request.body) !== null && _a !== void 0 ? _a : {});
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 1:
                            item = _b.sent();
                            if (!item) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_service_js_1.createPlanSourceWorkHistory)(id, payload)];
                        case 2:
                            row = _b.sent();
                            return [2 /*return*/, {
                                    ok: true,
                                    item: (0, plan_service_js_1.mapPlanSourceWorkRow)(row),
                                }];
                    }
                });
            }); });
            app.post('/api/plan/items/:id/actions/note', function (request, reply) { return __awaiter(_this, void 0, void 0, function () {
                var id, payload, item, row, releaseResumeResult, retryResult, _a, planLabel, _b;
                var _c;
                var _d, _e, _f, _g;
                return __generator(this, function (_h) {
                    switch (_h.label) {
                        case 0:
                            if (!requirePlanAccessToken(request, reply)) {
                                return [2 /*return*/];
                            }
                            id = zod_1.z.coerce.number().int().positive().parse(request.params.id);
                            payload = actionNoteSchema.parse((_d = request.body) !== null && _d !== void 0 ? _d : {});
                            return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 1:
                            item = _h.sent();
                            if (!item) {
                                return [2 /*return*/, reply.code(404).send({
                                        message: '작업 항목을 찾을 수 없습니다.',
                                    })];
                            }
                            if (!item.startedAt) {
                                return [2 /*return*/, reply.code(409).send({
                                        message: '작업시작 이후부터 조치 이력을 기록할 수 있습니다.',
                                    })];
                            }
                            return [4 /*yield*/, (0, plan_service_js_1.createPlanActionHistory)(id, (_e = payload.actionType) !== null && _e !== void 0 ? _e : '추가조치', payload.actionNote)];
                        case 2:
                            row = _h.sent();
                            return [4 /*yield*/, (0, plan_service_js_1.resumePlanDevelopmentFromRelease)(id, payload.actionNote)];
                        case 3:
                            releaseResumeResult = _h.sent();
                            if (!(releaseResumeResult === null || releaseResumeResult === void 0 ? void 0 : releaseResumeResult.message)) return [3 /*break*/, 4];
                            _a = releaseResumeResult;
                            return [3 /*break*/, 6];
                        case 4: return [4 /*yield*/, (0, plan_service_js_1.queuePlanRetryFromFailure)(id, payload.actionNote)];
                        case 5:
                            _a = _h.sent();
                            _h.label = 6;
                        case 6:
                            retryResult = _a;
                            if (!(0, plan_notification_policy_js_1.shouldNotifyPlanRestart)(retryResult)) return [3 /*break*/, 8];
                            planLabel = (0, plan_service_js_1.formatPlanNotificationLabel)(String(item.workId), id);
                            return [4 /*yield*/, (0, plan_notification_service_js_1.notifyPlanEvent)(id, "[".concat(planLabel, "] \uC791\uC5C5 \uC7AC\uC2DC\uC791"), (_f = retryResult === null || retryResult === void 0 ? void 0 : retryResult.message) !== null && _f !== void 0 ? _f : '작업 재시작을 요청했습니다.', 'plan-restarted')];
                        case 7:
                            _h.sent();
                            _h.label = 8;
                        case 8:
                            _c = {
                                ok: true,
                                item: (0, plan_service_js_1.mapPlanActionRow)(row)
                            };
                            if (!((_g = retryResult === null || retryResult === void 0 ? void 0 : retryResult.item) !== null && _g !== void 0)) return [3 /*break*/, 9];
                            _b = _g;
                            return [3 /*break*/, 11];
                        case 9: return [4 /*yield*/, (0, plan_service_js_1.getPlanItemById)(id)];
                        case 10:
                            _b = (_h.sent());
                            _h.label = 11;
                        case 11: return [2 /*return*/, (_c.planItem = _b,
                                _c.message = retryResult === null || retryResult === void 0 ? void 0 : retryResult.message,
                                _c)];
                    }
                });
            }); });
            return [2 /*return*/];
        });
    });
}
