import { z } from 'zod';
import { execa } from 'execa';
import { glob as globLib } from 'glob';
import { resolve, relative } from 'path';
import { defineTool, type ToolResult } from './types.js';

interface GrepMatch {
  file: string;
  line: number;
  text: string;
  context?: { before: string[]; after: string[] };
}

/**
 * Search for patterns in files using ripgrep
 */
export const grepTool = defineTool({
  name: 'grep',
  description:
    'Search for a pattern in files using ripgrep. Returns matching lines with file paths and line numbers.',
  category: 'search',
  schema: z.object({
    pattern: z.string().describe('Regex pattern to search for'),
    path: z.string().optional().describe('Directory or file to search (defaults to repo root)'),
    glob: z.string().optional().describe('File pattern filter (e.g., "*.ts", "*.{js,jsx}")'),
    context_lines: z.number().optional().describe('Lines of context around matches'),
    case_sensitive: z.boolean().optional().describe('Case-sensitive search (default: true)'),
    max_results: z.number().optional().default(50).describe('Maximum results to return'),
  }),

  async execute(
    input,
    context
  ): Promise<ToolResult<{ matches: GrepMatch[]; totalMatches: number }>> {
    const searchPath = input.path
      ? resolve(context.repoRoot, input.path)
      : context.repoRoot;

    if (!searchPath.startsWith(context.repoRoot)) {
      return { success: false, error: 'Path is outside repository' };
    }

    const args: string[] = [
      '--json',
      '--no-heading',
      '--line-number',
      '--color', 'never',
    ];

    if (input.case_sensitive === false) {
      args.push('-i');
    }

    if (input.context_lines) {
      args.push(`-C${input.context_lines}`);
    }

    if (input.glob) {
      args.push('--glob', input.glob);
    }

    // Always exclude common directories
    args.push('--glob', '!node_modules');
    args.push('--glob', '!.git');
    args.push('--glob', '!dist');
    args.push('--glob', '!build');

    args.push(input.pattern, searchPath);

    try {
      const { stdout, exitCode } = await execa('rg', args, {
        cwd: context.repoRoot,
        reject: false,
      });

      if (exitCode !== 0 && exitCode !== 1) {
        // Exit code 1 means no matches
        return { success: false, error: 'Ripgrep command failed' };
      }

      const matches: GrepMatch[] = [];

      // Parse JSON output
      const lines = stdout.split('\n').filter(Boolean);
      for (const line of lines) {
        try {
          const parsed = JSON.parse(line);
          if (parsed.type === 'match') {
            const data = parsed.data;
            matches.push({
              file: relative(context.repoRoot, data.path.text),
              line: data.line_number,
              text: data.lines.text.trimEnd(),
            });
          }
        } catch {
          // Skip malformed lines
        }
      }

      const totalMatches = matches.length;
      const limitedMatches = matches.slice(0, input.max_results);

      return {
        success: true,
        data: {
          matches: limitedMatches,
          totalMatches,
        },
        metadata: totalMatches > (input.max_results ?? 50) ? { truncated: true } : undefined,
      };
    } catch (err) {
      // Check if rg is not installed
      const error = err as Error;
      if (error.message?.includes('ENOENT')) {
        return {
          success: false,
          error: 'ripgrep (rg) is not installed. Please install it first.',
        };
      }
      return { success: false, error: `Grep failed: ${error.message}` };
    }
  },
});

/**
 * Find files matching a glob pattern
 */
export const globTool = defineTool({
  name: 'glob',
  description: 'Find files matching a glob pattern (e.g., "**/*.ts", "src/**/*.tsx").',
  category: 'search',
  schema: z.object({
    pattern: z.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.{js,ts}")'),
    path: z.string().optional().describe('Base directory (defaults to repo root)'),
    ignore: z
      .array(z.string())
      .optional()
      .describe('Patterns to ignore (e.g., ["**/test/**"])'),
  }),

  async execute(
    input,
    context
  ): Promise<ToolResult<{ files: string[]; count: number }>> {
    const basePath = input.path
      ? resolve(context.repoRoot, input.path)
      : context.repoRoot;

    if (!basePath.startsWith(context.repoRoot)) {
      return { success: false, error: 'Path is outside repository' };
    }

    try {
      const defaultIgnore = [
        '**/node_modules/**',
        '**/.git/**',
        '**/dist/**',
        '**/build/**',
        '**/.next/**',
        '**/coverage/**',
      ];

      const files = await globLib(input.pattern, {
        cwd: basePath,
        ignore: [...defaultIgnore, ...(input.ignore ?? [])],
        nodir: true,
        dot: false,
        absolute: false,
      });

      // Sort by path
      files.sort();

      // Limit results
      const maxResults = 200;
      const truncated = files.length > maxResults;
      const limitedFiles = files.slice(0, maxResults);

      return {
        success: true,
        data: {
          files: limitedFiles,
          count: files.length,
        },
        metadata: truncated ? { truncated: true } : undefined,
      };
    } catch (err) {
      const error = err as Error;
      return { success: false, error: `Glob failed: ${error.message}` };
    }
  },
});
