import fs from "node:fs";
import path from "node:path";
import { google } from "googleapis";
import { loadGoogleTokens, saveGoogleTokens } from "../config/index.js";
import { resolveGoogleOAuthCredentials } from "../google/oauth-credentials.js";
function getDrivePermissionErrorReason(error) {
    const reason = error
        .response?.data?.error?.errors?.[0]?.reason;
    return typeof reason === "string" ? reason : undefined;
}
/**
 * Wrapper around Google Drive API v3.
 * Uses OAuth tokens from onboarding to manage files in the agent's Google Drive.
 */
export class GoogleDriveClient {
    drive;
    constructor(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.drive = google.drive({ version: "v3", auth: oauth2Client });
    }
    /**
     * List files in Google Drive with optional query filtering.
     */
    async listFiles(options) {
        let q = "trashed = false";
        if (options.folderId) {
            q += ` and '${options.folderId}' in parents`;
        }
        if (options.query) {
            q += ` and (${options.query})`;
        }
        const res = await this.drive.files.list({
            q,
            pageSize: options.maxResults ?? 20,
            orderBy: options.orderBy ?? "modifiedTime desc",
            fields: "files(id, name, mimeType, size, modifiedTime, webViewLink, parents)",
        });
        return (res.data.files ?? []).map((f) => ({
            id: f.id ?? "",
            name: f.name ?? "",
            mimeType: f.mimeType ?? "",
            size: f.size ?? undefined,
            modifiedTime: f.modifiedTime ?? undefined,
            webViewLink: f.webViewLink ?? undefined,
            parents: f.parents ?? undefined,
        }));
    }
    /**
     * Get metadata for a specific file.
     */
    async getFile(fileId) {
        const res = await this.drive.files.get({
            fileId,
            fields: "id, name, mimeType, size, modifiedTime, webViewLink, parents",
        });
        const f = res.data;
        return {
            id: f.id ?? "",
            name: f.name ?? "",
            mimeType: f.mimeType ?? "",
            size: f.size ?? undefined,
            modifiedTime: f.modifiedTime ?? undefined,
            webViewLink: f.webViewLink ?? undefined,
            parents: f.parents ?? undefined,
        };
    }
    /**
     * Upload a local file to Google Drive.
     */
    async uploadFile(options) {
        const localPath = options.localPath;
        const name = options.name ?? path.basename(localPath);
        const fileStream = fs.createReadStream(localPath);
        let uploadedBytes = 0;
        fileStream.on("data", (chunk) => {
            uploadedBytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
            options.onProgress?.(uploadedBytes);
        });
        const res = await this.drive.files.create({
            requestBody: {
                name,
                parents: options.folderId ? [options.folderId] : undefined,
            },
            media: {
                mimeType: options.mimeType ?? "application/octet-stream",
                body: fileStream,
            },
            fields: "id, name, mimeType, size, webViewLink",
        });
        const f = res.data;
        return {
            id: f.id ?? "",
            name: f.name ?? "",
            mimeType: f.mimeType ?? "",
            webViewLink: f.webViewLink ?? undefined,
            size: f.size ?? undefined,
        };
    }
    /**
     * Download a file from Google Drive to a local path.
     */
    async downloadFile(options) {
        const dest = fs.createWriteStream(options.localPath);
        const res = await this.drive.files.get({ fileId: options.fileId, alt: "media" }, { responseType: "stream" });
        await new Promise((resolve, reject) => {
            res.data
                .on("end", resolve)
                .on("error", reject)
                .pipe(dest);
        });
    }
    /**
     * Create a folder in Google Drive.
     */
    async createFolder(options) {
        const res = await this.drive.files.create({
            requestBody: {
                name: options.name,
                mimeType: "application/vnd.google-apps.folder",
                parents: options.parentId ? [options.parentId] : undefined,
            },
            fields: "id, name, mimeType, webViewLink, parents",
        });
        const f = res.data;
        return {
            id: f.id ?? "",
            name: f.name ?? "",
            mimeType: f.mimeType ?? "",
            webViewLink: f.webViewLink ?? undefined,
            parents: f.parents ?? undefined,
        };
    }
    /**
     * Share a file with a specific email address.
     */
    async shareFile(options) {
        const requestBody = {
            type: "user",
            role: options.role,
            emailAddress: options.email,
        };
        const sendNotification = options.sendNotification ?? false;
        try {
            await this.drive.permissions.create({
                fileId: options.fileId,
                sendNotificationEmail: sendNotification,
                requestBody,
            });
        }
        catch (error) {
            // Google rejects silent shares to emails without a Google account.
            if (!sendNotification &&
                getDrivePermissionErrorReason(error) === "invalidSharingRequest") {
                await this.drive.permissions.create({
                    fileId: options.fileId,
                    sendNotificationEmail: true,
                    requestBody,
                });
                return;
            }
            throw error;
        }
    }
    /**
     * Move a file to trash (soft delete).
     */
    async deleteFile(fileId) {
        await this.drive.files.update({
            fileId,
            requestBody: { trashed: true },
        });
    }
    /**
     * Move a file to a different folder.
     */
    async moveFile(options) {
        // Get current parents first
        const current = await this.drive.files.get({
            fileId: options.fileId,
            fields: "parents",
        });
        const previousParents = (current.data.parents ?? []).join(",");
        const res = await this.drive.files.update({
            fileId: options.fileId,
            addParents: options.newFolderId,
            removeParents: previousParents,
            fields: "id, name, mimeType, size, modifiedTime, webViewLink, parents",
        });
        const f = res.data;
        return {
            id: f.id ?? "",
            name: f.name ?? "",
            mimeType: f.mimeType ?? "",
            size: f.size ?? undefined,
            modifiedTime: f.modifiedTime ?? undefined,
            webViewLink: f.webViewLink ?? undefined,
            parents: f.parents ?? undefined,
        };
    }
}
//# sourceMappingURL=google-drive.js.map