const CHANNEL_TIMEOUT_MS = 10_000;
const SPAM_WINDOW_MS = 300_000;
const MAX_SPAM_COUNT = 3;
export class NotificationDispatcher {
    channels;
    dedupeMap = new Map();
    constructor(channels) {
        this.channels = channels;
    }
    async notify(confirmation) {
        const dedupeKey = `${confirmation.actionType}:${confirmation.riskLevel}`;
        if (this.isSpam(dedupeKey)) {
            return {
                success: false,
                channel: null,
                error: 'Notification rate limit exceeded (max 3 per 5 minutes)',
            };
        }
        const message = this.formatMessage(confirmation);
        for (const channel of this.channels) {
            try {
                const sent = await this.sendWithTimeout(channel, confirmation, message);
                if (sent) {
                    this.trackSend(dedupeKey);
                    return { success: true, channel: channel.name };
                }
            }
            catch {
                // Fallback to next channel
            }
        }
        return {
            success: false,
            channel: null,
            error: 'All notification channels failed',
        };
    }
    formatMessage(confirmation) {
        const factors = this.parseFactors(confirmation.riskFactors);
        const expiresIn = this.calculateExpiresIn(confirmation.expiresAt);
        return [
            'Security Sentinel — Confirmation Required',
            '',
            `Action: ${confirmation.actionType}`,
            `Risk: ${confirmation.riskLevel} (${confirmation.riskScore}/100)`,
            `Summary: ${confirmation.actionSummary}`,
            `Factors: ${factors.join(', ')}`,
            '',
            `Expires in: ${expiresIn}`,
            '',
            '[Approve] [Reject]',
        ].join('\n');
    }
    getChannelCount() {
        return this.channels.length;
    }
    async sendWithTimeout(channel, confirmation, message) {
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                reject(new Error(`Channel ${channel.name} timed out`));
            }, CHANNEL_TIMEOUT_MS);
            channel
                .send(confirmation, message)
                .then((result) => {
                clearTimeout(timer);
                resolve(result);
            })
                .catch((err) => {
                clearTimeout(timer);
                reject(err);
            });
        });
    }
    isSpam(dedupeKey) {
        const entry = this.dedupeMap.get(dedupeKey);
        if (!entry)
            return false;
        if (Date.now() - entry.firstAt > SPAM_WINDOW_MS) {
            this.dedupeMap.delete(dedupeKey);
            return false;
        }
        return entry.count >= MAX_SPAM_COUNT;
    }
    trackSend(dedupeKey) {
        const entry = this.dedupeMap.get(dedupeKey);
        if (!entry || Date.now() - entry.firstAt > SPAM_WINDOW_MS) {
            this.dedupeMap.set(dedupeKey, { count: 1, firstAt: Date.now() });
        }
        else {
            entry.count++;
        }
    }
    parseFactors(factorsJson) {
        try {
            return JSON.parse(factorsJson);
        }
        catch {
            return [];
        }
    }
    calculateExpiresIn(expiresAt) {
        const remaining = new Date(expiresAt).getTime() - Date.now();
        if (remaining <= 0)
            return 'expired';
        const seconds = Math.floor(remaining / 1000);
        const minutes = Math.floor(seconds / 60);
        if (minutes > 0)
            return `${minutes}m ${seconds % 60}s`;
        return `${seconds}s`;
    }
}
//# sourceMappingURL=NotificationDispatcher.js.map