"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 };
    }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.APP_CONFIG_TABLE = void 0;
exports.resolveAppConfigByOrigin = resolveAppConfigByOrigin;
exports.getAppConfig = getAppConfig;
exports.sanitizePersistedChatTypes = sanitizePersistedChatTypes;
exports.normalizeAppConfigSnapshot = normalizeAppConfigSnapshot;
exports.getAppConfigSnapshot = getAppConfigSnapshot;
exports.upsertAppConfig = upsertAppConfig;
exports.getChatTypesConfig = getChatTypesConfig;
exports.upsertChatTypesConfig = upsertChatTypesConfig;
var client_js_1 = require("../db/client.js");
exports.APP_CONFIG_TABLE = 'app_configs';
var CHAT_TYPES_CONFIG_KEY = 'chatTypes';
var CHAT_CONTEXT_SETTINGS_CONFIG_KEY = 'chatContextSettings';
var SCOPED_APP_CONFIGS_KEY = 'scopedAppConfigs';
var DEFAULT_CHAT_APP_CONFIG = {
    maxContextMessages: 12,
    maxContextChars: 3200,
    codexLiveMaxExecutionSeconds: 600,
    codexLiveIdleTimeoutSeconds: 180,
    receiveRoomNotifications: true,
    restartReservationCompletionDelaySeconds: 10,
};
function ensureAppConfigTable() {
    return __awaiter(this, void 0, void 0, function () {
        var hasTable, 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.APP_CONFIG_TABLE)];
                case 1:
                    hasTable = _b.sent();
                    if (!!hasTable) return [3 /*break*/, 3];
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.APP_CONFIG_TABLE, function (table) {
                            table.increments('id').primary();
                            table.jsonb('config_json').notNullable().defaultTo('{}');
                            table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                        })];
                case 2:
                    _b.sent();
                    return [2 /*return*/];
                case 3:
                    requiredColumns = [
                        ['config_json', function (table) { return table.jsonb('config_json').notNullable().defaultTo('{}'); }],
                        ['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.APP_CONFIG_TABLE, columnName)];
                                case 1:
                                    hasColumn = _c.sent();
                                    if (!!hasColumn) return [3 /*break*/, 3];
                                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.APP_CONFIG_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 normalizeAppOrigin(appOrigin) {
    if (typeof appOrigin !== 'string') {
        return '';
    }
    try {
        var url = new URL(appOrigin.trim());
        return url.origin;
    }
    catch (_a) {
        return '';
    }
}
function normalizeAppDomain(appDomain) {
    return typeof appDomain === 'string' ? appDomain.trim().toLowerCase() : '';
}
function deepMergeConfigRecords(base, override) {
    var merged = __assign({}, base);
    for (var _i = 0, _a = Object.entries(override); _i < _a.length; _i++) {
        var _b = _a[_i], key = _b[0], value = _b[1];
        var current = merged[key];
        var canMergeObject = value &&
            current &&
            typeof value === 'object' &&
            typeof current === 'object' &&
            !Array.isArray(value) &&
            !Array.isArray(current);
        merged[key] = canMergeObject
            ? deepMergeConfigRecords(normalizeConfigRecord(current), normalizeConfigRecord(value))
            : value;
    }
    return merged;
}
function getBaseAppConfigRecord(value) {
    var normalized = normalizeConfigRecord(value);
    var nextConfig = __assign({}, normalized);
    delete nextConfig[SCOPED_APP_CONFIGS_KEY];
    return nextConfig;
}
function getScopedAppConfigsRecord(value) {
    return normalizeConfigRecord(normalizeConfigRecord(value)[SCOPED_APP_CONFIGS_KEY]);
}
function resolveScopedAppConfig(value, appOrigin) {
    var normalizedAppOrigin = normalizeAppOrigin(appOrigin);
    if (!normalizedAppOrigin) {
        return null;
    }
    var scopedEntry = normalizeConfigRecord(getScopedAppConfigsRecord(value)[normalizedAppOrigin]);
    var scopedConfig = normalizeConfigRecord(scopedEntry.config);
    return Object.keys(scopedConfig).length > 0 ? scopedConfig : null;
}
function mergeScopedAppConfig(currentConfig, nextConfig, appOrigin, appDomain) {
    var _a, _b;
    var _c;
    var normalizedAppOrigin = normalizeAppOrigin(appOrigin);
    if (!normalizedAppOrigin) {
        return deepMergeConfigRecords(currentConfig, nextConfig);
    }
    var baseConfig = getBaseAppConfigRecord(currentConfig);
    var scopedConfigs = getScopedAppConfigsRecord(currentConfig);
    var currentResolvedScopedConfig = (_c = resolveScopedAppConfig(currentConfig, normalizedAppOrigin)) !== null && _c !== void 0 ? _c : baseConfig;
    var nextResolvedScopedConfig = deepMergeConfigRecords(currentResolvedScopedConfig, nextConfig);
    var normalizedAppDomain = normalizeAppDomain(appDomain);
    return __assign(__assign({}, baseConfig), (_a = {}, _a[SCOPED_APP_CONFIGS_KEY] = __assign(__assign({}, scopedConfigs), (_b = {}, _b[normalizedAppOrigin] = {
        config: nextResolvedScopedConfig,
        updatedAt: new Date().toISOString(),
        appDomain: normalizedAppDomain || null,
    }, _b)), _a));
}
function resolveAppConfigByOrigin(value, appOrigin) {
    var baseConfig = getBaseAppConfigRecord(value);
    var scopedConfig = resolveScopedAppConfig(value, appOrigin);
    if (!scopedConfig) {
        return baseConfig;
    }
    return deepMergeConfigRecords(baseConfig, scopedConfig);
}
function getAppConfig(appOrigin) {
    return __awaiter(this, void 0, void 0, function () {
        var row;
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, ensureAppConfigTable()];
                case 1:
                    _b.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.APP_CONFIG_TABLE).first()];
                case 2:
                    row = _b.sent();
                    if (!row) {
                        return [2 /*return*/, null];
                    }
                    if (typeof row.config_json === 'string') {
                        try {
                            return [2 /*return*/, resolveAppConfigByOrigin(JSON.parse(row.config_json), appOrigin)];
                        }
                        catch (_c) {
                            return [2 /*return*/, {}];
                        }
                    }
                    return [2 /*return*/, resolveAppConfigByOrigin((_a = row.config_json) !== null && _a !== void 0 ? _a : {}, appOrigin)];
            }
        });
    });
}
function normalizeConfigRecord(value) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return {};
    }
    return value;
}
function normalizeIntegerInRange(value, fallback, min, max) {
    if (typeof value !== 'number' || !Number.isFinite(value)) {
        return fallback;
    }
    return Math.min(max, Math.max(min, Math.round(value)));
}
function normalizeText(value) {
    return typeof value === 'string' ? value.trim() : '';
}
function normalizePermissions(value) {
    if (!Array.isArray(value)) {
        return ['token-user'];
    }
    var permissions = Array.from(new Set(value.filter(function (item) { return item === 'guest' || item === 'token-user'; })));
    return permissions.length > 0 ? permissions : ['token-user'];
}
function normalizePositiveSortOrder(value) {
    if (typeof value !== 'number' || !Number.isFinite(value)) {
        return Number.NaN;
    }
    var nextValue = Math.trunc(value);
    return nextValue > 0 ? nextValue : Number.NaN;
}
function normalizeChatTypeRecord(value) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return null;
    }
    var record = value;
    var name = normalizeText(record.name);
    if (!name) {
        return null;
    }
    return {
        id: normalizeText(record.id) || "chat-type-".concat(Date.now().toString(36), "-").concat(Math.random().toString(36).slice(2, 8)),
        name: name,
        sortOrder: normalizePositiveSortOrder(record.sortOrder),
        description: normalizeText(record.description),
        permissions: normalizePermissions(record.permissions),
        enabled: record.enabled !== false,
        updatedAt: normalizeText(record.updatedAt) || new Date().toISOString(),
    };
}
function buildChatTypeSemanticKey(record) {
    return record.name.replace(/\s+/g, ' ').toLocaleLowerCase('ko-KR');
}
function compareUpdatedAt(left, right) {
    var leftTime = Date.parse(left.updatedAt);
    var rightTime = Date.parse(right.updatedAt);
    if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
        return leftTime - rightTime;
    }
    return 0;
}
function sanitizeChatTypes(items) {
    var normalized = items
        .map(function (item) { return normalizeChatTypeRecord(item); })
        .filter(function (item) { return Boolean(item); });
    var byId = new Map();
    var bySemanticKey = new Map();
    for (var _i = 0, normalized_1 = normalized; _i < normalized_1.length; _i++) {
        var item = normalized_1[_i];
        var current = byId.get(item.id);
        if (!current || compareUpdatedAt(current, item) <= 0) {
            byId.set(item.id, item);
        }
    }
    for (var _a = 0, _b = byId.values(); _a < _b.length; _a++) {
        var item = _b[_a];
        var semanticKey = buildChatTypeSemanticKey(item);
        var current = bySemanticKey.get(semanticKey);
        if (!current || compareUpdatedAt(current, item) <= 0) {
            bySemanticKey.set(semanticKey, item);
        }
    }
    return Array.from(bySemanticKey.values())
        .sort(function (left, right) {
        var leftSortOrder = Number.isFinite(left.sortOrder) ? left.sortOrder : null;
        var rightSortOrder = Number.isFinite(right.sortOrder) ? right.sortOrder : null;
        if (leftSortOrder !== null && rightSortOrder !== null && leftSortOrder !== rightSortOrder) {
            return leftSortOrder - rightSortOrder;
        }
        if (leftSortOrder !== null && rightSortOrder === null) {
            return -1;
        }
        if (leftSortOrder === null && rightSortOrder !== null) {
            return 1;
        }
        var nameCompare = left.name.localeCompare(right.name, 'ko-KR');
        if (nameCompare !== 0) {
            return nameCompare;
        }
        return compareUpdatedAt(left, right);
    })
        .map(function (item, index) { return (__assign(__assign({}, item), { sortOrder: index + 1 })); });
}
function sanitizePersistedChatTypes(items) {
    return sanitizeChatTypes(items);
}
function isSameChatTypeList(left, right) {
    if (left.length !== right.length) {
        return false;
    }
    return left.every(function (item, index) {
        var target = right[index];
        return (target &&
            item.id === target.id &&
            item.name === target.name &&
            item.sortOrder === target.sortOrder &&
            item.description === target.description &&
            item.enabled === target.enabled &&
            item.updatedAt === target.updatedAt &&
            item.permissions.length === target.permissions.length &&
            item.permissions.every(function (permission, permissionIndex) { return permission === target.permissions[permissionIndex]; }));
    });
}
function normalizeAppConfigSnapshot(value) {
    var normalized = normalizeConfigRecord(value);
    var chat = normalizeConfigRecord(normalized.chat);
    var worklogAutomation = normalizeConfigRecord(normalized.worklogAutomation);
    return __assign(__assign({}, normalized), { chat: {
            maxContextMessages: normalizeIntegerInRange(chat.maxContextMessages, DEFAULT_CHAT_APP_CONFIG.maxContextMessages, 1, 50),
            maxContextChars: normalizeIntegerInRange(chat.maxContextChars, DEFAULT_CHAT_APP_CONFIG.maxContextChars, 500, 20000),
            codexLiveMaxExecutionSeconds: normalizeIntegerInRange(chat.codexLiveMaxExecutionSeconds, DEFAULT_CHAT_APP_CONFIG.codexLiveMaxExecutionSeconds, 60, 7200),
            codexLiveIdleTimeoutSeconds: normalizeIntegerInRange(chat.codexLiveIdleTimeoutSeconds, DEFAULT_CHAT_APP_CONFIG.codexLiveIdleTimeoutSeconds, 30, 3600),
            receiveRoomNotifications: typeof chat.receiveRoomNotifications === 'boolean'
                ? chat.receiveRoomNotifications
                : DEFAULT_CHAT_APP_CONFIG.receiveRoomNotifications,
        }, worklogAutomation: Object.keys(worklogAutomation).length > 0
            ? __assign(__assign({}, normalized.worklogAutomation), { repeatRequestEnabled: false }) : undefined });
}
function getAppConfigSnapshot(appOrigin) {
    return __awaiter(this, void 0, void 0, function () {
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    _a = normalizeAppConfigSnapshot;
                    return [4 /*yield*/, getAppConfig(appOrigin)];
                case 1: return [2 /*return*/, _a.apply(void 0, [_b.sent()])];
            }
        });
    });
}
function upsertAppConfig(config, appOrigin, appDomain) {
    return __awaiter(this, void 0, void 0, function () {
        var nextConfig, existing, initialConfig, rows_1, mergedConfig, rows;
        var _a, _b, _c, _d;
        return __generator(this, function (_e) {
            switch (_e.label) {
                case 0: return [4 /*yield*/, ensureAppConfigTable()];
                case 1:
                    _e.sent();
                    nextConfig = normalizeConfigRecord(config);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.APP_CONFIG_TABLE).first()];
                case 2:
                    existing = _e.sent();
                    if (!!existing) return [3 /*break*/, 4];
                    initialConfig = mergeScopedAppConfig({}, nextConfig, appOrigin, appDomain);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.APP_CONFIG_TABLE)
                            .insert({
                            config_json: initialConfig,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 3:
                    rows_1 = _e.sent();
                    return [2 /*return*/, resolveAppConfigByOrigin((_b = (_a = rows_1[0]) === null || _a === void 0 ? void 0 : _a.config_json) !== null && _b !== void 0 ? _b : initialConfig, appOrigin)];
                case 4:
                    mergedConfig = mergeScopedAppConfig(normalizeConfigRecord(existing.config_json), nextConfig, appOrigin, appDomain);
                    return [4 /*yield*/, (0, client_js_1.db)(exports.APP_CONFIG_TABLE)
                            .update({
                            config_json: mergedConfig,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .returning('*')];
                case 5:
                    rows = _e.sent();
                    return [2 /*return*/, resolveAppConfigByOrigin((_d = (_c = rows[0]) === null || _c === void 0 ? void 0 : _c.config_json) !== null && _d !== void 0 ? _d : mergedConfig, appOrigin)];
            }
        });
    });
}
function getChatTypesConfig(appOrigin) {
    return __awaiter(this, void 0, void 0, function () {
        var config, normalized, chatTypes, savedChatTypes, resolvedChatTypes;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, getAppConfig(appOrigin)];
                case 1:
                    config = _b.sent();
                    normalized = normalizeConfigRecord(config);
                    chatTypes = normalized[CHAT_TYPES_CONFIG_KEY];
                    if (chatTypes == null) {
                        return [2 /*return*/, null];
                    }
                    savedChatTypes = Array.isArray(chatTypes) ? sanitizeChatTypes(chatTypes) : [];
                    resolvedChatTypes = sanitizePersistedChatTypes(savedChatTypes);
                    return [2 /*return*/, {
                            chatTypes: resolvedChatTypes,
                        }];
            }
        });
    });
}
function upsertChatTypesConfig(chatTypes, appOrigin, appDomain) {
    return __awaiter(this, void 0, void 0, function () {
        var current, _a, resolvedChatTypes, nextConfig;
        var _b;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    _a = normalizeConfigRecord;
                    return [4 /*yield*/, getAppConfig(appOrigin)];
                case 1:
                    current = _a.apply(void 0, [_c.sent()]);
                    resolvedChatTypes = sanitizePersistedChatTypes(chatTypes);
                    nextConfig = __assign(__assign({}, current), (_b = {}, _b[CHAT_TYPES_CONFIG_KEY] = resolvedChatTypes, _b));
                    return [4 /*yield*/, upsertAppConfig(nextConfig, appOrigin, appDomain)];
                case 2:
                    _c.sent();
                    return [2 /*return*/, {
                            chatTypes: resolvedChatTypes,
                        }];
            }
        });
    });
}
