"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
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 };
    }
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.updatePlanReleaseReviewSchema = exports.planReleaseReviewStatusSchema = exports.listPlanQuerySchema = exports.issueActionSchema = exports.updatePlanJangsingProcessingSchema = exports.updatePlanSchema = exports.createPlanSchema = exports.planAutomationTypeSchema = exports.setupSchema = exports.statusSchema = exports.planReleaseReviewStatuses = exports.planWorkerStatuses = exports.planAutomationTypes = exports.planStatuses = exports.PLAN_RELEASE_REVIEW_TABLE = exports.PLAN_SOURCE_WORK_TABLE = exports.PLAN_ACTION_TABLE = exports.PLAN_ISSUE_TABLE = exports.PLAN_TABLE = void 0;
exports.normalizePlanWorkId = normalizePlanWorkId;
exports.normalizePlanAutomationType = normalizePlanAutomationType;
exports.formatPlanNotificationLabel = formatPlanNotificationLabel;
exports.maskPlanNote = maskPlanNote;
exports.normalizeChangedFiles = normalizeChangedFiles;
exports.filterRetryWorklogEvidencePaths = filterRetryWorklogEvidencePaths;
exports.buildPlanBranchName = buildPlanBranchName;
exports.shouldUseLocalMainPlanMode = shouldUseLocalMainPlanMode;
exports.mapPlanRow = mapPlanRow;
exports.mapPlanIssueRow = mapPlanIssueRow;
exports.mapPlanActionRow = mapPlanActionRow;
exports.mapPlanSourceWorkRow = mapPlanSourceWorkRow;
exports.mapPlanAutomationUsageSnapshot = mapPlanAutomationUsageSnapshot;
exports.mapPlanReleaseReviewMetadata = mapPlanReleaseReviewMetadata;
exports.mapPlanReleaseReviewRow = mapPlanReleaseReviewRow;
exports.syncPlanAutomationUsageSnapshot = syncPlanAutomationUsageSnapshot;
exports.ensurePlanTable = ensurePlanTable;
exports.createPlanItem = createPlanItem;
exports.createCompletedPlanExecutionLogItem = createCompletedPlanExecutionLogItem;
exports.upsertAutoPlanItem = upsertAutoPlanItem;
exports.updatePlanItem = updatePlanItem;
exports.updatePlanItemJangsingProcessingRequired = updatePlanItemJangsingProcessingRequired;
exports.deletePlanItem = deletePlanItem;
exports.getBoardPostLinkedToPlanItem = getBoardPostLinkedToPlanItem;
exports.markPlanAsDevelopmentComplete = markPlanAsDevelopmentComplete;
exports.markPlanAsCompleted = markPlanAsCompleted;
exports.markPlanAsStarted = markPlanAsStarted;
exports.retryPlanBranch = retryPlanBranch;
exports.retryPlanMerge = retryPlanMerge;
exports.requestPlanMainMerge = requestPlanMainMerge;
exports.retryPlanWork = retryPlanWork;
exports.isPlanLockedByWorker = isPlanLockedByWorker;
exports.resumePlanDevelopmentFromRelease = resumePlanDevelopmentFromRelease;
exports.queuePlanRetryFromFailure = queuePlanRetryFromFailure;
exports.shouldResumePlanDevelopmentFromIssueAction = shouldResumePlanDevelopmentFromIssueAction;
exports.queuePlanRetryFromIssueAction = queuePlanRetryFromIssueAction;
exports.cancelPlanRelease = cancelPlanRelease;
exports.createPlanActionHistory = createPlanActionHistory;
exports.resolveAutomationIssueHistories = resolveAutomationIssueHistories;
exports.listPlanActionHistories = listPlanActionHistories;
exports.createPlanSourceWorkHistory = createPlanSourceWorkHistory;
exports.listPlanSourceWorkHistories = listPlanSourceWorkHistories;
exports.getPlanSourceWorkHistory = getPlanSourceWorkHistory;
exports.listLatestPlanSourceWorkMap = listLatestPlanSourceWorkMap;
exports.upsertPlanReleaseReview = upsertPlanReleaseReview;
exports.listPlanReleaseReviewBoardItems = listPlanReleaseReviewBoardItems;
exports.createPlanIssueHistory = createPlanIssueHistory;
exports.appendLatestIssueAction = appendLatestIssueAction;
exports.listPlanIssueHistories = listPlanIssueHistories;
exports.listPlanIssueSummaries = listPlanIssueSummaries;
exports.claimNextPlanForBranch = claimNextPlanForBranch;
exports.claimNextPlanForExecution = claimNextPlanForExecution;
exports.claimNextPlanForMerge = claimNextPlanForMerge;
exports.claimNextPlanForMainMerge = claimNextPlanForMainMerge;
exports.markPlanBranchReady = markPlanBranchReady;
exports.markPlanWorkCompleted = markPlanWorkCompleted;
exports.markPlanReleaseMerged = markPlanReleaseMerged;
exports.markPlanMerged = markPlanMerged;
exports.markPlanMainMergeFailure = markPlanMainMergeFailure;
exports.markPlanAutomationFailure = markPlanAutomationFailure;
exports.listPlanItems = listPlanItems;
exports.getPlanItemById = getPlanItemById;
exports.findLatestPlanItem = findLatestPlanItem;
exports.findPlanItemByWorkId = findPlanItemByWorkId;
exports.findPlanItemByPreviewUrl = findPlanItemByPreviewUrl;
var zod_1 = require("zod");
var env_js_1 = require("../config/env.js");
var client_js_1 = require("../db/client.js");
var automation_context_service_js_1 = require("./automation-context-service.js");
var automation_type_config_service_js_1 = require("./automation-type-config-service.js");
var plan_retry_policy_js_1 = require("./plan-retry-policy.js");
exports.PLAN_TABLE = 'plan_items';
exports.PLAN_ISSUE_TABLE = 'plan_issue_histories';
exports.PLAN_ACTION_TABLE = 'plan_action_histories';
exports.PLAN_SOURCE_WORK_TABLE = 'plan_source_work_histories';
exports.PLAN_RELEASE_REVIEW_TABLE = 'plan_release_reviews';
exports.planStatuses = ['등록', '작업중', '작업완료', '릴리즈완료', '완료'];
exports.planAutomationTypes = ['none', 'plan', 'command_execution', 'non_source_work', 'auto_worker'];
exports.planWorkerStatuses = [
    '대기',
    '브랜치생성중',
    '브랜치준비',
    '자동작업중',
    'release반영대기',
    'release반영중',
    'release반영완료',
    'main반영대기',
    'main반영중',
    'main반영완료',
    '자동완료',
    '브랜치실패',
    'release반영실패',
    'main반영실패',
    '자동작업실패',
    '작업취소',
];
exports.planReleaseReviewStatuses = ['pending', 'reviewing', 'approved', 'changes-requested'];
exports.statusSchema = zod_1.z.enum(exports.planStatuses);
exports.setupSchema = zod_1.z.object({
    recreate: zod_1.z.boolean().optional(),
});
function resolvePlanAutomationTypeAlias(value) {
    return (0, automation_type_config_service_js_1.normalizeLegacyAutomationBehaviorType)(value);
}
exports.planAutomationTypeSchema = zod_1.z.preprocess(resolvePlanAutomationTypeAlias, zod_1.z.string().trim().min(1).max(120));
exports.createPlanSchema = zod_1.z.object({
    workId: zod_1.z.string().trim().optional().default('작업ID'),
    note: zod_1.z.string().default(''),
    automationType: zod_1.z.preprocess(resolvePlanAutomationTypeAlias, zod_1.z.string().trim().min(1).max(120).default('none')),
    automationContextIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(160)).max(20).optional().default([]),
    releaseTarget: zod_1.z.string().trim().min(1).default('release'),
    jangsingProcessingRequired: zod_1.z.boolean().default(true),
    autoDeployToMain: zod_1.z.boolean().default(true),
    suppressWebPush: zod_1.z.boolean().default(false),
    repeatRequestEnabled: zod_1.z.boolean().default(false),
    repeatIntervalMinutes: zod_1.z.coerce.number().int().min(1).max(1440).default(60),
});
exports.updatePlanSchema = zod_1.z.object({
    workId: zod_1.z.string().trim().optional(),
    note: zod_1.z.string().optional(),
    automationType: zod_1.z.preprocess(resolvePlanAutomationTypeAlias, zod_1.z.string().trim().min(1).max(120).optional()),
    automationContextIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(160)).max(20).optional(),
    releaseTarget: zod_1.z.string().trim().min(1).optional(),
    jangsingProcessingRequired: zod_1.z.boolean().optional(),
    autoDeployToMain: zod_1.z.boolean().optional(),
    suppressWebPush: zod_1.z.boolean().optional(),
    repeatRequestEnabled: zod_1.z.boolean().optional(),
    repeatIntervalMinutes: zod_1.z.coerce.number().int().min(1).max(1440).optional(),
});
exports.updatePlanJangsingProcessingSchema = zod_1.z.object({
    jangsingProcessingRequired: zod_1.z.boolean(),
});
exports.issueActionSchema = zod_1.z.object({
    actionNote: zod_1.z.string().trim().min(1),
    resolve: zod_1.z.boolean().default(false),
    retry: zod_1.z.boolean().default(false),
});
var planListStatusFilterAliases = new Set(['all', 'in-progress', 'done', 'error']);
exports.listPlanQuerySchema = zod_1.z.object({
    status: zod_1.z.preprocess(function (value) {
        if (typeof value !== 'string') {
            return value;
        }
        var normalizedValue = value.trim();
        if (!normalizedValue || planListStatusFilterAliases.has(normalizedValue)) {
            return undefined;
        }
        return normalizedValue;
    }, exports.statusSchema.optional()),
});
exports.planReleaseReviewStatusSchema = zod_1.z.enum(exports.planReleaseReviewStatuses);
var planReleaseReviewMetadataSchema = zod_1.z.object({
    summary: zod_1.z.string().trim().min(1).max(500).optional(),
    pageSelectionIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(160)).max(12).optional(),
    checkedPageSelectionIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(160)).max(12).optional(),
    docIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(160)).max(12).optional(),
    componentIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(160)).max(12).optional(),
    widgetIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(160)).max(12).optional(),
});
exports.updatePlanReleaseReviewSchema = zod_1.z.object({
    status: exports.planReleaseReviewStatusSchema.optional(),
    reviewNote: zod_1.z.string().max(4000).optional(),
    metadata: planReleaseReviewMetadataSchema.optional(),
});
var planFailureWorkerStatuses = new Set([
    '브랜치실패',
    '자동작업실패',
    'release반영실패',
    'main반영실패',
]);
var functionCheckEditableStatuses = new Set(['작업완료', '릴리즈완료', '완료']);
var automationIssueTags = ['#브랜치실패', '#자동작업실패', '#release반영실패', '#main반영실패'];
var WORKLOG_EVIDENCE_PATH_PATTERN = /^docs\/(?:worklogs\/.+\.md|assets\/worklogs\/.+)/i;
function sanitizeBranchToken(value) {
    return value
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .slice(0, 48);
}
function normalizePlanWorkId(value) {
    var workId = String(value !== null && value !== void 0 ? value : '').trim();
    var normalized = workId.replace(/^\[|\]$/g, '').replace(/\s+/g, '').toLowerCase();
    if (!workId || normalized === '작업id' || normalized === 'workid' || normalized === 'undefined' || normalized === 'null') {
        return '작업ID';
    }
    return workId;
}
function normalizePlanAutomationType(value) {
    var normalizedValue = resolvePlanAutomationTypeAlias(value);
    return exports.planAutomationTypes.includes(normalizedValue)
        ? normalizedValue
        : 'none';
}
function shouldSkipLifecycleSourceWork(row) {
    return normalizePlanAutomationType(row === null || row === void 0 ? void 0 : row.automation_type) === 'plan';
}
function formatPlanNotificationLabel(workId, id) {
    var normalizedWorkId = normalizePlanWorkId(workId);
    return normalizedWorkId === '작업ID' ? "#".concat(id) : normalizedWorkId;
}
function maskPlanNote(value) {
    var normalized = String(value !== null && value !== void 0 ? value : '').replace(/\s+/g, ' ').trim();
    if (!normalized) {
        return '';
    }
    var words = normalized.split(' ').filter(Boolean);
    if (words.length === 1) {
        var word = words[0];
        if (word.length <= 1) {
            return '*';
        }
        return "".concat(word[0]).concat('*'.repeat(word.length - 1));
    }
    return words
        .map(function (word, index) {
        if (word.length <= 1) {
            return '*';
        }
        if (index === 0) {
            return "".concat(word[0]).concat('*'.repeat(word.length - 1));
        }
        if (index === words.length - 1) {
            return "".concat('*'.repeat(word.length - 1)).concat(word[word.length - 1]);
        }
        return word;
    })
        .join(' ');
}
function normalizeChangedFiles(rawChangedFiles) {
    return __spreadArray([], new Set((rawChangedFiles !== null && rawChangedFiles !== void 0 ? rawChangedFiles : []).map(function (file) { return String(file !== null && file !== void 0 ? file : '').trim(); }).filter(Boolean)), true);
}
function filterRetryWorklogEvidencePaths(changedFiles, existingChangedFilesList) {
    var normalizedChangedFiles = normalizeChangedFiles(changedFiles);
    var existingEvidencePaths = new Set(existingChangedFilesList
        .flatMap(function (files) { return normalizeChangedFiles(files); })
        .filter(function (file) { return WORKLOG_EVIDENCE_PATH_PATTERN.test(file); }));
    return normalizedChangedFiles.filter(function (file) { return !WORKLOG_EVIDENCE_PATH_PATTERN.test(file) || !existingEvidencePaths.has(file); });
}
function buildPlanBranchName(workId, id) {
    var token = sanitizeBranchToken(workId) || "plan-".concat(id);
    var prefix = /^auto-worklog-\d{4}-\d{2}-\d{2}$/i.test(String(workId !== null && workId !== void 0 ? workId : '').trim()) ? 'hotfix' : 'feature';
    return "".concat(prefix, "/plan-").concat(id, "-").concat(token);
}
function shouldUseLocalMainPlanMode(automationType) {
    var env = (0, env_js_1.getEnv)();
    return Boolean(env.PLAN_LOCAL_MAIN_MODE) && normalizePlanAutomationType(automationType) !== 'auto_worker';
}
function mapPlanRow(row, options) {
    var _a, _b, _c, _d, _e, _f, _g;
    return {
        id: row.id,
        workId: row.work_id,
        note: (options === null || options === void 0 ? void 0 : options.maskNote) ? maskPlanNote(row.note) : row.note,
        automationType: (options === null || options === void 0 ? void 0 : options.exposeConfiguredAutomationType) ? (0, automation_type_config_service_js_1.resolveStoredAutomationTypeId)(row) : normalizePlanAutomationType(row.automation_type),
        automationBehaviorType: normalizePlanAutomationType(row.automation_type),
        automationContextIds: (0, automation_context_service_js_1.parseAutomationContextIds)(row.automation_context_ids_json),
        releaseReviewNote: (_a = options === null || options === void 0 ? void 0 : options.releaseReviewNote) !== null && _a !== void 0 ? _a : '',
        noteMasked: Boolean(options === null || options === void 0 ? void 0 : options.noteMasked),
        status: row.status,
        jangsingProcessingRequired: typeof row.jangsing_processing_required === 'boolean'
            ? row.jangsing_processing_required
            : row.normal_processing_level === '상',
        autoDeployToMain: Boolean((_b = row.auto_deploy_to_main) !== null && _b !== void 0 ? _b : true),
        suppressWebPush: Boolean((_c = row.suppress_web_push) !== null && _c !== void 0 ? _c : false),
        repeatRequestEnabled: Boolean((_d = row.repeat_request_enabled) !== null && _d !== void 0 ? _d : false),
        repeatIntervalMinutes: Number((_e = row.repeat_interval_minutes) !== null && _e !== void 0 ? _e : 60),
        assignedBranch: row.assigned_branch,
        releaseTarget: row.release_target,
        workerStatus: row.worker_status,
        lastError: row.last_error,
        issueTags: (_f = options === null || options === void 0 ? void 0 : options.issueTags) !== null && _f !== void 0 ? _f : [],
        hasOpenIssues: (_g = options === null || options === void 0 ? void 0 : options.hasOpenIssues) !== null && _g !== void 0 ? _g : false,
        startedAt: row.started_at,
        completedAt: row.completed_at,
        mergedAt: row.merged_at,
        usageSnapshot: mapPlanAutomationUsageSnapshot(row.usage_snapshot),
        createdAt: row.created_at,
        updatedAt: row.updated_at,
    };
}
function mapPlanIssueRow(row) {
    return {
        id: row.id,
        planItemId: row.plan_item_id,
        issueTag: row.issue_tag,
        message: row.message,
        actionNote: row.action_note,
        resolved: row.resolved,
        resolvedAt: row.resolved_at,
        createdAt: row.created_at,
    };
}
function mapPlanActionRow(row) {
    return {
        id: row.id,
        planItemId: row.plan_item_id,
        actionType: row.action_type,
        note: row.note,
        createdAt: row.created_at,
    };
}
function mapPlanSourceWorkRow(row) {
    var _a, _b, _c;
    var changedFilesText = String((_a = row.changed_files) !== null && _a !== void 0 ? _a : '[]');
    var changedFiles = [];
    var sourceFilesText = String((_b = row.source_files) !== null && _b !== void 0 ? _b : '[]');
    var sourceFiles = [];
    try {
        changedFiles = normalizeChangedFiles(JSON.parse(changedFilesText));
    }
    catch (_d) {
        changedFiles = [];
    }
    try {
        sourceFiles = JSON.parse(sourceFilesText);
    }
    catch (_e) {
        sourceFiles = [];
    }
    return {
        id: row.id,
        planItemId: row.plan_item_id,
        summary: row.summary,
        branchName: row.branch_name,
        commitHash: row.commit_hash,
        previewUrl: (_c = row.preview_url) !== null && _c !== void 0 ? _c : null,
        changedFiles: changedFiles,
        commandLog: row.command_log,
        diffText: row.diff_text,
        sourceFiles: sourceFiles,
        createdAt: row.created_at,
    };
}
function normalizePlanAutomationUsageSnapshotMetric(value) {
    var normalized = Number(value !== null && value !== void 0 ? value : 0);
    return Number.isFinite(normalized) ? Math.max(0, Math.round(normalized)) : 0;
}
function mapPlanAutomationUsageSnapshot(value) {
    var _a;
    var parsedValue = value;
    if (typeof value === 'string') {
        var trimmedValue = value.trim();
        if (!trimmedValue) {
            return null;
        }
        try {
            parsedValue = JSON.parse(trimmedValue);
        }
        catch (_b) {
            return null;
        }
    }
    if (!parsedValue || typeof parsedValue !== 'object') {
        return null;
    }
    var snapshot = parsedValue;
    var tokenTotals = (_a = snapshot.tokenTotals) !== null && _a !== void 0 ? _a : {};
    return {
        tokenTotals: {
            total: normalizePlanAutomationUsageSnapshotMetric(tokenTotals.total),
            input: normalizePlanAutomationUsageSnapshotMetric(tokenTotals.input),
            output: normalizePlanAutomationUsageSnapshotMetric(tokenTotals.output),
            cached: normalizePlanAutomationUsageSnapshotMetric(tokenTotals.cached),
            reasoning: normalizePlanAutomationUsageSnapshotMetric(tokenTotals.reasoning),
        },
        totalTokens: normalizePlanAutomationUsageSnapshotMetric(snapshot.totalTokens),
        retryCount: normalizePlanAutomationUsageSnapshotMetric(snapshot.retryCount),
        sourceWorkCount: normalizePlanAutomationUsageSnapshotMetric(snapshot.sourceWorkCount),
        processingStartedAt: typeof snapshot.processingStartedAt === 'string' && snapshot.processingStartedAt.trim()
            ? snapshot.processingStartedAt
            : null,
        processingEndedAt: typeof snapshot.processingEndedAt === 'string' && snapshot.processingEndedAt.trim()
            ? snapshot.processingEndedAt
            : null,
        processingEndedAtSource: typeof snapshot.processingEndedAtSource === 'string' && snapshot.processingEndedAtSource.trim()
            ? snapshot.processingEndedAtSource
            : null,
        processingDurationSeconds: Number.isFinite(Number(snapshot.processingDurationSeconds))
            ? Math.max(0, Math.round(Number(snapshot.processingDurationSeconds)))
            : null,
    };
}
function mapPlanReleaseReviewMetadata(value) {
    if (typeof value === 'string' && value.trim()) {
        try {
            return planReleaseReviewMetadataSchema.parse(JSON.parse(value));
        }
        catch (_a) {
            return {};
        }
    }
    if (value && typeof value === 'object') {
        try {
            return planReleaseReviewMetadataSchema.parse(value);
        }
        catch (_b) {
            return {};
        }
    }
    return {};
}
function mapPlanReleaseReviewRow(row, planItemId) {
    var _a;
    var metadata = mapPlanReleaseReviewMetadata((_a = row === null || row === void 0 ? void 0 : row.metadata) !== null && _a !== void 0 ? _a : null);
    return {
        id: (row === null || row === void 0 ? void 0 : row.id) ? Number(row.id) : null,
        planItemId: planItemId,
        status: ((row === null || row === void 0 ? void 0 : row.status) ? String(row.status) : 'pending'),
        reviewNote: (row === null || row === void 0 ? void 0 : row.review_note) ? String(row.review_note) : '',
        checkedByClientId: (row === null || row === void 0 ? void 0 : row.checked_by_client_id) ? String(row.checked_by_client_id) : null,
        checkedByNickname: (row === null || row === void 0 ? void 0 : row.checked_by_nickname) ? String(row.checked_by_nickname) : null,
        checkedAt: (row === null || row === void 0 ? void 0 : row.checked_at) ? String(row.checked_at) : null,
        metadata: metadata,
        createdAt: (row === null || row === void 0 ? void 0 : row.created_at) ? String(row.created_at) : null,
        updatedAt: (row === null || row === void 0 ? void 0 : row.updated_at) ? String(row.updated_at) : null,
    };
}
function extractAutomationTokenUsageText(sourceWork) {
    var candidates = [
        typeof sourceWork.commandLog === 'string' ? sourceWork.commandLog : null,
        typeof sourceWork.summary === 'string' ? sourceWork.summary : null,
    ];
    for (var _i = 0, candidates_1 = candidates; _i < candidates_1.length; _i++) {
        var candidate = candidates_1[_i];
        if (!candidate) {
            continue;
        }
        var line = candidate
            .split('\n')
            .map(function (entry) { return entry.trim(); })
            .find(function (entry) { return /^토큰 사용량:\s*/.test(entry); });
        if (line) {
            return line.replace(/^토큰 사용량:\s*/u, '').trim();
        }
    }
    return null;
}
function parseTokenMetricValue(valueText, unitText) {
    var normalizedValue = Number(String(valueText !== null && valueText !== void 0 ? valueText : '').replace(/,/g, ''));
    if (!Number.isFinite(normalizedValue)) {
        return null;
    }
    var unit = String(unitText !== null && unitText !== void 0 ? unitText : '').trim().toLowerCase();
    if (unit === 'k') {
        return Math.round(normalizedValue * 1000);
    }
    if (unit === 'm') {
        return Math.round(normalizedValue * 1000000);
    }
    return Math.round(normalizedValue);
}
function parseAutomationTokenUsageMetrics(tokenUsageText) {
    var _a, _b, _c, _d, _e, _f;
    var normalizedText = tokenUsageText
        .replace(/^tokens?\s+used\s*:?\s*/iu, '')
        .replace(/\(([^)]+)\)/g, ', $1')
        .trim();
    var metrics = new Map();
    for (var _i = 0, _g = normalizedText.matchAll(/(\d[\d,]*(?:\.\d+)?)\s*([km]?)\s*(input|output|total|cached|reasoning)\b/giu); _i < _g.length; _i++) {
        var match = _g[_i];
        var label = (_b = (_a = match[3]) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
        var value = parseTokenMetricValue((_c = match[1]) !== null && _c !== void 0 ? _c : '', match[2]);
        if (!label || value === null) {
            continue;
        }
        metrics.set(label, value);
    }
    for (var _h = 0, _j = normalizedText.matchAll(/\b(input|output|total|cached|reasoning)\s*[:=]?\s*(\d[\d,]*(?:\.\d+)?)\s*([km]?)/giu); _h < _j.length; _h++) {
        var match = _j[_h];
        var label = (_e = (_d = match[1]) === null || _d === void 0 ? void 0 : _d.toLowerCase()) !== null && _e !== void 0 ? _e : '';
        var value = parseTokenMetricValue((_f = match[2]) !== null && _f !== void 0 ? _f : '', match[3]);
        if (!label || value === null) {
            continue;
        }
        metrics.set(label, value);
    }
    if (metrics.size > 0) {
        return metrics;
    }
    var fallbackMatch = normalizedText.match(/(\d[\d,]*(?:\.\d+)?)\s*([km]?)/i);
    if (!fallbackMatch) {
        return null;
    }
    var fallbackValue = parseTokenMetricValue(fallbackMatch[1], fallbackMatch[2]);
    if (fallbackValue === null) {
        return null;
    }
    return new Map([['total', fallbackValue]]);
}
function getAutomationTotalTokenCount(metrics) {
    var total = metrics.get('total');
    if (typeof total === 'number' && Number.isFinite(total)) {
        return total;
    }
    return ['input', 'output', 'cached', 'reasoning'].reduce(function (sum, key) { var _a; return sum + ((_a = metrics.get(key)) !== null && _a !== void 0 ? _a : 0); }, 0);
}
function isPlanAutomationWorkerActive(workerStatus) {
    return [
        '브랜치생성중',
        '브랜치준비',
        '자동작업중',
        'release반영대기',
        'release반영중',
        'main반영대기',
        'main반영중',
    ].includes(String(workerStatus !== null && workerStatus !== void 0 ? workerStatus : '').trim());
}
function getLatestPlanReviewCheckedAt(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        var row;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_RELEASE_REVIEW_TABLE)
                        .select('checked_at', 'updated_at', 'id')
                        .where({ plan_item_id: planItemId })
                        .orderBy([
                        { column: 'updated_at', order: 'desc' },
                        { column: 'id', order: 'desc' },
                    ])
                        .first()];
                case 1:
                    row = _a.sent();
                    return [2 /*return*/, (row === null || row === void 0 ? void 0 : row.checked_at) ? String(row.checked_at) : null];
            }
        });
    });
}
function resolvePlanProcessingEndedAt(row, reviewCheckedAt) {
    if (reviewCheckedAt) {
        return {
            endedAt: reviewCheckedAt,
            source: 'review_checked_at',
        };
    }
    if (row.merged_at) {
        return {
            endedAt: String(row.merged_at),
            source: 'merged_at',
        };
    }
    if (row.completed_at) {
        return {
            endedAt: String(row.completed_at),
            source: 'completed_at',
        };
    }
    if (row.updated_at && row.started_at && !isPlanAutomationWorkerActive(row.worker_status)) {
        return {
            endedAt: String(row.updated_at),
            source: 'updated_at',
        };
    }
    if (row.started_at && isPlanAutomationWorkerActive(row.worker_status)) {
        return {
            endedAt: new Date().toISOString(),
            source: 'in_progress',
        };
    }
    return {
        endedAt: null,
        source: null,
    };
}
function buildPlanAutomationUsageSnapshot(sourceWorks, row, reviewCheckedAt) {
    var _a, _b, _c, _d, _e, _f;
    var totals = new Map();
    sourceWorks.forEach(function (sourceWork) {
        var tokenUsageText = extractAutomationTokenUsageText(sourceWork);
        if (!tokenUsageText) {
            return;
        }
        var metrics = parseAutomationTokenUsageMetrics(tokenUsageText);
        if (!metrics) {
            return;
        }
        metrics.forEach(function (value, key) {
            var _a;
            totals.set(key, ((_a = totals.get(key)) !== null && _a !== void 0 ? _a : 0) + value);
        });
    });
    var sourceWorkCount = sourceWorks.length;
    var processingStartedAt = row.started_at ? String(row.started_at) : ((_a = sourceWorks[0]) === null || _a === void 0 ? void 0 : _a.createdAt) ? String(sourceWorks[0].createdAt) : null;
    var _g = resolvePlanProcessingEndedAt(row, reviewCheckedAt), endedAt = _g.endedAt, source = _g.source;
    var processingDurationSeconds = processingStartedAt && endedAt
        ? Math.max(0, Math.round((new Date(endedAt).getTime() - new Date(processingStartedAt).getTime()) / 1000))
        : null;
    var totalTokens = getAutomationTotalTokenCount(totals);
    if (sourceWorkCount === 0 && !processingStartedAt && totalTokens === 0) {
        return null;
    }
    return {
        tokenTotals: {
            total: Math.max(0, (_b = totals.get('total')) !== null && _b !== void 0 ? _b : 0),
            input: Math.max(0, (_c = totals.get('input')) !== null && _c !== void 0 ? _c : 0),
            output: Math.max(0, (_d = totals.get('output')) !== null && _d !== void 0 ? _d : 0),
            cached: Math.max(0, (_e = totals.get('cached')) !== null && _e !== void 0 ? _e : 0),
            reasoning: Math.max(0, (_f = totals.get('reasoning')) !== null && _f !== void 0 ? _f : 0),
        },
        totalTokens: totalTokens,
        retryCount: Math.max(0, sourceWorkCount - 1),
        sourceWorkCount: sourceWorkCount,
        processingStartedAt: processingStartedAt,
        processingEndedAt: endedAt,
        processingEndedAtSource: source,
        processingDurationSeconds: processingDurationSeconds,
    };
}
function syncPlanAutomationUsageSnapshot(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        var row, sourceWorkRows, reviewCheckedAt, snapshot;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: planItemId }).first()];
                case 2:
                    row = _a.sent();
                    if (!row) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .select('summary', 'command_log', 'created_at')
                            .where({ plan_item_id: planItemId })
                            .orderBy('created_at', 'asc')
                            .orderBy('id', 'asc')];
                case 3:
                    sourceWorkRows = _a.sent();
                    return [4 /*yield*/, getLatestPlanReviewCheckedAt(planItemId)];
                case 4:
                    reviewCheckedAt = _a.sent();
                    snapshot = buildPlanAutomationUsageSnapshot(sourceWorkRows.map(function (sourceWorkRow) { return ({
                        summary: sourceWorkRow.summary,
                        commandLog: sourceWorkRow.command_log,
                        createdAt: String(sourceWorkRow.created_at),
                    }); }), row, reviewCheckedAt);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: planItemId })
                            .update({
                            usage_snapshot: snapshot ? JSON.stringify(snapshot) : null,
                        })];
                case 5:
                    _a.sent();
                    return [2 /*return*/, snapshot];
            }
        });
    });
}
function listPlanReleaseReviewNoteMap(planItemIds) {
    return __awaiter(this, void 0, void 0, function () {
        var rows, noteMap, _i, rows_1, row, planItemId;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    if (planItemIds.length === 0) {
                        return [2 /*return*/, new Map()];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_RELEASE_REVIEW_TABLE)
                            .select('plan_item_id', 'review_note', 'updated_at', 'id')
                            .whereIn('plan_item_id', planItemIds)
                            .orderBy([
                            { column: 'updated_at', order: 'desc' },
                            { column: 'id', order: 'desc' },
                        ])];
                case 1:
                    rows = _a.sent();
                    noteMap = new Map();
                    for (_i = 0, rows_1 = rows; _i < rows_1.length; _i++) {
                        row = rows_1[_i];
                        planItemId = Number(row.plan_item_id);
                        if (!Number.isFinite(planItemId) || noteMap.has(planItemId)) {
                            continue;
                        }
                        noteMap.set(planItemId, row.review_note ? String(row.review_note) : '');
                    }
                    return [2 /*return*/, noteMap];
            }
        });
    });
}
function ensurePlanFailureIssueHistory(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        var planRow, existingIssue;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: planItemId }).first()];
                case 1:
                    planRow = _a.sent();
                    if (!planRow || !planRow.worker_status || !planFailureWorkerStatuses.has(planRow.worker_status)) {
                        return [2 /*return*/];
                    }
                    if (!planRow.last_error) {
                        return [2 /*return*/];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .where({
                            plan_item_id: planItemId,
                            issue_tag: "#".concat(planRow.worker_status),
                            message: planRow.last_error,
                        })
                            .first()];
                case 2:
                    existingIssue = _a.sent();
                    if (existingIssue) {
                        return [2 /*return*/];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE).insert({
                            plan_item_id: planItemId,
                            issue_tag: "#".concat(planRow.worker_status),
                            message: planRow.last_error,
                            resolved: false,
                        })];
                case 3:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function ensureColumn(columnName, addColumn) {
    return __awaiter(this, void 0, void 0, function () {
        var hasColumn;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasColumn(exports.PLAN_TABLE, columnName)];
                case 1:
                    hasColumn = _a.sent();
                    if (hasColumn) {
                        return [2 /*return*/];
                    }
                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.PLAN_TABLE, function (table) {
                            addColumn(table);
                        })];
                case 2:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function syncPlanColumns() {
    return __awaiter(this, void 0, void 0, function () {
        var hasIssueNoteColumn;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasColumn(exports.PLAN_TABLE, 'issue_note')];
                case 1:
                    hasIssueNoteColumn = _a.sent();
                    if (!hasIssueNoteColumn) return [3 /*break*/, 3];
                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.PLAN_TABLE, function (table) {
                            table.dropColumn('issue_note');
                        })];
                case 2:
                    _a.sent();
                    _a.label = 3;
                case 3: return [4 /*yield*/, ensureColumn('assigned_branch', function (table) {
                        table.string('assigned_branch', 200).nullable();
                    })];
                case 4:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('release_target', function (table) {
                            table.string('release_target', 120).notNullable().defaultTo('release');
                        })];
                case 5:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('automation_type', function (table) {
                            table.string('automation_type', 40).notNullable().defaultTo('none');
                        })];
                case 6:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('automation_type_id', function (table) {
                            table.string('automation_type_id', 120).nullable();
                        })];
                case 7:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('automation_context_ids_json', function (table) {
                            table.text('automation_context_ids_json').notNullable().defaultTo('[]');
                        })];
                case 8:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('auto_deploy_to_main', function (table) {
                            table.boolean('auto_deploy_to_main').notNullable().defaultTo(true);
                        })];
                case 9:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('suppress_web_push', function (table) {
                            table.boolean('suppress_web_push').notNullable().defaultTo(false);
                        })];
                case 10:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('repeat_request_enabled', function (table) {
                            table.boolean('repeat_request_enabled').notNullable().defaultTo(false);
                        })];
                case 11:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('repeat_interval_minutes', function (table) {
                            table.integer('repeat_interval_minutes').notNullable().defaultTo(60);
                        })];
                case 12:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('normal_processing_level', function (table) {
                            table.string('normal_processing_level', 20).notNullable().defaultTo('중');
                        })];
                case 13:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('jangsing_processing_required', function (table) {
                            table.boolean('jangsing_processing_required').notNullable().defaultTo(true);
                        })];
                case 14:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('worker_status', function (table) {
                            table.string('worker_status', 80).nullable();
                        })];
                case 15:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('last_error', function (table) {
                            table.text('last_error').nullable();
                        })];
                case 16:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('locked_by', function (table) {
                            table.string('locked_by', 120).nullable();
                        })];
                case 17:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('locked_at', function (table) {
                            table.timestamp('locked_at', { useTz: true }).nullable();
                        })];
                case 18:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('started_at', function (table) {
                            table.timestamp('started_at', { useTz: true }).nullable();
                        })];
                case 19:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('completed_at', function (table) {
                            table.timestamp('completed_at', { useTz: true }).nullable();
                        })];
                case 20:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('merged_at', function (table) {
                            table.timestamp('merged_at', { useTz: true }).nullable();
                        })];
                case 21:
                    _a.sent();
                    return [4 /*yield*/, ensureColumn('usage_snapshot', function (table) {
                            table.text('usage_snapshot').nullable();
                        })];
                case 22:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ automation_type: 'plan_registration' })
                            .update({ automation_type: 'plan' })];
                case 23:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ automation_type: 'general_development' })
                            .update({ automation_type: 'auto_worker' })];
                case 24:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .whereNull('automation_type_id')
                            .update({
                            automation_type_id: client_js_1.db.raw('automation_type'),
                        })];
                case 25:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function dropPlanWorkIdUniqueConstraint() {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, client_js_1.db.raw("\n      do $$\n      declare constraint_name text;\n      begin\n        for constraint_name in\n          select con.conname\n          from pg_constraint con\n          join pg_class rel on rel.oid = con.conrelid\n          join pg_namespace nsp on nsp.oid = rel.relnamespace\n          join pg_attribute att on att.attrelid = rel.oid and att.attnum = any(con.conkey)\n          where nsp.nspname = current_schema()\n            and rel.relname = '".concat(exports.PLAN_TABLE, "'\n            and con.contype = 'u'\n            and att.attname = 'work_id'\n        loop\n          execute format('alter table %I.%I drop constraint %I', current_schema(), '").concat(exports.PLAN_TABLE, "', constraint_name);\n        end loop;\n      end\n      $$;\n    "))];
                case 1:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function ensurePlanIssueTable() {
    return __awaiter(this, void 0, void 0, function () {
        var exists;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.PLAN_ISSUE_TABLE)];
                case 1:
                    exists = _a.sent();
                    if (exists) {
                        return [2 /*return*/];
                    }
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.PLAN_ISSUE_TABLE, function (table) {
                            table.increments('id').primary();
                            table.integer('plan_item_id').notNullable().index();
                            table.string('issue_tag', 120).notNullable();
                            table.text('message').notNullable();
                            table.text('action_note').nullable();
                            table.boolean('resolved').notNullable().defaultTo(false);
                            table.timestamp('resolved_at', { useTz: true }).nullable();
                            table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                        })];
                case 2:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function ensurePlanActionTable() {
    return __awaiter(this, void 0, void 0, function () {
        var exists;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.PLAN_ACTION_TABLE)];
                case 1:
                    exists = _a.sent();
                    if (exists) {
                        return [2 /*return*/];
                    }
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.PLAN_ACTION_TABLE, function (table) {
                            table.increments('id').primary();
                            table.integer('plan_item_id').notNullable().index();
                            table.string('action_type', 120).notNullable();
                            table.text('note').notNullable();
                            table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                        })];
                case 2:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function ensurePlanSourceWorkTable() {
    return __awaiter(this, void 0, void 0, function () {
        var exists, hasPreviewUrlColumn, hasSourceFilesColumn;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.PLAN_SOURCE_WORK_TABLE)];
                case 1:
                    exists = _a.sent();
                    if (!exists) return [3 /*break*/, 8];
                    return [4 /*yield*/, client_js_1.db.schema.hasColumn(exports.PLAN_SOURCE_WORK_TABLE, 'preview_url')];
                case 2:
                    hasPreviewUrlColumn = _a.sent();
                    if (!!hasPreviewUrlColumn) return [3 /*break*/, 4];
                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.PLAN_SOURCE_WORK_TABLE, function (table) {
                            table.string('preview_url', 2000).nullable();
                        })];
                case 3:
                    _a.sent();
                    _a.label = 4;
                case 4: return [4 /*yield*/, client_js_1.db.schema.hasColumn(exports.PLAN_SOURCE_WORK_TABLE, 'source_files')];
                case 5:
                    hasSourceFilesColumn = _a.sent();
                    if (!!hasSourceFilesColumn) return [3 /*break*/, 7];
                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.PLAN_SOURCE_WORK_TABLE, function (table) {
                            table.text('source_files').notNullable().defaultTo('[]');
                        })];
                case 6:
                    _a.sent();
                    _a.label = 7;
                case 7: return [2 /*return*/];
                case 8: return [4 /*yield*/, client_js_1.db.schema.createTable(exports.PLAN_SOURCE_WORK_TABLE, function (table) {
                        table.increments('id').primary();
                        table.integer('plan_item_id').notNullable().index();
                        table.text('summary').notNullable();
                        table.string('branch_name', 200).notNullable();
                        table.string('commit_hash', 80).nullable();
                        table.string('preview_url', 2000).nullable();
                        table.text('changed_files').notNullable().defaultTo('[]');
                        table.text('command_log').nullable();
                        table.text('diff_text').nullable();
                        table.text('source_files').notNullable().defaultTo('[]');
                        table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                    })];
                case 9:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function ensurePlanReleaseReviewTable() {
    return __awaiter(this, void 0, void 0, function () {
        var exists, requiredColumns, _loop_1, _i, requiredColumns_1, _a, columnName, createColumn;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.PLAN_RELEASE_REVIEW_TABLE)];
                case 1:
                    exists = _b.sent();
                    if (!!exists) return [3 /*break*/, 3];
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.PLAN_RELEASE_REVIEW_TABLE, function (table) {
                            table.increments('id').primary();
                            table.integer('plan_item_id').notNullable().unique().index();
                            table.string('status', 40).notNullable().defaultTo('pending');
                            table.text('review_note').notNullable().defaultTo('');
                            table.string('checked_by_client_id', 120).nullable();
                            table.string('checked_by_nickname', 80).nullable();
                            table.timestamp('checked_at', { useTz: true }).nullable();
                            table.text('metadata').notNullable().defaultTo('{}');
                            table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                            table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                        })];
                case 2:
                    _b.sent();
                    return [2 /*return*/];
                case 3:
                    requiredColumns = [
                        ['plan_item_id', function (table) { return table.integer('plan_item_id').notNullable().unique().index(); }],
                        ['status', function (table) { return table.string('status', 40).notNullable().defaultTo('pending'); }],
                        ['review_note', function (table) { return table.text('review_note').notNullable().defaultTo(''); }],
                        ['checked_by_client_id', function (table) { return table.string('checked_by_client_id', 120).nullable(); }],
                        ['checked_by_nickname', function (table) { return table.string('checked_by_nickname', 80).nullable(); }],
                        ['checked_at', function (table) { return table.timestamp('checked_at', { useTz: true }).nullable(); }],
                        ['metadata', function (table) { return table.text('metadata').notNullable().defaultTo('{}'); }],
                        ['created_at', function (table) { return table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); }],
                        ['updated_at', function (table) { return table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); }],
                    ];
                    _loop_1 = function (columnName, createColumn) {
                        var hasColumn;
                        return __generator(this, function (_c) {
                            switch (_c.label) {
                                case 0: return [4 /*yield*/, client_js_1.db.schema.hasColumn(exports.PLAN_RELEASE_REVIEW_TABLE, columnName)];
                                case 1:
                                    hasColumn = _c.sent();
                                    if (!!hasColumn) return [3 /*break*/, 3];
                                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.PLAN_RELEASE_REVIEW_TABLE, function (table) {
                                            createColumn(table);
                                        })];
                                case 2:
                                    _c.sent();
                                    _c.label = 3;
                                case 3: return [2 /*return*/];
                            }
                        });
                    };
                    _i = 0, requiredColumns_1 = requiredColumns;
                    _b.label = 4;
                case 4:
                    if (!(_i < requiredColumns_1.length)) return [3 /*break*/, 7];
                    _a = requiredColumns_1[_i], columnName = _a[0], createColumn = _a[1];
                    return [5 /*yield**/, _loop_1(columnName, createColumn)];
                case 5:
                    _b.sent();
                    _b.label = 6;
                case 6:
                    _i++;
                    return [3 /*break*/, 4];
                case 7: return [2 /*return*/];
            }
        });
    });
}
function ensurePlanTable() {
    return __awaiter(this, void 0, void 0, function () {
        var exists, error_1, dbError;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.PLAN_TABLE)];
                case 1:
                    exists = _a.sent();
                    if (!!exists) return [3 /*break*/, 5];
                    _a.label = 2;
                case 2:
                    _a.trys.push([2, 4, , 5]);
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.PLAN_TABLE, function (table) {
                            table.increments('id').primary();
                            table.string('work_id', 120).notNullable().unique();
                            table.text('note').notNullable().defaultTo('');
                            table.string('status', 40).notNullable().defaultTo('등록');
                            table.boolean('jangsing_processing_required').notNullable().defaultTo(true);
                            table.boolean('auto_deploy_to_main').notNullable().defaultTo(true);
                            table.boolean('suppress_web_push').notNullable().defaultTo(false);
                            table.string('assigned_branch', 200).nullable();
                            table.string('release_target', 120).notNullable().defaultTo('release');
                            table.string('worker_status', 80).nullable();
                            table.text('last_error').nullable();
                            table.string('locked_by', 120).nullable();
                            table.timestamp('locked_at', { useTz: true }).nullable();
                            table.timestamp('started_at', { useTz: true }).nullable();
                            table.timestamp('completed_at', { useTz: true }).nullable();
                            table.timestamp('merged_at', { useTz: true }).nullable();
                            table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                            table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                        })];
                case 3:
                    _a.sent();
                    return [3 /*break*/, 5];
                case 4:
                    error_1 = _a.sent();
                    dbError = error_1;
                    if (dbError.code !== '42P07' && dbError.code !== '23505') {
                        throw error_1;
                    }
                    return [3 /*break*/, 5];
                case 5: return [4 /*yield*/, syncPlanColumns()];
                case 6:
                    _a.sent();
                    return [4 /*yield*/, dropPlanWorkIdUniqueConstraint()];
                case 7:
                    _a.sent();
                    return [4 /*yield*/, ensurePlanIssueTable()];
                case 8:
                    _a.sent();
                    return [4 /*yield*/, ensurePlanActionTable()];
                case 9:
                    _a.sent();
                    return [4 /*yield*/, ensurePlanSourceWorkTable()];
                case 10:
                    _a.sent();
                    return [4 /*yield*/, ensurePlanReleaseReviewTable()];
                case 11:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .whereNull('jangsing_processing_required')
                            .update({
                            jangsing_processing_required: true,
                        })];
                case 12:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .whereNull('suppress_web_push')
                            .update({
                            suppress_web_push: false,
                        })];
                case 13:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .whereIn('status', ['작업완료', '릴리즈완료', '완료'])
                            .whereNull('completed_at')
                            .update({
                            completed_at: client_js_1.db.raw('coalesce(merged_at, updated_at, created_at)'),
                            updated_at: client_js_1.db.fn.now(),
                        })];
                case 14:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ status: '이슈' })
                            .update({
                            status: '등록',
                            worker_status: client_js_1.db.raw("coalesce(worker_status, '브랜치실패')"),
                            updated_at: client_js_1.db.fn.now(),
                        })];
                case 15:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ status: '개발완료' })
                            .update({
                            status: '작업완료',
                            worker_status: client_js_1.db.raw("case when worker_status in ('병합대기','병합중') then 'release반영대기' when worker_status='병합완료' then 'main반영완료' else worker_status end"),
                            updated_at: client_js_1.db.fn.now(),
                        })];
                case 16:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .whereIn('worker_status', ['release반영완료', 'main반영대기', 'main반영중', 'main반영실패'])
                            .whereNot({ status: '완료' })
                            .update({
                            status: '릴리즈완료',
                            updated_at: client_js_1.db.fn.now(),
                        })];
                case 17:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function createPlanItem(payload) {
    return __awaiter(this, void 0, void 0, function () {
        var workId, automationType, rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    workId = normalizePlanWorkId(payload.workId);
                    return [4 /*yield*/, (0, automation_type_config_service_js_1.resolveAutomationType)(payload.automationType)];
                case 2:
                    automationType = _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .insert({
                            work_id: workId,
                            note: payload.note,
                            status: '등록',
                            automation_type: automationType.behaviorType,
                            automation_type_id: automationType.id,
                            automation_context_ids_json: (0, automation_context_service_js_1.stringifyAutomationContextIds)(payload.automationContextIds),
                            release_target: payload.releaseTarget,
                            jangsing_processing_required: payload.jangsingProcessingRequired,
                            auto_deploy_to_main: payload.autoDeployToMain,
                            suppress_web_push: payload.suppressWebPush,
                            repeat_request_enabled: payload.repeatRequestEnabled,
                            repeat_interval_minutes: payload.repeatIntervalMinutes,
                            worker_status: '대기',
                            last_error: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _a.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function createCompletedPlanExecutionLogItem(payload) {
    return __awaiter(this, void 0, void 0, function () {
        var workId, automationType, rows;
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _b.sent();
                    workId = normalizePlanWorkId(payload.workId);
                    return [4 /*yield*/, (0, automation_type_config_service_js_1.resolveAutomationType)(payload.automationType)];
                case 2:
                    automationType = _b.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .insert({
                            work_id: workId,
                            note: payload.note,
                            status: '완료',
                            automation_type: automationType.behaviorType,
                            automation_type_id: automationType.id,
                            automation_context_ids_json: (0, automation_context_service_js_1.stringifyAutomationContextIds)(payload.automationContextIds),
                            release_target: payload.releaseTarget,
                            jangsing_processing_required: payload.jangsingProcessingRequired,
                            auto_deploy_to_main: payload.autoDeployToMain,
                            suppress_web_push: payload.suppressWebPush,
                            repeat_request_enabled: payload.repeatRequestEnabled,
                            repeat_interval_minutes: payload.repeatIntervalMinutes,
                            worker_status: '자동완료',
                            last_error: null,
                            started_at: client_js_1.db.fn.now(),
                            completed_at: client_js_1.db.fn.now(),
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _b.sent();
                    if (!((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.id)) return [3 /*break*/, 5];
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(Number(rows[0].id))];
                case 4:
                    _b.sent();
                    _b.label = 5;
                case 5: return [2 /*return*/, rows[0]];
            }
        });
    });
}
function upsertAutoPlanItem(args) {
    return __awaiter(this, void 0, void 0, function () {
        var workId, existingRow, automationType, nextReleaseTarget, nextAutomationType, nextJangsingProcessingRequired, nextAutoDeployToMain, nextSuppressWebPush, nextNote, nextAutomationContextIds, currentJangsingProcessingRequired, hasPayloadChange, rows_2, rows;
        var _a;
        var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
        return __generator(this, function (_p) {
            switch (_p.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _p.sent();
                    workId = normalizePlanWorkId(args.workId);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ work_id: workId })
                            .orderBy('id', 'desc')
                            .first()];
                case 2:
                    existingRow = _p.sent();
                    if (!!existingRow) return [3 /*break*/, 5];
                    return [4 /*yield*/, (0, automation_type_config_service_js_1.resolveAutomationType)(args.automationType)];
                case 3:
                    automationType = _p.sent();
                    _a = {};
                    return [4 /*yield*/, createPlanItem({
                            workId: workId,
                            note: args.note,
                            automationType: automationType.id,
                            automationContextIds: (_b = args.automationContextIds) !== null && _b !== void 0 ? _b : [],
                            releaseTarget: args.releaseTarget,
                            jangsingProcessingRequired: args.jangsingProcessingRequired,
                            autoDeployToMain: args.autoDeployToMain,
                            suppressWebPush: (_c = args.suppressWebPush) !== null && _c !== void 0 ? _c : false,
                            repeatRequestEnabled: false,
                            repeatIntervalMinutes: 60,
                        })];
                case 4: return [2 /*return*/, (_a.row = _p.sent(),
                        _a.action = 'created',
                        _a)];
                case 5:
                    nextReleaseTarget = args.releaseTarget || existingRow.release_target || 'release';
                    return [4 /*yield*/, (0, automation_type_config_service_js_1.resolveAutomationType)((_e = (_d = args.automationType) !== null && _d !== void 0 ? _d : existingRow.automation_type_id) !== null && _e !== void 0 ? _e : existingRow.automation_type)];
                case 6:
                    nextAutomationType = _p.sent();
                    nextJangsingProcessingRequired = args.jangsingProcessingRequired;
                    nextAutoDeployToMain = args.autoDeployToMain;
                    nextSuppressWebPush = (_f = args.suppressWebPush) !== null && _f !== void 0 ? _f : Boolean((_g = existingRow.suppress_web_push) !== null && _g !== void 0 ? _g : false);
                    nextNote = args.note;
                    nextAutomationContextIds = (_h = args.automationContextIds) !== null && _h !== void 0 ? _h : (0, automation_context_service_js_1.parseAutomationContextIds)(existingRow.automation_context_ids_json);
                    currentJangsingProcessingRequired = typeof existingRow.jangsing_processing_required === 'boolean'
                        ? existingRow.jangsing_processing_required
                        : existingRow.normal_processing_level === '상';
                    hasPayloadChange = existingRow.note !== nextNote ||
                        String((_k = (_j = existingRow.automation_type_id) !== null && _j !== void 0 ? _j : existingRow.automation_type) !== null && _k !== void 0 ? _k : 'none') !== nextAutomationType.id ||
                        ((_l = existingRow.release_target) !== null && _l !== void 0 ? _l : 'release') !== nextReleaseTarget ||
                        Boolean((_m = existingRow.auto_deploy_to_main) !== null && _m !== void 0 ? _m : true) !== nextAutoDeployToMain ||
                        Boolean((_o = existingRow.suppress_web_push) !== null && _o !== void 0 ? _o : false) !== nextSuppressWebPush ||
                        JSON.stringify((0, automation_context_service_js_1.parseAutomationContextIds)(existingRow.automation_context_ids_json)) !== JSON.stringify(nextAutomationContextIds) ||
                        Boolean(currentJangsingProcessingRequired) !== nextJangsingProcessingRequired;
                    if (!!args.requeue) return [3 /*break*/, 8];
                    if (!hasPayloadChange || existingRow.status !== '등록') {
                        return [2 /*return*/, {
                                row: existingRow,
                                action: 'unchanged',
                            }];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: existingRow.id })
                            .update({
                            note: nextNote,
                            automation_type: nextAutomationType.behaviorType,
                            automation_type_id: nextAutomationType.id,
                            automation_context_ids_json: (0, automation_context_service_js_1.stringifyAutomationContextIds)(nextAutomationContextIds),
                            release_target: nextReleaseTarget,
                            jangsing_processing_required: nextJangsingProcessingRequired,
                            auto_deploy_to_main: nextAutoDeployToMain,
                            suppress_web_push: nextSuppressWebPush,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 7:
                    rows_2 = _p.sent();
                    return [2 /*return*/, {
                            row: rows_2[0],
                            action: 'updated',
                        }];
                case 8: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                        .where({ id: existingRow.id })
                        .update({
                        work_id: workId,
                        note: nextNote,
                        status: '등록',
                        assigned_branch: null,
                        automation_type: nextAutomationType.behaviorType,
                        automation_type_id: nextAutomationType.id,
                        automation_context_ids_json: (0, automation_context_service_js_1.stringifyAutomationContextIds)(nextAutomationContextIds),
                        release_target: nextReleaseTarget,
                        jangsing_processing_required: nextJangsingProcessingRequired,
                        auto_deploy_to_main: nextAutoDeployToMain,
                        suppress_web_push: nextSuppressWebPush,
                        worker_status: '대기',
                        last_error: null,
                        locked_by: null,
                        locked_at: null,
                        started_at: null,
                        completed_at: null,
                        merged_at: null,
                        updated_at: client_js_1.db.fn.now(),
                    })
                        .returning('*')];
                case 9:
                    rows = _p.sent();
                    return [4 /*yield*/, createPlanActionHistory(Number(existingRow.id), '자동갱신', '업무일지 자동화 설정 기준으로 Plan 항목을 최신 상태로 갱신했습니다.')];
                case 10:
                    _p.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(Number(existingRow.id))];
                case 11:
                    _p.sent();
                    return [2 /*return*/, {
                            row: rows[0],
                            action: 'requeued',
                        }];
            }
        });
    });
}
function updatePlanItem(id, payload) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, nextWorkId, nextAutomationType, nextReleaseTarget, nextJangsingProcessingRequired, nextAutoDeployToMain, nextSuppressWebPush, nextRepeatRequestEnabled, nextRepeatIntervalMinutes, nextNote, nextAutomationContextIds, isOnlyJangsingUpdate, rows;
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
        return __generator(this, function (_y) {
            switch (_y.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _y.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _y.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    nextWorkId = payload.workId === undefined ? currentRow.work_id : normalizePlanWorkId(payload.workId);
                    return [4 /*yield*/, (0, automation_type_config_service_js_1.resolveAutomationType)((_b = (_a = payload.automationType) !== null && _a !== void 0 ? _a : currentRow.automation_type_id) !== null && _b !== void 0 ? _b : currentRow.automation_type)];
                case 3:
                    nextAutomationType = _y.sent();
                    nextReleaseTarget = (_d = (_c = payload.releaseTarget) !== null && _c !== void 0 ? _c : currentRow.release_target) !== null && _d !== void 0 ? _d : 'release';
                    nextJangsingProcessingRequired = (_e = payload.jangsingProcessingRequired) !== null && _e !== void 0 ? _e : (typeof currentRow.jangsing_processing_required === 'boolean'
                        ? currentRow.jangsing_processing_required
                        : currentRow.normal_processing_level === '상');
                    nextAutoDeployToMain = (_g = (_f = payload.autoDeployToMain) !== null && _f !== void 0 ? _f : currentRow.auto_deploy_to_main) !== null && _g !== void 0 ? _g : true;
                    nextSuppressWebPush = (_j = (_h = payload.suppressWebPush) !== null && _h !== void 0 ? _h : currentRow.suppress_web_push) !== null && _j !== void 0 ? _j : false;
                    nextRepeatRequestEnabled = (_l = (_k = payload.repeatRequestEnabled) !== null && _k !== void 0 ? _k : currentRow.repeat_request_enabled) !== null && _l !== void 0 ? _l : false;
                    nextRepeatIntervalMinutes = (_o = (_m = payload.repeatIntervalMinutes) !== null && _m !== void 0 ? _m : currentRow.repeat_interval_minutes) !== null && _o !== void 0 ? _o : 60;
                    nextNote = (_p = payload.note) !== null && _p !== void 0 ? _p : currentRow.note;
                    nextAutomationContextIds = (_q = payload.automationContextIds) !== null && _q !== void 0 ? _q : (0, automation_context_service_js_1.parseAutomationContextIds)(currentRow.automation_context_ids_json);
                    isOnlyJangsingUpdate = nextWorkId === currentRow.work_id &&
                        nextAutomationType.id === String((_s = (_r = currentRow.automation_type_id) !== null && _r !== void 0 ? _r : currentRow.automation_type) !== null && _s !== void 0 ? _s : 'none') &&
                        nextReleaseTarget === ((_t = currentRow.release_target) !== null && _t !== void 0 ? _t : 'release') &&
                        nextAutoDeployToMain === ((_u = currentRow.auto_deploy_to_main) !== null && _u !== void 0 ? _u : true) &&
                        nextSuppressWebPush === ((_v = currentRow.suppress_web_push) !== null && _v !== void 0 ? _v : false) &&
                        nextRepeatRequestEnabled === ((_w = currentRow.repeat_request_enabled) !== null && _w !== void 0 ? _w : false) &&
                        nextRepeatIntervalMinutes === ((_x = currentRow.repeat_interval_minutes) !== null && _x !== void 0 ? _x : 60) &&
                        JSON.stringify((0, automation_context_service_js_1.parseAutomationContextIds)(currentRow.automation_context_ids_json)) === JSON.stringify(nextAutomationContextIds) &&
                        nextNote === currentRow.note;
                    if (payload.jangsingProcessingRequired !== undefined && isOnlyJangsingUpdate) {
                        return [2 /*return*/, updatePlanItemJangsingProcessingRequired(id, nextJangsingProcessingRequired)];
                    }
                    if (currentRow.started_at || currentRow.status !== '등록') {
                        throw new Error('작업시작 이후에는 원본 요청을 수정할 수 없습니다. 추가 조치사항은 이력에 기록해 주세요.');
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            work_id: nextWorkId,
                            note: nextNote,
                            status: currentRow.status,
                            automation_type: nextAutomationType.behaviorType,
                            automation_type_id: nextAutomationType.id,
                            automation_context_ids_json: (0, automation_context_service_js_1.stringifyAutomationContextIds)(nextAutomationContextIds),
                            release_target: nextReleaseTarget,
                            jangsing_processing_required: nextJangsingProcessingRequired,
                            auto_deploy_to_main: nextAutoDeployToMain,
                            suppress_web_push: nextSuppressWebPush,
                            repeat_request_enabled: nextRepeatRequestEnabled,
                            repeat_interval_minutes: nextRepeatIntervalMinutes,
                            worker_status: currentRow.worker_status,
                            last_error: currentRow.last_error,
                            completed_at: currentRow.completed_at,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 4:
                    rows = _y.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function updatePlanItemJangsingProcessingRequired(id, jangsingProcessingRequired) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, rows;
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _b.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _b.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    if (!functionCheckEditableStatuses.has(currentRow.status)) {
                        throw new Error('기능동작확인은 작업완료 건만 수정할 수 있습니다.');
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            jangsing_processing_required: jangsingProcessingRequired,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _b.sent();
                    return [2 /*return*/, (_a = rows[0]) !== null && _a !== void 0 ? _a : null];
            }
        });
    });
}
function deletePlanItem(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow;
        var _this = this;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _a.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, client_js_1.db.transaction(function (trx) { return __awaiter(_this, void 0, void 0, function () {
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0: return [4 /*yield*/, trx(exports.PLAN_SOURCE_WORK_TABLE).where({ plan_item_id: id }).delete()];
                                    case 1:
                                        _a.sent();
                                        return [4 /*yield*/, trx(exports.PLAN_ACTION_TABLE).where({ plan_item_id: id }).delete()];
                                    case 2:
                                        _a.sent();
                                        return [4 /*yield*/, trx(exports.PLAN_ISSUE_TABLE).where({ plan_item_id: id }).delete()];
                                    case 3:
                                        _a.sent();
                                        return [4 /*yield*/, trx(exports.PLAN_TABLE).where({ id: id }).delete()];
                                    case 4:
                                        _a.sent();
                                        return [2 /*return*/];
                                }
                            });
                        }); })];
                case 3:
                    _a.sent();
                    return [2 /*return*/, currentRow];
            }
        });
    });
}
function getBoardPostLinkedToPlanItem(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        var boardPostsTable, boardPostRequestsTable, hasBoardPostsTable, hasBoardPostRequestsTable, linkedRequestRow;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    boardPostsTable = 'board_posts';
                    boardPostRequestsTable = 'board_post_requests';
                    return [4 /*yield*/, client_js_1.db.schema.hasTable(boardPostsTable)];
                case 1:
                    hasBoardPostsTable = _a.sent();
                    if (!hasBoardPostsTable) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, client_js_1.db.schema.hasTable(boardPostRequestsTable)];
                case 2:
                    hasBoardPostRequestsTable = _a.sent();
                    if (!hasBoardPostRequestsTable) return [3 /*break*/, 4];
                    return [4 /*yield*/, (0, client_js_1.db)(boardPostRequestsTable)
                            .join(boardPostsTable, "".concat(boardPostRequestsTable, ".board_post_id"), "".concat(boardPostsTable, ".id"))
                            .select("".concat(boardPostsTable, ".id"), "".concat(boardPostsTable, ".title"))
                            .where("".concat(boardPostRequestsTable, ".plan_item_id"), planItemId)
                            .first()];
                case 3:
                    linkedRequestRow = _a.sent();
                    if (linkedRequestRow) {
                        return [2 /*return*/, linkedRequestRow];
                    }
                    _a.label = 4;
                case 4: return [2 /*return*/, (0, client_js_1.db)(boardPostsTable).select('id', 'title').where({ automation_plan_item_id: planItemId }).first()];
            }
        });
    });
}
function markPlanAsDevelopmentComplete(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, rows;
        var _a, _b, _c, _d;
        return __generator(this, function (_e) {
            switch (_e.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _e.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _e.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            status: '작업완료',
                            jangsing_processing_required: true,
                            worker_status: 'release반영대기',
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            completed_at: (_a = currentRow.completed_at) !== null && _a !== void 0 ? _a : client_js_1.db.fn.now(),
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _e.sent();
                    return [4 /*yield*/, createPlanLifecycleSourceWorkHistory(id, '작업완료 처리로 release 반영 대기 상태로 전환했습니다.', (_c = (_b = currentRow.assigned_branch) !== null && _b !== void 0 ? _b : currentRow.release_target) !== null && _c !== void 0 ? _c : 'release', currentRow.assigned_branch
                            ? "\uD604\uC7AC \uC791\uC5C5 \uBE0C\uB79C\uCE58: ".concat(currentRow.assigned_branch)
                            : "release \uB300\uAE30 \uBE0C\uB79C\uCE58: ".concat((_d = currentRow.release_target) !== null && _d !== void 0 ? _d : 'release'))];
                case 4:
                    _e.sent();
                    return [4 /*yield*/, createPlanActionHistory(id, '작업완료', '작업완료 처리')];
                case 5:
                    _e.sent();
                    return [4 /*yield*/, resolveAutomationIssueHistories(id)];
                case 6:
                    _e.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 7:
                    _e.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function markPlanAsCompleted(id_1, note_1) {
    return __awaiter(this, arguments, void 0, function (id, note, workerStatus) {
        var currentRow, rows, sourceWorkCountRow, sourceWorkCount;
        var _a, _b, _c, _d, _e;
        if (workerStatus === void 0) { workerStatus = '자동완료'; }
        return __generator(this, function (_f) {
            switch (_f.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _f.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _f.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            status: '완료',
                            worker_status: workerStatus,
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            completed_at: (_a = currentRow.completed_at) !== null && _a !== void 0 ? _a : client_js_1.db.fn.now(),
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _f.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .where({ plan_item_id: id })
                            .count('id as count')
                            .first()];
                case 4:
                    sourceWorkCountRow = _f.sent();
                    sourceWorkCount = Number((_b = sourceWorkCountRow === null || sourceWorkCountRow === void 0 ? void 0 : sourceWorkCountRow.count) !== null && _b !== void 0 ? _b : 0);
                    if (!(sourceWorkCount === 0 && !shouldSkipLifecycleSourceWork(currentRow))) return [3 /*break*/, 6];
                    return [4 /*yield*/, createPlanLifecycleSourceWorkHistory(id, note !== null && note !== void 0 ? note : '작업 결과를 검토하고 완료 처리했습니다.', (_d = (_c = currentRow.assigned_branch) !== null && _c !== void 0 ? _c : currentRow.release_target) !== null && _d !== void 0 ? _d : (0, env_js_1.getEnv)().PLAN_MAIN_BRANCH, currentRow.assigned_branch
                            ? "\uC644\uB8CC \uAE30\uC900 \uBE0C\uB79C\uCE58: ".concat(currentRow.assigned_branch)
                            : "\uC644\uB8CC \uAE30\uC900 \uBE0C\uB79C\uCE58: ".concat((_e = currentRow.release_target) !== null && _e !== void 0 ? _e : (0, env_js_1.getEnv)().PLAN_MAIN_BRANCH))];
                case 5:
                    _f.sent();
                    _f.label = 6;
                case 6: return [4 /*yield*/, createPlanActionHistory(id, '완료처리', note !== null && note !== void 0 ? note : '작업을 완료 처리했습니다.')];
                case 7:
                    _f.sent();
                    return [4 /*yield*/, resolveAutomationIssueHistories(id)];
                case 8:
                    _f.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 9:
                    _f.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function markPlanAsStarted(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, rows;
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _b.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _b.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            status: '작업중',
                            worker_status: currentRow.worker_status === '대기' ? null : currentRow.worker_status,
                            last_error: currentRow.last_error,
                            started_at: (_a = currentRow.started_at) !== null && _a !== void 0 ? _a : client_js_1.db.fn.now(),
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _b.sent();
                    return [4 /*yield*/, createPlanActionHistory(id, '작업시작', '작업을 시작했습니다.')];
                case 4:
                    _b.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 5:
                    _b.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function retryPlanBranch(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _a.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            status: '등록',
                            worker_status: '대기',
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _a.sent();
                    return [4 /*yield*/, createPlanActionHistory(id, '브랜치재시도', '브랜치 재시도를 요청했습니다.')];
                case 4:
                    _a.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 5:
                    _a.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function retryPlanMerge(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, isMainRetry, rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _a.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    isMainRetry = currentRow.worker_status === 'main반영실패' ||
                        currentRow.worker_status === 'main반영대기' ||
                        currentRow.worker_status === 'main반영중' ||
                        currentRow.status === '릴리즈완료';
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            status: isMainRetry ? '릴리즈완료' : '작업완료',
                            worker_status: isMainRetry ? 'main반영대기' : 'release반영대기',
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _a.sent();
                    return [4 /*yield*/, createPlanActionHistory(id, isMainRetry ? 'main반영재시도' : 'release반영재시도', isMainRetry ? 'main 일괄 반영 재시도를 요청했습니다.' : 'release 반영 재시도를 요청했습니다.')];
                case 4:
                    _a.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 5:
                    _a.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function requestPlanMainMerge(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, releaseTarget, pendingRows, targetIds;
        var _a, _b;
        var _c;
        return __generator(this, function (_d) {
            switch (_d.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _d.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _d.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    if (!(currentRow.status !== '릴리즈완료')) return [3 /*break*/, 4];
                    _a = {};
                    return [4 /*yield*/, getPlanItemById(id)];
                case 3: return [2 /*return*/, (_a.item = _d.sent(),
                        _a.message = 'main 반영 요청은 release 반영이 완료된 이후에만 가능합니다.',
                        _a)];
                case 4:
                    releaseTarget = String((_c = currentRow.release_target) !== null && _c !== void 0 ? _c : 'release');
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .select('id')
                            .where({ status: '릴리즈완료', release_target: releaseTarget })];
                case 5:
                    pendingRows = _d.sent();
                    targetIds = pendingRows.map(function (row) { return Number(row.id); }).filter(Number.isFinite);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .whereIn('id', targetIds.length > 0 ? targetIds : [id])
                            .update({
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            worker_status: 'main반영대기',
                            updated_at: client_js_1.db.fn.now(),
                        })];
                case 6:
                    _d.sent();
                    return [4 /*yield*/, createPlanActionHistory(id, 'main반영요청', "".concat(releaseTarget, " \uBE0C\uB79C\uCE58 \uAE30\uC900 main \uC77C\uAD04 \uBC18\uC601\uC744 \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4."))];
                case 7:
                    _d.sent();
                    return [4 /*yield*/, Promise.all((targetIds.length > 0 ? targetIds : [id]).map(function (targetId) { return syncPlanAutomationUsageSnapshot(targetId); }))];
                case 8:
                    _d.sent();
                    _b = {};
                    return [4 /*yield*/, getPlanItemById(id)];
                case 9: return [2 /*return*/, (_b.item = _d.sent(),
                        _b.message = "".concat(releaseTarget, " \uBE0C\uB79C\uCE58 \uAE30\uC900\uC73C\uB85C ").concat(Math.max(targetIds.length, 1), "\uAC74 main \uC77C\uAD04 \uBC18\uC601\uC744 \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4."),
                        _b)];
            }
        });
    });
}
function retryPlanWork(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _a.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .update({
                            status: '작업중',
                            worker_status: '브랜치준비',
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _a.sent();
                    return [4 /*yield*/, createPlanActionHistory(id, '작업재처리', '자동 작업 재처리를 요청했습니다.')];
                case 4:
                    _a.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function isPlanLockedByWorker(id, workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var row;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .select('id')
                            .where({ id: id, locked_by: workerId })
                            .first()];
                case 2:
                    row = _a.sent();
                    return [2 /*return*/, Boolean(row)];
            }
        });
    });
}
function resumePlanDevelopmentFromRelease(id, actionNote) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, rows;
        var _a, _b;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _c.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _c.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    if (!(currentRow.status !== '릴리즈완료')) return [3 /*break*/, 4];
                    _a = {
                        didScheduleRetry: false
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 3: return [2 /*return*/, (_a.item = _c.sent(),
                        _a.message = null,
                        _a)];
                case 4: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                        .where({ id: id })
                        .update({
                        status: '작업중',
                        worker_status: '브랜치준비',
                        last_error: null,
                        locked_by: null,
                        locked_at: null,
                        merged_at: null,
                        updated_at: client_js_1.db.fn.now(),
                    })
                        .returning('*')];
                case 5:
                    rows = _c.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 6:
                    _c.sent();
                    return [4 /*yield*/, createPlanActionHistory(id, '릴리즈추가조치', "release \uC0C1\uD0DC \uCD94\uAC00 \uC870\uCE58\uB85C \uAC1C\uBC1C\uC744 \uC7AC\uAC1C\uD588\uC2B5\uB2C8\uB2E4.\n".concat(actionNote))];
                case 7:
                    _c.sent();
                    _b = {
                        didScheduleRetry: false
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 8: return [2 /*return*/, (_b.item = _c.sent(),
                        _b.message = 'release 상태의 추가 조치를 반영해 추가 개발로 전환했습니다.',
                        _b.row = rows[0],
                        _b)];
            }
        });
    });
}
function queuePlanRetryFromFailure(id, actionNote) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow;
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _b.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _b.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    _a = {
                        didScheduleRetry: false
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 3: return [2 /*return*/, (_a.item = _b.sent(),
                        _a.message = (0, plan_retry_policy_js_1.shouldTriggerRetryFromActionNote)(actionNote !== null && actionNote !== void 0 ? actionNote : '')
                            ? '조치 이력을 저장했습니다. 자동 재시작은 하지 않았습니다. 필요하면 재시도 액션을 직접 실행해 주세요.'
                            : '조치 이력을 저장했습니다. 자동화는 중단된 상태로 유지합니다.',
                        _a)];
            }
        });
    });
}
function shouldResumePlanDevelopmentFromIssueAction(status, retry) {
    return retry && status === '릴리즈완료';
}
function queuePlanRetryForCurrentStatus(id, workerStatus) {
    return __awaiter(this, void 0, void 0, function () {
        var _a;
        var _b, _c, _d, _e, _f;
        return __generator(this, function (_g) {
            switch (_g.label) {
                case 0:
                    if (!(workerStatus === '자동작업중')) return [3 /*break*/, 3];
                    return [4 /*yield*/, retryPlanWork(id)];
                case 1:
                    _g.sent();
                    _b = {
                        didScheduleRetry: true
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 2: return [2 /*return*/, (_b.item = _g.sent(),
                        _b.message = '실행 중인 자동 작업을 정리하고 이슈 조치를 반영하도록 다시 실행합니다.',
                        _b)];
                case 3:
                    if (!(workerStatus === '브랜치실패')) return [3 /*break*/, 6];
                    return [4 /*yield*/, retryPlanBranch(id)];
                case 4:
                    _g.sent();
                    _c = {
                        didScheduleRetry: true
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 5: return [2 /*return*/, (_c.item = _g.sent(),
                        _c.message = '이슈 조치를 반영해 브랜치 재시도를 예약했습니다.',
                        _c)];
                case 6:
                    if (!(workerStatus === '자동작업실패')) return [3 /*break*/, 9];
                    return [4 /*yield*/, retryPlanWork(id)];
                case 7:
                    _g.sent();
                    _d = {
                        didScheduleRetry: true
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 8: return [2 /*return*/, (_d.item = _g.sent(),
                        _d.message = '이슈 조치를 반영해 자동 작업 재처리를 예약했습니다.',
                        _d)];
                case 9:
                    if (!(workerStatus === 'release반영실패')) return [3 /*break*/, 12];
                    return [4 /*yield*/, retryPlanMerge(id)];
                case 10:
                    _g.sent();
                    _e = {
                        didScheduleRetry: true
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 11: return [2 /*return*/, (_e.item = _g.sent(),
                        _e.message = '이슈 조치를 반영해 release 반영 재처리를 예약했습니다.',
                        _e)];
                case 12:
                    if (!(workerStatus === 'main반영실패')) return [3 /*break*/, 14];
                    _a = [{ didScheduleRetry: true }];
                    return [4 /*yield*/, requestPlanMainMerge(id)];
                case 13: return [2 /*return*/, __assign.apply(void 0, _a.concat([(_g.sent())]))];
                case 14:
                    _f = {
                        didScheduleRetry: false
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 15: return [2 /*return*/, (_f.item = _g.sent(),
                        _f.message = '이슈 조치 이력을 저장했고 자동 재처리 대상은 없습니다.',
                        _f)];
            }
        });
    });
}
function queuePlanRetryFromIssueAction(id_1, actionNote_1) {
    return __awaiter(this, arguments, void 0, function (id, actionNote, retry) {
        var currentRow, resumeResult;
        var _a;
        if (retry === void 0) { retry = false; }
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _b.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _b.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    if (!!retry) return [3 /*break*/, 4];
                    _a = {
                        didScheduleRetry: false
                    };
                    return [4 /*yield*/, getPlanItemById(id)];
                case 3: return [2 /*return*/, (_a.item = _b.sent(),
                        _a.message = '이슈 조치 이력을 저장했습니다. 재처리는 요청하지 않았습니다.',
                        _a)];
                case 4:
                    if (!shouldResumePlanDevelopmentFromIssueAction(currentRow.status, retry)) return [3 /*break*/, 6];
                    return [4 /*yield*/, resumePlanDevelopmentFromRelease(id, actionNote)];
                case 5:
                    resumeResult = _b.sent();
                    return [2 /*return*/, __assign(__assign({}, resumeResult), { didScheduleRetry: Boolean(resumeResult === null || resumeResult === void 0 ? void 0 : resumeResult.message) })];
                case 6: return [2 /*return*/, queuePlanRetryForCurrentStatus(id, currentRow.worker_status)];
            }
        });
    });
}
function cancelPlanRelease(id) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, releaseTarget, sourceWorkCountRow, sourceWorkCount, isReleaseMergeFailure, skippedRollbackBecauseNoSourceWork, targetRows, _a, targetIds, historyMessage, resultMessage, _i, targetIds_1, targetId, issueRow;
        var _b, _c;
        var _d, _e;
        return __generator(this, function (_f) {
            switch (_f.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _f.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 2:
                    currentRow = _f.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    releaseTarget = (_d = currentRow.release_target) !== null && _d !== void 0 ? _d : 'release';
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .where({ plan_item_id: id })
                            .count('id as count')
                            .first()];
                case 3:
                    sourceWorkCountRow = _f.sent();
                    sourceWorkCount = Math.max(0, Number((_e = sourceWorkCountRow === null || sourceWorkCountRow === void 0 ? void 0 : sourceWorkCountRow.count) !== null && _e !== void 0 ? _e : 0) || 0);
                    isReleaseMergeFailure = currentRow.status === '작업완료' && currentRow.worker_status === 'release반영실패';
                    skippedRollbackBecauseNoSourceWork = sourceWorkCount === 0 &&
                        (currentRow.status === '릴리즈완료' || currentRow.worker_status === 'main반영실패');
                    if (!isReleaseMergeFailure) return [3 /*break*/, 4];
                    _a = [];
                    return [3 /*break*/, 6];
                case 4: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                        .select('*')
                        .where({ release_target: releaseTarget, status: '릴리즈완료' })];
                case 5:
                    _a = _f.sent();
                    _f.label = 6;
                case 6:
                    targetRows = _a;
                    targetIds = isReleaseMergeFailure
                        ? [id]
                        : __spreadArray([], new Set(targetRows.map(function (row) { return Number(row.id); }).concat(id)), true);
                    historyMessage = isReleaseMergeFailure
                        ? "release(".concat(releaseTarget, ") \uBC18\uC601 \uC2E4\uD328 \uC0C1\uD0DC\uC5D0\uC11C \uC791\uC5C5\uC744 \uCDE8\uC18C \uCC98\uB9AC\uD588\uC2B5\uB2C8\uB2E4.")
                        : skippedRollbackBecauseNoSourceWork
                            ? "\uC18C\uC2A4 \uC791\uC5C5 \uC99D\uC801\uC774 \uC5C6\uC5B4 release(".concat(releaseTarget, ") \uB864\uBC31 \uC5C6\uC774 \uC791\uC5C5\uC744 \uCDE8\uC18C \uCC98\uB9AC\uD588\uC2B5\uB2C8\uB2E4.")
                            : "release(".concat(releaseTarget, ") \uBC30\uD3EC \uB0B4\uC5ED\uC744 \uB864\uBC31\uD558\uACE0 \uC791\uC5C5\uC744 \uCDE8\uC18C \uCC98\uB9AC\uD588\uC2B5\uB2C8\uB2E4.");
                    resultMessage = isReleaseMergeFailure
                        ? "release(".concat(releaseTarget, ") \uBC18\uC601 \uC2E4\uD328 \uC0C1\uD0DC\uC5D0\uC11C \uC791\uC5C5\uCDE8\uC18C\uB85C \uC644\uB8CC \uCC98\uB9AC\uD588\uC2B5\uB2C8\uB2E4.")
                        : skippedRollbackBecauseNoSourceWork
                            ? "\uC18C\uC2A4 \uC791\uC5C5 \uC99D\uC801\uC774 \uC5C6\uC5B4 rollback \uC5C6\uC774 \uC791\uC5C5\uCDE8\uC18C\uB85C \uC644\uB8CC \uCC98\uB9AC\uD588\uC2B5\uB2C8\uB2E4."
                            : "release(".concat(releaseTarget, ") \uBC30\uD3EC \uB0B4\uC5ED\uC744 \uB864\uBC31\uD558\uACE0 \uC791\uC5C5\uCDE8\uC18C\uB85C \uC644\uB8CC \uCC98\uB9AC\uD588\uC2B5\uB2C8\uB2E4.");
                    if (!(targetIds.length === 0)) return [3 /*break*/, 8];
                    _b = {};
                    return [4 /*yield*/, getPlanItemById(id)];
                case 7: return [2 /*return*/, (_b.item = _f.sent(),
                        _b.message = '취소할 release 반영 이력이 없습니다.',
                        _b)];
                case 8: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                        .whereIn('id', targetIds)
                        .update({
                        status: '완료',
                        worker_status: '작업취소',
                        last_error: null,
                        locked_by: null,
                        locked_at: null,
                        completed_at: client_js_1.db.fn.now(),
                        updated_at: client_js_1.db.fn.now(),
                    })];
                case 9:
                    _f.sent();
                    _i = 0, targetIds_1 = targetIds;
                    _f.label = 10;
                case 10:
                    if (!(_i < targetIds_1.length)) return [3 /*break*/, 16];
                    targetId = targetIds_1[_i];
                    return [4 /*yield*/, createPlanActionHistory(targetId, '작업취소', historyMessage)];
                case 11:
                    _f.sent();
                    return [4 /*yield*/, createPlanIssueHistory(targetId, '작업취소', historyMessage)];
                case 12:
                    issueRow = _f.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .where({ id: issueRow.id })
                            .update({
                            resolved: true,
                            resolved_at: client_js_1.db.fn.now(),
                        })];
                case 13:
                    _f.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(targetId)];
                case 14:
                    _f.sent();
                    _f.label = 15;
                case 15:
                    _i++;
                    return [3 /*break*/, 10];
                case 16:
                    _c = {};
                    return [4 /*yield*/, getPlanItemById(id)];
                case 17: return [2 /*return*/, (_c.item = _f.sent(),
                        _c.message = resultMessage,
                        _c)];
            }
        });
    });
}
function createPlanActionHistory(planItemId, actionType, note) {
    return __awaiter(this, void 0, void 0, function () {
        var rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ACTION_TABLE)
                            .insert({
                            plan_item_id: planItemId,
                            action_type: actionType,
                            note: note,
                        })
                            .returning('*')];
                case 2:
                    rows = _a.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function resolveAutomationIssueHistories(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .where({ plan_item_id: planItemId, resolved: false })
                            .whereIn('issue_tag', __spreadArray([], automationIssueTags, true))
                            .update({
                            resolved: true,
                            resolved_at: client_js_1.db.fn.now(),
                        })];
                case 2:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function listPlanActionHistories(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [2 /*return*/, (0, client_js_1.db)(exports.PLAN_ACTION_TABLE)
                            .select('*')
                            .where({ plan_item_id: planItemId })
                            .orderBy('created_at', 'desc')
                            .orderBy('id', 'desc')];
            }
        });
    });
}
function createPlanSourceWorkHistory(planItemId, payload) {
    return __awaiter(this, void 0, void 0, function () {
        var rawChangedFiles, changedFiles, filteredChangedFiles, rows_3, existingChangedFilesList, rows;
        var _a, _b, _c, _d, _e, _f;
        return __generator(this, function (_g) {
            switch (_g.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _g.sent();
                    rawChangedFiles = (_a = payload.changedFiles) !== null && _a !== void 0 ? _a : [];
                    changedFiles = normalizeChangedFiles(rawChangedFiles);
                    filteredChangedFiles = changedFiles;
                    if (!changedFiles.some(function (file) { return WORKLOG_EVIDENCE_PATH_PATTERN.test(file); })) return [3 /*break*/, 3];
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .select('changed_files')
                            .where({ plan_item_id: planItemId })
                            .orderBy('created_at', 'asc')
                            .orderBy('id', 'asc')];
                case 2:
                    rows_3 = _g.sent();
                    existingChangedFilesList = rows_3.map(function (row) {
                        var _a;
                        try {
                            return JSON.parse(String((_a = row.changed_files) !== null && _a !== void 0 ? _a : '[]'));
                        }
                        catch (_b) {
                            return [];
                        }
                    });
                    filteredChangedFiles = filterRetryWorklogEvidencePaths(changedFiles, existingChangedFilesList);
                    _g.label = 3;
                case 3: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                        .insert({
                        plan_item_id: planItemId,
                        summary: payload.summary,
                        branch_name: payload.branchName,
                        commit_hash: (_b = payload.commitHash) !== null && _b !== void 0 ? _b : null,
                        preview_url: (_c = payload.previewUrl) !== null && _c !== void 0 ? _c : null,
                        changed_files: JSON.stringify(filteredChangedFiles),
                        command_log: (_d = payload.commandLog) !== null && _d !== void 0 ? _d : null,
                        diff_text: (_e = payload.diffText) !== null && _e !== void 0 ? _e : null,
                        source_files: JSON.stringify((_f = payload.sourceFiles) !== null && _f !== void 0 ? _f : []),
                    })
                        .returning('*')];
                case 4:
                    rows = _g.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(planItemId)];
                case 5:
                    _g.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function createPlanLifecycleSourceWorkHistory(planItemId, summary, branchName, commandLog, previewUrl) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            return [2 /*return*/, createPlanSourceWorkHistory(planItemId, {
                    summary: summary,
                    branchName: branchName,
                    previewUrl: previewUrl,
                    changedFiles: [],
                    commandLog: commandLog !== null && commandLog !== void 0 ? commandLog : null,
                    diffText: null,
                    sourceFiles: [],
                })];
        });
    });
}
function listPlanSourceWorkHistories(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [2 /*return*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .select('*')
                            .where({ plan_item_id: planItemId })
                            .orderBy('created_at', 'desc')
                            .orderBy('id', 'desc')];
            }
        });
    });
}
function getPlanSourceWorkHistory(planItemId, sourceWorkId) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [2 /*return*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .select('*')
                            .where({
                            id: sourceWorkId,
                            plan_item_id: planItemId,
                        })
                            .first()];
            }
        });
    });
}
function listLatestPlanSourceWorkMap(planItemIds) {
    return __awaiter(this, void 0, void 0, function () {
        var rows, latestMap, fallbackMap;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    if (planItemIds.length === 0) {
                        return [2 /*return*/, new Map()];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .select('*')
                            .whereIn('plan_item_id', planItemIds)
                            .orderBy('created_at', 'desc')
                            .orderBy('id', 'desc')];
                case 1:
                    rows = _a.sent();
                    latestMap = new Map();
                    fallbackMap = new Map();
                    rows.forEach(function (row) {
                        var planItemId = Number(row.plan_item_id);
                        var mappedRow = mapPlanSourceWorkRow(row);
                        var hasMeaningfulChanges = mappedRow.changedFiles.length > 0 || mappedRow.sourceFiles.length > 0;
                        if (!fallbackMap.has(planItemId)) {
                            fallbackMap.set(planItemId, mappedRow);
                        }
                        if (!latestMap.has(planItemId) && hasMeaningfulChanges) {
                            latestMap.set(planItemId, mappedRow);
                        }
                    });
                    planItemIds.forEach(function (planItemId) {
                        if (!latestMap.has(planItemId) && fallbackMap.has(planItemId)) {
                            latestMap.set(planItemId, fallbackMap.get(planItemId));
                        }
                    });
                    return [2 /*return*/, latestMap];
            }
        });
    });
}
function listPlanReleaseReviewRowMap(planItemIds) {
    return __awaiter(this, void 0, void 0, function () {
        var rows, reviewMap;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    if (planItemIds.length === 0) {
                        return [2 /*return*/, new Map()];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_RELEASE_REVIEW_TABLE)
                            .select('*')
                            .whereIn('plan_item_id', planItemIds)
                            .orderBy('updated_at', 'desc')
                            .orderBy('id', 'desc')];
                case 1:
                    rows = _a.sent();
                    reviewMap = new Map();
                    rows.forEach(function (row) {
                        var planItemId = Number(row.plan_item_id);
                        if (!reviewMap.has(planItemId)) {
                            reviewMap.set(planItemId, row);
                        }
                    });
                    return [2 /*return*/, reviewMap];
            }
        });
    });
}
function summarizePlanReviewText(value) {
    return String(value !== null && value !== void 0 ? value : '')
        .replace(/\s+/g, ' ')
        .trim()
        .slice(0, 220);
}
function inferReleaseReviewPageSelectionIds(changedFiles) {
    var selectionIds = new Set(['page:plans:release-review', 'page:plans:release']);
    changedFiles.forEach(function (file) {
        var normalized = String(file !== null && file !== void 0 ? file : '').trim();
        if (!normalized) {
            return;
        }
        if (normalized.startsWith('src/features/board/') || normalized.includes('/BoardPage.tsx')) {
            selectionIds.add('page:plans:board');
        }
        if (normalized.startsWith('src/features/history/')) {
            selectionIds.add('page:plans:history');
        }
        if (normalized.startsWith('src/components/') || normalized.startsWith('src/features/layout/')) {
            selectionIds.add('page:apis:components');
        }
        if (normalized.startsWith('src/widgets/')) {
            selectionIds.add('page:apis:widgets');
        }
    });
    return Array.from(selectionIds);
}
function inferReleaseReviewDocIds(changedFiles) {
    return Array.from(new Set(changedFiles
        .map(function (file) {
        var normalized = String(file !== null && file !== void 0 ? file : '').trim();
        return normalized.startsWith('docs/') ? normalized.replace(/[^\w-]+/g, '-') : '';
    })
        .filter(Boolean)));
}
function toKebabCase(value) {
    return value
        .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
        .replace(/[_\s]+/g, '-')
        .toLowerCase();
}
function extractSampleTargetIds(sourceFiles, changedFiles, pathPrefix) {
    var targetIds = new Set();
    (sourceFiles !== null && sourceFiles !== void 0 ? sourceFiles : []).forEach(function (sourceFile) {
        var _a, _b, _c;
        var normalizedPath = String((_a = sourceFile.path) !== null && _a !== void 0 ? _a : '').trim();
        if (!normalizedPath.startsWith(pathPrefix)) {
            return;
        }
        var matches = String((_b = sourceFile.content) !== null && _b !== void 0 ? _b : '').matchAll(/componentId\s*:\s*['"]([^'"]+)['"]/g);
        for (var _i = 0, matches_1 = matches; _i < matches_1.length; _i++) {
            var match = matches_1[_i];
            var targetId = String((_c = match[1]) !== null && _c !== void 0 ? _c : '').trim();
            if (targetId) {
                targetIds.add(targetId);
            }
        }
    });
    changedFiles.forEach(function (file) {
        var normalized = String(file !== null && file !== void 0 ? file : '').trim();
        if (!normalized.startsWith(pathPrefix)) {
            return;
        }
        var relativePath = normalized.slice(pathPrefix.length);
        var firstSegment = relativePath.split('/').filter(Boolean)[0];
        if (firstSegment) {
            targetIds.add(toKebabCase(firstSegment));
        }
    });
    return Array.from(targetIds);
}
function inferReleaseReviewComponentIds(sourceFiles, changedFiles) {
    return extractSampleTargetIds(sourceFiles, changedFiles, 'src/components/');
}
function inferReleaseReviewWidgetIds(sourceFiles, changedFiles) {
    return extractSampleTargetIds(sourceFiles, changedFiles, 'src/widgets/');
}
function buildReleaseReviewSummary(row, latestSourceWork, reviewRow) {
    var _a, _b, _c, _d, _e, _f;
    var metadata = mapPlanReleaseReviewMetadata((_a = reviewRow === null || reviewRow === void 0 ? void 0 : reviewRow.metadata) !== null && _a !== void 0 ? _a : null);
    var candidateSummary = (_d = (_b = metadata.summary) !== null && _b !== void 0 ? _b : summarizePlanReviewText(String((_c = latestSourceWork === null || latestSourceWork === void 0 ? void 0 : latestSourceWork.summary) !== null && _c !== void 0 ? _c : ''))) !== null && _d !== void 0 ? _d : summarizePlanReviewText(String((_e = row.note) !== null && _e !== void 0 ? _e : ''));
    return candidateSummary || summarizePlanReviewText(String((_f = row.note) !== null && _f !== void 0 ? _f : ''));
}
function upsertPlanReleaseReview(planItemId, payload, actor) {
    return __awaiter(this, void 0, void 0, function () {
        var currentItem, currentRow, currentMetadata, nextStatus, nextReviewNote, nextMetadata, hasReviewerTrace, nextRow, rows_4, rows;
        var _a, _b, _c, _d, _e, _f, _g;
        return __generator(this, function (_h) {
            switch (_h.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _h.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: planItemId }).first()];
                case 2:
                    currentItem = _h.sent();
                    if (!currentItem) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_RELEASE_REVIEW_TABLE).where({ plan_item_id: planItemId }).first()];
                case 3:
                    currentRow = _h.sent();
                    currentMetadata = mapPlanReleaseReviewMetadata((_a = currentRow === null || currentRow === void 0 ? void 0 : currentRow.metadata) !== null && _a !== void 0 ? _a : null);
                    nextStatus = (_b = payload.status) !== null && _b !== void 0 ? _b : ((currentRow === null || currentRow === void 0 ? void 0 : currentRow.status) ? String(currentRow.status) : 'pending');
                    nextReviewNote = (_c = payload.reviewNote) !== null && _c !== void 0 ? _c : String((_d = currentRow === null || currentRow === void 0 ? void 0 : currentRow.review_note) !== null && _d !== void 0 ? _d : '');
                    nextMetadata = payload.metadata ? __assign(__assign({}, currentMetadata), payload.metadata) : currentMetadata;
                    hasReviewerTrace = nextStatus !== 'pending' || nextReviewNote.trim().length > 0;
                    nextRow = {
                        plan_item_id: planItemId,
                        status: nextStatus,
                        review_note: nextReviewNote,
                        checked_by_client_id: hasReviewerTrace ? ((_e = actor === null || actor === void 0 ? void 0 : actor.clientId) === null || _e === void 0 ? void 0 : _e.trim()) || null : null,
                        checked_by_nickname: hasReviewerTrace ? ((_f = actor === null || actor === void 0 ? void 0 : actor.nickname) === null || _f === void 0 ? void 0 : _f.trim()) || ((_g = actor === null || actor === void 0 ? void 0 : actor.clientId) === null || _g === void 0 ? void 0 : _g.trim()) || null : null,
                        checked_at: hasReviewerTrace ? client_js_1.db.fn.now() : null,
                        metadata: JSON.stringify(nextMetadata),
                        updated_at: client_js_1.db.fn.now(),
                    };
                    if (!currentRow) return [3 /*break*/, 6];
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_RELEASE_REVIEW_TABLE)
                            .where({ plan_item_id: planItemId })
                            .update(nextRow)
                            .returning('*')];
                case 4:
                    rows_4 = _h.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(planItemId)];
                case 5:
                    _h.sent();
                    return [2 /*return*/, mapPlanReleaseReviewRow(rows_4[0], planItemId)];
                case 6: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_RELEASE_REVIEW_TABLE)
                        .insert(__assign(__assign({}, nextRow), { created_at: client_js_1.db.fn.now() }))
                        .returning('*')];
                case 7:
                    rows = _h.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(planItemId)];
                case 8:
                    _h.sent();
                    return [2 /*return*/, mapPlanReleaseReviewRow(rows[0], planItemId)];
            }
        });
    });
}
function listPlanReleaseReviewBoardItems(options) {
    return __awaiter(this, void 0, void 0, function () {
        var rows, releaseRows, planItemIds, issueSummaryMap, latestSourceWorkMap, reviewRowMap;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).select('*').orderBy('updated_at', 'desc').orderBy('id', 'desc')];
                case 2:
                    rows = _a.sent();
                    releaseRows = rows.filter(function (row) {
                        var _a;
                        var workerStatus = String((_a = row.worker_status) !== null && _a !== void 0 ? _a : '');
                        return (row.status === '릴리즈완료' ||
                            row.status === '완료' ||
                            ['release반영중', 'main반영대기', 'main반영중', 'main반영실패', 'release반영완료'].includes(workerStatus));
                    });
                    planItemIds = releaseRows.map(function (row) { return Number(row.id); });
                    return [4 /*yield*/, listPlanIssueSummaries(planItemIds)];
                case 3:
                    issueSummaryMap = _a.sent();
                    return [4 /*yield*/, listLatestPlanSourceWorkMap(planItemIds)];
                case 4:
                    latestSourceWorkMap = _a.sent();
                    return [4 /*yield*/, listPlanReleaseReviewRowMap(planItemIds)];
                case 5:
                    reviewRowMap = _a.sent();
                    return [2 /*return*/, releaseRows.map(function (row) {
                            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
                            var planItemId = Number(row.id);
                            var planItem = mapPlanRow(row, __assign(__assign({}, ((_a = issueSummaryMap.get(planItemId)) !== null && _a !== void 0 ? _a : { issueTags: [], hasOpenIssues: false })), { maskNote: options === null || options === void 0 ? void 0 : options.maskNote, noteMasked: options === null || options === void 0 ? void 0 : options.maskNote, exposeConfiguredAutomationType: true }));
                            var latestSourceWork = (_b = latestSourceWorkMap.get(planItemId)) !== null && _b !== void 0 ? _b : null;
                            var reviewRow = reviewRowMap.get(planItemId);
                            var review = mapPlanReleaseReviewRow(reviewRow, planItemId);
                            var changedFiles = (_c = latestSourceWork === null || latestSourceWork === void 0 ? void 0 : latestSourceWork.changedFiles) !== null && _c !== void 0 ? _c : [];
                            var sourceFiles = (_d = latestSourceWork === null || latestSourceWork === void 0 ? void 0 : latestSourceWork.sourceFiles) !== null && _d !== void 0 ? _d : [];
                            var metadata = __assign(__assign({}, review.metadata), { summary: (_e = review.metadata.summary) !== null && _e !== void 0 ? _e : buildReleaseReviewSummary(row, latestSourceWork !== null && latestSourceWork !== void 0 ? latestSourceWork : undefined, reviewRow), pageSelectionIds: (_f = review.metadata.pageSelectionIds) !== null && _f !== void 0 ? _f : inferReleaseReviewPageSelectionIds(changedFiles), docIds: (_g = review.metadata.docIds) !== null && _g !== void 0 ? _g : inferReleaseReviewDocIds(changedFiles), componentIds: (_h = review.metadata.componentIds) !== null && _h !== void 0 ? _h : inferReleaseReviewComponentIds(sourceFiles, changedFiles), widgetIds: (_j = review.metadata.widgetIds) !== null && _j !== void 0 ? _j : inferReleaseReviewWidgetIds(sourceFiles, changedFiles) });
                            return {
                                planItem: planItem,
                                review: __assign(__assign({}, review), { metadata: metadata }),
                                latestSourceWork: latestSourceWork,
                            };
                        })];
            }
        });
    });
}
function createPlanIssueHistory(planItemId, issueTag, message) {
    return __awaiter(this, void 0, void 0, function () {
        var normalizedIssueTag, previousIssueWithAction, rows;
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _b.sent();
                    normalizedIssueTag = issueTag.startsWith('#') ? issueTag : "#".concat(issueTag);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .select('*')
                            .where({
                            plan_item_id: planItemId,
                            issue_tag: normalizedIssueTag,
                            message: message,
                        })
                            .whereNotNull('action_note')
                            .orderBy('created_at', 'desc')
                            .orderBy('id', 'desc')
                            .first()];
                case 2:
                    previousIssueWithAction = _b.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .insert({
                            plan_item_id: planItemId,
                            issue_tag: normalizedIssueTag,
                            message: message,
                            action_note: (_a = previousIssueWithAction === null || previousIssueWithAction === void 0 ? void 0 : previousIssueWithAction.action_note) !== null && _a !== void 0 ? _a : null,
                        })
                            .returning('*')];
                case 3:
                    rows = _b.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function appendLatestIssueAction(planItemId_1, actionNote_1) {
    return __awaiter(this, arguments, void 0, function (planItemId, actionNote, resolve) {
        var issueRow, nextActionNote, rows;
        if (resolve === void 0) { resolve = false; }
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .where({ plan_item_id: planItemId })
                            .orderBy('resolved', 'asc')
                            .orderBy('created_at', 'desc')
                            .first()];
                case 2:
                    issueRow = _a.sent();
                    if (!issueRow) {
                        throw new Error('기록할 이슈 이력이 없습니다.');
                    }
                    nextActionNote = issueRow.action_note
                        ? "".concat(issueRow.action_note, "\n[").concat(new Date().toISOString(), "] ").concat(actionNote)
                        : "[".concat(new Date().toISOString(), "] ").concat(actionNote);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .where({ id: issueRow.id })
                            .update({
                            action_note: nextActionNote,
                            resolved: resolve ? true : issueRow.resolved,
                            resolved_at: resolve ? client_js_1.db.fn.now() : issueRow.resolved_at,
                        })
                            .returning('*')];
                case 3:
                    rows = _a.sent();
                    return [4 /*yield*/, createPlanActionHistory(planItemId, resolve ? '이슈해결조치' : '이슈조치', actionNote)];
                case 4:
                    _a.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function listPlanIssueHistories(planItemId) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, ensurePlanFailureIssueHistory(planItemId)];
                case 2:
                    _a.sent();
                    return [2 /*return*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                            .select('*')
                            .where({ plan_item_id: planItemId })
                            .orderBy('created_at', 'desc')
                            .orderBy('id', 'desc')];
            }
        });
    });
}
function listPlanIssueSummaries(planItemIds) {
    return __awaiter(this, void 0, void 0, function () {
        var _i, planItemIds_1, planItemId, rows, summaryMap;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    if (planItemIds.length === 0) {
                        return [2 /*return*/, new Map()];
                    }
                    _i = 0, planItemIds_1 = planItemIds;
                    _a.label = 2;
                case 2:
                    if (!(_i < planItemIds_1.length)) return [3 /*break*/, 5];
                    planItemId = planItemIds_1[_i];
                    return [4 /*yield*/, ensurePlanFailureIssueHistory(planItemId)];
                case 3:
                    _a.sent();
                    _a.label = 4;
                case 4:
                    _i++;
                    return [3 /*break*/, 2];
                case 5: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_ISSUE_TABLE)
                        .select('*')
                        .whereIn('plan_item_id', planItemIds)
                        .orderBy('created_at', 'desc')
                        .orderBy('id', 'desc')];
                case 6:
                    rows = _a.sent();
                    summaryMap = new Map();
                    rows.forEach(function (row) {
                        var _a;
                        var planItemId = Number(row.plan_item_id);
                        var current = (_a = summaryMap.get(planItemId)) !== null && _a !== void 0 ? _a : { issueTags: [], hasOpenIssues: false };
                        if (!row.resolved && typeof row.issue_tag === 'string' && !current.issueTags.includes(row.issue_tag)) {
                            current.issueTags.push(row.issue_tag);
                        }
                        if (!row.resolved) {
                            current.hasOpenIssues = true;
                        }
                        summaryMap.set(planItemId, current);
                    });
                    return [2 /*return*/, summaryMap];
            }
        });
    });
}
function claimNextPlanForBranch(workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var _this = this;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [2 /*return*/, client_js_1.db.transaction(function (trx) { return __awaiter(_this, void 0, void 0, function () {
                            var row, assignedBranch, rows;
                            var _a;
                            return __generator(this, function (_b) {
                                switch (_b.label) {
                                    case 0: return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                            .select('*')
                                            .where({ status: '등록' })
                                            .where(function (builder) {
                                            builder.whereNull('worker_status').orWhere('worker_status', '대기');
                                        })
                                            .orderBy('created_at', 'asc')
                                            .forUpdate()
                                            .skipLocked()
                                            .first()];
                                    case 1:
                                        row = _b.sent();
                                        if (!row) {
                                            return [2 /*return*/, null];
                                        }
                                        assignedBranch = shouldUseLocalMainPlanMode(row.automation_type)
                                            ? String((0, env_js_1.getEnv)().PLAN_MAIN_BRANCH)
                                            : buildPlanBranchName(String(row.work_id), Number(row.id));
                                        return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                                .where({ id: row.id })
                                                .update({
                                                status: '작업중',
                                                assigned_branch: assignedBranch,
                                                worker_status: '브랜치생성중',
                                                last_error: null,
                                                locked_by: workerId,
                                                locked_at: client_js_1.db.fn.now(),
                                                started_at: (_a = row.started_at) !== null && _a !== void 0 ? _a : client_js_1.db.fn.now(),
                                                updated_at: client_js_1.db.fn.now(),
                                            })
                                                .returning('*')];
                                    case 2:
                                        rows = _b.sent();
                                        return [2 /*return*/, rows[0]];
                                }
                            });
                        }); })];
            }
        });
    });
}
function claimNextPlanForExecution(workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var _this = this;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [2 /*return*/, client_js_1.db.transaction(function (trx) { return __awaiter(_this, void 0, void 0, function () {
                            var row, rows;
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0: return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                            .select('*')
                                            .where({ status: '작업중', worker_status: '브랜치준비' })
                                            .whereNotNull('assigned_branch')
                                            .orderBy('updated_at', 'asc')
                                            .forUpdate()
                                            .skipLocked()
                                            .first()];
                                    case 1:
                                        row = _a.sent();
                                        if (!row) {
                                            return [2 /*return*/, null];
                                        }
                                        return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                                .where({ id: row.id })
                                                .update({
                                                worker_status: '자동작업중',
                                                last_error: null,
                                                locked_by: workerId,
                                                locked_at: client_js_1.db.fn.now(),
                                                updated_at: client_js_1.db.fn.now(),
                                            })
                                                .returning('*')];
                                    case 2:
                                        rows = _a.sent();
                                        return [2 /*return*/, rows[0]];
                                }
                            });
                        }); })];
            }
        });
    });
}
function claimNextPlanForMerge(workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var _this = this;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [2 /*return*/, client_js_1.db.transaction(function (trx) { return __awaiter(_this, void 0, void 0, function () {
                            var row, rows;
                            return __generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0: return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                            .select('*')
                                            .where({ status: '작업완료' })
                                            .whereNotNull('assigned_branch')
                                            .where(function (builder) {
                                            builder.whereNull('worker_status').orWhere('worker_status', 'release반영대기');
                                        })
                                            .orderBy('updated_at', 'asc')
                                            .forUpdate()
                                            .skipLocked()
                                            .first()];
                                    case 1:
                                        row = _a.sent();
                                        if (!row) {
                                            return [2 /*return*/, null];
                                        }
                                        return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                                .where({ id: row.id })
                                                .update({
                                                worker_status: 'release반영중',
                                                last_error: null,
                                                locked_by: workerId,
                                                locked_at: client_js_1.db.fn.now(),
                                                updated_at: client_js_1.db.fn.now(),
                                            })
                                                .returning('*')];
                                    case 2:
                                        rows = _a.sent();
                                        return [2 /*return*/, rows[0]];
                                }
                            });
                        }); })];
            }
        });
    });
}
function claimNextPlanForMainMerge(workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var _this = this;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [2 /*return*/, client_js_1.db.transaction(function (trx) { return __awaiter(_this, void 0, void 0, function () {
                            var candidateRows, row, pendingReleaseRow, batchRows, batchIds, rows;
                            var _a;
                            return __generator(this, function (_b) {
                                switch (_b.label) {
                                    case 0: return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                            .select('*')
                                            .where({ status: '릴리즈완료', worker_status: 'main반영대기' })
                                            .whereNotNull('assigned_branch')
                                            .orderBy('updated_at', 'asc')
                                            .forUpdate()];
                                    case 1:
                                        candidateRows = _b.sent();
                                        row = candidateRows[0];
                                        if (!row) {
                                            return [2 /*return*/, null];
                                        }
                                        return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                                .select('id')
                                                .where({ release_target: row.release_target, status: '작업완료' })
                                                .first()];
                                    case 2:
                                        pendingReleaseRow = _b.sent();
                                        if (pendingReleaseRow) {
                                            return [2 /*return*/, null];
                                        }
                                        return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                                .select('id')
                                                .where({ status: '릴리즈완료', release_target: row.release_target })
                                                .whereIn('worker_status', ['main반영대기', 'main반영실패'])];
                                    case 3:
                                        batchRows = _b.sent();
                                        batchIds = batchRows.map(function (candidate) { return Number(candidate.id); }).filter(Number.isFinite);
                                        return [4 /*yield*/, trx(exports.PLAN_TABLE)
                                                .whereIn('id', batchIds.length > 0 ? batchIds : [Number(row.id)])
                                                .update({
                                                worker_status: 'main반영중',
                                                last_error: null,
                                                locked_by: workerId,
                                                locked_at: client_js_1.db.fn.now(),
                                                updated_at: client_js_1.db.fn.now(),
                                            })
                                                .returning('*')];
                                    case 4:
                                        rows = _b.sent();
                                        return [2 /*return*/, (_a = rows[0]) !== null && _a !== void 0 ? _a : null];
                                }
                            });
                        }); })];
            }
        });
    });
}
function markPlanBranchReady(id, workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                        .where({ id: id })
                        .where({ locked_by: workerId })
                        .update({
                        worker_status: '브랜치준비',
                        locked_by: null,
                        locked_at: null,
                        updated_at: client_js_1.db.fn.now(),
                    })
                        .returning('*')];
                case 1:
                    rows = _a.sent();
                    if (!rows[0]) {
                        return [2 /*return*/, null];
                    }
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function markPlanWorkCompleted(id, workerId, note) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, rows;
        var _a, _b, _c;
        return __generator(this, function (_d) {
            switch (_d.label) {
                case 0: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 1:
                    currentRow = _d.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .where({ locked_by: workerId })
                            .update({
                            status: '작업완료',
                            jangsing_processing_required: true,
                            worker_status: 'release반영대기',
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 2:
                    rows = _d.sent();
                    if (!rows[0]) {
                        return [2 /*return*/, null];
                    }
                    if (!currentRow) return [3 /*break*/, 4];
                    return [4 /*yield*/, createPlanLifecycleSourceWorkHistory(id, '자동 작업 완료로 release 반영 대기 상태로 전환했습니다.', (_b = (_a = currentRow.assigned_branch) !== null && _a !== void 0 ? _a : currentRow.release_target) !== null && _b !== void 0 ? _b : 'release', currentRow.assigned_branch
                            ? "\uD604\uC7AC \uC791\uC5C5 \uBE0C\uB79C\uCE58: ".concat(currentRow.assigned_branch)
                            : "release \uB300\uAE30 \uBE0C\uB79C\uCE58: ".concat((_c = currentRow.release_target) !== null && _c !== void 0 ? _c : 'release'))];
                case 3:
                    _d.sent();
                    _d.label = 4;
                case 4: return [4 /*yield*/, createPlanActionHistory(id, '작업완료', note !== null && note !== void 0 ? note : '자동 작업을 마치고 release 반영을 대기합니다.')];
                case 5:
                    _d.sent();
                    return [4 /*yield*/, resolveAutomationIssueHistories(id)];
                case 6:
                    _d.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function markPlanReleaseMerged(id, workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, autoDeployToMain, rows;
        var _a, _b, _c;
        return __generator(this, function (_d) {
            switch (_d.label) {
                case 0: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 1:
                    currentRow = _d.sent();
                    autoDeployToMain = Boolean((_a = currentRow === null || currentRow === void 0 ? void 0 : currentRow.auto_deploy_to_main) !== null && _a !== void 0 ? _a : true);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .where({ locked_by: workerId })
                            .update({
                            status: '릴리즈완료',
                            worker_status: autoDeployToMain ? 'main반영대기' : 'release반영완료',
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 2:
                    rows = _d.sent();
                    if (!rows[0]) {
                        return [2 /*return*/, null];
                    }
                    if (!currentRow) return [3 /*break*/, 4];
                    return [4 /*yield*/, createPlanLifecycleSourceWorkHistory(id, 'release 브랜치 반영을 완료했습니다.', (_b = currentRow.release_target) !== null && _b !== void 0 ? _b : 'release', "release \uBE0C\uB79C\uCE58: ".concat((_c = currentRow.release_target) !== null && _c !== void 0 ? _c : 'release'))];
                case 3:
                    _d.sent();
                    _d.label = 4;
                case 4: return [4 /*yield*/, createPlanActionHistory(id, 'release반영완료', autoDeployToMain
                        ? 'release 브랜치 반영을 완료했고 main 자동 반영을 예약했습니다.'
                        : 'release 브랜치 반영을 완료했습니다.')];
                case 5:
                    _d.sent();
                    return [4 /*yield*/, resolveAutomationIssueHistories(id)];
                case 6:
                    _d.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 7:
                    _d.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function markPlanMerged(id, workerId) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, batchRows, batchIds, targetRows, rows, _i, targetRows_1, row, planId;
        var _a, _b, _c;
        return __generator(this, function (_d) {
            switch (_d.label) {
                case 0: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 1:
                    currentRow = _d.sent();
                    if (!currentRow || currentRow.locked_by !== workerId) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .select('*')
                            .where({ status: '릴리즈완료', release_target: currentRow.release_target })
                            .whereIn('worker_status', ['main반영대기', 'main반영중', 'main반영실패'])];
                case 2:
                    batchRows = _d.sent();
                    batchIds = batchRows.map(function (row) { return Number(row.id); }).filter(Number.isFinite);
                    targetRows = batchRows.length > 0 ? batchRows : [currentRow];
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .whereIn('id', batchIds.length > 0 ? batchIds : [id])
                            .update({
                            status: '완료',
                            worker_status: 'main반영완료',
                            last_error: null,
                            locked_by: null,
                            locked_at: null,
                            merged_at: client_js_1.db.fn.now(),
                            completed_at: client_js_1.db.fn.now(),
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows = _d.sent();
                    _i = 0, targetRows_1 = targetRows;
                    _d.label = 4;
                case 4:
                    if (!(_i < targetRows_1.length)) return [3 /*break*/, 10];
                    row = targetRows_1[_i];
                    planId = Number(row.id);
                    return [4 /*yield*/, createPlanLifecycleSourceWorkHistory(planId, "".concat((0, env_js_1.getEnv)().PLAN_MAIN_BRANCH, " \uBE0C\uB79C\uCE58 \uBC18\uC601\uC744 \uC644\uB8CC\uD588\uC2B5\uB2C8\uB2E4."), (0, env_js_1.getEnv)().PLAN_MAIN_BRANCH, "".concat((_a = row.release_target) !== null && _a !== void 0 ? _a : 'release', " -> ").concat((0, env_js_1.getEnv)().PLAN_MAIN_BRANCH))];
                case 5:
                    _d.sent();
                    return [4 /*yield*/, createPlanActionHistory(planId, 'main반영완료', "".concat((_b = row.release_target) !== null && _b !== void 0 ? _b : 'release', " \uBE0C\uB79C\uCE58\uB97C ").concat((0, env_js_1.getEnv)().PLAN_MAIN_BRANCH, " \uBE0C\uB79C\uCE58\uC5D0 \uC77C\uAD04 \uBC18\uC601 \uC644\uB8CC\uD588\uC2B5\uB2C8\uB2E4."))];
                case 6:
                    _d.sent();
                    return [4 /*yield*/, resolveAutomationIssueHistories(planId)];
                case 7:
                    _d.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(planId)];
                case 8:
                    _d.sent();
                    _d.label = 9;
                case 9:
                    _i++;
                    return [3 /*break*/, 4];
                case 10: return [2 /*return*/, {
                        mergedRow: (_c = rows[0]) !== null && _c !== void 0 ? _c : null,
                        notificationRows: targetRows,
                    }];
            }
        });
    });
}
function markPlanMainMergeFailure(releaseTarget, workerId, errorMessage) {
    return __awaiter(this, void 0, void 0, function () {
        var rows, _i, rows_5, row;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ status: '릴리즈완료', release_target: releaseTarget, locked_by: workerId })
                            .whereIn('worker_status', ['main반영대기', 'main반영중'])
                            .update({
                            worker_status: 'main반영실패',
                            last_error: errorMessage,
                            locked_by: null,
                            locked_at: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 2:
                    rows = _a.sent();
                    _i = 0, rows_5 = rows;
                    _a.label = 3;
                case 3:
                    if (!(_i < rows_5.length)) return [3 /*break*/, 7];
                    row = rows_5[_i];
                    return [4 /*yield*/, createPlanActionHistory(Number(row.id), 'main반영실패', "".concat(releaseTarget, " \uBE0C\uB79C\uCE58 \uAE30\uC900 main \uC77C\uAD04 \uBC18\uC601\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4."))];
                case 4:
                    _a.sent();
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(Number(row.id))];
                case 5:
                    _a.sent();
                    _a.label = 6;
                case 6:
                    _i++;
                    return [3 /*break*/, 3];
                case 7: return [2 /*return*/, rows];
            }
        });
    });
}
function markPlanAutomationFailure(id, workerId, workerStatus, errorMessage) {
    return __awaiter(this, void 0, void 0, function () {
        var currentRow, nextStatus, rows, issueHistory;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 1:
                    currentRow = _a.sent();
                    if (!currentRow) {
                        return [2 /*return*/, null];
                    }
                    nextStatus = workerStatus === '브랜치실패'
                        ? '등록'
                        : workerStatus === '자동작업실패'
                            ? '작업중'
                            : workerStatus === 'main반영실패'
                                ? '릴리즈완료'
                                : '작업완료';
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .where({ id: id })
                            .where({ locked_by: workerId })
                            .update({
                            status: nextStatus,
                            worker_status: workerStatus,
                            last_error: errorMessage,
                            locked_by: null,
                            locked_at: null,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 2:
                    rows = _a.sent();
                    if (!rows[0]) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, createPlanIssueHistory(id, workerStatus, errorMessage)];
                case 3:
                    issueHistory = _a.sent();
                    if (!(issueHistory === null || issueHistory === void 0 ? void 0 : issueHistory.action_note)) return [3 /*break*/, 5];
                    return [4 /*yield*/, createPlanActionHistory(id, '오류재발', "\uB3D9\uC77C \uC624\uB958\uAC00 \uB2E4\uC2DC \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.\n\uAE30\uC874 \uC870\uCE58\uB0B4\uC5ED:\n".concat(issueHistory.action_note))];
                case 4:
                    _a.sent();
                    _a.label = 5;
                case 5: return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 6:
                    _a.sent();
                    return [2 /*return*/, rows[0]];
            }
        });
    });
}
function listPlanItems(status, options) {
    return __awaiter(this, void 0, void 0, function () {
        var buildListQuery, rows, missingSnapshotIds, planItemIds, issueSummaryMap, releaseReviewNoteMap;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    buildListQuery = function () {
                        var builder = (0, client_js_1.db)(exports.PLAN_TABLE).select('*').orderBy('id', 'desc');
                        if (status) {
                            builder.where('status', status);
                        }
                        return builder;
                    };
                    return [4 /*yield*/, buildListQuery()];
                case 2:
                    rows = _a.sent();
                    missingSnapshotIds = rows
                        .filter(function (row) { return row.usage_snapshot === null || row.usage_snapshot === undefined || String(row.usage_snapshot).trim() === ''; })
                        .map(function (row) { return Number(row.id); })
                        .filter(Number.isFinite);
                    if (!(missingSnapshotIds.length > 0)) return [3 /*break*/, 5];
                    return [4 /*yield*/, Promise.all(missingSnapshotIds.map(function (planItemId) { return syncPlanAutomationUsageSnapshot(planItemId); }))];
                case 3:
                    _a.sent();
                    return [4 /*yield*/, buildListQuery()];
                case 4:
                    rows = _a.sent();
                    _a.label = 5;
                case 5:
                    planItemIds = rows.map(function (row) { return Number(row.id); });
                    return [4 /*yield*/, listPlanIssueSummaries(planItemIds)];
                case 6:
                    issueSummaryMap = _a.sent();
                    return [4 /*yield*/, listPlanReleaseReviewNoteMap(planItemIds)];
                case 7:
                    releaseReviewNoteMap = _a.sent();
                    return [2 /*return*/, rows.map(function (row) {
                            var _a, _b;
                            return mapPlanRow(row, __assign(__assign({}, ((_a = issueSummaryMap.get(Number(row.id))) !== null && _a !== void 0 ? _a : { issueTags: [], hasOpenIssues: false })), { maskNote: options === null || options === void 0 ? void 0 : options.maskNote, noteMasked: options === null || options === void 0 ? void 0 : options.maskNote, releaseReviewNote: (_b = releaseReviewNoteMap.get(Number(row.id))) !== null && _b !== void 0 ? _b : '', exposeConfiguredAutomationType: true }));
                        })];
            }
        });
    });
}
function getPlanItemById(id, options) {
    return __awaiter(this, void 0, void 0, function () {
        var row, issueSummaryMap, releaseReviewNoteMap;
        var _a, _b;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _c.sent();
                    return [4 /*yield*/, ensurePlanFailureIssueHistory(id)];
                case 2:
                    _c.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 3:
                    row = _c.sent();
                    if (!row) {
                        return [2 /*return*/, null];
                    }
                    if (!(row.usage_snapshot === null || row.usage_snapshot === undefined || String(row.usage_snapshot).trim() === '')) return [3 /*break*/, 6];
                    return [4 /*yield*/, syncPlanAutomationUsageSnapshot(id)];
                case 4:
                    _c.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).where({ id: id }).first()];
                case 5:
                    row = _c.sent();
                    _c.label = 6;
                case 6: return [4 /*yield*/, listPlanIssueSummaries([id])];
                case 7:
                    issueSummaryMap = _c.sent();
                    return [4 /*yield*/, listPlanReleaseReviewNoteMap([id])];
                case 8:
                    releaseReviewNoteMap = _c.sent();
                    return [2 /*return*/, mapPlanRow(row, __assign(__assign({}, ((_a = issueSummaryMap.get(id)) !== null && _a !== void 0 ? _a : { issueTags: [], hasOpenIssues: false })), { maskNote: options === null || options === void 0 ? void 0 : options.maskNote, noteMasked: options === null || options === void 0 ? void 0 : options.maskNote, releaseReviewNote: (_b = releaseReviewNoteMap.get(id)) !== null && _b !== void 0 ? _b : '', exposeConfiguredAutomationType: true }))];
            }
        });
    });
}
function normalizePreviewLookupValue(value, stripSearch) {
    if (stripSearch === void 0) { stripSearch = false; }
    var trimmed = String(value !== null && value !== void 0 ? value : '').trim();
    if (!trimmed) {
        return '';
    }
    try {
        var url = new URL(trimmed);
        url.hash = '';
        if (stripSearch) {
            url.search = '';
        }
        return url.toString().replace(/\/+$/, '');
    }
    catch (_a) {
        var withoutHash = trimmed.replace(/#.*$/, '');
        var withoutSearch = stripSearch ? withoutHash.replace(/\?.*$/, '') : withoutHash;
        return withoutSearch.replace(/\/+$/, '');
    }
}
function buildPreviewLookupCandidates(value) {
    return Array.from(new Set([
        normalizePreviewLookupValue(value, false),
        normalizePreviewLookupValue(value, true),
    ].filter(Boolean)));
}
function findLatestPlanItem() {
    return __awaiter(this, void 0, void 0, function () {
        var row;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE).select('id').orderBy('id', 'desc').first()];
                case 2:
                    row = _a.sent();
                    if (!(row === null || row === void 0 ? void 0 : row.id)) {
                        return [2 /*return*/, null];
                    }
                    return [2 /*return*/, getPlanItemById(Number(row.id))];
            }
        });
    });
}
function findPlanItemByWorkId(workId) {
    return __awaiter(this, void 0, void 0, function () {
        var normalizedWorkId, rows, matchedRow;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    normalizedWorkId = normalizePlanWorkId(workId);
                    if (normalizedWorkId === '작업ID') {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_TABLE)
                            .select('id', 'work_id')
                            .orderBy('id', 'desc')];
                case 2:
                    rows = _a.sent();
                    matchedRow = rows.find(function (row) { var _a; return normalizePlanWorkId(String((_a = row.work_id) !== null && _a !== void 0 ? _a : '')) === normalizedWorkId; });
                    if (!(matchedRow === null || matchedRow === void 0 ? void 0 : matchedRow.id)) {
                        return [2 /*return*/, null];
                    }
                    return [2 /*return*/, getPlanItemById(Number(matchedRow.id))];
            }
        });
    });
}
function findPlanItemByPreviewUrl(previewUrl) {
    return __awaiter(this, void 0, void 0, function () {
        var lookupCandidates, rows, matchedRow;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensurePlanTable()];
                case 1:
                    _a.sent();
                    lookupCandidates = new Set(buildPreviewLookupCandidates(previewUrl));
                    if (lookupCandidates.size === 0) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.PLAN_SOURCE_WORK_TABLE)
                            .select('plan_item_id', 'preview_url')
                            .whereNotNull('preview_url')
                            .orderBy('created_at', 'desc')
                            .orderBy('id', 'desc')];
                case 2:
                    rows = _a.sent();
                    matchedRow = rows.find(function (row) { var _a; return buildPreviewLookupCandidates(String((_a = row.preview_url) !== null && _a !== void 0 ? _a : '')).some(function (candidate) { return lookupCandidates.has(candidate); }); });
                    if (!(matchedRow === null || matchedRow === void 0 ? void 0 : matchedRow.plan_item_id)) {
                        return [2 /*return*/, null];
                    }
                    return [2 /*return*/, getPlanItemById(Number(matchedRow.plan_item_id))];
            }
        });
    });
}
