import z from "zod";
import { safeJsonStringify } from "../../../util/log-sanitizer.js";
import { getErrorMeta, getPrefixedLogger } from "../../../util/logger.js";
import { uploadAttachmentToBucketeer } from "../../attachment-upload.js";
import { createToolFactory, ToolCategory, PermissionLevel, } from "../tools2/types.js";
export const buildCaptureScreenshotToolFactory = createToolFactory("build_captureScreenshot", ({ clark, services, }) => {
    return {
        category: ToolCategory.DEBUG,
        defaultPermission: PermissionLevel.ALLOW,
        readOnly: true,
        skipOutputLimit: () => true, // Output is transformed to content type with media parts
        description: `
      Capture a screenshot of the current page of the running application.
      This tool allows you to visually verify that the UI matches the user's expectations
      and identify any layout, styling, or visual issues.

      Be thorough in your visual verification.

      **IMPORTANT: Describe before concluding**
      When verifying that a visual change worked, do NOT jump to a conclusion.
      Instead, first DESCRIBE in concrete detail what you actually see in the screenshot
      (e.g. colors, borders, spacing, text content, element positions). THEN compare
      your description against the expected result. This avoids confirmation bias where
      you assume changes worked without carefully examining the image.

      **MANDATORY: Loading / partial-render state detection and retry**
      After every screenshot, you MUST check for ANY of these loading signals before drawing conclusions:
        • skeleton/shimmer placeholders (pulsing grey rectangles)
        • spinner icons or circular progress indicators
        • empty tables, empty charts, or "No data" placeholders that should have data
        • grey or blank boxes where content is expected
        • text like "Loading…", "Fetching…", or similar

      If you detect ANY of the above:
        1. State explicitly: "Loading indicators detected — retrying."
        2. Wait at least 3 seconds.
        3. Take another screenshot.
        4. Repeat up to 3 times. Only after 3 consecutive retries still show loading may you proceed, and you MUST note that the page may not be fully loaded.
      Do NOT skip this procedure. Do NOT conclude the UI "looks correct" while loading indicators are visible. Any conclusion made on a loading-state screenshot is invalid.

      Do NOT take multiple selector-based screenshots to inspect different parts of the same page unless you need element-level detail.
      Use a CSS selector only when the user asks to focus on a specific element, or when a full-page screenshot is unreadable and one targeted follow-up is necessary.
      If the selector doesn't match any element or the matched element has no visible dimensions, an error is returned.

      **LIMITATIONS:**
      - This tool only captures the currently visible page/route in the app preview
      - You CANNOT navigate to different pages or routes to take screenshots - you can only see what's currently displayed
      - This is a read-only tool: you cannot interact with, click, or modify the UI through this tool
      - Very tall or wide pages (>8000px in either dimension) will be automatically scaled down to fit within model limits
      `,
        inputSchema: z.object({
            selector: z
                .string()
                .optional()
                .describe("Optional CSS selector to capture a specific element (e.g. '#my-component', '.sidebar', '[data-testid=\"header\"]'). If omitted, captures the full page. Prefer omitting this and taking one full-page screenshot first; use selector only for explicit, narrow follow-up checks."),
        }),
        execute: async ({ selector }) => {
            const logger = clark.logger;
            logger.info(`[build_captureScreenshot] Starting screenshot capture${selector ? ` (selector: "${selector}")` : ""}`);
            if (!clark.context.peer) {
                const error = "Editor client not available - cannot capture screenshot";
                logger.error(`[build_captureScreenshot] ${error}`);
                throw new Error(error);
            }
            logger.info(`[build_captureScreenshot] Editor client available, sending capture request...`);
            try {
                const requestStartTime = Date.now();
                const response = await clark.context.peer.call.aiCaptureScreenshot(selector ? { selector } : undefined);
                const requestDuration = Date.now() - requestStartTime;
                logger.info(`[build_captureScreenshot] Received RPC response after ${requestDuration}ms`);
                const { dataUrl } = validateScreenshotResponse(response, "build_captureScreenshot");
                logger.info(`[build_captureScreenshot] Successfully captured screenshot (${dataUrl.length} chars)`);
                const jwt = clark.context.jwt;
                if (jwt) {
                    try {
                        const uploadResult = await uploadAttachmentToBucketeer({
                            applicationId: services.applicationId,
                            file: { data: dataUrl, mimeType: "image/webp" },
                            fileName: "captured-screenshot.webp",
                            prefix: "captured-screenshot",
                            useServerUpload: services.features.aiFileUploadEnabled === true,
                            superblocksBaseUrl: services.superblocksBaseUrl,
                            jwt,
                            logger,
                        });
                        logger.info(`[build_captureScreenshot] Uploaded to S3: ${uploadResult.fileName}`);
                        return {
                            dataUrl,
                            fileName: uploadResult.fileName,
                            shortLivedUrl: uploadResult.url,
                            mimeType: "image/webp",
                            expirationAt: uploadResult.expirationDate,
                            message: `Successfully captured screenshot.
REMINDER: Before making any conclusions, check this screenshot for loading indicators (skeletons, spinners, empty tables/charts, grey placeholder boxes). If ANY are visible, you MUST wait a few seconds and take another screenshot — do NOT treat a loading-state screenshot as the final result.
If the screenshot is blank, check error logs. If there is nothing reported, consider using the reloadFile tool to reload the entire app.`,
                        };
                    }
                    catch (uploadError) {
                        logger.warn(`[build_captureScreenshot] S3 upload failed, falling back to base64`, getErrorMeta(uploadError));
                        // Fall through to base64 fallback
                    }
                }
                else {
                    logger.warn(`[build_captureScreenshot] No JWT available, falling back to base64`);
                }
                return {
                    dataUrl,
                    message: `Successfully captured screenshot.
REMINDER: Before making any conclusions, check this screenshot for loading indicators (skeletons, spinners, empty tables/charts, grey placeholder boxes). If ANY are visible, you MUST wait a few seconds and take another screenshot — do NOT treat a loading-state screenshot as the final result.
If the screenshot is blank, check error logs. If there is nothing reported, consider using the reloadFile tool to reload the entire app.`,
                };
            }
            catch (error) {
                const errorMeta = getErrorMeta(error);
                logger.error(`[build_captureScreenshot] Error capturing screenshot: ${errorMeta.error.message}`, errorMeta);
                throw error;
            }
        },
        getActionName: () => ({
            future: "capture a screenshot of the running app",
            present: "capturing a screenshot of the running app",
            past: "captured a screenshot of the running app",
        }),
        toModelOutput: ({ output }) => screenshotToModelOutput(output, "build_captureScreenshot"),
        getSummary: () => {
            return { output: "Screenshot captured" };
        },
    };
});
/**
 * Validates a screenshot capture response and throws if invalid.
 * @param response The response from the screenshot RPC call
 * @param toolName Tool name prefix for logging (e.g., "build_captureScreenshot")
 * @returns The validated response with dataUrl
 * @throws Error if response is invalid
 */
export function validateScreenshotResponse(response, toolName) {
    const logger = getPrefixedLogger(`[${toolName}]`);
    if (!response) {
        const error = "Screenshot capture returned null/undefined response";
        logger.error(error);
        throw new Error(error);
    }
    if (response.error) {
        const error = `Screenshot capture failed: ${response.error}`;
        logger.error(error);
        throw new Error(error);
    }
    if (!response.dataUrl ||
        typeof response.dataUrl !== "string" ||
        response.dataUrl.trim() === "") {
        const error = "Screenshot capture returned no data URL or invalid data URL";
        logger.error(`${error}. Response: ${safeJsonStringify(response)}`);
        throw new Error(error);
    }
    return { dataUrl: response.dataUrl };
}
function isScreenshotModelOutput(value) {
    if (!value || typeof value !== "object") {
        return false;
    }
    const output = value;
    if (output.type === "text" || output.type === "error-text") {
        return typeof output.value === "string";
    }
    if (output.type !== "content" || !Array.isArray(output.value)) {
        return false;
    }
    return output.value.every((part) => !!part &&
        typeof part === "object" &&
        part.type === "image-data" &&
        typeof part.data === "string" &&
        typeof part.mediaType === "string");
}
export function screenshotToModelOutput(output, toolName) {
    const logger = getPrefixedLogger(`[${toolName}]`);
    if (!output || typeof output !== "object") {
        logger.debug(`output is missing or invalid. Output: ${safeJsonStringify(output)}`);
        return {
            type: "text",
            value: "Screenshot captured but output is missing or invalid",
        };
    }
    if (isScreenshotModelOutput(output) && output.type === "error-text") {
        return output;
    }
    if ("dataUrl" in output && output.dataUrl !== undefined) {
        const dataUrl = output.dataUrl;
        if (typeof dataUrl !== "string") {
            logger.debug(`dataUrl is missing or invalid. Output: ${safeJsonStringify(output)}`);
            return {
                type: "text",
                value: "Screenshot captured but data URL is missing or invalid",
            };
        }
        const parts = dataUrl.split(",");
        if (parts.length < 2) {
            logger.debug(`data URL format is unexpected. dataUrl length: ${dataUrl.length}`);
            return {
                type: "text",
                value: "Screenshot captured (data URL format error)",
            };
        }
        const [header, base64Data] = parts;
        if (!base64Data) {
            logger.debug(`base64 data is missing after split. Header: ${header}`);
            return {
                type: "text",
                value: "Screenshot captured (base64 data missing)",
            };
        }
        const mediaTypeMatch = header?.match(/data:([^;]+)/);
        const mediaType = mediaTypeMatch?.[1] || "image/png";
        logger.debug(`Successfully converted to content type with media (type: ${mediaType}, data length: ${base64Data.length})`);
        return {
            type: "content",
            value: [
                {
                    type: "image-data",
                    data: base64Data,
                    mediaType: mediaType,
                },
            ],
        };
    }
    if ("type" in output &&
        (output.type === "content" || output.type === "text")) {
        return output;
    }
    logger.debug(`output.type is missing or invalid. Output: ${safeJsonStringify(output)}`);
    return {
        type: "text",
        value: "Output is missing or invalid",
    };
}
//# sourceMappingURL=build-capture-screenshot.js.map