import { z } from "zod";
import { getLogger } from "../../../../util/logger.js";
import { isDatabricksIntegration } from "../../../integrations/store.js";
import { withRpcTimeout } from "../../../util/rpc-timeout.js";
import { createToolFactory, PermissionLevel, ToolCategory } from "../types.js";
import { searchGronWithRipgrep, buildResultFromGronMatches, } from "./grep-metadata-ripgrep.js";
/**
 * Matches standard UUID format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
 * Used to distinguish saved integration IDs from plugin type strings.
 */
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/**
 * Grep Metadata Tool - Search integration metadata using ripgrep regex patterns
 *
 * This tool searches gron-formatted metadata cache files using ripgrep.
 */
export const grepMetadataToolFactory = createToolFactory("grepMetadata", ({ clark, services, }) => {
    const MAX_RESPONSE_CHARS = 25_000; // Character limit for response (no hard result limit)
    return {
        category: ToolCategory.METADATA,
        description: `
Search integration metadata using ripgrep regex patterns on gron-formatted cache files.

WHEN TO USE:
- Explore database schemas (tables, columns, types)
- Find REST API paths, operations, schemas
- Search GraphQL types, queries, mutations, fields
- Discover what data is available before writing queries

METADATA FORMAT:
Metadata is cached as gron-formatted files where JSON is flattened to searchable lines:
  json.dbSchema.tables[0].name = "users";
  json.dbSchema.tables[0].columns[3].name = "email";
  json.openApiSpec.paths["/api/users"].get.operationId = "getUsers";

PATTERN SYNTAX:
Write ripgrep regex patterns that match these gron lines.
- Database: Use \\d+ for array indices (tables[0], columns[5])
- OpenAPI: Use .* for string keys (paths["/users"], paths[".*"])

COMMON PATTERNS:

Find schemas (database namespaces):
  '\\.schemas\\[\\d+\\]\\.name'
  (Returns unique schema names like "samples", "uber", "system")

  ⚠️ DATABRICKS NOTE: For Databricks, \`schemas[]\` contains CATALOG names, not schema names!
  - To get actual schemas for Databricks, extract from \`table.schema\` values
  - \`table.schema\` contains "catalog.schema" format (e.g., "uber.default")
  - Parse by splitting on ".": first part = catalog, second part = schema

Find tables:
  '\\.tables\\[\\d+\\]\\.name = ".*user.*"'
  (Returns full table objects with all columns included!)

Find columns across all tables:
  '\\.columns\\[\\d+\\]\\.name = ".*email.*"'

Find specific table with all its columns:
  '\\.tables\\[\\d+\\]\\.name = "users"'
  (Searching for a table by name returns the complete table object with columns - no need for OR patterns!)

Find OpenAPI paths (string keys, not arrays!):
  '\\.paths\\[".*repos.*"\\]'
  (Quotes constrain search to the path key, not descriptions)

Find OpenAPI operations by method:
  '\\.paths\\[.*\\]\\.get\\.operationId'
  → Returns: Operation with method, path, parameters array, requestBody, and responses
  → parameters array includes path/query/header/cookie parameters with types and descriptions
  → responses object shows response schemas by status code (200, 404, etc.)

Find OpenAPI by operation ID or summary:
  '\\.operationId = ".*keyword.*"'
  '\\.summary = ".*keyword.*"'

Find GraphQL types:
  '\\.graphql\\.data\\.__schema\\.types\\[\\d+\\]\\.name = ".*User.*"'

Find GraphQL queries (query fields are in the Query type):
  '\\.graphql\\.data\\.__schema\\.types\\[\\d+\\]\\.name = "Query"'
  (Returns the Query type with all query fields)

Find fields in any type (searches both 'fields' and 'inputFields'):
  '\\.graphql\\.data\\.__schema\\.types\\[\\d+\\]\\.(fields|inputFields)\\[\\d+\\]\\.name = ".*email.*"'

Two-step workflow for large GraphQL schemas (recommended):
  Step 1: Use includeDetails: false to get type with field names
    grepMetadata({
      pattern: '\\.types\\[\\d+\\]\\.name = "Query"',
      includeDetails: false
    })
    → Returns: { name: "Query", fieldNames: ["issue", "issues", "projects", ...] }
    → Works for all types including INPUT_OBJECT (shows inputFields)
    → Just search for the type name - the tool automatically includes all fields

  Step 2: Search for 1-3 specific fields at a time to get full details
    grepMetadata({ pattern: '\\.(fields|inputFields)\\[\\d+\\]\\.name = "issue|assignee"' })
    → Returns: Field objects with typeName, description, returnType, and args array
    → args array shows what arguments the field accepts (e.g., filter: IssueFilter)
    → Check typeName in results to filter by type
    ⚠️  Limit to 2-3 fields per search to stay under 25k char limit

OR LOGIC (use | for multiple patterns):
  '\\.columns\\[\\d+\\]\\.name = ".*email.*|.*phone.*|.*address.*"'

**UNSTRUCTURED TEXT FIELDS (info, descriptions, examples):**
Search for text fields that aren't in arrays:
  '\\.info\\.description'
  → Returns: { type: "value", name: "description", jsonPath: "openApiSpec.info.description", value: "..." }

  '\\.example'
  → Returns all example values in the spec

  '\\.info\\.title'
  → Returns the API title

This is useful for:
- Reading API descriptions and setup instructions
- Finding example parameter values
- Discovering available models (e.g., in AI integrations like Gemini, OpenAI)

TIPS:
- Use .* for wildcard matching
- Use \\d+ for array indices (never hardcode numbers!)
- Escape regex special chars: \\. \\[ \\] \\(
- Use | for OR logic (much faster than multiple calls)
- Set includeDetails: false for name-only lists (faster)
- For OpenAPI integrations, check \\.info\\.description for setup guidance and available options

The tool returns structured results with full context (table names, schemas, types).

**Integration Type Detection**: The result includes \`pluginId\` (e.g., "databricks", "postgres", "mysql") and \`isDatabricksIntegration\` boolean fields. When \`isDatabricksIntegration: true\`, apply Databricks-specific parsing rules (see Database Schema Naming Conventions section).
`,
        defaultPermission: PermissionLevel.ALLOW,
        readOnly: true,
        inputSchema: z.object({
            integrationId: z
                .string()
                .describe("The saved integration ID (UUID) to search. This must be the UUID returned by saveIntegration or from availableIntegrations — NOT a plugin type like 'circleci' or 'postgres'. Call saveIntegration first if the integration hasn't been saved yet."),
            grepPattern: z.string().describe(`Ripgrep regex pattern for searching gron-formatted metadata.

GRON FORMAT: Metadata is stored as gron lines (each JSON path becomes a separate line)

DATABASE GRON FORMAT EXAMPLES:
  json.dbSchema.schemas[0].name = "public";
  json.dbSchema.tables[3].name = "users";
  json.dbSchema.tables[3].schema = "public";
  json.dbSchema.tables[3].columns = [];
  json.dbSchema.tables[3].columns[0].name = "id";
  json.dbSchema.tables[3].columns[0].type = "integer";
  json.dbSchema.tables[3].columns[5].name = "email";
  json.dbSchema.tables[3].columns[5].type = "varchar";

OPENAPI GRON FORMAT EXAMPLES:
  json.openApiSpec.paths = {};
  json.openApiSpec.paths["/api/users"] = {};
  json.openApiSpec.paths["/api/users"].get = {};
  json.openApiSpec.paths["/api/users"].get.operationId = "getUsers";
  json.openApiSpec.paths["/api/users"].get.summary = "List all users";
  json.openApiSpec.paths["/api/repos/{owner}"] = {};
  json.openApiSpec.paths["/api/repos/{owner}"].get.operationId = "listRepos";

GRAPHQL GRON FORMAT EXAMPLES:
  json.graphql.data.__schema.types[42].name = "Query";
  json.graphql.data.__schema.types[42].kind = "OBJECT";
  json.graphql.data.__schema.types[42].fields[0].name = "issues";
  json.graphql.data.__schema.types[42].fields[0].args[0].name = "filter";
  json.graphql.data.__schema.types[42].fields[0].args[0].type.name = "IssueFilter";
  json.graphql.data.__schema.types[16].name = "Issue";
  json.graphql.data.__schema.types[16].fields[2].name = "title";

HANDLING TRUNCATION (25k char limit):
Truncation is NORMAL and EXPECTED for large schemas. When you see truncated: true:

**For EXPLORATORY queries** ("What's in this database?", "Tell me about the tables"):
✅ DO: Note what you got, then search more specifically (by topic, schema, name)
✅ DO: Use narrower patterns (search 1-3 fields instead of 5+)
✅ DO: Use includeDetails: false to get just field names
❌ DON'T: Paginate - user doesn't need ALL results, just representative sample

**For EXHAUSTIVE queries** ("Give me ALL tables", "List EVERY endpoint"):
✅ DO: Use startIndex (from stoppedAt) to paginate through all results
✅ DO: Continue until hasMore: false
❌ DON'T: Sample - user explicitly wants complete data

❌ NEVER: Give up or assume the data isn't available
❌ NEVER: Immediately paginate without trying specific searches first (unless user said "all")

PAGINATION:
When truncated: true is returned:
1. **If exploratory**: Try more specific patterns (by topic, schema, name)
2. **If exhaustive** (user said "all"/"every"): Use startIndex from stoppedAt value to continue
   Example: { stoppedAt: 7, hasMore: true } → next call: { startIndex: 7 }


PATTERN EXAMPLES:

Database (tables/columns use numeric indices):
- Find schemas: '\\\\.schemas\\\\[\\\\d+\\\\]\\\\.name' (all unique schema names)
  ⚠️ DATABRICKS: \`schemas[]\` contains catalog names. For actual schemas, extract from \`table.schema\` (format: "catalog.schema")
- Find tables: '\\\\.tables\\\\[\\\\d+\\\\]\\\\.name = ".*user.*"'
- Find columns: '\\\\.columns\\\\[\\\\d+\\\\]\\\\.name = ".*email.*"'
- Specific table columns: '\\\\.tables\\\\[\\\\d+\\\\]\\\\.name = "users"|\\\\.tables\\\\[\\\\d+\\\\]\\\\.columns\\\\[\\\\d+\\\\]\\\\.name'
- DATABRICKS: Extract schemas from table.schema: '\\\\.tables\\\\[\\\\d+\\\\]\\\\.schema' (returns "catalog.schema" format - split by "." to get catalog and schema)

OpenAPI (paths use STRING keys, not arrays!):
- Find paths with "repos": '\\\\.paths\\\\[".*repos.*"\\\\]' (quotes constrain to key)
- Find paths with "user": '\\\\.paths\\\\[".*user.*"\\\\]'
- Exact path: '\\\\.paths\\\\["/exact/path"\\\\]'
- GET operations: '\\\\.paths\\\\[.*\\\\]\\\\.get\\\\.operationId'
- By operation ID (anchored): '\\\\.operationId = ".*repo.*"' (matches value only)
- By summary (anchored): '\\\\.summary = ".*keyword.*"' (matches value only)
- ⚠️  AVOID: '\\\\.operationId.*repo.*' (too broad, matches nested content, hits limits!)

GraphQL (standard introspection format at json.graphql.data.__schema):
- Broad search (many results, no fields): '\\\\.graphql\\\\.data\\\\.__schema\\\\.types\\\\[\\\\d+\\\\]\\\\.name = ".*User.*"'
- Narrow search (≤10 results, WITH fields): '\\\\.graphql\\\\.data\\\\.__schema\\\\.types\\\\[\\\\d+\\\\]\\\\.name = "User"'
- Find Query type WITH all queries: '\\\\.graphql\\\\.data\\\\.__schema\\\\.types\\\\[\\\\d+\\\\]\\\\.name = "Query"'
- Find Mutation type WITH all mutations: '\\\\.graphql\\\\.data\\\\.__schema\\\\.types\\\\[\\\\d+\\\\]\\\\.name = "Mutation"'
- Find 1-3 specific fields: '\\\\.fields\\\\[\\\\d+\\\\]\\\\.name = "assignee|title"' (limit to 2-3 fields)
  ⚠️  Returns field results across all types - check typeName to filter
  ⚠️  Searching 5+ fields may exceed 25k char limit on large schemas
- Auto-optimization: ≤10 type matches = includes fields, >10 = omits fields

TIPS:
- Use \\\\d+ for array indices (matches any number)
- Use .* for wildcard matching
- Escape special regex chars: \\\\. \\\\[ \\\\] \\\\(
- Use | for OR: "pattern1|pattern2"
`),
            includeDetails: z
                .boolean()
                .optional()
                .default(true)
                .describe(`Controls level of detail in results:
- true (default): Full details with complete field objects (may be large for complex types)
- false: Compact mode - GraphQL types return fieldNames array instead of full fields

For GraphQL types:
- includeDetails: false → { name: "Issue", fieldNames: ["id", "title", "assignee", ...] }
- includeDetails: true → { name: "Issue", fields: [{name: "id", type: {...}, ...}, ...] }

Use false for exploration, true when you need complete type information.`),
            startIndex: z
                .number()
                .optional()
                .default(0)
                .describe(`Index to start returning results from (0-indexed). Use this to continue from where a previous truncated search stopped.

⚠️ WHEN TO USE PAGINATION:

USE PAGINATION WHEN:
1. **User explicitly wants ALL results** ("Give me ALL tables", "List EVERY endpoint")
2. **Specific search returns truncated results AND you need more data** to answer the question
   - Example: User asks "What user tables exist?" → Search returns 30 truncated results → Need more
   - Example: User asks about a specific schema → Returns 50 tables truncated → Need more
3. **User requests continuation** ("Show me more", "Continue", "Next page")

DON'T PAGINATE WHEN:
1. **Exploratory sampling is sufficient** ("What's in this database?" → 7 tables is a good sample)
2. **You already have enough data** to answer the question (don't fetch more just because hasMore: true)
3. **You can refine the search** instead (search by topic, schema, name)

REMEMBER: Pagination is a tool, not a last resort. Use it whenever you need more data.

TWO COMMON PATTERNS:

1. **EXPLORATORY** (user discovering):
   → Return representative sample
   → Let user know they can see more if needed
   → Example: "Found 7 of 100+ tables. Here's a sample: [list]. Would you like to see more, or search for specific types?"

2. **EXHAUSTIVE** (user wants complete data):
   → Use pagination to get all results
   → Continue until hasMore: false
   → Example: User says "all" → Paginate through everything

ONLY use this when results are truncated AND you cannot make your search pattern more specific
- FIRST try: More specific patterns (search by topic, schema, or specific names)
- LAST resort: Pagination through all results

How it works:
1. First call returns: { matches: [...], stoppedAt: 7, totalCount: 100, hasMore: true }
2. Next call: Use startIndex: 7 to continue from where it stopped
3. Repeat until hasMore: false

Examples:
✅ GOOD: Search for "user" tables first → grepPattern: '\\.tables\\[\\d+\\]\\.name = ".*user.*"'
✅ GOOD: Search by schema → grepPattern: '\\.tables\\[\\d+\\]\\.schema = "public"'
❌ AVOID: Immediately paginating through 5000 tables without trying specific searches

Only paginate when:
1. User explicitly asks for "all" results
2. No reasonable way to refine the search
3. You've already tried topic-based searches`),
            forceRefresh: z
                .boolean()
                .optional()
                .default(false)
                .describe(`Set to true to bypass cache and fetch fresh metadata from the server.

WHEN TO USE:
- User explicitly says they've updated the integration ("I just updated the schema", "I added a new table")
- User asks to refresh or check again ("Can you refresh?", "Check again", "Try again")
- User indicates cached data might be stale ("It should be there now", "I just added it")

WHEN NOT TO USE:
- Normal queries - cached metadata is usually sufficient
- The default TTL is 10 minutes, so most queries use reasonably fresh data

Setting this to true will:
1. Invalidate the cached metadata
2. Fetch fresh metadata from the server
3. Return the updated metadata immediately

Note: This increases load on the orchestrator, so only use when truly needed.`),
        }),
        execute: async ({ integrationId, grepPattern, includeDetails = true, startIndex = 0, forceRefresh = false, }) => {
            const logger = getLogger();
            const startTime = Date.now();
            // Validate that integrationId looks like a saved UUID, not a plugin type string.
            // Clark sometimes passes plugin IDs (e.g. "circleci") before the integration is saved.
            if (!UUID_PATTERN.test(integrationId)) {
                logger.warn(`[grepMetadata] Received non-UUID integrationId: "${integrationId}" — likely a plugin type, not a saved integration ID`);
                return {
                    error: `"${integrationId}" is not a valid integration ID (expected a UUID like "ad0298ea-558b-4a43-..."). ` +
                        `If this is a plugin type, call saveIntegration first. ` +
                        `grepMetadata requires the UUID returned by saveIntegration or from availableIntegrations.`,
                    matches: [],
                    totalCount: 0,
                    truncated: false,
                };
            }
            // Log the incoming request
            logger.info(`[grepMetadata] Starting search for integration ${integrationId} with pattern: "${grepPattern}"${forceRefresh ? " (force refresh)" : ""}`);
            try {
                // Get integration header to determine plugin type
                const header = services.integrationStore.getHeader(integrationId);
                const pluginId = header?.plugin.id;
                const isDatabricks = header ? isDatabricksIntegration(header) : false;
                // Fetch metadata with a timeout to prevent indefinite hangs when
                // the OPA is unreachable (see: 2026-04-14 Airwallex incident).
                const metadata = await withRpcTimeout(services.integrationStore.getMetadata(integrationId, clark.context.peer, { forceRefresh }), undefined, // use DEFAULT_RPC_TIMEOUT_MS (60s)
                `grepMetadata.getMetadata(${integrationId})`);
                if (!metadata) {
                    logger.warn(`[grepMetadata] No metadata found for integration ${integrationId}`);
                    return {
                        error: `No metadata found for integration ${integrationId}`,
                        matches: [],
                        totalCount: 0,
                        truncated: false,
                        pluginId,
                        isDatabricksIntegration: isDatabricks,
                    };
                }
                // Metadata must be cached as gron file
                if (!metadata._gronPath) {
                    logger.error(`[grepMetadata] No gron cache found for integration ${integrationId}`);
                    return {
                        error: `Metadata not cached for integration ${integrationId}. Please retry.`,
                        matches: [],
                        totalCount: 0,
                        truncated: false,
                        pluginId,
                        isDatabricksIntegration: isDatabricks,
                    };
                }
                logger.info(`[grepMetadata] Using ripgrep on cached gron file: ${metadata._gronPath}`);
                // Search with ripgrep using the pattern directly
                const gronMatches = await searchGronWithRipgrep(metadata._gronPath, grepPattern);
                // Build structured result from gron matches (with character limit)
                const result = await buildResultFromGronMatches(metadata._gronPath, gronMatches, undefined, // No limit - character limit is the boundary
                includeDetails, MAX_RESPONSE_CHARS, startIndex);
                const elapsedMs = Date.now() - startTime;
                // Truncate response if it exceeds MAX_RESPONSE_CHARS
                // This prevents the middleware from throwing an error and allows Clark to handle it gracefully
                let finalResult = result;
                const resultString = JSON.stringify(result);
                if (resultString.length > MAX_RESPONSE_CHARS) {
                    // Linear backtracking: count how many matches fit
                    let matchesKept = 0;
                    const baseResult = {
                        ...result,
                        matches: [],
                        truncated: true,
                    };
                    const baseSize = JSON.stringify(baseResult).length;
                    let currentSize = baseSize;
                    for (let i = 0; i < result.matches.length; i++) {
                        const matchSize = JSON.stringify(result.matches[i]).length;
                        if (currentSize + matchSize > MAX_RESPONSE_CHARS) {
                            break; // Stop before adding this match
                        }
                        currentSize += matchSize;
                        matchesKept++;
                    }
                    // Extract pagination info from last kept result
                    const lastMatch = result.matches[matchesKept - 1];
                    const nextGlobalIndex = lastMatch?._globalIndex !== undefined
                        ? lastMatch._globalIndex + 1
                        : undefined;
                    finalResult = {
                        ...result,
                        matches: result.matches.slice(0, matchesKept),
                        truncated: true,
                        stoppedAt: nextGlobalIndex,
                        hasMore: true,
                    };
                    logger.info(`[grepMetadata] Truncated response from ${result.matches.length} to ${matchesKept} matches to stay under ${MAX_RESPONSE_CHARS} chars`);
                }
                logger.info(`[grepMetadata] Found ${result.totalCount} matches in ${elapsedMs}ms (truncated: ${finalResult.truncated})`);
                logger.info(`[grepMetadata] Returning ${finalResult.matches.length} matches (truncated: ${finalResult.truncated})`);
                // Add helpful suggestions based on results
                let suggestion = finalResult.suggestion;
                if (finalResult.totalCount === 0) {
                    // No results - suggest broadening the pattern
                    suggestion = `No matches found. Try: 1) Broaden your regex (use .* instead of specific text), 2) Check spelling, 3) Try different field names (name, operationId, summary)`;
                }
                else if (finalResult.truncated) {
                    // Response was truncated - guide Clark to refine first, paginate only as last resort
                    const resultType = finalResult.matches[0]?.type || "result";
                    const sampleNames = finalResult.matches
                        .slice(0, 3)
                        .map((m) => m.name);
                    const stoppedAt = finalResult.stoppedAt || finalResult.matches.length;
                    const hasMore = finalResult.hasMore !== false;
                    if (resultType === "table" || resultType === "schema") {
                        // For database results, suggest topic-based search first
                        suggestion = `Truncated: showing ${finalResult.matches.length} of ${finalResult.totalCount}+ ${resultType}s. FIRST try searching by topic (e.g., ".*user.*", ".*order.*") or schema name. ${hasMore ? `Only use startIndex: ${stoppedAt} if you need ALL results and cannot refine further.` : ""}`;
                    }
                    else if (resultType === "operation" || resultType === "path") {
                        // For API results, suggest searching by path or method
                        suggestion = `Truncated: showing ${finalResult.matches.length} of ${finalResult.totalCount}+ operations. FIRST try searching by specific paths, methods, or operation IDs. ${hasMore ? `Only use startIndex: ${stoppedAt} if you need ALL operations and cannot refine further.` : ""}`;
                    }
                    else if (resultType === "graphql_type" ||
                        resultType === "field") {
                        // For GraphQL, suggest limiting field searches
                        suggestion = `Truncated: showing ${finalResult.matches.length} of ${finalResult.totalCount}+ ${resultType}s. FIRST try searching for 1-3 specific fields or types. ${hasMore ? `Only use startIndex: ${stoppedAt} if you need ALL results and cannot refine further.` : ""}`;
                    }
                    else {
                        // Generic truncation message
                        suggestion = `Truncated: showing ${finalResult.matches.length} of ${finalResult.totalCount}+ results. FIRST try more specific patterns. ${hasMore ? `Only use startIndex: ${stoppedAt} if you need ALL results and cannot refine further.` : ""}`;
                    }
                    if (sampleNames.length > 0 && startIndex === 0) {
                        suggestion += ` Examples from results: ${sampleNames.join(", ")}`;
                    }
                }
                // Strip internal pagination fields (_globalIndex) from all matches
                finalResult.matches = finalResult.matches.map((match) => {
                    const { _globalIndex, ...cleanMatch } = match;
                    return cleanMatch;
                });
                // Add plugin information to the result
                finalResult.pluginId = pluginId;
                finalResult.isDatabricksIntegration = isDatabricks;
                // Filter out details if not requested
                if (!includeDetails) {
                    finalResult.matches = finalResult.matches.map((match) => {
                        // For GraphQL field/query/mutation results, keep essential properties even when includeDetails: false
                        if (match.type === "field" ||
                            match.type === "query" ||
                            match.type === "mutation") {
                            return {
                                type: match.type,
                                name: match.name,
                                typeName: match.typeName,
                                description: match.description,
                                returnType: match.returnType,
                                args: match.args,
                            };
                        }
                        // For OpenAPI operations, keep parameters and responses even when includeDetails: false
                        if (match.type === "operation") {
                            return {
                                type: match.type,
                                name: match.name,
                                method: match.method,
                                path: match.path,
                                description: match.description,
                                parameters: match.parameters,
                                requestBody: match.requestBody,
                                responses: match.responses,
                            };
                        }
                        // For value type results, keep the jsonPath and value
                        if (match.type === "value") {
                            return {
                                type: match.type,
                                name: match.name,
                                jsonPath: match.jsonPath,
                                value: match.value,
                            };
                        }
                        // For other types (tables, types, etc.), use compact mode
                        return {
                            type: match.type,
                            name: match.name,
                            schema: match.schema,
                            table: match.table,
                            columnType: match.columnType,
                            fieldNames: match.fieldNames, // GraphQL compact mode
                        };
                    });
                    logger.debug(`[grepMetadata] Filtered out details from response`);
                }
                return {
                    ...finalResult,
                    suggestion,
                    metadataType: metadata._type,
                };
            }
            catch (error) {
                logger.error(`[grepMetadata] Error searching integration ${integrationId} with pattern "${grepPattern}": ${error.message}`, { error });
                return {
                    error: `Search failed: ${error.message}`,
                    matches: [],
                    totalCount: 0,
                    truncated: false,
                };
            }
        },
        getSummary: (input, output) => {
            const count = output.totalCount ?? 0;
            const pattern = input.grepPattern || "";
            const truncated = output.truncated ? " (truncated)" : "";
            return {
                input: `pattern: "${pattern}"`,
                output: `${count} match${count === 1 ? "" : "es"}${truncated}`,
            };
        },
    };
});
//# sourceMappingURL=grep-metadata.js.map