/**
 * Grep tool — search file contents using regex.
 * Falls back to Node.js implementation if ripgrep is not available.
 */

import { readdirSync, readFileSync, statSync } from 'fs';
import { join, resolve, normalize, relative, extname } from 'path';
import { execSync } from 'child_process';

const MAX_RESULTS = 500;

export const definition = {
  name: 'Grep',
  description: 'Search file contents using regex patterns. Supports file type filters and multiple output modes.',
  input_schema: {
    type: 'object',
    properties: {
      pattern: { type: 'string', description: 'Regex pattern to search for' },
      path: { type: 'string', description: 'File or directory to search in (defaults to workspace root)' },
      glob: { type: 'string', description: 'Glob pattern to filter files (e.g., "*.js")' },
      type: { type: 'string', description: 'File type filter (e.g., "js", "py", "mjs")' },
      output_mode: { type: 'string', enum: ['content', 'files_with_matches', 'count'], description: 'Output mode (default: files_with_matches)' },
      '-i': { type: 'boolean', description: 'Case insensitive search' },
      '-n': { type: 'boolean', description: 'Show line numbers (default true for content mode)' },
      '-A': { type: 'number', description: 'Lines to show after each match' },
      '-B': { type: 'number', description: 'Lines to show before each match' },
      '-C': { type: 'number', description: 'Context lines (before and after)' },
      context: { type: 'number', description: 'Alias for -C' },
      head_limit: { type: 'number', description: 'Limit output to first N entries' },
      offset: { type: 'number', description: 'Skip first N entries' }
    },
    required: ['pattern']
  }
};

// File extension to type mapping
const TYPE_MAP = {
  js: ['.js', '.jsx'],
  ts: ['.ts', '.tsx'],
  mjs: ['.mjs'],
  py: ['.py'],
  rust: ['.rs'],
  go: ['.go'],
  java: ['.java'],
  rb: ['.rb'],
  php: ['.php'],
  css: ['.css'],
  html: ['.html', '.htm'],
  json: ['.json'],
  md: ['.md'],
  yaml: ['.yaml', '.yml'],
  xml: ['.xml'],
  sh: ['.sh', '.bash'],
  c: ['.c', '.h'],
  cpp: ['.cpp', '.hpp', '.cc']
};

function walkDir(dir, maxDepth = 20, depth = 0) {
  if (depth > maxDepth) return [];
  const results = [];
  let entries;
  try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return []; }
  for (const entry of entries) {
    if (entry.name.startsWith('.')) continue;
    const full = join(dir, entry.name);
    if (entry.isDirectory()) {
      if (entry.name === 'node_modules' || entry.name === '.git') continue;
      results.push(...walkDir(full, maxDepth, depth + 1));
    } else if (entry.isFile()) {
      results.push(full);
    }
  }
  return results;
}

function matchesGlob(filename, globPattern) {
  if (!globPattern) return true;
  const pattern = globPattern.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.');
  return new RegExp('^' + pattern + '$', 'i').test(filename);
}

function matchesType(filePath, typeFilter) {
  if (!typeFilter) return true;
  const exts = TYPE_MAP[typeFilter.toLowerCase()];
  if (!exts) return extname(filePath).toLowerCase() === '.' + typeFilter.toLowerCase();
  return exts.includes(extname(filePath).toLowerCase());
}

export async function execute(input, context) {
  const {
    pattern, path: searchPath, glob: globFilter, type: typeFilter,
    output_mode, '-i': caseInsensitive, '-n': showLineNumbers,
    '-A': afterContext, '-B': beforeContext, '-C': contextLines,
    context: contextAlias, head_limit, offset
  } = input;

  if (!pattern) return { content: 'Error: pattern is required', is_error: true };

  const baseDir = searchPath ? resolve(normalize(searchPath)) : context.workspaceRoot;
  const wsNorm = resolve(normalize(context.workspaceRoot));
  const agNorm = resolve(normalize(context.agentRoot));

  if (!resolve(normalize(baseDir)).startsWith(wsNorm) && !resolve(normalize(baseDir)).startsWith(agNorm)) {
    return { content: `Error: Path "${baseDir}" is outside allowed directories`, is_error: true };
  }

  const mode = output_mode || 'files_with_matches';
  const ctxLines = contextLines || contextAlias || 0;
  const linesAfter = afterContext || ctxLines;
  const linesBefore = beforeContext || ctxLines;

  try {
    const flags = caseInsensitive ? 'gi' : 'g';
    const regex = new RegExp(pattern, flags);

    // Determine files to search
    let files;
    try {
      const s = statSync(baseDir);
      if (s.isFile()) {
        files = [baseDir];
      } else {
        files = walkDir(baseDir);
      }
    } catch {
      return { content: `Error: Path not found: ${baseDir}`, is_error: true };
    }

    // Apply filters
    files = files.filter(f => matchesGlob(relative(baseDir, f).replace(/\\/g, '/'), globFilter));
    files = files.filter(f => matchesType(f, typeFilter));

    const matchingFiles = [];
    const contentResults = [];
    const countResults = [];

    for (const filePath of files) {
      let content;
      try { content = readFileSync(filePath, 'utf-8'); } catch { continue; }

      const lines = content.split('\n');
      const matchedLineNums = [];

      for (let i = 0; i < lines.length; i++) {
        if (regex.test(lines[i])) {
          matchedLineNums.push(i);
        }
        regex.lastIndex = 0; // Reset for global regex
      }

      if (matchedLineNums.length === 0) continue;

      matchingFiles.push(filePath);
      countResults.push({ file: filePath, count: matchedLineNums.length });

      if (mode === 'content') {
        for (const lineNum of matchedLineNums) {
          const start = Math.max(0, lineNum - linesBefore);
          const end = Math.min(lines.length - 1, lineNum + linesAfter);
          for (let i = start; i <= end; i++) {
            const prefix = (showLineNumbers !== false) ? `${filePath}:${i + 1}: ` : `${filePath}: `;
            contentResults.push(prefix + lines[i]);
          }
          if (linesBefore || linesAfter) contentResults.push('--');
        }
      }
    }

    // Apply offset and head_limit
    let results;
    if (mode === 'content') {
      results = contentResults;
    } else if (mode === 'count') {
      results = countResults.map(r => `${r.file}: ${r.count}`);
    } else {
      results = matchingFiles;
    }

    const skip = offset || 0;
    const limit = head_limit || results.length;
    results = results.slice(skip, skip + limit);

    if (results.length === 0) {
      return { content: `No matches for "${pattern}" in ${baseDir}`, is_error: false };
    }

    return { content: results.join('\n'), is_error: false };
  } catch (err) {
    if (err.message.includes('Invalid regular expression')) {
      return { content: `Error: Invalid regex pattern: ${err.message}`, is_error: true };
    }
    return { content: `Error: ${err.message}`, is_error: true };
  }
}
