/**
 * Browser Automation tools for Clawx.
 *
 * Uses Playwright to control a real browser — navigate, click, type, screenshot,
 * extract content, and run JavaScript. Enables agents to interact with web apps:
 * sign in, fill forms, navigate dashboards, scrape dynamic content, etc.
 *
 * Playwright browsers are installed on first use via `npx playwright install chromium`.
 * A single browser instance is shared across tool calls and reused between invocations.
 */
import { Type } from "@sinclair/typebox";
import { log } from "../utils/logger.js";
// ─── Lazy Playwright import ──────────────────────────────────────────────────
// Playwright is optional — only loaded when browser tools are actually called.
// This avoids crashing agents that don't need browser control.
let pw = null;
let browser = null;
let browserContext = null;
let activePage = null;
let lastElementMap = [];
/** Get element from the last snapshot by its number */
function getSnapshotElement(num) {
    return lastElementMap.find(e => e.index === num);
}
async function getPlaywright() {
    if (!pw) {
        try {
            pw = await import('playwright');
        }
        catch {
            throw new Error('Playwright is not installed. Run: npm install playwright && npx playwright install chromium');
        }
    }
    return pw;
}
async function ensureBrowser() {
    if (browser && browser.isConnected())
        return;
    const { chromium } = await getPlaywright();
    browser = await chromium.launch({
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });
    browserContext = await browser.newContext({
        viewport: { width: 1280, height: 720 },
        userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    });
    activePage = await browserContext.newPage();
    log.info('Browser launched (Chromium, headless)');
}
async function getPage() {
    await ensureBrowser();
    if (!activePage || activePage.isClosed()) {
        activePage = await browserContext.newPage();
    }
    return activePage;
}
/** Take a screenshot and return it as base64 */
async function takeScreenshot() {
    const page = await getPage();
    const buffer = await page.screenshot({ type: 'png', fullPage: false });
    return buffer.toString('base64');
}
/** Wrap a base64 PNG screenshot in the image content format expected by Clawx/pi-ai */
function pngImage(base64) {
    return { type: "image", data: base64, mimeType: "image/png" };
}
/** Get a text summary of the current page state */
async function getPageState() {
    const page = await getPage();
    const url = page.url();
    const title = await page.title();
    return `Page: ${title}\nURL: ${url}`;
}
// ─── browser_open ────────────────────────────────────────────────────────────
const BrowserOpenSchema = Type.Object({
    url: Type.String({ description: "URL to navigate to" }),
    wait_until: Type.Optional(Type.String({ description: "Wait condition: 'load', 'domcontentloaded', 'networkidle' (default: 'domcontentloaded')" })),
});
export function createBrowserOpenTool() {
    return {
        name: "browser_open",
        label: "Browser Open",
        description: "Launch a browser and navigate to a URL. Returns the page title and a screenshot. " +
            "The browser stays open for subsequent browser_click, browser_type, browser_screenshot calls.",
        parameters: BrowserOpenSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            log.info(`browser_open: ${params.url}`);
            try {
                const page = await getPage();
                const waitUntil = (params.wait_until || 'domcontentloaded');
                await page.goto(params.url, { waitUntil, timeout: 30000 });
                const title = await page.title();
                const screenshot = await takeScreenshot();
                const durationMs = Date.now() - start;
                return {
                    content: [
                        { type: "text", text: `Navigated to: ${params.url}\nTitle: ${title}` },
                        pngImage(screenshot),
                    ],
                    details: { url: params.url, title, durationMs },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Failed to open ${params.url}: ${message}` }],
                    details: { url: params.url, title: '', durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_click ───────────────────────────────────────────────────────────
const BrowserClickSchema = Type.Object({
    element: Type.Optional(Type.Number({ description: "Element number from browser_snapshot (e.g. 5). Preferred over selector/text — just snapshot and click by number" })),
    selector: Type.Optional(Type.String({ description: "CSS selector of element to click (e.g. 'button.submit', '#login-btn', 'a[href=\"/signup\"]')" })),
    text: Type.Optional(Type.String({ description: "Click element containing this exact text (e.g. 'Sign In', 'Submit')" })),
    x: Type.Optional(Type.Number({ description: "X coordinate to click (use with y for coordinate-based clicking)" })),
    y: Type.Optional(Type.Number({ description: "Y coordinate to click (use with x for coordinate-based clicking)" })),
});
export function createBrowserClickTool() {
    return {
        name: "browser_click",
        label: "Browser Click",
        description: "Click an element on the current page. Best used with element number from browser_snapshot. " +
            "Also supports CSS selector, text content, or x/y coordinates. Returns a screenshot after clicking.",
        parameters: BrowserClickSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            log.info(`browser_click: selector=${params.selector} text=${params.text} xy=${params.x},${params.y}`);
            try {
                const page = await getPage();
                let clicked = '';
                if (params.element !== undefined) {
                    const el = getSnapshotElement(params.element);
                    if (!el) {
                        return {
                            content: [{ type: "text", text: `Element ${params.element} not found. Run browser_snapshot first to get element numbers.` }],
                            details: { error: 'element not found' },
                        };
                    }
                    await page.click(el.selector, { timeout: 10000 });
                    clicked = `element [${el.index}] <${el.tag}> "${el.text || el.ariaLabel || el.placeholder || ''}"`;
                }
                else if (params.selector) {
                    await page.click(params.selector, { timeout: 10000 });
                    clicked = `selector: ${params.selector}`;
                }
                else if (params.text) {
                    await page.getByText(params.text, { exact: false }).first().click({ timeout: 10000 });
                    clicked = `text: "${params.text}"`;
                }
                else if (params.x !== undefined && params.y !== undefined) {
                    await page.mouse.click(params.x, params.y);
                    clicked = `coordinates: (${params.x}, ${params.y})`;
                }
                else {
                    return {
                        content: [{ type: "text", text: "Provide element number, selector, text, or x/y coordinates to click." }],
                        details: { error: 'no target' },
                    };
                }
                // Wait for SPA transitions — try to detect navigation or DOM changes
                try {
                    await page.waitForLoadState('domcontentloaded', { timeout: 3000 });
                }
                catch { /* page may not navigate — that's fine */ }
                await page.waitForTimeout(1000);
                const screenshot = await takeScreenshot();
                const state = await getPageState();
                return {
                    content: [
                        { type: "text", text: `Clicked: ${clicked}\n${state}` },
                        pngImage(screenshot),
                    ],
                    details: { clicked, durationMs: Date.now() - start },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Click failed: ${message}` }],
                    details: { error: message, durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_type ────────────────────────────────────────────────────────────
const BrowserTypeSchema = Type.Object({
    element: Type.Optional(Type.Number({ description: "Element number from browser_snapshot (e.g. 3). Preferred over selector — just snapshot and type by number" })),
    selector: Type.Optional(Type.String({ description: "CSS selector of input field (e.g. 'input[name=\"email\"]', '#password', 'textarea.comment')" })),
    label: Type.Optional(Type.String({ description: "Label text of the input field (e.g. 'Email', 'Password', 'Username')" })),
    placeholder: Type.Optional(Type.String({ description: "Placeholder text of the input field (e.g. 'Enter your email')" })),
    text: Type.String({ description: "Text to type into the field" }),
    clear: Type.Optional(Type.Boolean({ description: "Clear the field before typing (default: true)" })),
    press_enter: Type.Optional(Type.Boolean({ description: "Press Enter after typing (default: false)" })),
});
export function createBrowserTypeTool() {
    return {
        name: "browser_type",
        label: "Browser Type",
        description: "Type text into an input field on the current page. Best used with element number from browser_snapshot. " +
            "Also supports CSS selector, field label, or placeholder text. Optionally clear first and/or press Enter after.",
        parameters: BrowserTypeSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            log.info(`browser_type: selector=${params.selector} label=${params.label} text=${params.text.substring(0, 20)}...`);
            try {
                const page = await getPage();
                let target = '';
                let locator;
                if (params.element !== undefined) {
                    const el = getSnapshotElement(params.element);
                    if (!el) {
                        return {
                            content: [{ type: "text", text: `Element ${params.element} not found. Run browser_snapshot first to get element numbers.` }],
                            details: { error: 'element not found' },
                        };
                    }
                    locator = page.locator(el.selector).first();
                    target = `element [${el.index}] <${el.tag}> "${el.placeholder || el.name || el.ariaLabel || ''}"`;
                }
                else if (params.selector) {
                    locator = page.locator(params.selector).first();
                    target = `selector: ${params.selector}`;
                }
                else if (params.label) {
                    locator = page.getByLabel(params.label).first();
                    target = `label: "${params.label}"`;
                }
                else if (params.placeholder) {
                    locator = page.getByPlaceholder(params.placeholder).first();
                    target = `placeholder: "${params.placeholder}"`;
                }
                else {
                    return {
                        content: [{ type: "text", text: "Provide element number, selector, label, or placeholder to identify the input field." }],
                        details: { error: 'no target' },
                    };
                }
                // Click to focus the element first
                await locator.click({ timeout: 10000 });
                // Clear field if requested (default: true)
                if (params.clear !== false) {
                    // Select all and delete — works with React/Vue controlled inputs
                    await page.keyboard.press('Control+A');
                    await page.keyboard.press('Backspace');
                }
                // Use pressSequentially to simulate real keystrokes one at a time.
                // This fires keydown/keypress/keyup for each character, which React's
                // synthetic event system picks up (unlike fill() which sets value directly).
                await locator.pressSequentially(params.text, { delay: 30 });
                if (params.press_enter) {
                    await page.keyboard.press('Enter');
                    await page.waitForTimeout(500);
                }
                const screenshot = await takeScreenshot();
                const state = await getPageState();
                return {
                    content: [
                        { type: "text", text: `Typed into ${target}\n${state}` },
                        pngImage(screenshot),
                    ],
                    details: { target, textLength: params.text.length, durationMs: Date.now() - start },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Type failed: ${message}` }],
                    details: { error: message, durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_screenshot ──────────────────────────────────────────────────────
const BrowserScreenshotSchema = Type.Object({
    full_page: Type.Optional(Type.Boolean({ description: "Capture full scrollable page (default: false, captures viewport only)" })),
    selector: Type.Optional(Type.String({ description: "CSS selector to screenshot a specific element" })),
});
export function createBrowserScreenshotTool() {
    return {
        name: "browser_screenshot",
        label: "Browser Screenshot",
        description: "Take a screenshot of the current browser page. Returns the image so you can see what's on screen. " +
            "Use full_page to capture the entire scrollable page, or selector to capture a specific element.",
        parameters: BrowserScreenshotSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            log.info(`browser_screenshot: full_page=${params.full_page} selector=${params.selector}`);
            try {
                const page = await getPage();
                let buffer;
                if (params.selector) {
                    const element = page.locator(params.selector).first();
                    buffer = await element.screenshot({ type: 'png' });
                }
                else {
                    buffer = await page.screenshot({ type: 'png', fullPage: params.full_page || false });
                }
                const screenshot = buffer.toString('base64');
                const state = await getPageState();
                return {
                    content: [
                        { type: "text", text: state },
                        pngImage(screenshot),
                    ],
                    details: { durationMs: Date.now() - start },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Screenshot failed: ${message}` }],
                    details: { error: message, durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_navigate ────────────────────────────────────────────────────────
const BrowserNavigateSchema = Type.Object({
    action: Type.String({ description: "Navigation action: 'back', 'forward', 'refresh', or a URL to navigate to" }),
});
export function createBrowserNavigateTool() {
    return {
        name: "browser_navigate",
        label: "Browser Navigate",
        description: "Navigate the browser: go to a URL, go back, go forward, or refresh the page. " +
            "Returns a screenshot after navigation.",
        parameters: BrowserNavigateSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            log.info(`browser_navigate: ${params.action}`);
            try {
                const page = await getPage();
                switch (params.action) {
                    case 'back':
                        await page.goBack({ timeout: 15000 });
                        break;
                    case 'forward':
                        await page.goForward({ timeout: 15000 });
                        break;
                    case 'refresh':
                        await page.reload({ timeout: 15000 });
                        break;
                    default:
                        // Treat as URL
                        await page.goto(params.action, { waitUntil: 'domcontentloaded', timeout: 30000 });
                        break;
                }
                const screenshot = await takeScreenshot();
                const state = await getPageState();
                return {
                    content: [
                        { type: "text", text: `Navigated: ${params.action}\n${state}` },
                        pngImage(screenshot),
                    ],
                    details: { action: params.action, durationMs: Date.now() - start },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Navigation failed: ${message}` }],
                    details: { error: message, durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_extract ─────────────────────────────────────────────────────────
const BrowserExtractSchema = Type.Object({
    selector: Type.Optional(Type.String({ description: "CSS selector to extract from (default: extracts from entire page)" })),
    format: Type.Optional(Type.String({ description: "'text' for readable text, 'html' for raw HTML, 'links' for all links, 'forms' for form fields (default: 'text')" })),
    max_length: Type.Optional(Type.Number({ description: "Maximum characters to return (default: 20000)" })),
});
export function createBrowserExtractTool() {
    return {
        name: "browser_extract",
        label: "Browser Extract",
        description: "Extract content from the current browser page. Get readable text, raw HTML, all links, or form field details. " +
            "Use a CSS selector to extract from a specific element. Unlike web_read, this works on JavaScript-rendered pages.",
        parameters: BrowserExtractSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            const format = params.format || 'text';
            const maxLength = params.max_length || 20000;
            log.info(`browser_extract: format=${format} selector=${params.selector}`);
            try {
                const page = await getPage();
                const state = await getPageState();
                let content = '';
                if (format === 'links') {
                    // Runs in browser context — use string evaluate to avoid TS DOM errors
                    const links = await page.evaluate(`
            (function() {
              var sel = ${JSON.stringify(params.selector || null)};
              var root = sel ? document.querySelector(sel) : document;
              if (!root) return [];
              return Array.from(root.querySelectorAll('a[href]')).map(function(a) {
                return { text: (a.textContent || '').trim(), href: a.href };
              });
            })()
          `);
                    content = links.map((l, i) => `${i + 1}. [${l.text}](${l.href})`).join('\n');
                }
                else if (format === 'forms') {
                    const forms = await page.evaluate(`
            (function() {
              var sel = ${JSON.stringify(params.selector || null)};
              var root = sel ? document.querySelector(sel) : document;
              if (!root) return [];
              return Array.from(root.querySelectorAll('input, select, textarea, button')).map(function(el) {
                return {
                  tag: el.tagName.toLowerCase(),
                  type: el.type || '',
                  name: el.name || '',
                  id: el.id || '',
                  placeholder: el.placeholder || '',
                  value: el.value || '',
                  label: el.labels && el.labels[0] ? el.labels[0].textContent.trim() : '',
                  options: el.tagName === 'SELECT' ? Array.from(el.options).map(function(o) { return o.textContent.trim(); }) : undefined
                };
              });
            })()
          `);
                    content = forms.map((f) => {
                        let desc = `<${f.tag}`;
                        if (f.type)
                            desc += ` type="${f.type}"`;
                        if (f.name)
                            desc += ` name="${f.name}"`;
                        if (f.id)
                            desc += ` id="${f.id}"`;
                        if (f.placeholder)
                            desc += ` placeholder="${f.placeholder}"`;
                        if (f.label)
                            desc += ` label="${f.label}"`;
                        if (f.value)
                            desc += ` value="${f.value}"`;
                        desc += '>';
                        if (f.options)
                            desc += ` options: [${f.options.join(', ')}]`;
                        return desc;
                    }).join('\n');
                }
                else if (format === 'html') {
                    if (params.selector) {
                        content = await page.locator(params.selector).first().innerHTML();
                    }
                    else {
                        content = await page.content();
                    }
                }
                else {
                    // text
                    if (params.selector) {
                        content = await page.locator(params.selector).first().innerText();
                    }
                    else {
                        content = await page.evaluate('document.body.innerText');
                    }
                }
                if (content.length > maxLength) {
                    content = content.substring(0, maxLength) + `\n\n[... truncated at ${maxLength} characters]`;
                }
                return {
                    content: [{ type: "text", text: `${state}\n\n${content}` }],
                    details: { format, contentLength: content.length, durationMs: Date.now() - start },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Extract failed: ${message}` }],
                    details: { error: message, durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_eval ────────────────────────────────────────────────────────────
const BrowserEvalSchema = Type.Object({
    javascript: Type.String({ description: "JavaScript code to execute in the browser page context. Has access to document, window, etc. Return a value to get it back." }),
});
export function createBrowserEvalTool() {
    return {
        name: "browser_eval",
        label: "Browser Eval",
        description: "Execute JavaScript in the browser page context. Access document, window, localStorage, etc. " +
            "Use for scrolling, complex form interactions, reading cookies, or any DOM manipulation. " +
            "Return a value to get it back as the result.",
        parameters: BrowserEvalSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            log.info(`browser_eval: ${params.javascript.substring(0, 80)}...`);
            try {
                const page = await getPage();
                // Wrap in async IIFE so return statements work naturally.
                // If the code already works as an expression, the wrapping is harmless.
                const wrapped = `(async function() { ${params.javascript} })()`;
                const result = await page.evaluate(wrapped);
                const output = result === undefined ? '(undefined)' : JSON.stringify(result, null, 2);
                return {
                    content: [{ type: "text", text: `Result:\n${output}` }],
                    details: { resultType: typeof result, durationMs: Date.now() - start },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Eval failed: ${message}` }],
                    details: { error: message, durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_snapshot ────────────────────────────────────────────────────────
const BrowserSnapshotSchema = Type.Object({
    scroll_to: Type.Optional(Type.String({ description: "Scroll position before snapshot: 'top', 'bottom', or a number of pixels from top (default: no scroll)" })),
});
/**
 * JavaScript to run inside the page that:
 * 1. Finds all interactive elements
 * 2. Gets their bounding rects
 * 3. Injects numbered labels
 * 4. Returns the element map
 */
const SNAPSHOT_INJECT_JS = `
(function() {
  // Remove old labels if any
  document.querySelectorAll('[data-clawx-label]').forEach(function(el) { el.remove(); });

  var interactiveSelectors = 'a[href], button, input:not([type="hidden"]), textarea, select, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [onclick], [contenteditable="true"]';
  var elements = Array.from(document.querySelectorAll(interactiveSelectors));

  // Filter to visible elements only
  elements = elements.filter(function(el) {
    var rect = el.getBoundingClientRect();
    if (rect.width === 0 || rect.height === 0) return false;
    var style = window.getComputedStyle(el);
    if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
    return true;
  });

  var results = [];
  for (var i = 0; i < elements.length; i++) {
    var el = elements[i];
    var rect = el.getBoundingClientRect();
    var num = i + 1;

    // Build a reliable CSS selector for this element
    var selector = '';
    if (el.id) {
      selector = '#' + CSS.escape(el.id);
    } else {
      // Build a path-based selector
      var path = [];
      var current = el;
      while (current && current !== document.body) {
        var tag = current.tagName.toLowerCase();
        if (current.id) {
          path.unshift('#' + CSS.escape(current.id));
          break;
        }
        var parent = current.parentElement;
        if (parent) {
          var siblings = Array.from(parent.children).filter(function(c) {
            return c.tagName === current.tagName;
          });
          if (siblings.length > 1) {
            var idx = siblings.indexOf(current) + 1;
            tag += ':nth-of-type(' + idx + ')';
          }
        }
        path.unshift(tag);
        current = parent;
      }
      selector = path.join(' > ');
    }

    // Inject label badge
    var badge = document.createElement('div');
    badge.setAttribute('data-clawx-label', String(num));
    badge.style.cssText = 'position:fixed;z-index:2147483647;pointer-events:none;' +
      'background:#e63946;color:#fff;font-size:11px;font-weight:bold;font-family:monospace;' +
      'min-width:18px;height:18px;line-height:18px;text-align:center;border-radius:9px;' +
      'padding:0 4px;box-shadow:0 1px 3px rgba(0,0,0,0.4);' +
      'top:' + Math.max(0, rect.top - 10) + 'px;left:' + Math.max(0, rect.left - 10) + 'px;';
    badge.textContent = String(num);
    document.body.appendChild(badge);

    results.push({
      index: num,
      tag: el.tagName.toLowerCase(),
      type: el.type || '',
      text: (el.textContent || '').trim().substring(0, 80),
      name: el.name || '',
      id: el.id || '',
      placeholder: el.placeholder || '',
      href: el.href || '',
      ariaLabel: el.getAttribute('aria-label') || '',
      selector: selector,
      rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) }
    });
  }

  return results;
})()
`;
const SNAPSHOT_CLEANUP_JS = `
(function() {
  document.querySelectorAll('[data-clawx-label]').forEach(function(el) { el.remove(); });
})()
`;
export function createBrowserSnapshotTool() {
    return {
        name: "browser_snapshot",
        label: "Browser Snapshot",
        description: "Take an annotated screenshot of the current page — every interactive element (links, buttons, inputs, etc.) " +
            "gets a numbered red label. Returns the annotated screenshot plus an element list. " +
            "Then use browser_click or browser_type with the element number to interact. " +
            "This is the primary way to understand and interact with any web page — no CSS selectors needed.",
        parameters: BrowserSnapshotSchema,
        async execute(_toolCallId, params) {
            const start = Date.now();
            log.info('browser_snapshot');
            try {
                const page = await getPage();
                // Scroll if requested
                if (params.scroll_to) {
                    if (params.scroll_to === 'top') {
                        await page.evaluate('window.scrollTo(0, 0)');
                    }
                    else if (params.scroll_to === 'bottom') {
                        await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
                    }
                    else {
                        const px = parseInt(params.scroll_to, 10);
                        if (!isNaN(px)) {
                            await page.evaluate(`window.scrollTo(0, ${px})`);
                        }
                    }
                    await page.waitForTimeout(300);
                }
                // Inject labels and get element map
                const elements = await page.evaluate(SNAPSHOT_INJECT_JS);
                lastElementMap = elements;
                // Take screenshot with labels visible
                const buffer = await page.screenshot({ type: 'png', fullPage: false });
                const screenshot = buffer.toString('base64');
                // Remove labels after screenshot
                await page.evaluate(SNAPSHOT_CLEANUP_JS);
                const state = await getPageState();
                // Format element list
                const elementList = elements.map(el => {
                    let desc = `[${el.index}] <${el.tag}`;
                    if (el.type)
                        desc += ` type="${el.type}"`;
                    desc += '>';
                    if (el.text && el.tag !== 'input' && el.tag !== 'textarea')
                        desc += ` "${el.text}"`;
                    if (el.name)
                        desc += ` name="${el.name}"`;
                    if (el.placeholder)
                        desc += ` placeholder="${el.placeholder}"`;
                    if (el.ariaLabel)
                        desc += ` aria-label="${el.ariaLabel}"`;
                    if (el.href && el.tag === 'a')
                        desc += ` href="${el.href}"`;
                    return desc;
                }).join('\n');
                return {
                    content: [
                        { type: "text", text: `${state}\n\n${elements.length} interactive elements found:\n${elementList}` },
                        pngImage(screenshot),
                    ],
                    details: { elementCount: elements.length, durationMs: Date.now() - start },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Snapshot failed: ${message}` }],
                    details: { error: message, durationMs: Date.now() - start },
                };
            }
        },
    };
}
// ─── browser_close ───────────────────────────────────────────────────────────
const BrowserCloseSchema = Type.Object({});
export function createBrowserCloseTool() {
    return {
        name: "browser_close",
        label: "Browser Close",
        description: "Close the browser and free resources. A new browser will be launched on the next browser_open call.",
        parameters: BrowserCloseSchema,
        async execute() {
            log.info('browser_close');
            try {
                if (browser) {
                    await browser.close();
                    browser = null;
                    browserContext = null;
                    activePage = null;
                }
                return {
                    content: [{ type: "text", text: "Browser closed." }],
                    details: { closed: true },
                };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Close failed: ${message}` }],
                    details: { error: message },
                };
            }
        },
    };
}
// ─── Convenience: get all browser tools ──────────────────────────────────────
/** All browser tool names for use in whitelists */
export const BROWSER_TOOL_NAMES = [
    'browser_open',
    'browser_snapshot',
    'browser_click',
    'browser_type',
    'browser_screenshot',
    'browser_navigate',
    'browser_extract',
    'browser_eval',
    'browser_close',
];
/** Create all browser tools */
export function createAllBrowserTools() {
    return [
        createBrowserOpenTool(),
        createBrowserSnapshotTool(),
        createBrowserClickTool(),
        createBrowserTypeTool(),
        createBrowserScreenshotTool(),
        createBrowserNavigateTool(),
        createBrowserExtractTool(),
        createBrowserEvalTool(),
        createBrowserCloseTool(),
    ];
}
//# sourceMappingURL=browserTools.js.map