"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.sendIosNotificationSchema = exports.unregisterWebPushSubscriptionSchema = exports.registerWebPushSubscriptionSchema = exports.unregisterIosTokenSchema = exports.registerIosTokenSchema = exports.registerAutomationNotificationPreferenceSchema = exports.NOTIFICATION_PREFERENCE_TABLE = exports.WEB_PUSH_SUBSCRIPTION_TABLE = exports.NOTIFICATION_TOKEN_TABLE = void 0;
exports.setupNotificationTables = setupNotificationTables;
exports.getWebPushConfig = getWebPushConfig;
exports.listIosNotificationTokens = listIosNotificationTokens;
exports.listWebPushSubscriptions = listWebPushSubscriptions;
exports.registerIosNotificationToken = registerIosNotificationToken;
exports.getAutomationNotificationPreference = getAutomationNotificationPreference;
exports.upsertAutomationNotificationPreference = upsertAutomationNotificationPreference;
exports.unregisterIosNotificationToken = unregisterIosNotificationToken;
exports.registerWebPushSubscription = registerWebPushSubscription;
exports.unregisterWebPushSubscription = unregisterWebPushSubscription;
exports.sendIosNotifications = sendIosNotifications;
exports.sendNotifications = sendNotifications;
exports.resolveNotificationAggregateResult = resolveNotificationAggregateResult;
exports.shutdownNotificationProvider = shutdownNotificationProvider;
var promises_1 = require("node:fs/promises");
var node_apn_1 = require("@parse/node-apn");
var web_push_1 = require("web-push");
var zod_1 = require("zod");
var env_js_1 = require("../config/env.js");
var client_js_1 = require("../db/client.js");
var notification_message_service_js_1 = require("./notification-message-service.js");
exports.NOTIFICATION_TOKEN_TABLE = 'notification_tokens';
exports.WEB_PUSH_SUBSCRIPTION_TABLE = 'web_push_subscriptions';
exports.NOTIFICATION_PREFERENCE_TABLE = 'notification_preferences';
var automationNotificationPreferenceSchema = zod_1.z.object({
    notifyOnAutomationStart: zod_1.z.boolean().optional(),
    notifyOnAutomationProgress: zod_1.z.boolean().optional(),
    notifyOnAutomationCompletion: zod_1.z.boolean().optional(),
    notifyOnAutomationRelease: zod_1.z.boolean().optional(),
    notifyOnAutomationMain: zod_1.z.boolean().optional(),
    notifyOnAutomationFailure: zod_1.z.boolean().optional(),
    notifyOnAutomationRestart: zod_1.z.boolean().optional(),
    notifyOnAutomationIssueResolved: zod_1.z.boolean().optional(),
});
var notificationTargetKindSchema = zod_1.z.enum(['client', 'ios-token', 'ios-token-client', 'web-endpoint']);
exports.registerAutomationNotificationPreferenceSchema = zod_1.z.object({
    targetKind: notificationTargetKindSchema.default('client'),
    targetId: zod_1.z.string().trim().min(1).max(1000).optional(),
    automation: automationNotificationPreferenceSchema,
});
exports.registerIosTokenSchema = zod_1.z.object({
    token: zod_1.z.string().trim().min(1),
    deviceId: zod_1.z.string().trim().min(1).max(200).optional(),
    appOrigin: zod_1.z.string().trim().url().max(500).optional(),
    appDomain: zod_1.z.string().trim().min(1).max(255).optional(),
    enabled: zod_1.z.boolean().default(true),
});
exports.unregisterIosTokenSchema = zod_1.z.object({
    token: zod_1.z.string().trim().min(1),
});
exports.registerWebPushSubscriptionSchema = zod_1.z.object({
    subscription: zod_1.z.object({
        endpoint: zod_1.z.string().trim().url(),
        expirationTime: zod_1.z.number().nullable().optional(),
        keys: zod_1.z.object({
            p256dh: zod_1.z.string().trim().min(1),
            auth: zod_1.z.string().trim().min(1),
        }),
    }),
    deviceId: zod_1.z.string().trim().min(1).max(200).optional(),
    userAgent: zod_1.z.string().trim().max(500).optional(),
    appOrigin: zod_1.z.string().trim().url().max(500).optional(),
    appDomain: zod_1.z.string().trim().min(1).max(255).optional(),
    enabled: zod_1.z.boolean().default(true),
});
exports.unregisterWebPushSubscriptionSchema = zod_1.z.object({
    endpoint: zod_1.z.string().trim().url(),
});
exports.sendIosNotificationSchema = zod_1.z.object({
    title: zod_1.z.string().trim().min(1),
    body: zod_1.z.string().trim().min(1),
    data: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
    threadId: zod_1.z.string().trim().min(1).optional(),
    targetClientIds: zod_1.z.array(zod_1.z.string().trim().min(1).max(200)).max(50).optional(),
    targetAppOrigins: zod_1.z.array(zod_1.z.string().trim().url().max(500)).max(50).optional(),
    targetAppDomains: zod_1.z.array(zod_1.z.string().trim().min(1).max(255)).max(50).optional(),
});
function normalizeTargetClientIds(targetClientIds) {
    return __spreadArray([], new Set((targetClientIds !== null && targetClientIds !== void 0 ? targetClientIds : []).map(function (value) { return String(value !== null && value !== void 0 ? value : '').trim(); }).filter(Boolean)), true);
}
function normalizeTargetAppOrigins(targetAppOrigins) {
    return __spreadArray([], new Set((targetAppOrigins !== null && targetAppOrigins !== void 0 ? targetAppOrigins : []).map(function (value) { return normalizeAppOrigin(value); }).filter(Boolean)), true);
}
function normalizeTargetAppDomains(targetAppDomains) {
    return __spreadArray([], new Set((targetAppDomains !== null && targetAppDomains !== void 0 ? targetAppDomains : []).map(function (value) { return normalizeAppDomain(value); }).filter(Boolean)), true);
}
function isAllowedTargetClientId(deviceId, targetClientIds) {
    if (targetClientIds.length === 0) {
        return true;
    }
    return Boolean(deviceId) && targetClientIds.includes(deviceId);
}
function normalizeAppOrigin(value) {
    var normalized = String(value !== null && value !== void 0 ? value : '').trim();
    if (!normalized) {
        return '';
    }
    try {
        var url = new URL(normalized);
        return url.origin;
    }
    catch (_a) {
        return '';
    }
}
function normalizeAppDomain(value) {
    return String(value !== null && value !== void 0 ? value : '').trim().toLowerCase();
}
function resolveAppDomainFromOrigin(origin) {
    if (!origin) {
        return '';
    }
    try {
        return new URL(origin).hostname.trim().toLowerCase();
    }
    catch (_a) {
        return '';
    }
}
function isAllowedAppTarget(item, targetAppOrigins, targetAppDomains) {
    if (targetAppOrigins.length === 0 && targetAppDomains.length === 0) {
        return true;
    }
    if (targetAppOrigins.length > 0) {
        if (!item.appOrigin || !targetAppOrigins.includes(item.appOrigin)) {
            return false;
        }
    }
    if (targetAppDomains.length > 0) {
        if (!item.appDomain || !targetAppDomains.includes(item.appDomain)) {
            return false;
        }
    }
    return true;
}
function buildScopedPwaNotificationTargetId(token, clientId) {
    return [token.trim(), clientId.trim()].filter(Boolean).join('::client::');
}
var providerPromise = null;
var providerSignature = null;
function normalizePrivateKey(privateKey) {
    return privateKey.replace(/\\n/g, '\n');
}
function loadPrivateKey(env) {
    return __awaiter(this, void 0, void 0, function () {
        var file;
        var _a, _b;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    if ((_a = env.APNS_PRIVATE_KEY) === null || _a === void 0 ? void 0 : _a.trim()) {
                        return [2 /*return*/, normalizePrivateKey(env.APNS_PRIVATE_KEY.trim())];
                    }
                    if (!((_b = env.APNS_PRIVATE_KEY_PATH) === null || _b === void 0 ? void 0 : _b.trim())) return [3 /*break*/, 2];
                    return [4 /*yield*/, (0, promises_1.readFile)(env.APNS_PRIVATE_KEY_PATH.trim(), 'utf8')];
                case 1:
                    file = _c.sent();
                    return [2 /*return*/, file.trim()];
                case 2: return [2 /*return*/, null];
            }
        });
    });
}
function hasWebPushConfig(env) {
    var _a, _b, _c;
    return Boolean(env.WEB_PUSH_ENABLED &&
        ((_a = env.WEB_PUSH_VAPID_PUBLIC_KEY) === null || _a === void 0 ? void 0 : _a.trim()) &&
        ((_b = env.WEB_PUSH_VAPID_PRIVATE_KEY) === null || _b === void 0 ? void 0 : _b.trim()) &&
        ((_c = env.WEB_PUSH_SUBJECT) === null || _c === void 0 ? void 0 : _c.trim()));
}
function ensureWebPushConfigured(env) {
    if (!hasWebPushConfig(env)) {
        return false;
    }
    web_push_1.default.setVapidDetails(env.WEB_PUSH_SUBJECT, env.WEB_PUSH_VAPID_PUBLIC_KEY.trim(), env.WEB_PUSH_VAPID_PRIVATE_KEY.trim());
    return true;
}
function normalizeNotificationDetailText(text) {
    var normalized = String(text !== null && text !== void 0 ? text : '').trim();
    return normalized || undefined;
}
function isChatNotificationPayload(payload) {
    var _a, _b, _c;
    var category = String((_b = (_a = payload.data) === null || _a === void 0 ? void 0 : _a.category) !== null && _b !== void 0 ? _b : '').trim().toLowerCase();
    var threadId = String((_c = payload.threadId) !== null && _c !== void 0 ? _c : '').trim().toLowerCase();
    return category === 'chat' || threadId.startsWith('chat:');
}
function isRetryableWebPushError(error) {
    var _a, _b;
    var statusCode = Number((_a = error === null || error === void 0 ? void 0 : error.statusCode) !== null && _a !== void 0 ? _a : 0);
    if ([408, 425, 429, 500, 502, 503, 504].includes(statusCode)) {
        return true;
    }
    var code = String((_b = error === null || error === void 0 ? void 0 : error.code) !== null && _b !== void 0 ? _b : '').trim().toUpperCase();
    return ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'EAI_AGAIN', 'UND_ERR_CONNECT_TIMEOUT'].includes(code);
}
function waitForRetry(delayMs) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, delayMs); })];
                case 1:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function sendWebPushWithRetry(subscription, payloadText) {
    return __awaiter(this, void 0, void 0, function () {
        var lastError, attempt, error_1;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    attempt = 1;
                    _a.label = 1;
                case 1:
                    if (!(attempt <= 2)) return [3 /*break*/, 7];
                    _a.label = 2;
                case 2:
                    _a.trys.push([2, 4, , 6]);
                    return [4 /*yield*/, web_push_1.default.sendNotification(subscription, payloadText)];
                case 3:
                    _a.sent();
                    return [2 /*return*/, { attemptCount: attempt }];
                case 4:
                    error_1 = _a.sent();
                    lastError = error_1;
                    if (attempt >= 2 || !isRetryableWebPushError(error_1)) {
                        throw {
                            error: error_1,
                            attemptCount: attempt,
                        };
                    }
                    return [4 /*yield*/, waitForRetry(250 * attempt)];
                case 5:
                    _a.sent();
                    return [3 /*break*/, 6];
                case 6:
                    attempt += 1;
                    return [3 /*break*/, 1];
                case 7: throw {
                    error: lastError,
                    attemptCount: 2,
                };
            }
        });
    });
}
function buildProviderSignature(env) {
    return [
        env.IOS_NOTIFICATION_ENABLED,
        env.APNS_KEY_ID,
        env.APNS_TEAM_ID,
        env.APNS_BUNDLE_ID,
        env.APNS_PRIVATE_KEY,
        env.APNS_PRIVATE_KEY_PATH,
        env.APNS_PRODUCTION,
    ].join('|');
}
function createProvider(env) {
    return __awaiter(this, void 0, void 0, function () {
        var privateKey;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    if (!env.IOS_NOTIFICATION_ENABLED ||
                        !env.APNS_KEY_ID ||
                        !env.APNS_TEAM_ID ||
                        !env.APNS_BUNDLE_ID) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, loadPrivateKey(env)];
                case 1:
                    privateKey = _a.sent();
                    if (!privateKey) {
                        return [2 /*return*/, null];
                    }
                    return [2 /*return*/, new node_apn_1.Provider({
                            token: {
                                key: privateKey,
                                keyId: env.APNS_KEY_ID,
                                teamId: env.APNS_TEAM_ID,
                            },
                            production: env.APNS_PRODUCTION,
                        })];
            }
        });
    });
}
function getProvider() {
    return __awaiter(this, void 0, void 0, function () {
        var env, signature;
        return __generator(this, function (_a) {
            env = (0, env_js_1.getEnv)();
            signature = buildProviderSignature(env);
            if (signature !== providerSignature) {
                providerSignature = signature;
                providerPromise = null;
            }
            if (!providerPromise) {
                providerPromise = createProvider(env);
            }
            return [2 /*return*/, providerPromise];
        });
    });
}
function ensureNotificationTokenTable() {
    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.NOTIFICATION_TOKEN_TABLE)];
                case 1:
                    hasTable = _b.sent();
                    if (!!hasTable) return [3 /*break*/, 3];
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.NOTIFICATION_TOKEN_TABLE, function (table) {
                            table.increments('id').primary();
                            table.string('platform', 20).notNullable().defaultTo('ios');
                            table.string('device_token', 255).notNullable().unique();
                            table.string('device_id', 200).nullable();
                            table.string('app_origin', 500).nullable();
                            table.string('app_domain', 255).nullable();
                            table.boolean('is_enabled').notNullable().defaultTo(true);
                            table.timestamp('last_registered_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                            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 = [
                        ['platform', function (table) { return table.string('platform', 20).notNullable().defaultTo('ios'); }],
                        ['device_token', function (table) { return table.string('device_token', 255).notNullable(); }],
                        ['device_id', function (table) { return table.string('device_id', 200).nullable(); }],
                        ['app_origin', function (table) { return table.string('app_origin', 500).nullable(); }],
                        ['app_domain', function (table) { return table.string('app_domain', 255).nullable(); }],
                        ['is_enabled', function (table) { return table.boolean('is_enabled').notNullable().defaultTo(true); }],
                        [
                            'last_registered_at',
                            function (table) { return table.timestamp('last_registered_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); },
                        ],
                        ['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.NOTIFICATION_TOKEN_TABLE, columnName)];
                                case 1:
                                    hasColumn = _c.sent();
                                    if (!!hasColumn) return [3 /*break*/, 3];
                                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.NOTIFICATION_TOKEN_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 ensureWebPushSubscriptionTable() {
    return __awaiter(this, void 0, void 0, function () {
        var hasTable, requiredColumns, _loop_2, _i, requiredColumns_2, _a, columnName, createColumn;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.WEB_PUSH_SUBSCRIPTION_TABLE)];
                case 1:
                    hasTable = _b.sent();
                    if (!!hasTable) return [3 /*break*/, 3];
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.WEB_PUSH_SUBSCRIPTION_TABLE, function (table) {
                            table.increments('id').primary();
                            table.string('endpoint', 1000).notNullable().unique();
                            table.jsonb('subscription_json').notNullable();
                            table.string('device_id', 200).nullable();
                            table.text('user_agent').nullable();
                            table.string('app_origin', 500).nullable();
                            table.string('app_domain', 255).nullable();
                            table.boolean('is_enabled').notNullable().defaultTo(true);
                            table.timestamp('last_registered_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now());
                            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 = [
                        ['endpoint', function (table) { return table.string('endpoint', 1000).notNullable(); }],
                        ['subscription_json', function (table) { return table.jsonb('subscription_json').notNullable().defaultTo('{}'); }],
                        ['device_id', function (table) { return table.string('device_id', 200).nullable(); }],
                        ['user_agent', function (table) { return table.text('user_agent').nullable(); }],
                        ['app_origin', function (table) { return table.string('app_origin', 500).nullable(); }],
                        ['app_domain', function (table) { return table.string('app_domain', 255).nullable(); }],
                        ['is_enabled', function (table) { return table.boolean('is_enabled').notNullable().defaultTo(true); }],
                        [
                            'last_registered_at',
                            function (table) { return table.timestamp('last_registered_at', { useTz: true }).notNullable().defaultTo(client_js_1.db.fn.now()); },
                        ],
                        ['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_2 = 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.WEB_PUSH_SUBSCRIPTION_TABLE, columnName)];
                                case 1:
                                    hasColumn = _c.sent();
                                    if (!!hasColumn) return [3 /*break*/, 3];
                                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.WEB_PUSH_SUBSCRIPTION_TABLE, function (table) {
                                            createColumn(table);
                                        })];
                                case 2:
                                    _c.sent();
                                    _c.label = 3;
                                case 3: return [2 /*return*/];
                            }
                        });
                    };
                    _i = 0, requiredColumns_2 = requiredColumns;
                    _b.label = 4;
                case 4:
                    if (!(_i < requiredColumns_2.length)) return [3 /*break*/, 7];
                    _a = requiredColumns_2[_i], columnName = _a[0], createColumn = _a[1];
                    return [5 /*yield**/, _loop_2(columnName, createColumn)];
                case 5:
                    _b.sent();
                    _b.label = 6;
                case 6:
                    _i++;
                    return [3 /*break*/, 4];
                case 7: return [2 /*return*/];
            }
        });
    });
}
function ensureNotificationPreferenceTable() {
    return __awaiter(this, void 0, void 0, function () {
        var hasTable, requiredColumns, _loop_3, _i, requiredColumns_3, _a, columnName, createColumn;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, client_js_1.db.schema.hasTable(exports.NOTIFICATION_PREFERENCE_TABLE)];
                case 1:
                    hasTable = _b.sent();
                    if (!!hasTable) return [3 /*break*/, 3];
                    return [4 /*yield*/, client_js_1.db.schema.createTable(exports.NOTIFICATION_PREFERENCE_TABLE, function (table) {
                            table.increments('id').primary();
                            table.string('target_kind', 40).notNullable();
                            table.string('target_id', 1000).notNullable();
                            table.jsonb('config_json').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());
                            table.unique(['target_kind', 'target_id']);
                        })];
                case 2:
                    _b.sent();
                    return [2 /*return*/];
                case 3:
                    requiredColumns = [
                        ['target_kind', function (table) { return table.string('target_kind', 40).notNullable().defaultTo('client'); }],
                        ['target_id', function (table) { return table.string('target_id', 1000).notNullable().defaultTo(''); }],
                        ['config_json', function (table) { return table.jsonb('config_json').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_3 = 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.NOTIFICATION_PREFERENCE_TABLE, columnName)];
                                case 1:
                                    hasColumn = _c.sent();
                                    if (!!hasColumn) return [3 /*break*/, 3];
                                    return [4 /*yield*/, client_js_1.db.schema.alterTable(exports.NOTIFICATION_PREFERENCE_TABLE, function (table) {
                                            createColumn(table);
                                        })];
                                case 2:
                                    _c.sent();
                                    _c.label = 3;
                                case 3: return [2 /*return*/];
                            }
                        });
                    };
                    _i = 0, requiredColumns_3 = requiredColumns;
                    _b.label = 4;
                case 4:
                    if (!(_i < requiredColumns_3.length)) return [3 /*break*/, 7];
                    _a = requiredColumns_3[_i], columnName = _a[0], createColumn = _a[1];
                    return [5 /*yield**/, _loop_3(columnName, createColumn)];
                case 5:
                    _b.sent();
                    _b.label = 6;
                case 6:
                    _i++;
                    return [3 /*break*/, 4];
                case 7: return [2 /*return*/];
            }
        });
    });
}
function setupNotificationTables() {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensureNotificationTokenTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, ensureWebPushSubscriptionTable()];
                case 2:
                    _a.sent();
                    return [4 /*yield*/, ensureNotificationPreferenceTable()];
                case 3:
                    _a.sent();
                    return [4 /*yield*/, (0, notification_message_service_js_1.ensureNotificationMessagesTable)()];
                case 4:
                    _a.sent();
                    return [2 /*return*/, {
                            ok: true,
                            tables: [exports.NOTIFICATION_TOKEN_TABLE, exports.WEB_PUSH_SUBSCRIPTION_TABLE, exports.NOTIFICATION_PREFERENCE_TABLE, 'notification_messages'],
                        }];
            }
        });
    });
}
function getWebPushConfig() {
    var env = (0, env_js_1.getEnv)();
    return {
        enabled: hasWebPushConfig(env),
        publicKey: hasWebPushConfig(env) ? env.WEB_PUSH_VAPID_PUBLIC_KEY.trim() : '',
    };
}
function listIosNotificationTokens() {
    return __awaiter(this, void 0, void 0, function () {
        var rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensureNotificationTokenTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_TOKEN_TABLE)
                            .where({ platform: 'ios' })
                            .orderBy('updated_at', 'desc')];
                case 2:
                    rows = _a.sent();
                    return [2 /*return*/, rows.map(function (row) { return ({
                            id: row.id,
                            platform: row.platform,
                            token: row.device_token,
                            deviceId: row.device_id,
                            appOrigin: row.app_origin ? String(row.app_origin) : '',
                            appDomain: row.app_domain ? String(row.app_domain) : '',
                            enabled: row.is_enabled,
                            lastRegisteredAt: row.last_registered_at,
                            createdAt: row.created_at,
                            updatedAt: row.updated_at,
                        }); })];
            }
        });
    });
}
function listWebPushSubscriptions() {
    return __awaiter(this, void 0, void 0, function () {
        var rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensureWebPushSubscriptionTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.WEB_PUSH_SUBSCRIPTION_TABLE).orderBy('updated_at', 'desc')];
                case 2:
                    rows = _a.sent();
                    return [2 /*return*/, rows.map(function (row) {
                            var _a;
                            return ({
                                id: row.id,
                                endpoint: String((_a = row.endpoint) !== null && _a !== void 0 ? _a : ''),
                                deviceId: row.device_id ? String(row.device_id) : '',
                                userAgent: row.user_agent ? String(row.user_agent) : '',
                                appOrigin: row.app_origin ? String(row.app_origin) : '',
                                appDomain: row.app_domain ? String(row.app_domain) : '',
                                enabled: Boolean(row.is_enabled),
                                lastRegisteredAt: row.last_registered_at,
                                createdAt: row.created_at,
                                updatedAt: row.updated_at,
                            });
                        })];
            }
        });
    });
}
function registerIosNotificationToken(payload) {
    return __awaiter(this, void 0, void 0, function () {
        var appOrigin, appDomain;
        var _a, _b;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0: return [4 /*yield*/, ensureNotificationTokenTable()];
                case 1:
                    _c.sent();
                    appOrigin = normalizeAppOrigin(payload.appOrigin);
                    appDomain = normalizeAppDomain(payload.appDomain) || resolveAppDomainFromOrigin(appOrigin);
                    if (!!payload.enabled) return [3 /*break*/, 3];
                    return [4 /*yield*/, unregisterIosNotificationToken(payload.token)];
                case 2:
                    _c.sent();
                    return [2 /*return*/, {
                            ok: true,
                            removed: true,
                            token: payload.token,
                        }];
                case 3: return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_TOKEN_TABLE)
                        .insert({
                        platform: 'ios',
                        device_token: payload.token,
                        device_id: (_a = payload.deviceId) !== null && _a !== void 0 ? _a : null,
                        app_origin: appOrigin || null,
                        app_domain: appDomain || null,
                        is_enabled: true,
                        last_registered_at: client_js_1.db.fn.now(),
                        updated_at: client_js_1.db.fn.now(),
                    })
                        .onConflict('device_token')
                        .merge({
                        platform: 'ios',
                        device_id: (_b = payload.deviceId) !== null && _b !== void 0 ? _b : null,
                        app_origin: appOrigin || null,
                        app_domain: appDomain || null,
                        is_enabled: true,
                        last_registered_at: client_js_1.db.fn.now(),
                        updated_at: client_js_1.db.fn.now(),
                    })];
                case 4:
                    _c.sent();
                    return [2 /*return*/, {
                            ok: true,
                            token: payload.token,
                        }];
            }
        });
    });
}
function parseAutomationNotificationPreference(raw) {
    if (typeof raw === 'string') {
        try {
            return automationNotificationPreferenceSchema.parse(JSON.parse(raw));
        }
        catch (_a) {
            return {};
        }
    }
    return automationNotificationPreferenceSchema.parse(raw !== null && raw !== void 0 ? raw : {});
}
function getAutomationNotificationPreference(targetId_1) {
    return __awaiter(this, arguments, void 0, function (targetId, targetKind) {
        var normalizedTargetId, row;
        if (targetKind === void 0) { targetKind = 'client'; }
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    normalizedTargetId = targetId.trim();
                    if (!normalizedTargetId) {
                        return [2 /*return*/, null];
                    }
                    return [4 /*yield*/, ensureNotificationPreferenceTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_PREFERENCE_TABLE)
                            .where({
                            target_kind: targetKind,
                            target_id: normalizedTargetId,
                        })
                            .first()];
                case 2:
                    row = _a.sent();
                    if (!row) {
                        return [2 /*return*/, null];
                    }
                    return [2 /*return*/, parseAutomationNotificationPreference(row.config_json)];
            }
        });
    });
}
function upsertAutomationNotificationPreference(payload) {
    return __awaiter(this, void 0, void 0, function () {
        var targetId;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    targetId = payload.targetId.trim();
                    return [4 /*yield*/, ensureNotificationPreferenceTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_PREFERENCE_TABLE)
                            .insert({
                            target_kind: payload.targetKind,
                            target_id: targetId,
                            config_json: payload.automation,
                            updated_at: client_js_1.db.fn.now(),
                        })
                            .onConflict(['target_kind', 'target_id'])
                            .merge({
                            config_json: payload.automation,
                            updated_at: client_js_1.db.fn.now(),
                        })];
                case 2:
                    _a.sent();
                    return [2 /*return*/, {
                            ok: true,
                            targetKind: payload.targetKind,
                            targetId: targetId,
                            automation: payload.automation,
                        }];
            }
        });
    });
}
function unregisterIosNotificationToken(token) {
    return __awaiter(this, void 0, void 0, function () {
        var deletedCount;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensureNotificationTokenTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_TOKEN_TABLE)
                            .where({
                            platform: 'ios',
                            device_token: token,
                        })
                            .delete()];
                case 2:
                    deletedCount = _a.sent();
                    return [2 /*return*/, {
                            ok: true,
                            removed: deletedCount > 0,
                            token: token,
                        }];
            }
        });
    });
}
function registerWebPushSubscription(payload) {
    return __awaiter(this, void 0, void 0, function () {
        var appOrigin, appDomain;
        var _a, _b, _c, _d, _e;
        return __generator(this, function (_f) {
            switch (_f.label) {
                case 0: return [4 /*yield*/, ensureWebPushSubscriptionTable()];
                case 1:
                    _f.sent();
                    appOrigin = normalizeAppOrigin(payload.appOrigin);
                    appDomain = normalizeAppDomain(payload.appDomain) || resolveAppDomainFromOrigin(appOrigin);
                    if (!!payload.enabled) return [3 /*break*/, 3];
                    return [4 /*yield*/, unregisterWebPushSubscription(payload.subscription.endpoint)];
                case 2:
                    _f.sent();
                    return [2 /*return*/, {
                            ok: true,
                            removed: true,
                            endpoint: payload.subscription.endpoint,
                        }];
                case 3: return [4 /*yield*/, (0, client_js_1.db)(exports.WEB_PUSH_SUBSCRIPTION_TABLE)
                        .insert({
                        endpoint: payload.subscription.endpoint,
                        subscription_json: payload.subscription,
                        device_id: (_a = payload.deviceId) !== null && _a !== void 0 ? _a : null,
                        user_agent: (_b = payload.userAgent) !== null && _b !== void 0 ? _b : null,
                        app_origin: appOrigin || null,
                        app_domain: appDomain || null,
                        is_enabled: true,
                        last_registered_at: client_js_1.db.fn.now(),
                        updated_at: client_js_1.db.fn.now(),
                    })
                        .onConflict('endpoint')
                        .merge({
                        subscription_json: payload.subscription,
                        device_id: (_c = payload.deviceId) !== null && _c !== void 0 ? _c : null,
                        user_agent: (_d = payload.userAgent) !== null && _d !== void 0 ? _d : null,
                        app_origin: appOrigin || null,
                        app_domain: appDomain || null,
                        is_enabled: true,
                        last_registered_at: client_js_1.db.fn.now(),
                        updated_at: client_js_1.db.fn.now(),
                    })];
                case 4:
                    _f.sent();
                    if (!((_e = payload.deviceId) === null || _e === void 0 ? void 0 : _e.trim())) return [3 /*break*/, 6];
                    return [4 /*yield*/, (0, client_js_1.db)(exports.WEB_PUSH_SUBSCRIPTION_TABLE)
                            .where({ device_id: payload.deviceId.trim() })
                            .whereNot({ endpoint: payload.subscription.endpoint })
                            .delete()];
                case 5:
                    _f.sent();
                    _f.label = 6;
                case 6: return [2 /*return*/, {
                        ok: true,
                        endpoint: payload.subscription.endpoint,
                    }];
            }
        });
    });
}
function unregisterWebPushSubscription(endpoint) {
    return __awaiter(this, void 0, void 0, function () {
        var deletedCount;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensureWebPushSubscriptionTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.WEB_PUSH_SUBSCRIPTION_TABLE).where({ endpoint: endpoint }).delete()];
                case 2:
                    deletedCount = _a.sent();
                    return [2 /*return*/, {
                            ok: true,
                            removed: deletedCount > 0,
                            endpoint: endpoint,
                        }];
            }
        });
    });
}
function getEnabledIosTokens() {
    return __awaiter(this, void 0, void 0, function () {
        var rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensureNotificationTokenTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_TOKEN_TABLE)
                            .where({
                            platform: 'ios',
                            is_enabled: true,
                        })
                            .select('device_token', 'device_id', 'app_origin', 'app_domain')];
                case 2:
                    rows = _a.sent();
                    return [2 /*return*/, rows.map(function (row) { return ({
                            token: String(row.device_token),
                            deviceId: row.device_id ? String(row.device_id) : '',
                            appOrigin: row.app_origin ? String(row.app_origin) : '',
                            appDomain: row.app_domain ? String(row.app_domain) : '',
                        }); })];
            }
        });
    });
}
function getEnabledWebPushSubscriptions() {
    return __awaiter(this, void 0, void 0, function () {
        var rows;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, ensureWebPushSubscriptionTable()];
                case 1:
                    _a.sent();
                    return [4 /*yield*/, (0, client_js_1.db)(exports.WEB_PUSH_SUBSCRIPTION_TABLE)
                            .where({
                            is_enabled: true,
                        })
                            .select('endpoint', 'subscription_json', 'device_id', 'app_origin', 'app_domain')];
                case 2:
                    rows = _a.sent();
                    return [2 /*return*/, rows.map(function (row) { return ({
                            endpoint: String(row.endpoint),
                            subscription: row.subscription_json,
                            deviceId: row.device_id ? String(row.device_id) : '',
                            appOrigin: row.app_origin ? String(row.app_origin) : '',
                            appDomain: row.app_domain ? String(row.app_domain) : '',
                        }); })];
            }
        });
    });
}
function removeInvalidIosTokens(tokens) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    if (!tokens.length) {
                        return [2 /*return*/];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.NOTIFICATION_TOKEN_TABLE)
                            .whereIn('device_token', tokens)
                            .delete()];
                case 1:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function removeInvalidWebPushSubscriptions(endpoints) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    if (!endpoints.length) {
                        return [2 /*return*/];
                    }
                    return [4 /*yield*/, (0, client_js_1.db)(exports.WEB_PUSH_SUBSCRIPTION_TABLE)
                            .whereIn('endpoint', endpoints)
                            .delete()];
                case 1:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}
function shouldNotifyAutomationEvent(automation, eventType) {
    var _a, _b, _c, _d, _e, _f, _g, _h;
    if (eventType === 'work-started') {
        return (_a = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationStart) !== null && _a !== void 0 ? _a : true;
    }
    if (eventType === 'work-progress') {
        return (_b = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationProgress) !== null && _b !== void 0 ? _b : true;
    }
    if (eventType === 'work-completed' ||
        eventType === 'work-noop-complete' ||
        eventType === 'development-completed' ||
        eventType === 'plan-completed') {
        return (_c = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationCompletion) !== null && _c !== void 0 ? _c : true;
    }
    if (eventType === 'release-merged') {
        return (_d = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationRelease) !== null && _d !== void 0 ? _d : true;
    }
    if (eventType === 'main-merged') {
        return (_e = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationMain) !== null && _e !== void 0 ? _e : true;
    }
    if (eventType === 'branch-failed' ||
        eventType === 'work-failed' ||
        eventType === 'release-failed' ||
        eventType === 'main-failed') {
        return (_f = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationFailure) !== null && _f !== void 0 ? _f : true;
    }
    if (eventType === 'plan-restarted') {
        return (_g = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationRestart) !== null && _g !== void 0 ? _g : true;
    }
    if (eventType === 'issue-resolved') {
        return (_h = automation === null || automation === void 0 ? void 0 : automation.notifyOnAutomationIssueResolved) !== null && _h !== void 0 ? _h : true;
    }
    return true;
}
function isNotificationRecipientAllowed(preferenceTargets, payload) {
    return __awaiter(this, void 0, void 0, function () {
        var eventType, _i, preferenceTargets_1, target, automation;
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    eventType = (_a = payload.data.eventType) === null || _a === void 0 ? void 0 : _a.trim();
                    if (!eventType) {
                        return [2 /*return*/, true];
                    }
                    _i = 0, preferenceTargets_1 = preferenceTargets;
                    _b.label = 1;
                case 1:
                    if (!(_i < preferenceTargets_1.length)) return [3 /*break*/, 4];
                    target = preferenceTargets_1[_i];
                    if (!target.id.trim()) {
                        return [3 /*break*/, 3];
                    }
                    return [4 /*yield*/, getAutomationNotificationPreference(target.id, target.kind)];
                case 2:
                    automation = _b.sent();
                    if (automation) {
                        return [2 /*return*/, shouldNotifyAutomationEvent(automation, eventType)];
                    }
                    _b.label = 3;
                case 3:
                    _i++;
                    return [3 /*break*/, 1];
                case 4: return [2 /*return*/, true];
            }
        });
    });
}
function sendIosNotifications(payload) {
    return __awaiter(this, void 0, void 0, function () {
        var env, provider, targetClientIds, targetAppOrigins, targetAppDomains, tokenRows, tokens, notification, response, invalidTokens;
        var _this = this;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    env = (0, env_js_1.getEnv)();
                    return [4 /*yield*/, getProvider()];
                case 1:
                    provider = _a.sent();
                    targetClientIds = normalizeTargetClientIds(payload.targetClientIds);
                    targetAppOrigins = normalizeTargetAppOrigins(payload.targetAppOrigins);
                    targetAppDomains = normalizeTargetAppDomains(payload.targetAppDomains);
                    if (!provider || !env.APNS_BUNDLE_ID) {
                        return [2 /*return*/, {
                                ok: false,
                                skipped: true,
                                reason: 'APNs 설정이 비어 있습니다.',
                                sentCount: 0,
                                failedCount: 0,
                            }];
                    }
                    return [4 /*yield*/, getEnabledIosTokens()];
                case 2:
                    tokenRows = _a.sent();
                    return [4 /*yield*/, Promise.all(tokenRows.map(function (row) { return __awaiter(_this, void 0, void 0, function () {
                            var _a;
                            return __generator(this, function (_b) {
                                switch (_b.label) {
                                    case 0:
                                        _a = {
                                            token: row.token,
                                            deviceId: row.deviceId,
                                            appOrigin: row.appOrigin,
                                            appDomain: row.appDomain
                                        };
                                        return [4 /*yield*/, isNotificationRecipientAllowed([
                                                { kind: 'ios-token-client', id: buildScopedPwaNotificationTargetId(row.token, row.deviceId) },
                                                { kind: 'ios-token', id: row.token },
                                                { kind: 'client', id: row.deviceId },
                                            ], payload)];
                                    case 1: return [2 /*return*/, (_a.allowed = _b.sent(),
                                            _a)];
                                }
                            });
                        }); }))];
                case 3:
                    tokens = (_a.sent())
                        .filter(function (row) {
                        return row.allowed &&
                            isAllowedTargetClientId(row.deviceId, targetClientIds) &&
                            isAllowedAppTarget(row, targetAppOrigins, targetAppDomains);
                    })
                        .map(function (row) { return row.token; });
                    if (!tokens.length) {
                        return [2 /*return*/, {
                                ok: true,
                                skipped: true,
                                reason: '등록된 iOS 알림 토큰이 없습니다.',
                                sentCount: 0,
                                failedCount: 0,
                            }];
                    }
                    notification = new node_apn_1.Notification();
                    notification.topic = env.APNS_BUNDLE_ID;
                    notification.pushType = 'alert';
                    notification.priority = 10;
                    notification.expiry = Math.floor(Date.now() / 1000) + 3600;
                    notification.alert = {
                        title: payload.title,
                        body: payload.body,
                    };
                    notification.sound = 'default';
                    notification.badge = 1;
                    notification.payload = payload.data;
                    if (payload.threadId) {
                        notification.threadId = payload.threadId;
                    }
                    return [4 /*yield*/, provider.send(notification, tokens)];
                case 4:
                    response = _a.sent();
                    invalidTokens = response.failed
                        .map(function (result) { return result.device; })
                        .filter(function (device) { return Boolean(device); });
                    return [4 /*yield*/, removeInvalidIosTokens(invalidTokens)];
                case 5:
                    _a.sent();
                    return [2 /*return*/, {
                            ok: response.failed.length === 0,
                            skipped: false,
                            sentCount: response.sent.length,
                            failedCount: response.failed.length,
                            invalidTokens: invalidTokens,
                        }];
            }
        });
    });
}
function sendWebPushNotifications(payload) {
    return __awaiter(this, void 0, void 0, function () {
        var env, targetClientIds, targetAppOrigins, targetAppDomains, subscriptions, _a, _b, payloadText, preserveSubscriptions, invalidEndpoints, sentCount, failedCount, failures;
        var _this = this;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    env = (0, env_js_1.getEnv)();
                    targetClientIds = normalizeTargetClientIds(payload.targetClientIds);
                    targetAppOrigins = normalizeTargetAppOrigins(payload.targetAppOrigins);
                    targetAppDomains = normalizeTargetAppDomains(payload.targetAppDomains);
                    if (!ensureWebPushConfigured(env)) {
                        return [2 /*return*/, {
                                ok: false,
                                skipped: true,
                                reason: 'Web Push 설정이 비어 있습니다.',
                                sentCount: 0,
                                failedCount: 0,
                            }];
                    }
                    _b = (_a = Promise).all;
                    return [4 /*yield*/, getEnabledWebPushSubscriptions()];
                case 1: return [4 /*yield*/, _b.apply(_a, [(_c.sent()).map(function (row) { return __awaiter(_this, void 0, void 0, function () {
                            var _a;
                            var _b;
                            return __generator(this, function (_c) {
                                switch (_c.label) {
                                    case 0:
                                        _a = [__assign({}, row)];
                                        _b = {};
                                        return [4 /*yield*/, isNotificationRecipientAllowed([
                                                { kind: 'web-endpoint', id: row.endpoint },
                                                { kind: 'client', id: row.deviceId },
                                            ], payload)];
                                    case 1: return [2 /*return*/, (__assign.apply(void 0, _a.concat([(_b.allowed = _c.sent(), _b)])))];
                                }
                            });
                        }); })])];
                case 2:
                    subscriptions = (_c.sent()).filter(function (row) {
                        return row.allowed &&
                            isAllowedTargetClientId(row.deviceId, targetClientIds) &&
                            isAllowedAppTarget(row, targetAppOrigins, targetAppDomains);
                    });
                    if (!subscriptions.length) {
                        return [2 /*return*/, {
                                ok: true,
                                skipped: true,
                                reason: '등록된 Web Push 구독이 없습니다.',
                                sentCount: 0,
                                failedCount: 0,
                            }];
                    }
                    payloadText = JSON.stringify({
                        title: payload.title,
                        body: payload.body,
                        data: payload.data,
                        threadId: payload.threadId,
                    });
                    preserveSubscriptions = isChatNotificationPayload(payload);
                    invalidEndpoints = [];
                    sentCount = 0;
                    failedCount = 0;
                    failures = [];
                    return [4 /*yield*/, Promise.all(subscriptions.map(function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
                            var error_2, deliveryError, attemptCount, statusCode, detail, code;
                            var _c, _d, _e, _f;
                            var endpoint = _b.endpoint, subscription = _b.subscription;
                            return __generator(this, function (_g) {
                                switch (_g.label) {
                                    case 0:
                                        _g.trys.push([0, 2, , 3]);
                                        return [4 /*yield*/, sendWebPushWithRetry(subscription, payloadText)];
                                    case 1:
                                        _g.sent();
                                        sentCount += 1;
                                        return [3 /*break*/, 3];
                                    case 2:
                                        error_2 = _g.sent();
                                        deliveryError = (_c = error_2 === null || error_2 === void 0 ? void 0 : error_2.error) !== null && _c !== void 0 ? _c : error_2;
                                        attemptCount = Number((_d = error_2 === null || error_2 === void 0 ? void 0 : error_2.attemptCount) !== null && _d !== void 0 ? _d : 1);
                                        failedCount += 1;
                                        statusCode = Number((_e = deliveryError === null || deliveryError === void 0 ? void 0 : deliveryError.statusCode) !== null && _e !== void 0 ? _e : 0);
                                        detail = (_f = normalizeNotificationDetailText(deliveryError === null || deliveryError === void 0 ? void 0 : deliveryError.body)) !== null && _f !== void 0 ? _f : normalizeNotificationDetailText(deliveryError === null || deliveryError === void 0 ? void 0 : deliveryError.message);
                                        code = normalizeNotificationDetailText(deliveryError === null || deliveryError === void 0 ? void 0 : deliveryError.code);
                                        if (!preserveSubscriptions && (statusCode === 404 || statusCode === 410)) {
                                            invalidEndpoints.push(endpoint);
                                        }
                                        failures.push({
                                            endpoint: endpoint,
                                            statusCode: statusCode || undefined,
                                            detail: detail,
                                            code: code,
                                            attemptCount: attemptCount,
                                        });
                                        return [3 /*break*/, 3];
                                    case 3: return [2 /*return*/];
                                }
                            });
                        }); }))];
                case 3:
                    _c.sent();
                    if (!!preserveSubscriptions) return [3 /*break*/, 5];
                    return [4 /*yield*/, removeInvalidWebPushSubscriptions(invalidEndpoints)];
                case 4:
                    _c.sent();
                    _c.label = 5;
                case 5:
                    if (failures.length) {
                        console.warn('[notification-service] web push delivery failed', JSON.stringify({
                            failedCount: failedCount,
                            preserveSubscriptions: preserveSubscriptions,
                            invalidEndpointCount: invalidEndpoints.length,
                            failures: failures,
                        }));
                    }
                    return [2 /*return*/, {
                            ok: failedCount === 0,
                            skipped: false,
                            sentCount: sentCount,
                            failedCount: failedCount,
                            invalidEndpoints: invalidEndpoints,
                        }];
            }
        });
    });
}
function sendNotifications(payload, options) {
    return __awaiter(this, void 0, void 0, function () {
        var _a, ios, web, aggregate;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0: return [4 /*yield*/, Promise.all([
                        (options === null || options === void 0 ? void 0 : options.disableIos)
                            ? Promise.resolve({
                                ok: true,
                                skipped: true,
                                reason: '요청 설정에 따라 iOS 알림 발송을 건너뛰었습니다.',
                                sentCount: 0,
                                failedCount: 0,
                                invalidTokens: [],
                            })
                            : sendIosNotifications(payload),
                        (options === null || options === void 0 ? void 0 : options.disableWebPush)
                            ? Promise.resolve({
                                ok: true,
                                skipped: true,
                                reason: '요청 설정에 따라 Web Push 발송을 건너뛰었습니다.',
                                sentCount: 0,
                                failedCount: 0,
                                invalidEndpoints: [],
                            })
                            : sendWebPushNotifications(payload),
                    ])];
                case 1:
                    _a = _b.sent(), ios = _a[0], web = _a[1];
                    aggregate = resolveNotificationAggregateResult({
                        ios: ios,
                        web: web,
                    }, options);
                    return [2 /*return*/, {
                            ok: aggregate.ok,
                            skipped: aggregate.skipped,
                            ios: ios,
                            web: web,
                        }];
            }
        });
    });
}
function resolveNotificationAggregateResult(result, options) {
    var enabledResults = [
        (options === null || options === void 0 ? void 0 : options.disableIos) ? null : result.ios,
        (options === null || options === void 0 ? void 0 : options.disableWebPush) ? null : result.web,
    ].filter(function (entry) { return Boolean(entry); });
    return {
        ok: enabledResults.length === 0 ? true : enabledResults.every(function (entry) { return entry.ok; }),
        skipped: enabledResults.length === 0 ? true : enabledResults.every(function (entry) { return entry.skipped; }),
    };
}
function shutdownNotificationProvider() {
    return __awaiter(this, void 0, void 0, function () {
        var provider;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, getProvider()];
                case 1:
                    provider = _a.sent();
                    provider === null || provider === void 0 ? void 0 : provider.shutdown();
                    providerPromise = null;
                    return [2 /*return*/];
            }
        });
    });
}
