/**
 * Browser Automation Tool
 *
 * Provides headless browser capabilities using Playwright for:
 * - Web page navigation and interaction
 * - Form filling and button clicking
 * - Screenshot capture
 * - JavaScript execution
 * - DOM element selection
 *
 * Playwright is loaded as an optional dependency - if not installed,
 * the tool will provide installation instructions.
 */
import { UnifiedVfsRouter } from '../services/vfs/unified-vfs-router.js';
import { EventEmitter } from 'events';
import * as path from 'path';
import * as os from 'os';
// ============================================================================
// Default Configuration
// ============================================================================
const DEFAULT_CONFIG = {
    headless: true,
    viewport: { width: 1280, height: 720 },
    timeout: 30000,
    screenshotDir: path.join(os.tmpdir(), 'codebuddy-browser-screenshots'),
    browserType: 'chromium',
    javaScriptEnabled: true,
    blockedUrls: [],
};
// Internal URL patterns to block for security
const BLOCKED_URL_PATTERNS = [
    /^file:\/\//i,
    /^localhost/i,
    /^127\.\d+\.\d+\.\d+/,
    /^192\.168\./,
    /^10\./,
    /^172\.(1[6-9]|2\d|3[0-1])\./,
    /^0\.0\.0\.0/,
    /^::1$/,
    /^fe80:/i,
];
// ============================================================================
// Browser Tool Implementation
// ============================================================================
/**
 * BrowserTool provides headless browser automation capabilities
 * using Playwright for web page interaction, testing, and scraping.
 */
export class BrowserTool extends EventEmitter {
    name = 'browser';
    description = 'Automate web browser for navigation, interaction, screenshots, and testing';
    dangerLevel = 'medium';
    inputSchema = {
        type: 'object',
        properties: {
            action: {
                type: 'string',
                enum: [
                    'navigate',
                    'click',
                    'fill',
                    'screenshot',
                    'getText',
                    'getHtml',
                    'evaluate',
                    'waitForSelector',
                    'getLinks',
                    'getForms',
                    'submit',
                    'select',
                    'hover',
                    'scroll',
                    'goBack',
                    'goForward',
                    'reload',
                    'close',
                ],
                description: 'Browser action to perform',
            },
            url: {
                type: 'string',
                description: 'URL to navigate to (for navigate action)',
            },
            selector: {
                type: 'string',
                description: 'CSS selector for element operations',
            },
            value: {
                type: 'string',
                description: 'Value for fill/select operations',
            },
            script: {
                type: 'string',
                description: 'JavaScript code for evaluate action',
            },
            timeout: {
                type: 'number',
                description: 'Timeout in milliseconds (default: 30000)',
            },
            screenshotOptions: {
                type: 'object',
                description: 'Options for screenshot (fullPage, path, type, quality)',
            },
        },
        required: ['action'],
    };
    config;
    playwright = null;
    browser = null;
    context = null;
    page = null;
    isPlaywrightAvailable = null;
    currentUrl = '';
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.ensureScreenshotDir();
    }
    /**
     * Inject playwright module for testing purposes
     * @internal
     */
    _injectPlaywright(playwright) {
        this.playwright = playwright;
        this.isPlaywrightAvailable = true;
    }
    /**
     * Ensure screenshot directory exists
     */
    async ensureScreenshotDir() {
        await UnifiedVfsRouter.Instance.ensureDir(this.config.screenshotDir);
    }
    /**
     * Check if Playwright is available
     */
    async checkPlaywrightAvailable() {
        if (this.isPlaywrightAvailable !== null) {
            return this.isPlaywrightAvailable;
        }
        try {
            // Dynamic import of playwright using Function to avoid TypeScript module resolution
            const dynamicImport = new Function('specifier', 'return import(specifier)');
            this.playwright = await dynamicImport('playwright');
            this.isPlaywrightAvailable = true;
            return true;
        }
        catch {
            this.isPlaywrightAvailable = false;
            return false;
        }
    }
    /**
     * Get installation instructions for Playwright
     */
    getInstallInstructions() {
        return `Playwright is not installed. To enable browser automation, run:

npm install playwright
npx playwright install chromium

Or for all browsers:
npx playwright install

This will install the browser binaries needed for automation.`;
    }
    /**
     * Check if URL is blocked (internal/local)
     */
    isBlockedUrl(url) {
        try {
            const parsed = new URL(url);
            const hostname = parsed.hostname;
            for (const pattern of BLOCKED_URL_PATTERNS) {
                if (pattern.test(hostname) || pattern.test(url)) {
                    return true;
                }
            }
            // Check custom blocked patterns
            for (const blocked of this.config.blockedUrls || []) {
                if (url.includes(blocked)) {
                    return true;
                }
            }
            return false;
        }
        catch {
            return true; // Invalid URLs are blocked
        }
    }
    /**
     * Initialize browser instance
     */
    async initBrowser() {
        if (!(await this.checkPlaywrightAvailable())) {
            return {
                success: false,
                error: this.getInstallInstructions(),
            };
        }
        if (this.browser && this.page) {
            return { success: true, output: 'Browser already initialized' };
        }
        try {
            const pw = this.playwright;
            const browserLauncher = pw[this.config.browserType];
            this.browser = await browserLauncher.launch({
                headless: this.config.headless,
            });
            this.context = await this.browser.newContext({
                viewport: this.config.viewport,
                userAgent: this.config.userAgent,
                javaScriptEnabled: this.config.javaScriptEnabled,
            });
            this.page = await this.context.newPage();
            this.emit('browser:initialized', { browserType: this.config.browserType });
            return { success: true, output: `Browser initialized (${this.config.browserType})` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Failed to initialize browser: ${message}` };
        }
    }
    /**
     * Execute browser action
     */
    async execute(params) {
        const { action, timeout = this.config.timeout } = params;
        // Handle close action without init
        if (action === 'close') {
            return this.close();
        }
        // Initialize browser if needed
        const initResult = await this.initBrowser();
        if (!initResult.success) {
            return initResult;
        }
        try {
            switch (action) {
                case 'navigate':
                    return this.navigate(params.url, timeout);
                case 'click':
                    return this.click(params.selector, timeout);
                case 'fill':
                    return this.fill(params.selector, params.value, timeout);
                case 'screenshot':
                    return this.screenshot(params.screenshotOptions);
                case 'getText':
                    return this.getText(params.selector);
                case 'getHtml':
                    return this.getHtml(params.selector);
                case 'evaluate':
                    return this.evaluate(params.script);
                case 'waitForSelector':
                    return this.waitForSelector(params.selector, timeout);
                case 'getLinks':
                    return this.getLinks();
                case 'getForms':
                    return this.getForms();
                case 'submit':
                    return this.submit(params.selector);
                case 'select':
                    return this.select(params.selector, params.value);
                case 'hover':
                    return this.hover(params.selector, timeout);
                case 'scroll':
                    return this.scroll(params.scrollOptions);
                case 'goBack':
                    return this.goBack(timeout);
                case 'goForward':
                    return this.goForward(timeout);
                case 'reload':
                    return this.reload(timeout);
                default:
                    return { success: false, error: `Unknown action: ${action}` };
            }
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            this.emit('browser:error', { action, error: message });
            return { success: false, error: `Browser action '${action}' failed: ${message}` };
        }
    }
    /**
     * Navigate to URL
     */
    async navigate(url, timeout) {
        if (!url) {
            return { success: false, error: 'URL is required for navigate action' };
        }
        if (this.isBlockedUrl(url)) {
            return { success: false, error: 'Access to internal/local URLs is blocked for security' };
        }
        try {
            await this.page.goto(url, { timeout, waitUntil: 'domcontentloaded' });
            this.currentUrl = this.page.url();
            const title = await this.page.title();
            this.emit('browser:navigate', { url: this.currentUrl, title });
            return {
                success: true,
                output: `Navigated to: ${this.currentUrl}\nTitle: ${title}`,
                metadata: { url: this.currentUrl, title },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Navigation failed: ${message}` };
        }
    }
    /**
     * Click on element
     */
    async click(selector, timeout) {
        if (!selector) {
            return { success: false, error: 'Selector is required for click action' };
        }
        try {
            await this.page.waitForSelector(selector, { timeout });
            await this.page.click(selector, { timeout });
            this.emit('browser:click', { selector });
            return { success: true, output: `Clicked on: ${selector}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Click failed on '${selector}': ${message}` };
        }
    }
    /**
     * Fill input field
     */
    async fill(selector, value, timeout) {
        if (!selector) {
            return { success: false, error: 'Selector is required for fill action' };
        }
        if (value === undefined) {
            return { success: false, error: 'Value is required for fill action' };
        }
        try {
            await this.page.waitForSelector(selector, { timeout });
            await this.page.fill(selector, value, { timeout });
            this.emit('browser:fill', { selector, valueLength: value.length });
            return { success: true, output: `Filled '${selector}' with ${value.length} characters` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Fill failed on '${selector}': ${message}` };
        }
    }
    /**
     * Take screenshot
     */
    async screenshot(options) {
        try {
            const filename = `screenshot-${Date.now()}.${options?.type || 'png'}`;
            const filepath = options?.path || path.join(this.config.screenshotDir, filename);
            const buffer = await this.page.screenshot({
                path: filepath,
                fullPage: options?.fullPage ?? false,
                type: options?.type || 'png',
                quality: options?.type === 'jpeg' ? options?.quality : undefined,
            });
            this.emit('browser:screenshot', { path: filepath, size: buffer.length });
            return {
                success: true,
                output: `Screenshot saved: ${filepath}`,
                metadata: { path: filepath, size: buffer.length },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Screenshot failed: ${message}` };
        }
    }
    /**
     * Get text content
     */
    async getText(selector) {
        try {
            let text;
            if (selector) {
                const element = await this.page.$(selector);
                if (!element) {
                    return { success: false, error: `Element not found: ${selector}` };
                }
                text = (await this.page.textContent(selector)) || '';
            }
            else {
                // Get all visible text
                text = await this.page.evaluate(() => document.body.innerText);
            }
            // Truncate if too long
            const maxLength = 50000;
            const truncated = text.length > maxLength;
            const output = truncated ? text.substring(0, maxLength) + '\n... (truncated)' : text;
            return {
                success: true,
                output,
                metadata: { length: text.length, truncated },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Get text failed: ${message}` };
        }
    }
    /**
     * Get HTML content
     */
    async getHtml(selector) {
        try {
            let html;
            if (selector) {
                html = await this.page.innerHTML(selector);
            }
            else {
                html = await this.page.content();
            }
            // Truncate if too long
            const maxLength = 100000;
            const truncated = html.length > maxLength;
            const output = truncated ? html.substring(0, maxLength) + '\n... (truncated)' : html;
            return {
                success: true,
                output,
                metadata: { length: html.length, truncated },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Get HTML failed: ${message}` };
        }
    }
    /**
     * Evaluate JavaScript
     */
    async evaluate(script) {
        if (!script) {
            return { success: false, error: 'Script is required for evaluate action' };
        }
        try {
            // Create function from script string
            const fn = new Function(`return (${script})`);
            const result = await this.page.evaluate(fn);
            const output = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result ?? 'undefined');
            this.emit('browser:evaluate', { scriptLength: script.length });
            return {
                success: true,
                output,
                metadata: { resultType: typeof result },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Evaluate failed: ${message}` };
        }
    }
    /**
     * Wait for selector
     */
    async waitForSelector(selector, timeout) {
        if (!selector) {
            return { success: false, error: 'Selector is required for waitForSelector action' };
        }
        try {
            await this.page.waitForSelector(selector, { timeout });
            return { success: true, output: `Element found: ${selector}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Wait for selector failed: ${message}` };
        }
    }
    /**
     * Get all links on page
     */
    async getLinks() {
        try {
            const links = await this.page.$$eval('a[href]', (elements) => elements.map((el) => ({
                href: el.href,
                text: el.textContent?.trim() || '',
                title: el.getAttribute('title') || undefined,
            })));
            const output = links
                .slice(0, 100) // Limit to 100 links
                .map((l) => `${l.text || '(no text)'}: ${l.href}`)
                .join('\n');
            return {
                success: true,
                output: output || 'No links found',
                metadata: { count: links.length, shown: Math.min(links.length, 100) },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Get links failed: ${message}` };
        }
    }
    /**
     * Get all forms on page
     */
    async getForms() {
        try {
            const forms = await this.page.$$eval('form', (elements) => elements.map((form) => {
                const formEl = form;
                const inputs = Array.from(formEl.querySelectorAll('input, select, textarea'));
                return {
                    action: formEl.action,
                    method: formEl.method.toUpperCase() || 'GET',
                    id: formEl.id || undefined,
                    name: formEl.name || undefined,
                    fields: inputs.map((input) => {
                        const inputEl = input;
                        return {
                            name: inputEl.name,
                            type: inputEl.type || 'text',
                            id: inputEl.id || undefined,
                            placeholder: inputEl.placeholder || undefined,
                            required: inputEl.required,
                        };
                    }),
                };
            }));
            if (forms.length === 0) {
                return { success: true, output: 'No forms found on page' };
            }
            const output = forms
                .map((f, i) => {
                const fields = f.fields.map((field) => `    - ${field.name} (${field.type})`).join('\n');
                return `Form ${i + 1}: ${f.method} ${f.action}\n${fields}`;
            })
                .join('\n\n');
            return {
                success: true,
                output,
                metadata: { count: forms.length },
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Get forms failed: ${message}` };
        }
    }
    /**
     * Submit form
     */
    async submit(selector) {
        if (!selector) {
            return { success: false, error: 'Form selector is required for submit action' };
        }
        try {
            // Find submit button or trigger form submit
            const submitButton = await this.page.$(`${selector} [type="submit"], ${selector} button`);
            if (submitButton) {
                await this.page.click(`${selector} [type="submit"], ${selector} button`);
            }
            else {
                // Evaluate form.submit()
                const safeSelector = JSON.stringify(selector);
                await this.page.evaluate(new Function(`document.querySelector(${safeSelector}).submit()`));
            }
            // Wait for navigation
            await new Promise((resolve) => setTimeout(resolve, 1000));
            this.emit('browser:submit', { selector });
            return {
                success: true,
                output: `Form submitted: ${selector}\nCurrent URL: ${this.page.url()}`,
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Submit failed: ${message}` };
        }
    }
    /**
     * Select option from dropdown
     */
    async select(selector, value) {
        if (!selector) {
            return { success: false, error: 'Selector is required for select action' };
        }
        if (!value) {
            return { success: false, error: 'Value is required for select action' };
        }
        try {
            await this.page.selectOption(selector, value);
            this.emit('browser:select', { selector, value });
            return { success: true, output: `Selected '${value}' in ${selector}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Select failed: ${message}` };
        }
    }
    /**
     * Hover over element
     */
    async hover(selector, timeout) {
        if (!selector) {
            return { success: false, error: 'Selector is required for hover action' };
        }
        try {
            await this.page.waitForSelector(selector, { timeout });
            await this.page.hover(selector);
            return { success: true, output: `Hovering over: ${selector}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Hover failed: ${message}` };
        }
    }
    /**
     * Scroll page
     */
    async scroll(options) {
        try {
            const x = options?.x ?? 0;
            const y = options?.y ?? 500;
            const behavior = options?.behavior ?? 'auto';
            const safeBehavior = ['auto', 'smooth', 'instant'].includes(behavior) ? behavior : 'auto';
            await this.page.evaluate(new Function(`window.scrollTo({ left: ${Number(x)}, top: ${Number(y)}, behavior: '${safeBehavior}' })`));
            return { success: true, output: `Scrolled to x:${x}, y:${y}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Scroll failed: ${message}` };
        }
    }
    /**
     * Go back in history
     */
    async goBack(timeout) {
        try {
            await this.page.goBack({ timeout });
            const url = this.page.url();
            return { success: true, output: `Navigated back to: ${url}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Go back failed: ${message}` };
        }
    }
    /**
     * Go forward in history
     */
    async goForward(timeout) {
        try {
            await this.page.goForward({ timeout });
            const url = this.page.url();
            return { success: true, output: `Navigated forward to: ${url}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Go forward failed: ${message}` };
        }
    }
    /**
     * Reload page
     */
    async reload(timeout) {
        try {
            await this.page.reload({ timeout });
            const url = this.page.url();
            return { success: true, output: `Page reloaded: ${url}` };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Reload failed: ${message}` };
        }
    }
    /**
     * Close browser
     */
    async close() {
        try {
            if (this.page) {
                await this.page.close();
                this.page = null;
            }
            if (this.context) {
                await this.context.close();
                this.context = null;
            }
            if (this.browser) {
                await this.browser.close();
                this.browser = null;
            }
            this.currentUrl = '';
            this.emit('browser:closed');
            return { success: true, output: 'Browser closed' };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return { success: false, error: `Close failed: ${message}` };
        }
    }
    /**
     * Get current page info
     */
    async getPageInfo() {
        if (!this.page) {
            return null;
        }
        try {
            return {
                url: this.page.url(),
                title: await this.page.title(),
            };
        }
        catch {
            return null;
        }
    }
    /**
     * Check if browser is initialized
     */
    isInitialized() {
        return this.browser !== null && this.page !== null;
    }
    /**
     * Update configuration
     */
    updateConfig(config) {
        this.config = { ...this.config, ...config };
        this.emit('config:updated', this.config);
    }
    /**
     * Get current configuration
     */
    getConfig() {
        return { ...this.config };
    }
    /**
     * Dispose and cleanup resources
     */
    async dispose() {
        await this.close();
        this.removeAllListeners();
    }
}
// ============================================================================
// Singleton
// ============================================================================
let browserToolInstance = null;
/**
 * Get the singleton BrowserTool instance
 */
export function getBrowserTool(config) {
    if (!browserToolInstance) {
        browserToolInstance = new BrowserTool(config);
    }
    return browserToolInstance;
}
/**
 * Reset the singleton instance (for testing)
 */
export async function resetBrowserTool() {
    if (browserToolInstance) {
        await browserToolInstance.dispose();
        browserToolInstance = null;
    }
}
//# sourceMappingURL=browser-tool.js.map