import { Type } from "@sinclair/typebox";
import { StringEnum } from "@nerf/pi-ai";
import { diffCompactStates, } from "../core.js";
import { setLastActionBeforeState, setLastActionAfterState, } from "../state.js";
import { readFocusedDescriptor } from "../settle.js";
/** Escape special RegExp characters before passing user-controlled strings to new RegExp(). */
function escapeRegExp(s) {
    return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
export function registerInteractionTools(pi, deps) {
    // -------------------------------------------------------------------------
    // browser_click
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_click",
        label: "Browser Click",
        description: "Click an element on the page by CSS selector or by x,y coordinates. Returns a compact page summary plus lightweight verification details after clicking. Provide either selector or both x and y. Prefer selector over coordinates — selectors are more reliable because they handle shadow DOM via getByRole fallbacks. Use coordinates only when you have no other option.",
        parameters: Type.Object({
            selector: Type.Optional(Type.String({ description: "CSS selector of the element to click. The tool will try getByRole fallbacks if the CSS selector fails (handles shadow DOM)." })),
            x: Type.Optional(Type.Number({ description: "X coordinate to click" })),
            y: Type.Optional(Type.Number({ description: "Y coordinate to click" })),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            let actionId = null;
            let beforeState = null;
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                beforeState = await deps.captureCompactPageState(p, { selectors: params.selector ? [params.selector] : [], includeBodyText: true, target });
                actionId = deps.beginTrackedAction("browser_click", params, beforeState.url).id;
                const beforeUrl = p.url();
                const beforeHash = deps.getUrlHash(beforeUrl);
                const beforeTargetState = params.selector
                    ? await deps.captureClickTargetState(target, params.selector)
                    : null;
                if (params.selector) {
                    try {
                        await target.locator(params.selector).first().click({ timeout: 5000 });
                    }
                    catch {
                        const nameMatch = params.selector.match(/\[(?:aria-label|name|placeholder)="([^"]+)"\]/i);
                        const roleName = nameMatch?.[1];
                        let clicked = false;
                        for (const role of ["combobox", "searchbox", "textbox", "button", "link"]) {
                            try {
                                const loc = roleName
                                    ? target.getByRole(role, { name: new RegExp(escapeRegExp(roleName), "i") })
                                    : target.getByRole(role);
                                await loc.first().click({ timeout: 3000 });
                                clicked = true;
                                break;
                            }
                            catch { /* try next role */ }
                        }
                        if (!clicked) {
                            if (params.x !== undefined && params.y !== undefined) {
                                await p.mouse.click(params.x, params.y);
                            }
                            else {
                                throw new Error(`Could not click selector "${params.selector}" — element not found (shadow DOM?)`);
                            }
                        }
                    }
                }
                else if (params.x !== undefined && params.y !== undefined) {
                    await p.mouse.click(params.x, params.y);
                }
                else {
                    return {
                        content: [
                            {
                                type: "text",
                                text: "Must provide either selector or both x and y coordinates",
                            },
                        ],
                        details: {},
                        isError: true,
                    };
                }
                const settle = await deps.settleAfterActionAdaptive(p);
                const afterState = await deps.captureCompactPageState(p, { selectors: params.selector ? [params.selector] : [], includeBodyText: true, target });
                const url = afterState.url;
                const hash = deps.getUrlHash(url);
                const afterTargetState = params.selector
                    ? await deps.captureClickTargetState(target, params.selector)
                    : null;
                const targetStateChanged = !!beforeTargetState && !!afterTargetState && (beforeTargetState.exists !== afterTargetState.exists ||
                    beforeTargetState.ariaExpanded !== afterTargetState.ariaExpanded ||
                    beforeTargetState.ariaPressed !== afterTargetState.ariaPressed ||
                    beforeTargetState.ariaSelected !== afterTargetState.ariaSelected ||
                    beforeTargetState.open !== afterTargetState.open);
                const verification = deps.verificationFromChecks([
                    { name: "url_changed", passed: url !== beforeUrl, value: url, expected: `!= ${beforeUrl}` },
                    { name: "hash_changed", passed: hash !== beforeHash, value: hash, expected: `!= ${beforeHash}` },
                    { name: "target_state_changed", passed: targetStateChanged, value: afterTargetState, expected: beforeTargetState },
                    { name: "dialog_open", passed: afterState.dialog.count > beforeState.dialog.count, value: afterState.dialog.count, expected: `> ${beforeState.dialog.count}` },
                ], "Try a more specific selector or click a clearly interactive element.");
                const clickTarget = params.selector ?? `(${params.x}, ${params.y})`;
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                const diff = diffCompactStates(beforeState, afterState);
                setLastActionBeforeState(beforeState);
                setLastActionAfterState(afterState);
                deps.finishTrackedAction(actionId, {
                    status: "success",
                    afterUrl: afterState.url,
                    verificationSummary: verification.verificationSummary,
                    warningSummary: jsErrors.trim() || undefined,
                    diffSummary: diff.summary,
                    changed: diff.changed,
                    beforeState: beforeState,
                    afterState,
                });
                return {
                    content: [{ type: "text", text: `Clicked: ${clickTarget}\nURL: ${url}\nAction: ${actionId}\n${deps.verificationLine(verification)}${jsErrors}\n\nDiff:\n${deps.formatDiffText(diff)}\n\nPage summary:\n${summary}` }],
                    details: { target: clickTarget, url, actionId, diff, ...settle, ...verification },
                };
            }
            catch (err) {
                if (actionId !== null) {
                    deps.finishTrackedAction(actionId, { status: "error", afterUrl: deps.getActivePageOrNull()?.url() ?? "", error: err.message, beforeState: beforeState ?? undefined });
                }
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Click failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return {
                    content,
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_drag
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_drag",
        label: "Browser Drag",
        description: "Drag an element and drop it onto another element. Use for sortable lists, kanban boards, sliders, and any drag-and-drop UI.",
        parameters: Type.Object({
            sourceSelector: Type.String({
                description: "CSS selector of the element to drag",
            }),
            targetSelector: Type.String({
                description: "CSS selector of the element to drop onto",
            }),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                await target.dragAndDrop(params.sourceSelector, params.targetSelector, { timeout: 10000 });
                const settle = await deps.settleAfterActionAdaptive(p);
                const afterState = await deps.captureCompactPageState(p, { includeBodyText: false, target });
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                return {
                    content: [{
                            type: "text",
                            text: `Dragged "${params.sourceSelector}" → "${params.targetSelector}"${jsErrors}\n\nPage summary:\n${summary}`,
                        }],
                    details: { source: params.sourceSelector, target: params.targetSelector, ...settle },
                };
            }
            catch (err) {
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Drag failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return { content, details: { error: err.message }, isError: true };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_type
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_type",
        label: "Browser Type",
        description: "Type text into an input element. By default uses atomic fill (clears and sets value instantly). Use 'slowly' for character-by-character typing when you need to trigger key handlers (e.g. search autocomplete). Use 'submit' to press Enter after typing. Returns a compact page summary plus lightweight verification details. IMPORTANT: Always provide a selector — do NOT rely on coordinate clicks to focus an input before calling this. CSS attribute selectors like combobox[aria-label='X'] work for most inputs; for shadow DOM inputs (e.g. Google Search), the tool automatically tries getByRole fallbacks.",
        parameters: Type.Object({
            text: Type.String({ description: "Text to type" }),
            selector: Type.Optional(Type.String({ description: "CSS selector of the input to type into (clicks it first). Examples: 'input[name=q]', 'textarea', 'combobox[aria-label=\"Search\"]'. The tool will try getByRole fallbacks if the CSS selector fails." })),
            clearFirst: Type.Optional(Type.Boolean({
                description: "Clear the input's existing value before typing (default: false). Use this when replacing existing text.",
            })),
            submit: Type.Optional(Type.Boolean({
                description: "Press Enter after typing to submit the form (default: false).",
            })),
            slowly: Type.Optional(Type.Boolean({
                description: "Type one character at a time instead of filling atomically. Use when you need to trigger key handlers (e.g. search autocomplete). Default: false.",
            })),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            let actionId = null;
            let beforeState = null;
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                beforeState = await deps.captureCompactPageState(p, { selectors: params.selector ? [params.selector] : [], includeBodyText: true, target });
                actionId = deps.beginTrackedAction("browser_type", params, beforeState.url).id;
                const beforeUrl = p.url();
                async function focusViaRole(selector) {
                    const nameMatch = selector.match(/\[(?:aria-label|name|placeholder)="([^"]+)"\]/i);
                    const roleName = nameMatch?.[1];
                    for (const role of ["combobox", "searchbox", "textbox"]) {
                        try {
                            const loc = roleName
                                ? target.getByRole(role, { name: new RegExp(escapeRegExp(roleName), "i") })
                                : target.getByRole(role);
                            await loc.first().click({ timeout: 3000 });
                            return true;
                        }
                        catch { /* try next */ }
                    }
                    return false;
                }
                if (params.selector) {
                    if (params.slowly) {
                        let focused = false;
                        try {
                            await target.locator(params.selector).first().click({ timeout: 5000 });
                            focused = true;
                        }
                        catch {
                            focused = await focusViaRole(params.selector);
                        }
                        if (!focused)
                            throw new Error(`Could not focus selector "${params.selector}"`);
                        if (params.clearFirst) {
                            await p.keyboard.press("Control+A");
                            await p.keyboard.press("Delete");
                        }
                        await p.keyboard.type(params.text);
                    }
                    else {
                        let filled = false;
                        try {
                            await target.locator(params.selector).first().fill(params.text, { timeout: 5000 });
                            filled = true;
                        }
                        catch { /* fall through */ }
                        if (!filled) {
                            const nameMatch = params.selector.match(/\[(?:aria-label|name|placeholder)="([^"]+)"\]/i);
                            const roleName = nameMatch?.[1];
                            for (const role of ["combobox", "searchbox", "textbox"]) {
                                try {
                                    const loc = roleName
                                        ? target.getByRole(role, { name: new RegExp(escapeRegExp(roleName), "i") })
                                        : target.getByRole(role);
                                    await loc.first().fill(params.text, { timeout: 3000 });
                                    filled = true;
                                    break;
                                }
                                catch { /* try next */ }
                            }
                        }
                        if (!filled) {
                            let focused = false;
                            try {
                                await target.locator(params.selector).first().click({ timeout: 5000 });
                                focused = true;
                            }
                            catch {
                                focused = await focusViaRole(params.selector);
                            }
                            if (!focused)
                                throw new Error(`Could not focus selector "${params.selector}"`);
                            if (params.clearFirst) {
                                await p.keyboard.press("Control+A");
                                await p.keyboard.press("Delete");
                            }
                            await target.locator(":focus").pressSequentially(params.text, { timeout: 5000 }).catch(() => p.keyboard.type(params.text));
                        }
                        else if (params.clearFirst) {
                            // fill() already replaced the value; clearFirst is a no-op here
                        }
                    }
                }
                else {
                    const hasFocus = await target.evaluate(() => {
                        const el = document.activeElement;
                        return !!(el && el !== document.body && el !== document.documentElement);
                    });
                    if (!hasFocus) {
                        return {
                            content: [{ type: "text", text: "Type failed: no element is focused. Use browser_click to focus an input first, or provide a selector." }],
                            details: { error: "no focused element" },
                            isError: true,
                        };
                    }
                    await target.locator(":focus").pressSequentially(params.text, { timeout: 10000 }).catch(() => p.keyboard.type(params.text));
                }
                if (params.submit) {
                    await p.keyboard.press("Enter");
                }
                const settle = await deps.settleAfterActionAdaptive(p);
                const typedValue = await deps.readInputLikeValue(target, params.selector);
                const afterUrl = p.url();
                const verification = deps.verificationFromChecks([
                    { name: "value_equals_expected", passed: typedValue === params.text, value: typedValue, expected: params.text },
                    { name: "value_contains_expected", passed: typeof typedValue === "string" && typedValue.includes(params.text), value: typedValue, expected: params.text },
                    { name: "url_changed_after_submit", passed: !!params.submit && afterUrl !== beforeUrl, value: afterUrl, expected: `!= ${beforeUrl}` },
                ], "Try clearFirst=true, use a more specific selector, or set slowly=true for key-driven inputs.");
                const typeTarget = params.selector ? ` into "${params.selector}"` : "";
                const afterState = await deps.captureCompactPageState(p, { selectors: params.selector ? [params.selector] : [], includeBodyText: true, target });
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                const diff = diffCompactStates(beforeState, afterState);
                setLastActionBeforeState(beforeState);
                setLastActionAfterState(afterState);
                deps.finishTrackedAction(actionId, {
                    status: "success",
                    afterUrl: afterState.url,
                    verificationSummary: verification.verificationSummary,
                    warningSummary: jsErrors.trim() || undefined,
                    diffSummary: diff.summary,
                    changed: diff.changed,
                    beforeState: beforeState,
                    afterState,
                });
                return {
                    content: [{ type: "text", text: `Typed "${params.text}"${typeTarget}\nAction: ${actionId}\n${deps.verificationLine(verification)}${jsErrors}\n\nDiff:\n${deps.formatDiffText(diff)}\n\nPage summary:\n${summary}` }],
                    details: { text: params.text, selector: params.selector, typedValue, actionId, diff, ...settle, ...verification },
                };
            }
            catch (err) {
                if (actionId !== null) {
                    deps.finishTrackedAction(actionId, { status: "error", afterUrl: deps.getActivePageOrNull()?.url() ?? "", error: err.message, beforeState: beforeState ?? undefined });
                }
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Type failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return {
                    content,
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_upload_file
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_upload_file",
        label: "Browser Upload File",
        description: "Set files on a file input element. The selector must target an <input type=\"file\"> element. Accepts one or more absolute file paths.",
        parameters: Type.Object({
            selector: Type.String({
                description: 'CSS selector targeting the <input type="file"> element',
            }),
            files: Type.Array(Type.String({ description: "Absolute path to a file" }), {
                description: "One or more file paths to upload",
            }),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                const cleanFiles = params.files.map((f) => f.replace(/^@/, ""));
                await target.locator(params.selector).first().setInputFiles(cleanFiles);
                const settle = await deps.settleAfterActionAdaptive(p);
                const afterState = await deps.captureCompactPageState(p, { includeBodyText: false, target });
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                return {
                    content: [{
                            type: "text",
                            text: `Uploaded ${cleanFiles.length} file(s) to "${params.selector}": ${cleanFiles.join(", ")}${jsErrors}\n\nPage summary:\n${summary}`,
                        }],
                    details: { selector: params.selector, files: cleanFiles, ...settle },
                };
            }
            catch (err) {
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Upload failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return { content, details: { error: err.message }, isError: true };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_scroll
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_scroll",
        label: "Browser Scroll",
        description: "Scroll the page up or down by a given number of pixels. Returns scroll position (px and percentage) and an accessibility snapshot of the visible content.",
        parameters: Type.Object({
            direction: StringEnum(["up", "down"]),
            amount: Type.Optional(Type.Number({ description: "Pixels to scroll (default: 300)" })),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                const pixels = params.amount ?? 300;
                const delta = params.direction === "up" ? -pixels : pixels;
                await p.mouse.wheel(0, delta);
                const settle = await deps.settleAfterActionAdaptive(p);
                const scrollInfo = await target.evaluate(() => ({
                    scrollY: Math.round(window.scrollY),
                    scrollHeight: document.documentElement.scrollHeight,
                    clientHeight: document.documentElement.clientHeight,
                }));
                const maxScroll = scrollInfo.scrollHeight - scrollInfo.clientHeight;
                const percent = maxScroll > 0 ? Math.round((scrollInfo.scrollY / maxScroll) * 100) : 0;
                const afterState = await deps.captureCompactPageState(p, { includeBodyText: false, target });
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                return {
                    content: [
                        {
                            type: "text",
                            text: `Scrolled ${params.direction} by ${pixels}px\n` +
                                `Position: ${scrollInfo.scrollY}px / ${scrollInfo.scrollHeight}px (${percent}% down)\n` +
                                `Viewport height: ${scrollInfo.clientHeight}px${jsErrors}\n\nPage summary:\n${summary}`,
                        },
                    ],
                    details: { direction: params.direction, amount: pixels, ...scrollInfo, percent, ...settle },
                };
            }
            catch (err) {
                return {
                    content: [{ type: "text", text: `Scroll failed: ${err.message}` }],
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_hover
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_hover",
        label: "Browser Hover",
        description: "Move the mouse over an element to trigger hover states — reveals tooltips, dropdown menus, CSS :hover effects, and other hover-dependent UI. Returns a compact page summary showing the resulting hover state.",
        parameters: Type.Object({
            selector: Type.String({
                description: "CSS selector of the element to hover over",
            }),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                await target.locator(params.selector).first().hover({ timeout: 10000 });
                const settle = await deps.settleAfterActionAdaptive(p);
                const afterState = await deps.captureCompactPageState(p, { includeBodyText: false, target });
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                return {
                    content: [{ type: "text", text: `Hovering over "${params.selector}"${jsErrors}\n\nPage summary:\n${summary}` }],
                    details: { selector: params.selector, ...settle },
                };
            }
            catch (err) {
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Hover failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return {
                    content,
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_key_press
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_key_press",
        label: "Browser Key Press",
        description: "Press a keyboard key or key combination. Returns a compact page summary plus lightweight verification details after the key press. Use for: submitting forms (Enter), closing modals (Escape), navigating focusable elements (Tab / Shift+Tab), operating dropdowns and menus (ArrowDown, ArrowUp, Space), copying/pasting (Meta+C, Meta+V). Key names follow the DOM KeyboardEvent key convention.",
        parameters: Type.Object({
            key: Type.String({
                description: "Key or combination to press, e.g. 'Enter', 'Escape', 'Tab', 'ArrowDown', 'ArrowUp', 'Space', 'Meta+A', 'Shift+Tab', 'Control+Enter'",
            }),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            let actionId = null;
            let beforeState = null;
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                beforeState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
                actionId = deps.beginTrackedAction("browser_key_press", params, beforeState.url).id;
                const beforeUrl = p.url();
                const beforeFocus = await readFocusedDescriptor(target);
                await p.keyboard.press(params.key);
                const settle = await deps.settleAfterActionAdaptive(p, { checkFocusStability: true });
                const afterState = await deps.captureCompactPageState(p, { includeBodyText: true, target });
                const afterUrl = afterState.url;
                const afterFocus = await readFocusedDescriptor(target);
                const verification = deps.verificationFromChecks([
                    { name: "url_changed", passed: afterUrl !== beforeUrl, value: afterUrl, expected: `!= ${beforeUrl}` },
                    { name: "focus_changed", passed: afterFocus !== beforeFocus, value: afterFocus, expected: `!= ${beforeFocus}` },
                    { name: "dialog_open", passed: afterState.dialog.count > beforeState.dialog.count, value: afterState.dialog.count, expected: `> ${beforeState.dialog.count}` },
                ], "If this key should trigger UI changes, confirm focus is on the intended element first.");
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                const diff = diffCompactStates(beforeState, afterState);
                setLastActionBeforeState(beforeState);
                setLastActionAfterState(afterState);
                deps.finishTrackedAction(actionId, {
                    status: "success",
                    afterUrl: afterState.url,
                    verificationSummary: verification.verificationSummary,
                    warningSummary: jsErrors.trim() || undefined,
                    diffSummary: diff.summary,
                    changed: diff.changed,
                    beforeState: beforeState,
                    afterState,
                });
                return {
                    content: [{ type: "text", text: `Pressed "${params.key}"\nAction: ${actionId}\n${deps.verificationLine(verification)}${jsErrors}\n\nDiff:\n${deps.formatDiffText(diff)}\n\nPage summary:\n${summary}` }],
                    details: { key: params.key, beforeFocus, afterFocus, actionId, diff, ...settle, ...verification },
                };
            }
            catch (err) {
                if (actionId !== null) {
                    deps.finishTrackedAction(actionId, { status: "error", afterUrl: deps.getActivePageOrNull()?.url() ?? "", error: err.message, beforeState: beforeState ?? undefined });
                }
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Key press failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return {
                    content,
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_select_option
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_select_option",
        label: "Browser Select Option",
        description: "Select an option from a <select> dropdown element by its visible label or value. Returns a compact page summary plus lightweight verification details. For custom-built dropdowns use browser_click to open them then browser_click to pick the option.",
        parameters: Type.Object({
            selector: Type.String({
                description: "CSS selector targeting the <select> element",
            }),
            option: Type.String({
                description: "The option to select — can be the visible label text or the value attribute. Will try label first, then value.",
            }),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            let actionId = null;
            let beforeState = null;
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                beforeState = await deps.captureCompactPageState(p, { selectors: [params.selector], includeBodyText: true, target });
                actionId = deps.beginTrackedAction("browser_select_option", params, beforeState.url).id;
                let selected;
                try {
                    selected = await target.selectOption(params.selector, { label: params.option }, { timeout: 5000 });
                }
                catch {
                    selected = await target.selectOption(params.selector, { value: params.option }, { timeout: 5000 });
                }
                const settle = await deps.settleAfterActionAdaptive(p);
                const selectedState = await target.locator(params.selector).first().evaluate((el) => {
                    if (!(el instanceof HTMLSelectElement)) {
                        return { selectedValues: [], selectedLabels: [] };
                    }
                    const selectedOptions = Array.from(el.selectedOptions || []);
                    return {
                        selectedValues: selectedOptions.map((opt) => opt.value),
                        selectedLabels: selectedOptions.map((opt) => (opt.textContent || "").trim()),
                    };
                });
                const optionNeedle = params.option.toLowerCase();
                const verification = deps.verificationFromChecks([
                    { name: "selected_values_include_option", passed: selectedState.selectedValues.includes(params.option), value: selectedState.selectedValues, expected: params.option },
                    { name: "selected_labels_include_option", passed: selectedState.selectedLabels.some((label) => label.toLowerCase().includes(optionNeedle)), value: selectedState.selectedLabels, expected: params.option },
                ], "Confirm whether the target select uses option label or value, then retry with that exact text.");
                const afterState = await deps.captureCompactPageState(p, { selectors: [params.selector], includeBodyText: true, target });
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                const diff = diffCompactStates(beforeState, afterState);
                setLastActionBeforeState(beforeState);
                setLastActionAfterState(afterState);
                deps.finishTrackedAction(actionId, {
                    status: "success",
                    afterUrl: afterState.url,
                    verificationSummary: verification.verificationSummary,
                    warningSummary: jsErrors.trim() || undefined,
                    diffSummary: diff.summary,
                    changed: diff.changed,
                    beforeState: beforeState,
                    afterState,
                });
                return {
                    content: [
                        {
                            type: "text",
                            text: `Selected "${params.option}" in "${params.selector}". Values: ${selected.join(", ")}\nAction: ${actionId}\n${deps.verificationLine(verification)}${jsErrors}\n\nDiff:\n${deps.formatDiffText(diff)}\n\nPage summary:\n${summary}`,
                        },
                    ],
                    details: { selector: params.selector, option: params.option, selected, selectedState, actionId, diff, ...settle, ...verification },
                };
            }
            catch (err) {
                if (actionId !== null) {
                    deps.finishTrackedAction(actionId, { status: "error", afterUrl: deps.getActivePageOrNull()?.url() ?? "", error: err.message, beforeState: beforeState ?? undefined });
                }
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Select option failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return {
                    content,
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_set_checked
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_set_checked",
        label: "Browser Set Checked",
        description: "Check or uncheck a checkbox or radio button. More reliable than clicking for form elements where you need a specific state.",
        parameters: Type.Object({
            selector: Type.String({
                description: "CSS selector targeting the checkbox or radio input",
            }),
            checked: Type.Boolean({
                description: "true to check, false to uncheck",
            }),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            let actionId = null;
            let beforeState = null;
            try {
                const { page: p } = await deps.ensureBrowser();
                const target = deps.getActiveTarget();
                beforeState = await deps.captureCompactPageState(p, { selectors: [params.selector], includeBodyText: true, target });
                actionId = deps.beginTrackedAction("browser_set_checked", params, beforeState.url).id;
                await target.locator(params.selector).first().setChecked(params.checked, { timeout: 10000 });
                const settle = await deps.settleAfterActionAdaptive(p);
                const actualChecked = await target.locator(params.selector).first().isChecked().catch(() => null);
                const verification = deps.verificationFromChecks([
                    { name: "checked_state_matches", passed: actualChecked === params.checked, value: actualChecked, expected: params.checked },
                ], "Ensure selector points to a checkbox/radio input and retry.");
                const state = params.checked ? "checked" : "unchecked";
                const afterState = await deps.captureCompactPageState(p, { selectors: [params.selector], includeBodyText: true, target });
                const summary = deps.formatCompactStateSummary(afterState);
                const jsErrors = deps.getRecentErrors(p.url());
                const diff = diffCompactStates(beforeState, afterState);
                setLastActionBeforeState(beforeState);
                setLastActionAfterState(afterState);
                deps.finishTrackedAction(actionId, {
                    status: "success",
                    afterUrl: afterState.url,
                    verificationSummary: verification.verificationSummary,
                    warningSummary: jsErrors.trim() || undefined,
                    diffSummary: diff.summary,
                    changed: diff.changed,
                    beforeState: beforeState,
                    afterState,
                });
                return {
                    content: [{
                            type: "text",
                            text: `Set "${params.selector}" to ${state}\nAction: ${actionId}\n${deps.verificationLine(verification)}${jsErrors}\n\nDiff:\n${deps.formatDiffText(diff)}\n\nPage summary:\n${summary}`,
                        }],
                    details: { selector: params.selector, checked: params.checked, actualChecked, actionId, diff, ...settle, ...verification },
                };
            }
            catch (err) {
                if (actionId !== null) {
                    deps.finishTrackedAction(actionId, { status: "error", afterUrl: deps.getActivePageOrNull()?.url() ?? "", error: err.message, beforeState: beforeState ?? undefined });
                }
                const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
                const content = [{ type: "text", text: `Set checked failed: ${err.message}` }];
                if (errorShot) {
                    content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
                }
                return { content, details: { error: err.message }, isError: true };
            }
        },
    });
    // -------------------------------------------------------------------------
    // browser_set_viewport
    // -------------------------------------------------------------------------
    pi.registerTool({
        name: "browser_set_viewport",
        label: "Browser Set Viewport",
        description: "Resize the browser viewport to test responsive layouts at different screen sizes. Use presets for common breakpoints or specify exact pixel dimensions. Essential for verifying mobile/tablet/desktop layouts.",
        parameters: Type.Object({
            preset: Type.Optional(StringEnum(["mobile", "tablet", "desktop", "wide"])),
            width: Type.Optional(Type.Number({ description: "Custom viewport width in pixels (requires height too)" })),
            height: Type.Optional(Type.Number({ description: "Custom viewport height in pixels (requires width too)" })),
        }),
        async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
            try {
                const { page: p } = await deps.ensureBrowser();
                let width;
                let height;
                let label;
                if (params.preset) {
                    switch (params.preset) {
                        case "mobile":
                            width = 390;
                            height = 844;
                            label = "mobile (390×844)";
                            break;
                        case "tablet":
                            width = 768;
                            height = 1024;
                            label = "tablet (768×1024)";
                            break;
                        case "desktop":
                            width = 1280;
                            height = 800;
                            label = "desktop (1280×800)";
                            break;
                        case "wide":
                            width = 1920;
                            height = 1080;
                            label = "wide (1920×1080)";
                            break;
                    }
                }
                else if (params.width !== undefined && params.height !== undefined) {
                    width = params.width;
                    height = params.height;
                    label = `custom (${width}×${height})`;
                }
                else {
                    return {
                        content: [
                            {
                                type: "text",
                                text: "Provide either a preset (mobile/tablet/desktop/wide) or both width and height.",
                            },
                        ],
                        details: {},
                        isError: true,
                    };
                }
                await p.setViewportSize({ width: width, height: height });
                return {
                    content: [{ type: "text", text: `Viewport set to ${label}` }],
                    details: { width: width, height: height, label: label },
                };
            }
            catch (err) {
                return {
                    content: [{ type: "text", text: `Set viewport failed: ${err.message}` }],
                    details: { error: err.message },
                    isError: true,
                };
            }
        },
    });
}
