import * as z from "zod/v4";
import { jsonResult, errorResult } from "../utils/response-formatter.js";
import { isUserError, isUserFixableApiError, toMcpError } from "../utils/errors.js";
import { compactItems } from "../utils/compact.js";
import { createLogger } from "../utils/logger.js";
import { sendProgress } from "../utils/progress.js";
import { applyUrlDefaults } from "../utils/url-parser.js";
import { searchOutputSchema } from "./output-schemas.js";
const log = createLogger("search");
const RESOURCE_SCOPES = ["account", "org", "project"];
/** Relevance tiers — lower number = more relevant. */
const RELEVANCE_TIERS = {
    pipeline: 1, service: 1, environment: 1, connector: 1, execution: 1,
    template: 2, trigger: 2, input_set: 2, secret: 2, fme_feature_flag: 2,
    repository: 2, infrastructure: 2,
    // SCS types at tier 2 — same priority as other domain-specific resources
    scs_artifact_source: 2, artifact_security: 2, code_repo_security: 2,
    scs_artifact_component: 2, scs_compliance_result: 2,
    // Everything else defaults to tier 3
};
function getTier(resourceType) {
    return RELEVANCE_TIERS[resourceType] ?? 3;
}
function asResourceScope(value) {
    return typeof value === "string" && RESOURCE_SCOPES.includes(value)
        ? value
        : undefined;
}
export function registerSearchTool(server, registry, client) {
    const listableTypes = registry.getTypesForOperation("list");
    server.registerTool("harness_search", {
        description: "Search across multiple Harness resource types. Returns results ranked by relevance. Accepts a Harness URL for scope.",
        inputSchema: {
            query: z.string().describe("Search term"),
            resource_types: z.array(z.enum(listableTypes)).describe("Types to search (defaults to all listable)").optional(),
            url: z.string().describe("Harness UI URL — auto-extracts org and project").optional(),
            resource_scope: z.enum(["account", "org", "project"]).optional().describe("Scope to search. Use account for account-level resources and to omit org/project defaults; org injects only org; project injects org+project. Auto-detected from url."),
            org_id: z.string().describe("Organization identifier (overrides default)").optional(),
            project_id: z.string().describe("Project identifier (overrides default)").optional(),
            max_per_type: z.number().describe("Max results per type").default(5).optional(),
            compact: z.boolean().describe("Strip verbose metadata (default true)").default(true).optional(),
        },
        outputSchema: searchOutputSchema,
        annotations: {
            title: "Search Harness Resources",
            readOnlyHint: true,
            destructiveHint: false,
            openWorldHint: true,
        },
    }, async (args, extra) => {
        try {
            const signal = extra.signal;
            const mergedArgs = applyUrlDefaults(args, args.url, { includeResourceScope: true });
            const requestedScope = asResourceScope(mergedArgs.resource_scope);
            const hasExplicitResourceTypes = (args.resource_types?.length ?? 0) > 0;
            // Determine which resource types to search
            let targetTypes = args.resource_types ?? [];
            if (targetTypes.length === 0) {
                // Search all types that support list
                targetTypes = registry.getAllResourceTypes().filter((rt) => registry.supportsOperation(rt, "list"));
            }
            if (requestedScope && !hasExplicitResourceTypes) {
                targetTypes = targetTypes.filter((rt) => registry.getSupportedScopes(rt).includes(requestedScope));
            }
            const entries = [];
            const errors = {};
            // Run searches with concurrency limit to avoid overwhelming the API
            const MAX_CONCURRENCY = 5;
            const settled = [];
            let searched = 0;
            for (let i = 0; i < targetTypes.length; i += MAX_CONCURRENCY) {
                // Check for cancellation between batches
                signal.throwIfAborted();
                const batch = targetTypes.slice(i, i + MAX_CONCURRENCY);
                await sendProgress(extra, searched, targetTypes.length, `Searching batch ${Math.floor(i / MAX_CONCURRENCY) + 1}...`);
                const batchResults = await Promise.all(batch.map(async (rt) => {
                    try {
                        const result = await registry.dispatch(client, rt, "list", {
                            ...mergedArgs,
                            search_term: args.query,
                            name: args.query,
                            query: args.query,
                            search: args.query,
                            size: args.max_per_type ?? 5,
                            limit: args.max_per_type ?? 5,
                            page: 0,
                        }, { tool: "harness_search" }, signal);
                        return { rt, result, error: null };
                    }
                    catch (err) {
                        log.debug(`Search failed for ${rt}`, { error: String(err) });
                        return { rt, result: null, error: String(err) };
                    }
                }));
                settled.push(...batchResults);
                searched += batch.length;
            }
            await sendProgress(extra, targetTypes.length, targetTypes.length, "Processing results...");
            let totalMatches = 0;
            for (const { rt, result, error } of settled) {
                if (result) {
                    const r = result;
                    if (r.items && r.items.length > 0) {
                        const items = args.compact !== false ? compactItems(r.items) : r.items;
                        const matchCount = r.items.length;
                        totalMatches += matchCount;
                        entries.push({
                            resource_type: rt,
                            tier: getTier(rt),
                            match_count: matchCount,
                            items,
                            total: r.total ?? matchCount,
                            ...(r.openInHarness ? { openInHarness: r.openInHarness } : {}),
                        });
                    }
                }
                if (error) {
                    errors[rt] = error;
                }
            }
            // Sort by tier ascending, then by match_count descending within tier
            entries.sort((a, b) => {
                if (a.tier !== b.tier)
                    return a.tier - b.tier;
                return b.match_count - a.match_count;
            });
            return jsonResult({
                query: args.query,
                total_matches: totalMatches,
                searched_types: targetTypes.length,
                results: entries,
                ...(Object.keys(errors).length > 0 ? { errors } : {}),
            });
        }
        catch (err) {
            if (isUserError(err))
                return errorResult(err.message);
            if (isUserFixableApiError(err))
                return errorResult(err.message);
            throw toMcpError(err);
        }
    });
}
//# sourceMappingURL=harness-search.js.map