"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActHandler = void 0;
// lib/v3/handlers/actHandler.ts
const inference_js_1 = require("../../inference.js");
const prompt_js_1 = require("../../prompt.js");
const utils_js_1 = require("../../utils.js");
const logger_js_1 = require("../logger.js");
const methods_js_1 = require("../types/public/methods.js");
const sdkErrors_js_1 = require("../types/public/sdkErrors.js");
const index_js_1 = require("../understudy/a11y/snapshot/index.js");
const index_js_2 = require("../types/private/index.js");
const actHandlerUtils_js_1 = require("./handlerUtils/actHandlerUtils.js");
const timeoutGuard_js_1 = require("./handlerUtils/timeoutGuard.js");
const variables_js_1 = require("../agent/utils/variables.js");
class ActHandler {
    llmClient;
    defaultModelName;
    defaultClientOptions;
    resolveLlmClient;
    systemPrompt;
    logInferenceToFile;
    selfHeal;
    onMetrics;
    defaultDomSettleTimeoutMs;
    constructor(llmClient, defaultModelName, defaultClientOptions, resolveLlmClient, systemPrompt, logInferenceToFile, selfHeal, onMetrics, defaultDomSettleTimeoutMs) {
        this.llmClient = llmClient;
        this.defaultModelName = defaultModelName;
        this.defaultClientOptions = defaultClientOptions;
        this.resolveLlmClient = resolveLlmClient;
        this.systemPrompt = systemPrompt ?? "";
        this.logInferenceToFile = logInferenceToFile ?? false;
        this.selfHeal = !!selfHeal;
        this.onMetrics = onMetrics;
        this.defaultDomSettleTimeoutMs = defaultDomSettleTimeoutMs;
    }
    recordActMetrics(response) {
        this.onMetrics?.(methods_js_1.V3FunctionName.ACT, response.prompt_tokens ?? 0, response.completion_tokens ?? 0, response.reasoning_tokens ?? 0, response.cached_input_tokens ?? 0, response.inference_time_ms ?? 0);
    }
    async getActionFromLLM({ instruction, domElements, xpathMap, llmClient, requireMethodAndArguments = true, }) {
        const response = await (0, inference_js_1.act)({
            instruction,
            domElements,
            llmClient,
            userProvidedInstructions: this.systemPrompt,
            logger: logger_js_1.v3Logger,
            logInferenceToFile: this.logInferenceToFile,
        });
        this.recordActMetrics(response);
        const normalized = normalizeActInferenceElement(response.element, xpathMap, requireMethodAndArguments);
        if (!normalized) {
            return { response };
        }
        return {
            action: { ...normalized },
            response,
        };
    }
    async act(params) {
        const { instruction, page, variables, timeout, model } = params;
        const llmClient = this.resolveLlmClient(model);
        const effectiveTimeoutMs = typeof timeout === "number" && timeout > 0 ? timeout : undefined;
        const ensureTimeRemaining = (0, timeoutGuard_js_1.createTimeoutGuard)(effectiveTimeoutMs, (ms) => new sdkErrors_js_1.ActTimeoutError(ms));
        ensureTimeRemaining();
        await (0, actHandlerUtils_js_1.waitForDomNetworkQuiet)(page.mainFrame(), this.defaultDomSettleTimeoutMs);
        ensureTimeRemaining();
        const { combinedTree, combinedXpathMap } = await (0, index_js_1.captureHybridSnapshot)(page, { experimental: true });
        const actInstruction = (0, prompt_js_1.buildActPrompt)(instruction, Object.values(index_js_2.SupportedUnderstudyAction), variables);
        ensureTimeRemaining();
        const { action: firstAction, response: actInferenceResponse } = await this.getActionFromLLM({
            instruction: actInstruction,
            domElements: combinedTree,
            xpathMap: combinedXpathMap,
            llmClient,
        });
        if (!firstAction) {
            (0, logger_js_1.v3Logger)({
                category: "action",
                message: "no actionable element returned by LLM",
                level: 1,
            });
            return {
                success: false,
                message: "Failed to perform act: No action found",
                actionDescription: instruction,
                actions: [],
            };
        }
        // First action (self-heal aware path)
        ensureTimeRemaining();
        const firstResult = await this.takeDeterministicAction(firstAction, page, this.defaultDomSettleTimeoutMs, llmClient, ensureTimeRemaining, variables);
        // If not two-step, return the first action result
        if (actInferenceResponse?.twoStep !== true) {
            return firstResult;
        }
        // Take a new focused snapshot and observe again
        ensureTimeRemaining();
        const { combinedTree: combinedTree2, combinedXpathMap: combinedXpathMap2 } = await (0, index_js_1.captureHybridSnapshot)(page, {
            experimental: true,
        });
        let diffedTree = (0, index_js_1.diffCombinedTrees)(combinedTree, combinedTree2);
        if (!diffedTree.trim()) {
            // Fallback: if no diff detected, use the fresh tree to avoid empty context
            diffedTree = combinedTree2;
        }
        const previousAction = `method: ${firstAction.method}, description: ${firstAction.description}, arguments: ${firstAction.arguments}`;
        const stepTwoInstructions = (0, prompt_js_1.buildStepTwoPrompt)(instruction, previousAction, Object.values(index_js_2.SupportedUnderstudyAction).filter((action) => action !== index_js_2.SupportedUnderstudyAction.SELECT_OPTION_FROM_DROPDOWN), variables);
        ensureTimeRemaining();
        const { action: secondAction } = await this.getActionFromLLM({
            instruction: stepTwoInstructions,
            domElements: diffedTree,
            xpathMap: combinedXpathMap2,
            llmClient,
        });
        if (!secondAction) {
            // No second action found — return first result as-is
            return firstResult;
        }
        ensureTimeRemaining();
        const secondResult = await this.takeDeterministicAction(secondAction, page, this.defaultDomSettleTimeoutMs, llmClient, ensureTimeRemaining, variables);
        // Combine results
        return {
            success: firstResult.success && secondResult.success,
            message: secondResult.success
                ? `${firstResult.message} → ${secondResult.message}`
                : `${firstResult.message} → ${secondResult.message}`,
            actionDescription: firstResult.actionDescription,
            actions: [
                ...(firstResult.actions || []),
                ...(secondResult.actions || []),
            ],
        };
    }
    async takeDeterministicAction(action, page, domSettleTimeoutMs, llmClientOverride, ensureTimeRemaining, variables) {
        ensureTimeRemaining?.();
        const settleTimeout = domSettleTimeoutMs ?? this.defaultDomSettleTimeoutMs;
        const effectiveClient = llmClientOverride ?? this.llmClient;
        const method = action.method?.trim();
        if (!method || method === "not-supported") {
            (0, logger_js_1.v3Logger)({
                category: "action",
                message: "action has no supported method",
                level: 0,
                auxiliary: {
                    act: { value: JSON.stringify(action), type: "object" },
                },
            });
            return {
                success: false,
                message: `Unable to perform action: The method '${method ?? ""}' is not supported in Action. Please use a supported Playwright locator method.`,
                actionDescription: action.description || `Action (${method ?? "unknown"})`,
                actions: [],
            };
        }
        const placeholderArgs = Array.isArray(action.arguments)
            ? [...action.arguments]
            : [];
        const resolvedArgs = substituteVariablesInArguments(action.arguments, variables) ?? [];
        try {
            ensureTimeRemaining?.();
            await (0, actHandlerUtils_js_1.performUnderstudyMethod)(page, page.mainFrame(), method, action.selector, resolvedArgs, settleTimeout);
            return {
                success: true,
                message: `Action [${method}] performed successfully on selector: ${action.selector}`,
                actionDescription: action.description || `action (${method})`,
                actions: [
                    {
                        selector: action.selector,
                        description: action.description || `action (${method})`,
                        method,
                        arguments: placeholderArgs,
                    },
                ],
            };
        }
        catch (err) {
            if (err instanceof sdkErrors_js_1.ActTimeoutError) {
                throw err;
            }
            const msg = err instanceof Error ? err.message : String(err);
            // Attempt self-heal: rerun actInference and retry with updated selector
            if (this.selfHeal) {
                (0, logger_js_1.v3Logger)({
                    category: "action",
                    message: "Error performing action. Reprocessing the page and trying again",
                    level: 1,
                    auxiliary: {
                        error: { value: msg, type: "string" },
                        action: {
                            value: JSON.stringify(action),
                            type: "object",
                        },
                    },
                });
                try {
                    // Build an instruction combining method + description, avoiding duplication
                    const actCommand = action.description
                        ? action.description.toLowerCase().startsWith(method.toLowerCase())
                            ? action.description
                            : `${method} ${action.description}`
                        : method;
                    // Take a fresh snapshot and ask for a new actionable element
                    ensureTimeRemaining?.();
                    const { combinedTree, combinedXpathMap } = await (0, index_js_1.captureHybridSnapshot)(page, {
                        experimental: true,
                    });
                    const instruction = (0, prompt_js_1.buildActPrompt)(actCommand, Object.values(index_js_2.SupportedUnderstudyAction), {});
                    ensureTimeRemaining?.();
                    const { action: fallbackAction, response: fallbackResponse } = await this.getActionFromLLM({
                        instruction,
                        domElements: combinedTree,
                        xpathMap: combinedXpathMap,
                        llmClient: effectiveClient,
                        requireMethodAndArguments: false,
                    });
                    const fallbackElement = fallbackResponse.element;
                    if (!fallbackElement) {
                        return {
                            success: false,
                            message: "Failed to self-heal act: No observe results found for action",
                            actionDescription: actCommand,
                            actions: [],
                        };
                    }
                    // Retry with original method/args but new selector from fallback
                    let newSelector = action.selector;
                    if (fallbackAction?.selector) {
                        newSelector = fallbackAction.selector;
                    }
                    ensureTimeRemaining?.();
                    await (0, actHandlerUtils_js_1.performUnderstudyMethod)(page, page.mainFrame(), method, newSelector, resolvedArgs, settleTimeout);
                    return {
                        success: true,
                        message: `Action [${method}] performed successfully on selector: ${newSelector}`,
                        actionDescription: action.description || `action (${method})`,
                        actions: [
                            {
                                selector: newSelector,
                                description: action.description || `action (${method})`,
                                method,
                                arguments: placeholderArgs,
                            },
                        ],
                    };
                }
                catch (retryErr) {
                    if (retryErr instanceof sdkErrors_js_1.ActTimeoutError) {
                        throw retryErr;
                    }
                    const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
                    return {
                        success: false,
                        message: `Failed to perform act after self-heal: ${retryMsg}`,
                        actionDescription: action.description || `action (${method})`,
                        actions: [],
                    };
                }
            }
            return {
                success: false,
                message: `Failed to perform act: ${msg}`,
                actionDescription: action.description || `action (${method})`,
                actions: [],
            };
        }
    }
}
exports.ActHandler = ActHandler;
function normalizeActInferenceElement(element, xpathMap, requireMethodAndArguments = true) {
    if (!element) {
        return undefined;
    }
    const { elementId, description, method, arguments: args } = element;
    const hasArgs = Array.isArray(args);
    if (requireMethodAndArguments &&
        (!method || method === "not-supported" || !hasArgs)) {
        return undefined;
    }
    if (typeof elementId !== "string" || !elementId.includes("-")) {
        return undefined;
    }
    const xp = xpathMap[elementId];
    const trimmed = (0, utils_js_1.trimTrailingTextNode)(xp);
    if (!trimmed) {
        return undefined;
    }
    // For dragAndDrop, convert element ID in arguments to xpath (target element)
    let resolvedArgs = hasArgs ? args : undefined;
    if (method === "dragAndDrop" && hasArgs && args.length > 0) {
        const targetArg = args[0];
        // Check if argument looks like an element ID (e.g., "1-67")
        if (typeof targetArg === "string" && /^\d+-\d+$/.test(targetArg)) {
            const argXpath = xpathMap[targetArg];
            const trimmedArgXpath = (0, utils_js_1.trimTrailingTextNode)(argXpath);
            if (trimmedArgXpath) {
                resolvedArgs = [`xpath=${trimmedArgXpath}`, ...args.slice(1)];
            }
            else {
                // Target element lookup failed, filter out this action
                (0, logger_js_1.v3Logger)({
                    category: "action",
                    message: "dragAndDrop target element lookup failed",
                    level: 1,
                    auxiliary: {
                        targetElementId: { value: targetArg, type: "string" },
                        sourceElementId: { value: elementId, type: "string" },
                    },
                });
                return undefined;
            }
        }
        else {
            (0, logger_js_1.v3Logger)({
                category: "action",
                message: "dragAndDrop target element invalid ID format",
                level: 0,
                auxiliary: {
                    targetElementId: { value: String(targetArg), type: "string" },
                    sourceElementId: { value: elementId, type: "string" },
                },
            });
            return undefined;
        }
    }
    return {
        description,
        method,
        arguments: resolvedArgs,
        selector: `xpath=${trimmed}`,
    };
}
function substituteVariablesInArguments(args, variables) {
    if (!variables || !Array.isArray(args)) {
        return args;
    }
    return args.map((arg) => {
        let out = arg;
        for (const [key, v] of Object.entries(variables)) {
            const token = `%${key}%`;
            out = out.split(token).join((0, variables_js_1.resolveVariableValue)(v));
        }
        return out;
    });
}
//# sourceMappingURL=actHandler.js.map