/**
 * SQLiteAgentMessageBus — durable cross-process adapter for {@link IAgentMessageBus}.
 *
 * Wraps {@link IAgentMessageRepository} for persistence and runs a single
 * shared poll loop that periodically reads undelivered messages from the
 * shared SQLite database. Each subscription receives messages whose scope
 * matches its filter; messages are marked delivered_at after the first
 * read so the partial undelivered index stays small (research decision 2).
 *
 * Cross-process delivery is "free" by virtue of every Shep process opening
 * the same `~/.shep/<repo-hash>/shep.db` file in WAL mode — see CLAUDE.md
 * + research.yaml.
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { inject, injectable } from 'tsyringe';
import { PeerAddressingForbiddenError } from '../../../../domain/errors/peer-addressing-forbidden.error.js';
/** Default poll cadence — matches StreamAgentEventsUseCase (2s). */
const DEFAULT_POLL_INTERVAL_MS = 2_000;
function toMillis(value) {
    if (value instanceof Date)
        return value.getTime();
    if (typeof value === 'number')
        return value;
    if (typeof value === 'string')
        return new Date(value).getTime();
    return 0;
}
function matchesAgentRun(filter, message) {
    if (filter.agentRunId === undefined)
        return true;
    return (message.fromAgentRunId === filter.agentRunId ||
        (message.toKind === 'agent' && message.toTarget === filter.agentRunId));
}
let SQLiteAgentMessageBus = class SQLiteAgentMessageBus {
    repository;
    subscriptions = new Set();
    pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
    timer = null;
    polling = false;
    constructor(repository) {
        this.repository = repository;
    }
    /** Test-only override; production code relies on {@link DEFAULT_POLL_INTERVAL_MS}. */
    setPollIntervalMs(ms) {
        this.pollIntervalMs = ms;
        if (this.timer !== null) {
            this.stopPolling();
            this.ensurePolling();
        }
    }
    async publish(message) {
        if (message.toKind === 'peer') {
            throw new PeerAddressingForbiddenError(message.toKind);
        }
        await this.repository.create(message);
    }
    subscribe(filter, handler) {
        const sub = {
            filter,
            handler,
            lastSeenAt: filter.since ? filter.since.getTime() : 0,
            delivered: new Set(),
        };
        this.subscriptions.add(sub);
        this.ensurePolling();
        return () => {
            this.subscriptions.delete(sub);
            if (this.subscriptions.size === 0) {
                this.stopPolling();
            }
        };
    }
    async listFor(filter, limit) {
        const rows = await this.repository.listByScope(filter.appId, filter.featureId, {
            since: filter.since,
            limit,
        });
        return filter.agentRunId === undefined ? rows : rows.filter((m) => matchesAgentRun(filter, m));
    }
    /**
     * Stop the poll loop. Idempotent. Tests should call this in afterEach to
     * avoid leaking the interval; production code can rely on process exit.
     */
    shutdown() {
        this.subscriptions.clear();
        this.stopPolling();
    }
    ensurePolling() {
        if (this.timer !== null)
            return;
        this.timer = setInterval(() => {
            void this.pollOnce();
        }, this.pollIntervalMs);
        if (typeof this.timer.unref === 'function') {
            this.timer.unref();
        }
    }
    stopPolling() {
        if (this.timer !== null) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }
    async pollOnce() {
        if (this.polling)
            return;
        this.polling = true;
        try {
            // Snapshot subscriptions to allow reentrant unsubscribe inside handlers.
            const subs = Array.from(this.subscriptions);
            for (const sub of subs) {
                const rows = await this.repository.listByScope(sub.filter.appId, sub.filter.featureId, {
                    since: new Date(sub.lastSeenAt),
                });
                for (const row of rows) {
                    if (sub.delivered.has(row.id))
                        continue;
                    if (!matchesAgentRun(sub.filter, row))
                        continue;
                    sub.delivered.add(row.id);
                    const createdMs = toMillis(row.createdAt);
                    if (createdMs > sub.lastSeenAt)
                        sub.lastSeenAt = createdMs;
                    try {
                        await sub.handler(row);
                    }
                    catch {
                        // Handler errors must not break the poll loop or other subscribers.
                    }
                    // Mark delivered_at so the partial undelivered index stays flat.
                    if (!row.deliveredAt) {
                        try {
                            await this.repository.markDelivered(row.appId ?? '', row.id, new Date());
                        }
                        catch {
                            // Best-effort — ignore races where another reader marked it first.
                        }
                    }
                }
            }
        }
        finally {
            this.polling = false;
        }
    }
};
SQLiteAgentMessageBus = __decorate([
    injectable(),
    __param(0, inject('IAgentMessageRepository')),
    __metadata("design:paramtypes", [Object])
], SQLiteAgentMessageBus);
export { SQLiteAgentMessageBus };
