import { GoogleCalendarClient } from "./google-calendar.js";
import { logger } from "../logger.js";
import { sanitizeJsonString } from "../utils/json-sanitize.js";
import { formatNowWithTz } from "../agent/context.js";
const DEFAULT_POLL_INTERVAL_MS = 3 * 60_000; // 3 minutes
const DEFAULT_LEAD_TIME_MS = 15 * 60_000; // 15 minutes before event start
const IMMEDIATE_QUERY_THRESHOLD_MS = 5 * 60_000; // Query immediately for urgent upcoming events
/**
 * Background poller that watches for upcoming calendar events and injects
 * interrupt messages into the active agent session, so the agent can react
 * to events even during long-running tasks.
 */
export class CalendarInterruptPoller {
    config;
    sessionManager;
    messageQueue;
    getAgentState;
    wakeMainLoop;
    calendarClient = null;
    timer = null;
    notifiedEventIds = new Set();
    pollIntervalMs;
    leadTimeMs;
    constructor(options) {
        this.config = options.config;
        this.sessionManager = options.sessionManager;
        this.messageQueue = options.messageQueue;
        this.getAgentState = options.getAgentState;
        this.wakeMainLoop = options.wakeMainLoop;
        this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
        this.leadTimeMs = options.leadTimeMs ?? DEFAULT_LEAD_TIME_MS;
    }
    start() {
        if (this.timer)
            return;
        logger.system(`Calendar interrupt poller started (poll every ${this.pollIntervalMs / 1000}s, lead time ${this.leadTimeMs / 60_000}min)`);
        void this.poll(); // immediate first poll
        this.timer = setInterval(() => {
            void this.poll();
        }, this.pollIntervalMs);
    }
    stop() {
        if (this.timer) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }
    async poll() {
        try {
            this.calendarClient ??= new GoogleCalendarClient(this.config);
            const now = new Date();
            const windowEnd = new Date(now.getTime() + this.leadTimeMs);
            // Query only for events that START in the future by using now as
            // timeMin. Google Calendar API still returns ongoing events whose
            // start is before timeMin, so we filter those out below.
            const events = await this.calendarClient.listUpcomingEvents(now, windowEnd);
            const imminentEvents = events.filter((event) => {
                const eventId = event.id;
                if (!eventId)
                    return false;
                if (this.notifiedEventIds.has(eventId))
                    return false;
                const startStr = event.start?.dateTime ?? event.start?.date;
                if (!startStr)
                    return false;
                const startMs = new Date(startStr).getTime();
                // Only consider events that haven't started yet
                return startMs > now.getTime() && startMs <= windowEnd.getTime();
            });
            if (imminentEvents.length === 0) {
                // Only log when there are events in the window (ongoing/past-start)
                // to reduce noise. Completely empty windows are not worth logging.
                if (events.length > 0) {
                    logger.debug(`Calendar interrupt poll: ${events.length} event(s) in window, all already started or notified`);
                }
                return;
            }
            for (const event of imminentEvents) {
                const eventId = event.id;
                if (!eventId)
                    continue;
                this.notifiedEventIds.add(eventId);
            }
            // Prune old event IDs (keep only last 200)
            if (this.notifiedEventIds.size > 200) {
                const ids = [...this.notifiedEventIds];
                this.notifiedEventIds = new Set(ids.slice(ids.length - 200));
            }
            const interruptText = this.buildCalendarInterruptText(imminentEvents);
            const agentState = this.getAgentState();
            const shouldQueryImmediately = imminentEvents.some((event) => {
                const startStr = event.start?.dateTime ?? event.start?.date;
                if (!startStr)
                    return false;
                const startMs = new Date(startStr).getTime();
                return startMs - now.getTime() <= IMMEDIATE_QUERY_THRESHOLD_MS;
            });
            if (agentState.busy) {
                const blocks = [
                    { type: "text", text: interruptText },
                ];
                const injected = this.sessionManager.activeSession.injectMessage(blocks, shouldQueryImmediately ? undefined : { shouldQuery: false });
                if (injected) {
                    logger.system(`Calendar interrupt injected: ${imminentEvents.length} upcoming event(s)${shouldQueryImmediately ? " (immediate query)" : " (queued for next turn)"}`);
                }
                else {
                    // Session closed — enqueue for next wake
                    this.enqueueCalendarMessage(interruptText);
                }
            }
            else {
                // Agent is idle — enqueue and wake
                this.enqueueCalendarMessage(interruptText);
            }
        }
        catch (err) {
            logger.debug(`Calendar interrupt poll failed: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    enqueueCalendarMessage(text) {
        this.messageQueue.enqueue({
            id: `calendar_interrupt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
            channel: "system",
            sender: "calendar",
            text,
            attachments: [],
            timestamp: new Date().toISOString(),
        });
        this.wakeMainLoop();
    }
    buildCalendarInterruptText(events) {
        const now = new Date();
        const lines = [];
        lines.push(`[Calendar Interrupt] time=${formatNowWithTz()} — ${events.length} upcoming event(s) detected:`);
        lines.push("");
        for (const event of events) {
            const summary = sanitizeJsonString(event.summary ?? "(no title)");
            const startStr = sanitizeJsonString(event.start?.dateTime ?? event.start?.date ?? "unknown");
            const startDate = new Date(startStr);
            const minutesUntil = Math.round((startDate.getTime() - now.getTime()) / 60_000);
            const endStr = sanitizeJsonString(event.end?.dateTime ?? event.end?.date ?? "");
            const description = event.description
                ? ` — ${sanitizeJsonString(event.description.substring(0, 150))}`
                : "";
            const meetLink = event.hangoutLink
                ? ` (Meet: ${sanitizeJsonString(event.hangoutLink)})`
                : "";
            lines.push(`- **${summary}** in ${minutesUntil} min (${startStr}${endStr ? ` to ${endStr}` : ""})${meetLink}${description}`);
        }
        lines.push("");
        lines.push("Save your current progress if needed and prepare for this event. " +
            "If it requires a meeting or action, notify the owner via their messaging channel.");
        return lines.join("\n");
    }
}
//# sourceMappingURL=calendar-interrupt-poller.js.map