src / ripgrep.ts

import { readFile } from "fs/promises";
import * as path from "path";
import { existsSync, statSync } from "fs";

interface SearchResult {
  filePath: string;
  line: number;
  column: number;
  match: string;
  beforeContext: string[];
  afterContext: string[];
}

const MAX_RESULTS = 300;
const MAX_FILE_SIZE_SEARCH = 2 * 1024 * 1024; // Skip files larger than 2MB

export async function regexSearchFiles(
  cwd: string,
  directoryPath: string,
  regexPattern: string,
  filePattern: string = "*"
): Promise<string> {
  try {
    let globby;
    try {
        const module = await import("globby");
        globby = module.globby;
    } catch (e) {
        return "Error: Dependency 'globby' not found. Please run 'npm install globby' in the plugin directory.";
    }

    const regex = new RegExp(regexPattern, "g");
    const absoluteDirPath = path.resolve(cwd, directoryPath);
    const normalizedDir = absoluteDirPath.split(path.sep).join("/");
    
    const globPattern = filePattern 
      ? path.posix.join(normalizedDir, filePattern)
      : path.posix.join(normalizedDir, "**/*");

    const files = await globby(globPattern, {
      cwd: absoluteDirPath,
      absolute: true,
      onlyFiles: true,
      dot: true,
      ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**"]
    });

    const results: SearchResult[] = [];
    let matchCount = 0;

    for (const filePath of files) {
      if (matchCount >= MAX_RESULTS) break;

      try {
        const stats = statSync(filePath);
        if (stats.size > MAX_FILE_SIZE_SEARCH) continue;

        const content = await readFile(filePath, "utf-8");
        const lines = content.split("\n");

        for (let i = 0; i < lines.length; i++) {
          const line = lines[i];
          regex.lastIndex = 0;
          
          const match = regex.exec(line);
          if (match) {
            matchCount++;
            
            const beforeContext = i > 0 ? [lines[i - 1]] : [];
            const afterContext = i < lines.length - 1 ? [lines[i + 1]] : [];

            results.push({
              filePath: filePath,
              line: i + 1,
              column: match.index + 1,
              match: line,
              beforeContext,
              afterContext
            });

            if (matchCount >= MAX_RESULTS) break;
          }
        }
      } catch (err) {
        continue;
      }
    }

    if (results.length === 0) return "No results found";
    return formatResults(results, cwd);

  } catch (error: any) {
    return `Search Error: ${error.message}`;
  }
}

const MAX_OUTPUT_MB = 0.5;
const MAX_BYTE_SIZE = MAX_OUTPUT_MB * 1024 * 1024;

function formatResults(results: SearchResult[], cwd: string): string {
  const groupedResults: { [key: string]: SearchResult[] } = {};
  const output: string[] = [];

  if (results.length >= MAX_RESULTS) {
    output.push(`Showing first ${MAX_RESULTS} results. Use a more specific search if necessary.\n\n`);
  } else {
    output.push(`Found ${results.length === 1 ? "1 result" : `${results.length.toLocaleString()} results`}.\n\n`);
  }

  results.forEach((result) => {
    const relativeFilePath = path.relative(cwd, result.filePath);
    if (!groupedResults[relativeFilePath]) {
      groupedResults[relativeFilePath] = [];
    }
    groupedResults[relativeFilePath].push(result);
  });

  let byteSize = Buffer.byteLength(output.join(""), "utf8");
  let wasLimitReached = false;

  for (const [filePath, fileResults] of Object.entries(groupedResults)) {
    const filePathString = `${filePath.replace(/\\/g, "/")}\n│----\n`;
    const filePathBytes = Buffer.byteLength(filePathString, "utf8");

    if (byteSize + filePathBytes >= MAX_BYTE_SIZE) {
      wasLimitReached = true;
      break;
    }

    output.push(filePathString);
    byteSize += filePathBytes;

    for (let i = 0; i < fileResults.length; i++) {
      const res = fileResults[i];
      const allLines = [...res.beforeContext, res.match, ...res.afterContext];

      const resultLines: string[] = [];
      let resultBytes = 0;

      for (const line of allLines) {
        const trimmed = line.trimEnd();
        const str = `│${trimmed}\n`;
        const len = Buffer.byteLength(str, "utf8");
        resultLines.push(str);
        resultBytes += len;
      }

      if (byteSize + resultBytes >= MAX_BYTE_SIZE) {
        wasLimitReached = true;
        break;
      }

      output.push(...resultLines);
      byteSize += resultBytes;

      if (i < fileResults.length - 1) {
        const sep = "│----\n";
        const sepLen = Buffer.byteLength(sep, "utf8");
        if (byteSize + sepLen >= MAX_BYTE_SIZE) {
          wasLimitReached = true;
          break;
        }
        output.push(sep);
        byteSize += sepLen;
      }
    }

    if (wasLimitReached) break;

    const closing = "│----\n\n";
    const closingLen = Buffer.byteLength(closing, "utf8");
    if (byteSize + closingLen >= MAX_BYTE_SIZE) {
        wasLimitReached = true;
        break;
    }
    output.push(closing);
    byteSize += closingLen;
  }

  if (wasLimitReached) {
    output.push(`\n[Results truncated due to exceeding ${MAX_OUTPUT_MB}MB limit.]`);
  }

  return output.join("").trim();
}

export async function getBinPath(): Promise<string | undefined> {
    return "INTERNAL_JS_SEARCH";
}