import { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from "./truncate.js";
import { resolveToCwd } from "../../utils/helpers.js";
import path from "path";
import { existsSync, readFileSync, statSync } from "fs";
import { Type } from "@sinclair/typebox";
import { globSync } from "glob";
//#region src/agent/tools/grep.ts
const grepSchema = Type.Object({
	pattern: Type.String({ description: "Search pattern (regex or literal string)" }),
	path: Type.Optional(Type.String({ description: "Directory or file to search (default: current directory)" })),
	glob: Type.Optional(Type.String({ description: "Filter files by glob pattern, e.g. '*.ts'" })),
	ignoreCase: Type.Optional(Type.Boolean({ description: "Case-insensitive search (default: false)" })),
	literal: Type.Optional(Type.Boolean({ description: "Treat pattern as literal string instead of regex (default: false)" })),
	context: Type.Optional(Type.Number({ description: "Number of lines to show before and after each match (default: 0)" })),
	limit: Type.Optional(Type.Number({ description: "Maximum number of matches to return (default: 100)" }))
});
const DEFAULT_LIMIT = 100;
/**
* Simple grep implementation using Node.js regex
*/
function simpleGrep(content, pattern, options) {
	const matches = [];
	const lines = content.split("\n");
	const regexPattern = options.literal ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
	const flags = options.ignoreCase ? "i" : "";
	const regex = new RegExp(regexPattern, flags);
	for (let i = 0; i < lines.length; i++) if (regex.test(lines[i])) matches.push({
		lineNumber: i + 1,
		line: lines[i]
	});
	return matches;
}
/**
* Grep tool - Search file contents for a pattern
*/
function createGrepTool(cwd) {
	return {
		name: "grep",
		label: "🔍 grep",
		description: "Search file contents for a pattern (regex or literal).",
		parameters: grepSchema,
		execute: async (_toolCallId, params, signal) => {
			try {
				const p = params;
				const searchPath = resolveToCwd(p.path || ".", cwd);
				if (!existsSync(searchPath)) return {
					content: [{
						type: "text",
						text: `Error: Path not found: ${searchPath}`
					}],
					details: {}
				};
				const isDirectory = statSync(searchPath).isDirectory();
				const context = p.context || 0;
				const effectiveLimit = p.limit || DEFAULT_LIMIT;
				const files = [];
				if (isDirectory) {
					const filePaths = globSync(p.glob || "**/*", {
						cwd: searchPath,
						dot: true,
						ignore: ["**/node_modules/**", "**/.git/**"]
					});
					files.push(...filePaths.map((f) => path.join(searchPath, f)));
				} else files.push(searchPath);
				const matches = [];
				let matchCount = 0;
				let linesTruncated = false;
				for (const filePath of files) {
					if (signal?.aborted) return {
						content: [{
							type: "text",
							text: "Operation aborted"
						}],
						details: {}
					};
					try {
						if (!existsSync(filePath)) continue;
						const fileMatches = simpleGrep(readFileSync(filePath, "utf-8"), p.pattern, {
							ignoreCase: p.ignoreCase || false,
							literal: p.literal || false
						});
						for (const match of fileMatches) {
							if (matchCount >= effectiveLimit) break;
							matches.push({
								file: filePath,
								...match
							});
							matchCount++;
						}
					} catch {}
					if (matchCount >= effectiveLimit) break;
				}
				if (matches.length === 0) return {
					content: [{
						type: "text",
						text: "No matches found"
					}],
					details: {}
				};
				const outputLines = [];
				const basePath = isDirectory ? searchPath : path.dirname(searchPath);
				for (const match of matches) {
					const relativePath = path.relative(basePath, match.file);
					const { text: truncatedLine, wasTruncated } = truncateLine(match.line);
					if (wasTruncated) linesTruncated = true;
					if (context > 0) try {
						const allLines = readFileSync(match.file, "utf-8").split("\n");
						const start = Math.max(1, match.lineNumber - context);
						const end = Math.min(allLines.length, match.lineNumber + context);
						for (let i = start; i <= end; i++) {
							const { text: truncatedContextLine } = truncateLine(allLines[i - 1] || "");
							if (i === match.lineNumber) outputLines.push(`${relativePath}:${i}: ${truncatedContextLine}`);
							else outputLines.push(`${relativePath}-${i}- ${truncatedContextLine}`);
						}
					} catch {
						outputLines.push(`${relativePath}:${match.lineNumber}: ${truncatedLine}`);
					}
					else outputLines.push(`${relativePath}:${match.lineNumber}: ${truncatedLine}`);
				}
				const truncation = truncateHead(outputLines.join("\n"), { maxLines: Number.MAX_SAFE_INTEGER });
				let output = truncation.content;
				const details = {};
				const notices = [];
				if (matches.length >= effectiveLimit) {
					notices.push(`${effectiveLimit} matches limit reached`);
					details.matchLimitReached = effectiveLimit;
				}
				if (truncation.truncated) {
					notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
					details.truncation = truncation;
				}
				if (linesTruncated) {
					notices.push(`Some lines truncated to 500 chars`);
					details.linesTruncated = true;
				}
				if (notices.length > 0) output += `\n\n[${notices.join(". ")}]`;
				return {
					content: [{
						type: "text",
						text: output
					}],
					details: Object.keys(details).length > 0 ? details : void 0
				};
			} catch (error) {
				return {
					content: [{
						type: "text",
						text: `Error during grep: ${error instanceof Error ? error.message : String(error)}`
					}],
					details: {}
				};
			}
		}
	};
}
/** Default grep tool using process.cwd() */
const grepTool = createGrepTool(process.cwd());
//#endregion
export { createGrepTool, grepTool };

//# sourceMappingURL=grep.js.map