import path from "node:path";
import { AiToolExecutionErrorCode } from "@superblocksteam/library-shared/types";
import { getLogger } from "../../util/logger.js";
import { OperationQueue } from "../../util/operation-queue.js";
import { MCPClient } from "../judge/integration/mcp-client.js";
import { getActiveContext } from "../llm/context-v2/utils/index.js";
import { McpToolAdapter } from "../mcp/adapter/mcp-tool-adapter.js";
import { profileTool, } from "../profiler/tool-profiler.js";
import { createToolRecordingMiddleware } from "../recording/index.js";
import { clearPendingToolPermissionRequest } from "../state-machine/helpers/peer.js";
import { getToolCallSignature } from "../util/tool-signature.js";
import { outputLimiter, secretScanningMiddleware, toolLoggingMiddleware, } from "./middleware.js";
import { buildApiToolFactory } from "./tools/apis/build-api.js";
import { getApiDocsToolFactory } from "./tools/apis/get-api-docs.js";
import { checkTestApiPermissions, testApiToolFactory, } from "./tools/apis/test-api.js";
import { writeApiToolFactory } from "./tools/apis/write-api.js";
import { buildInstallPackagesToolFactory, buildWriteFileToolFactory, buildEditFileToolFactory, buildMultiEditFileToolFactory, buildReadFileToolFactory, buildListFilesToolFactory, buildValidateIconsToolFactory, buildDebugToolFactory, clearErrorCacheToolFactory, buildFinalizeToolFactory, buildManageChecklistToolFactory, buildDeleteFileToolFactory, checkDeleteFilePermissions, buildReloadFileToolFactory, executeRequestToIntegrationToolFactory, grepMetadataToolFactory, searchIntegrationsToolFactory, runCodeToolFactory, exitPlanModeToolFactory, askMultiChoiceToolFactory, listAttachmentsToolFactory, buildCaptureScreenshotToolFactory, buildGetRuntimeErrorsToolFactory, buildGetLogsToolFactory, startTestRunToolFactory, endTestRunToolFactory, updateTestCaseStatusToolFactory, getIntegrationTypesToolFactory, } from "./tools/index.js";
import { presets, blockTools, forTools } from "./tools2/access-control.js";
import { summaryInjector, } from "./tools2/registry.js";
import { globToolFactory, grepToolFactory, lsToolFactory, webSearchToolFactory, webFetchToolFactory, rememberFactsToolFactory, } from "./tools2/tools/index.js";
import { PermissionLevel, ToolCategory, ToolExecutionError, } from "./tools2/types.js";
const fileToolConfig = {
    extractDisplayName: (parameters) => {
        const params = parameters;
        if (params.filePath && typeof params.filePath === "string") {
            const fileName = path.basename(params.filePath);
            return `:${fileName}`;
        }
        return "";
    },
    extractMetadata: (parameters) => {
        const params = parameters;
        return {
            filePath: params.filePath,
            hasOldString: "oldString" in params,
            hasNewString: "newString" in params,
            hasContent: "content" in params,
        };
    },
};
const componentToolConfig = {
    extractDisplayName: (parameters) => {
        const params = parameters;
        if (params.componentName && typeof params.componentName === "string") {
            return `:${params.componentName}`;
        }
        if (params.name && typeof params.name === "string") {
            return `:${params.name}`;
        }
        return "";
    },
    extractMetadata: (parameters) => {
        const params = parameters;
        return {
            componentName: params.componentName,
            name: params.name,
            varName: params.varName,
            eventName: params.eventName,
            timerName: params.timerName,
        };
    },
};
export const buildTools = async (clark, services) => {
    const scopeUpdateQueue = new OperationQueue();
    const disabledTools = clark.context.llmConfig?.disabledTools;
    const customerDataAccessEnabled = clark.context.llmConfig?.customerDataAccessEnabled ?? false;
    const registry = services.toolRegistry;
    // Clear any existing tools and rules
    registry.clear();
    // Set the entity permission store
    registry.setEntityPermissionStore(services.entityPermissionStore);
    // Set the prompt handler
    registry.setPromptHandler(async (tool, input, permissions, _context, options) => {
        const actionName = tool.getActionName?.();
        const formattedInput = tool.formatInput?.(input);
        const messageId = options?.toolCallId ?? crypto.randomUUID();
        if (clark.context.pendingToolPermissionRequest ||
            clark.context.activeInteractiveMessage) {
            clark.logger.info("skipping tool permission request as there is already a pending one or active interactive message", messageId);
            return;
        }
        const signature = getToolCallSignature(tool.name, input);
        // Capture timestamp before storing - used to truncate stale "blocked" error when permission is granted
        // The assistant message containing the tool call will have a timestamp <= this value,
        // while the error result will have a timestamp > this value
        const toolCallTimestamp = Date.now();
        // Store pending permission request in context for replay on reconnect
        clark.updateContext({
            pendingToolPermissionRequest: {
                messageId,
                toolName: tool.name,
                actionName,
                formattedInput,
                rawInput: input,
                permissions,
                signature,
                toolCallTimestamp,
            },
        });
        try {
            if (!clark.context.peer) {
                getLogger().warn(`[ai-service] Cannot request tool permission: no peer. tool=${tool.name}`);
            }
            await clark.context.peer?.call.aiRequestToolPermission({
                messageId,
                toolName: tool.name,
                actionName,
                formattedInput,
                rawInput: input,
                permissions,
                signature,
            });
        }
        catch (error) {
            // Clear pending request on error and notify UI
            clearPendingToolPermissionRequest(clark);
            throw error;
        }
    });
    // Add access control rules
    registry.addAccessRule(...presets.base());
    // Testing an API has possibility to prompt if we can statically detect mutations
    // Only register testApi access rule if customer data access is enabled
    registry.addAccessRule(forTools("testApi", async (_tool, _context, input) => {
        if (!customerDataAccessEnabled) {
            return PermissionLevel.BLOCK;
        }
        return checkTestApiPermissions(services, input);
    }));
    // Deleting files conditionally prompts for pages/APIs (except default Page1)
    registry.addAccessRule(forTools("build_deleteFile", async (_tool, _context, input) => {
        return checkDeleteFilePermissions(input);
    }));
    if (disabledTools && disabledTools.length > 0) {
        registry.addAccessRule(blockTools(disabledTools));
    }
    // Register all factory-based tools
    registry.register(buildInstallPackagesToolFactory, { clark, services });
    registry.register(buildWriteFileToolFactory, { clark, services });
    registry.register(buildEditFileToolFactory, { clark, services });
    registry.register(buildMultiEditFileToolFactory, { clark, services });
    registry.register(buildReadFileToolFactory, { services });
    registry.register(buildListFilesToolFactory, services);
    registry.register(buildValidateIconsToolFactory, {});
    registry.register(buildDebugToolFactory, { clark, services });
    registry.register(clearErrorCacheToolFactory, { clark, services });
    registry.register(buildCaptureScreenshotToolFactory, { clark, services });
    registry.register(buildFinalizeToolFactory, { clark, services });
    registry.register(buildManageChecklistToolFactory, { clark, services });
    registry.register(buildDeleteFileToolFactory, { clark, services });
    registry.register(buildReloadFileToolFactory, { clark, services });
    registry.register(executeRequestToIntegrationToolFactory, {
        clark,
        services,
    });
    registry.register(runCodeToolFactory, clark);
    registry.register(grepMetadataToolFactory, { clark, services });
    registry.register(searchIntegrationsToolFactory, services);
    registry.register(exitPlanModeToolFactory, { clark, services });
    registry.register(askMultiChoiceToolFactory, { clark, services });
    registry.register(listAttachmentsToolFactory, { clark, services });
    const isAppKnowledgeEnabled = clark.context.llmConfig?.flags?.isAppKnowledgeEnabled ?? false;
    if (isAppKnowledgeEnabled) {
        registry.register(rememberFactsToolFactory, { clark, services });
    }
    // YAML API tools - only when SDK API is disabled
    if (!services.features.sdkApiEnabled) {
        registry.register(writeApiToolFactory, services);
        if (services.features.useSystemSkills) {
            registry.register(getIntegrationTypesToolFactory, services);
        }
        else {
            registry.register(getApiDocsToolFactory, services);
        }
        registry.register(buildApiToolFactory, {
            clark,
            services,
            scopeUpdateQueue,
        });
    }
    // testApi always available (tests existing APIs in either mode)
    registry.register(testApiToolFactory, { clark, services });
    registry.register(globToolFactory, { appShell: services.appShell });
    registry.register(grepToolFactory, {});
    registry.register(lsToolFactory, { appShell: services.appShell });
    registry.register(buildGetRuntimeErrorsToolFactory, { clark, services });
    registry.register(buildGetLogsToolFactory, { clark, services });
    // Web tools are feature-flagged
    if (services.features.webToolsEnabled) {
        registry.register(webSearchToolFactory, {});
        registry.register(webFetchToolFactory, {
            llmProvider: services.llmProvider,
        });
    }
    // Initialize Playwright MCP server and register browser tools
    try {
        const { mcpServerManager } = services;
        const logger = clark.logger;
        // Get browser context from Clark (contains iframe URL and init/bootstrap data from UI)
        const browserContext = clark.context.browserContext;
        const isTestingEnabled = clark.context.llmConfig?.flags?.isTestingEnabled;
        if (!browserContext || !isTestingEnabled) {
            // Skip Playwright MCP server initialization but continue to register other tools
        }
        else {
            const { iframeBaseUrl, initData, bootstrapData } = browserContext;
            // Start the MCP server with init and bootstrap data for direct iframe access
            // This simulates the parent window sending sb-init and sb-bootstrap-response messages
            const serverUrl = await mcpServerManager.start({
                appUrl: iframeBaseUrl,
                jwt: clark.context.jwt,
                headless: true,
                initData,
                bootstrapData,
                saveArtifact: services.appShell.saveGeneratedArtifact.bind(services.appShell),
                runTimestamp: clark.context.runTimestamp,
            });
            const mcpClient = new MCPClient(serverUrl, logger);
            await mcpClient.connect();
            const mcpAdapter = new McpToolAdapter(registry, logger);
            const registeredTools = await mcpAdapter.addServer({
                name: "playwright",
                url: serverUrl,
                category: ToolCategory.TEST,
                defaultPermission: PermissionLevel.ALLOW,
                toolNameTransform: (_serverName, toolName) => `browser_${toolName}`,
                skipOutputLimit: (_toolName) => (_output) => true,
                toModelOutput: (_toolName) => (output) => {
                    if (output &&
                        typeof output === "object" &&
                        "screenshot" in output) {
                        // Determine media type based on format (defaults to png)
                        const format = output.format ?? "png";
                        const mediaType = format === "png"
                            ? "image/png"
                            : format === "jpeg"
                                ? "image/jpeg"
                                : format === "webp"
                                    ? "image/webp"
                                    : "image/png";
                        return {
                            type: "content",
                            value: [
                                {
                                    type: "media",
                                    data: output.screenshot,
                                    mediaType,
                                },
                            ],
                        };
                    }
                    else {
                        return {
                            type: "json",
                            value: output,
                        };
                    }
                },
            }, mcpClient);
            logger.info(`[Playwright] ✓ Registered ${registeredTools.length} browser tools: ${registeredTools.join(", ")}`);
            // Register test subagent tools only if the mcp server is successfully started
            registry.register(startTestRunToolFactory, { clark, services });
            registry.register(endTestRunToolFactory, { clark, services });
            registry.register(updateTestCaseStatusToolFactory, { clark, services });
            // If a user skips a test case, prevent any further Playwright actions from being executed under that skipped testCaseId.
            registry.addMiddleware((tool) => {
                if (tool.name !== "browser_playwright_action") {
                    return tool;
                }
                return {
                    ...tool,
                    execute: async (input, options) => {
                        const testCaseId = input && typeof input === "object" && "testCaseId" in input
                            ? input.testCaseId
                            : undefined;
                        if (typeof testCaseId === "string" && testCaseId.length > 0) {
                            const status = clark.context.testCaseStatuses?.get(testCaseId);
                            if (status === "skipped") {
                                throw new ToolExecutionError(`Test case "${testCaseId}" was skipped by the user. Do NOT run browser actions for it; move to the next test case.`, {
                                    toolName: tool.name,
                                    input,
                                    context: {},
                                    code: AiToolExecutionErrorCode.NOT_ALLOWED,
                                });
                            }
                        }
                        return tool.execute(input, options);
                    },
                };
            });
        }
    }
    catch (error) {
        // Use warn locally to avoid Datadog error alerts (expected when Playwright not configured)
        const isLocal = process.env.SUPERBLOCKS_SERVER_ENV === "local";
        const errorMessage = String(error);
        services.logger[isLocal ? "warn" : "error"](`[Playwright] Error initializing Playwright MCP server: ${errorMessage}`);
    }
    // Add output limiting middleware to all tools
    // This is added first so it becomes the outermost wrapper - truncation happens
    // AFTER inner middleware (like summaryInjector) have processed the original output
    registry.addMiddleware(outputLimiter(25_000));
    // Add recording middleware if recording is enabled
    // This captures tool inputs and outputs for later replay
    if (services.recordingManager?.isEnabled()) {
        registry.addMiddleware(createToolRecordingMiddleware(services.recordingManager));
    }
    // Add summary injection middleware to capture tool summaries for context compaction
    // The callback looks up the current context at execution time and stores summaries there
    // This is added after outputLimiter so it sees the original (non-truncated) output,
    // allowing getSummary to access structured properties like counts and statuses
    const storeSummary = (toolCallId, summary) => {
        try {
            const context = getActiveContext(clark, services);
            context?.storeSummary(toolCallId, summary);
        }
        catch {
            // Silently ignore - context may not be available in all scenarios
        }
    };
    registry.addMiddleware(summaryInjector(storeSummary));
    // Add secret scanning middleware to redact secrets from tool outputs
    registry.addMiddleware(secretScanningMiddleware({
        enabled: services.features.secretRedactionEnabled,
    }));
    // Add profiling middleware
    registry.addMiddleware((tool) => {
        let profilingConfig;
        switch (tool.category) {
            case ToolCategory.FILE:
                profilingConfig = fileToolConfig;
                break;
            case ToolCategory.COMPONENT:
                profilingConfig = componentToolConfig;
                break;
        }
        return profileTool(tool.name, tool, profilingConfig);
    });
    // Add logging middleware with safe error handling
    // Secret sanitization is handled by the wrapped logger (regex-based, always-on)
    registry.addMiddleware(toolLoggingMiddleware(clark.logger));
    // Get filtered tools from registry based on mode and plan approval
    const tools = await registry.getToolsRecord({
        ...clark.getAccessControl(services.features),
    });
    const enabledToolNames = Object.keys(tools);
    const totalRegistered = registry.listTools().length;
    const filtered = totalRegistered - enabledToolNames.length;
    clark.logger.info(`[buildTools] ${enabledToolNames.length} tools enabled (${filtered} filtered by access control)`);
    if (disabledTools && disabledTools.length > 0) {
        clark.logger.info(`[buildTools] Explicitly disabled: ${disabledTools.join(", ")}`);
    }
    clark.logger.info(`[buildTools] Enabled tools: ${enabledToolNames.join(", ")}`);
    return tools;
};
//# sourceMappingURL=tools.js.map