import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { getDriveClient } from '../../clients.js';
import { escapeDriveQuery } from '../../driveQueryUtils.js';

/**
 * Convenience shortcuts for common MIME types.
 * Users can also pass any full MIME type string directly.
 */
const MIME_TYPE_SHORTCUTS: Record<string, string> = {
  document: 'application/vnd.google-apps.document',
  spreadsheet: 'application/vnd.google-apps.spreadsheet',
  presentation: 'application/vnd.google-apps.presentation',
  folder: 'application/vnd.google-apps.folder',
  form: 'application/vnd.google-apps.form',
  pdf: 'application/pdf',
  zip: 'application/zip',
};

export function register(server: FastMCP) {
  server.addTool({
    name: 'searchDriveFiles',
    description:
      'Searches across all file types in Google Drive by name or content. ' +
      'Unlike searchDocuments (which only searches Google Docs), this tool finds Sheets, PDFs, ' +
      'presentations, folders, and any other Drive file. Supports filtering by MIME type, ' +
      'scoping to a specific folder subtree, controllable sort order, and pagination via pageToken.',
    parameters: z.strictObject({
      query: z.string().min(1).describe('Search term to find in file names or content.'),
      searchIn: z
        .enum(['name', 'content', 'both'])
        .optional()
        .default('both')
        .describe(
          'Where to search: "name" matches file titles only, "content" searches inside files, ' +
            '"both" searches names and content (default).'
        ),
      mimeType: z
        .string()
        .optional()
        .describe(
          'Restrict search to a specific file type. ' +
            'Shortcuts: "document", "spreadsheet", "presentation", "folder", "form", "pdf", "zip". ' +
            'Or pass a full MIME type string.'
        ),
      folderId: z
        .string()
        .optional()
        .describe(
          'Restrict search to files inside this folder (and its subfolders). ' +
            'Use "root" for the top-level Drive. Omit to search all of Drive.'
        ),
      orderBy: z
        .enum(['name', 'modifiedTime', 'createdTime'])
        .optional()
        .default('modifiedTime')
        .describe('Field to sort results by.'),
      sortDirection: z
        .enum(['asc', 'desc'])
        .optional()
        .default('desc')
        .describe('Sort direction: "asc" for oldest first, "desc" for newest first (default).'),
      maxResults: z
        .number()
        .int()
        .min(1)
        .max(100)
        .optional()
        .default(10)
        .describe('Maximum number of results to return per page (1-100).'),
      modifiedAfter: z
        .string()
        .optional()
        .describe(
          'Only return files modified after this date (ISO 8601 format, e.g. "2024-01-01").'
        ),
      pageToken: z
        .string()
        .optional()
        .describe(
          'Pagination token from a previous searchDriveFiles response. ' +
            'Pass this to retrieve the next page of results.'
        ),
    }),
    execute: async (args, { log }) => {
      const drive = await getDriveClient();
      log.info(
        `Searching Drive files for: "${args.query}" in ${args.searchIn}, ` +
          `mimeType=${args.mimeType || 'any'}, folder=${args.folderId || 'all'}, ` +
          `orderBy=${args.orderBy} ${args.sortDirection}`
      );

      try {
        const conditions: string[] = ['trashed=false'];

        // Search term
        if (args.searchIn === 'name') {
          conditions.push(`name contains '${escapeDriveQuery(args.query)}'`);
        } else if (args.searchIn === 'content') {
          conditions.push(`fullText contains '${escapeDriveQuery(args.query)}'`);
        } else {
          conditions.push(
            `(name contains '${escapeDriveQuery(args.query)}' or fullText contains '${escapeDriveQuery(args.query)}')`
          );
        }

        // Resolve MIME type shortcut or use value as-is
        if (args.mimeType) {
          const resolved = MIME_TYPE_SHORTCUTS[args.mimeType] ?? args.mimeType;
          conditions.push(`mimeType='${escapeDriveQuery(resolved)}'`);
        }

        // Scope to a specific folder (searches within the folder subtree via fullText,
        // but Drive API does not support recursive parent filtering natively — using
        // ancestor query instead which covers all descendants)
        if (args.folderId) {
          conditions.push(`'${escapeDriveQuery(args.folderId)}' in ancestors`);
        }

        // Date filter
        if (args.modifiedAfter) {
          const cutoff = new Date(args.modifiedAfter).toISOString();
          conditions.push(`modifiedTime > '${escapeDriveQuery(cutoff)}'`);
        }

        const queryString = conditions.join(' and ');
        const orderByParam = args.sortDirection === 'desc' ? `${args.orderBy} desc` : args.orderBy;

        const response = await drive.files.list({
          q: queryString,
          pageSize: args.maxResults,
          orderBy: orderByParam,
          pageToken: args.pageToken,
          fields:
            'nextPageToken,files(id,name,mimeType,size,modifiedTime,createdTime,webViewLink,owners(displayName,emailAddress),parents)',
          supportsAllDrives: true,
          includeItemsFromAllDrives: true,
        });

        const files = (response.data.files || []).map((file) => ({
          id: file.id,
          name: file.name,
          mimeType: file.mimeType,
          size: file.size != null ? Number(file.size) : null,
          modifiedTime: file.modifiedTime,
          createdTime: file.createdTime,
          owner: file.owners?.[0]?.displayName || null,
          url: file.webViewLink,
        }));

        const result: Record<string, unknown> = { files, total: files.length };
        if (response.data.nextPageToken) {
          result.nextPageToken = response.data.nextPageToken;
          result.hasMore = true;
        } else {
          result.hasMore = false;
        }

        return JSON.stringify(result, null, 2);
      } catch (error: any) {
        log.error(`Error searching Drive files: ${error.message || error}`);
        if (error.code === 403)
          throw new UserError(
            'Permission denied. Make sure you have granted Google Drive access to the application.'
          );
        throw new UserError(`Failed to search files: ${error.message || 'Unknown error'}`);
      }
    },
  });
}
