import { randomUUID } from "crypto";
import { google } from "googleapis";
import { loadGoogleTokens, saveGoogleTokens } from "../config/index.js";
import { resolveGoogleOAuthCredentials } from "../google/oauth-credentials.js";
import { GoogleMeetClient } from "../meet/google-meet.js";
/**
 * Wrapper around Google Calendar API.
 * Uses OAuth tokens from onboarding to read/write events on the agent's calendar.
 */
export class GoogleCalendarClient {
    calendar;
    config;
    constructor(config) {
        this.config = config;
        const tokens = loadGoogleTokens();
        const { clientId, clientSecret } = resolveGoogleOAuthCredentials(config);
        const oauth2Client = new google.auth.OAuth2(clientId, clientSecret);
        oauth2Client.setCredentials({
            access_token: tokens.access_token,
            refresh_token: tokens.refresh_token,
            token_type: tokens.token_type,
            expiry_date: tokens.expiry_date,
        });
        // Auto-refresh tokens
        oauth2Client.on("tokens", (newTokens) => {
            const existing = loadGoogleTokens();
            saveGoogleTokens({
                ...existing,
                access_token: newTokens.access_token ?? existing.access_token,
                refresh_token: newTokens.refresh_token ?? existing.refresh_token,
                expiry_date: newTokens.expiry_date ?? existing.expiry_date,
            });
        });
        this.calendar = google.calendar({ version: "v3", auth: oauth2Client });
    }
    /**
     * List events between timeMin and timeMax.
     */
    async listUpcomingEvents(timeMin, timeMax, maxResults = 20) {
        const res = await this.calendar.events.list({
            calendarId: "primary",
            timeMin: timeMin.toISOString(),
            timeMax: timeMax.toISOString(),
            maxResults,
            singleEvents: true,
            orderBy: "startTime",
        });
        return res.data.items ?? [];
    }
    /**
     * Create a new calendar event.
     */
    async createEvent(options) {
        const res = await this.calendar.events.insert({
            calendarId: "primary",
            // conferenceDataVersion=1 is required for Google Meet link creation
            conferenceDataVersion: 1,
            sendUpdates: options.attendees?.length ? "all" : "none",
            requestBody: {
                summary: options.summary,
                description: options.description,
                location: options.location,
                start: {
                    dateTime: options.start.toISOString(),
                    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
                },
                end: {
                    dateTime: options.end.toISOString(),
                    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
                },
                recurrence: options.recurrence,
                attendees: options.attendees?.map((email) => ({ email })),
                // Always create a Google Meet link for every event.
                conferenceData: {
                    createRequest: {
                        requestId: randomUUID(),
                        conferenceSolutionKey: { type: "hangoutsMeet" },
                    },
                },
            },
        });
        const event = res.data;
        // Set the meeting space to OPEN access so participants can join directly
        // without being admitted from a waiting room/lobby.
        // hangoutLink format: "https://meet.google.com/abc-defg-hij"
        const meetingCode = event.hangoutLink?.split("meet.google.com/")[1];
        if (meetingCode) {
            const meetClient = new GoogleMeetClient(this.config);
            await meetClient.setSpaceAccessOpen(meetingCode);
        }
        return event;
    }
    /**
     * Update an existing event.
     */
    async updateEvent(eventId, updates) {
        const requestBody = {};
        if (updates.summary)
            requestBody.summary = updates.summary;
        if (updates.description)
            requestBody.description = updates.description;
        if (updates.location)
            requestBody.location = updates.location;
        if (updates.start) {
            requestBody.start = {
                dateTime: updates.start.toISOString(),
                timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
            };
        }
        if (updates.end) {
            requestBody.end = {
                dateTime: updates.end.toISOString(),
                timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
            };
        }
        if (updates.attendees) {
            requestBody.attendees = updates.attendees.map((email) => ({ email }));
        }
        const res = await this.calendar.events.patch({
            calendarId: "primary",
            eventId,
            sendUpdates: updates.attendees?.length ? "all" : "none",
            requestBody,
        });
        return res.data;
    }
    /**
     * Delete an event.
     */
    async deleteEvent(eventId) {
        await this.calendar.events.delete({
            calendarId: "primary",
            eventId,
            sendUpdates: "all",
        });
    }
    /**
     * Clear all events from the primary calendar.
     */
    async clearPrimaryCalendar() {
        await this.calendar.calendars.clear({
            calendarId: "primary",
        });
    }
    /**
     * Find free time slots in a given range.
     */
    async findFreeTime(timeMin, timeMax) {
        const res = await this.calendar.freebusy.query({
            requestBody: {
                timeMin: timeMin.toISOString(),
                timeMax: timeMax.toISOString(),
                items: [{ id: "primary" }],
            },
        });
        const primaryCal = res.data.calendars?.primary;
        const busySlots = primaryCal?.busy ?? [];
        // Compute free slots from busy slots
        const freeSlots = [];
        let cursor = timeMin.toISOString();
        for (const busy of busySlots) {
            if (busy.start && cursor < busy.start) {
                freeSlots.push({ start: cursor, end: busy.start });
            }
            cursor = busy.end ?? cursor;
        }
        if (cursor < timeMax.toISOString()) {
            freeSlots.push({ start: cursor, end: timeMax.toISOString() });
        }
        return freeSlots;
    }
}
//# sourceMappingURL=google-calendar.js.map