#!/usr/bin/env node
/**
 * Playwright Bridge for OpenQode TUI - Persistent Session Version
 * Uses CDP to maintain browser session across multiple command invocations
 * 
 * Credit: Inspired by browser-use/browser-use (https://github.com/browser-use/browser-use)
 * License: MIT
 */

const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const net = require('net');

// State file to persist CDP endpoint between calls
const STATE_FILE = path.join(__dirname, '.playwright-session.json');
const CDP_PORT = 9222;

let browser = null;
let page = null;

async function dismissCommonPopups() {
    if (!page) return;
    try {
        // First, try escaping common modals.
        await page.keyboard.press('Escape').catch(() => { });
        await page.keyboard.press('Escape').catch(() => { });

        // Then, try clicking common consent/first-run buttons if present.
        const labels = [
            'Accept',
            'I agree',
            'Agree',
            'OK',
            'Ok',
            'Continue',
            'Next',
            'Get started',
            'Start',
            'No thanks',
            'Not now',
            'Skip',
            'Dismiss',
            'Close',
            'Got it'
        ];

        for (const name of labels) {
            const btn = page.getByRole('button', { name, exact: false }).first();
            const count = await btn.count().catch(() => 0);
            if (count > 0) {
                await btn.click({ timeout: 1200 }).catch(() => { });
            }
        }

        // Also attempt to close dialogs that present as links.
        for (const name of ['No thanks', 'Skip', 'Continue', 'Close']) {
            const link = page.getByRole('link', { name, exact: false }).first();
            const count = await link.count().catch(() => 0);
            if (count > 0) {
                await link.click({ timeout: 1200 }).catch(() => { });
            }
        }
    } catch (e) {
        // best-effort; ignore
    }
}

/**
 * Check if a port is in use
 */
function isPortInUse(port) {
    return new Promise((resolve) => {
        const server = net.createServer();
        server.once('error', () => resolve(true));
        server.once('listening', () => {
            server.close();
            resolve(false);
        });
        server.listen(port, '127.0.0.1');
    });
}

/**
 * Load saved session state
 */
function loadState() {
    try {
        if (fs.existsSync(STATE_FILE)) {
            return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
        }
    } catch (e) { }
    return null;
}

/**
 * Save session state
 */
function saveState(state) {
    fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}

/**
 * Clear session state
 */
function clearState() {
    if (fs.existsSync(STATE_FILE)) {
        fs.unlinkSync(STATE_FILE);
    }
}

/**
 * Launch browser with CDP enabled or connect to existing one
 */
async function ensureBrowser() {
    const state = loadState();

    // Try to connect to existing browser first
    if (state && state.wsEndpoint) {
        try {
            browser = await chromium.connectOverCDP(state.wsEndpoint);
            const contexts = browser.contexts();
            if (contexts.length > 0) {
                const pages = contexts[0].pages();
                page = pages.length > 0 ? pages[0] : await contexts[0].newPage();
            } else {
                const context = await browser.newContext({ viewport: null });
                page = await context.newPage();
            }
            return { browser, page };
        } catch (e) {
            // Connection failed, browser might have closed
            clearState();
        }
    }

    // Check if CDP port is already in use
    const portInUse = await isPortInUse(CDP_PORT);

    if (portInUse) {
        // Try to connect to existing browser on that port
        try {
            browser = await chromium.connectOverCDP(`http://127.0.0.1:${CDP_PORT}`);
            const wsEndpoint = `http://127.0.0.1:${CDP_PORT}`;
            saveState({ wsEndpoint });

            const contexts = browser.contexts();
            if (contexts.length > 0) {
                const pages = contexts[0].pages();
                page = pages.length > 0 ? pages[0] : await contexts[0].newPage();
            } else {
                const context = await browser.newContext({ viewport: null });
                page = await context.newPage();
            }
            return { browser, page };
        } catch (e) {
            console.log('Could not connect to existing browser, launching new one...');
        }
    }

    // Launch new browser with CDP enabled
    browser = await chromium.launch({
        headless: false,
        args: [
            '--start-maximized',
            `--remote-debugging-port=${CDP_PORT}`
        ]
    });

    // Get the WebSocket endpoint
    const wsEndpoint = `http://127.0.0.1:${CDP_PORT}`;
    saveState({ wsEndpoint, launchTime: Date.now() });

    const context = await browser.newContext({ viewport: null });
    page = await context.newPage();

    console.log('Browser launched with persistent session');
    return { browser, page };
}

/**
 * Run multiple commands in sequence (for batch execution)
 */
async function runBatch(commands) {
    await ensureBrowser();
    const results = [];

    for (const cmd of commands) {
        try {
            const result = await executeCommand(cmd.command, cmd.args);
            results.push({ success: true, command: cmd.command, result });
        } catch (e) {
            results.push({ success: false, command: cmd.command, error: e.message });
        }
    }

    return results;
}

/**
 * Execute a single command
 */
async function executeCommand(command, args) {
    switch (command) {
        case 'navigate': {
            const url = args[0];
            if (!url) throw new Error('URL required');
            await dismissCommonPopups();
            await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
            await dismissCommonPopups();
            const title = await page.title();
            return { navigated: url, title };
        }

        case 'fill': {
            const selector = args[0];
            const text = args.slice(1).join(' ');
            if (!selector || !text) throw new Error('Selector and text required');

            await dismissCommonPopups();
            try {
                await page.fill(selector, text, { timeout: 5000 });
            } catch (e) {
                try {
                    await page.getByPlaceholder(selector).fill(text, { timeout: 5000 });
                } catch (e2) {
                    await page.getByLabel(selector).fill(text, { timeout: 5000 });
                }
            }
            return { filled: selector, text };
        }

        case 'click': {
            const selector = args.join(' ');
            if (!selector) throw new Error('Selector required');

            await dismissCommonPopups();
            try {
                await page.click(selector, { timeout: 5000 });
            } catch (e) {
                try {
                    await page.click(`text="${selector}"`, { timeout: 5000 });
                } catch (e2) {
                    try {
                        await page.getByRole('button', { name: selector }).click({ timeout: 5000 });
                    } catch (e3) {
                        await page.getByText(selector).first().click({ timeout: 5000 });
                    }
                }
            }
            return { clicked: selector };
        }

        case 'press': {
            const key = args[0];
            if (!key) throw new Error('Key required');
            await dismissCommonPopups();
            await page.keyboard.press(key);
            await dismissCommonPopups();
            return { pressed: key };
        }

        case 'type': {
            const text = args.join(' ');
            if (!text) throw new Error('Text required');
            await page.keyboard.type(text);
            return { typed: text };
        }

        case 'screenshot': {
            const filename = args[0] || 'screenshot.png';
            const fullPath = path.resolve(filename);
            await page.screenshot({ path: fullPath, fullPage: true });
            return { screenshot: fullPath };
        }

        case 'content': {
            const content = await page.textContent('body');
            return { content: content?.substring(0, 5000) };
        }

        case 'title': {
            return { title: await page.title() };
        }

        case 'url': {
            return { url: page.url() };
        }

        case 'elements': {
            const elements = await page.evaluate(() => {
                const els = document.querySelectorAll('button, a, input, textarea, select, [role="button"]');
                return Array.from(els).slice(0, 30).map((el, i) => ({
                    i,
                    tag: el.tagName.toLowerCase(),
                    text: el.textContent?.trim().substring(0, 40) || '',
                    name: el.name || el.id || ''
                })).filter(e => e.text || e.name);
            });
            return { elements };
        }

        case 'wait': {
            const selector = args[0];
            const timeout = parseInt(args[1]) || 10000;
            await page.waitForSelector(selector, { timeout });
            return { waited: selector };
        }

        case 'close': {
            if (browser) {
                await browser.close();
                browser = null;
                page = null;
            }
            clearState();
            return { closed: true };
        }

        default:
            throw new Error(`Unknown command: ${command}`);
    }
}

async function main() {
    const args = process.argv.slice(2);
    const command = args[0]?.toLowerCase();

    if (!command) {
        console.log('Playwright Bridge - Persistent Session');
        console.log('Commands: navigate, fill, click, press, type, screenshot, content, title, url, elements, wait, close');
        console.log('');
        console.log('Example: node playwright-bridge.js navigate https://google.com');
        return;
    }

    // Special batch mode for multiple commands
    if (command === 'batch') {
        const batchFile = args[1];
        if (batchFile && fs.existsSync(batchFile)) {
            const commands = JSON.parse(fs.readFileSync(batchFile, 'utf8'));
            const results = await runBatch(commands);
            console.log(JSON.stringify(results, null, 2));
        }
        return;
    }

    try {
        await ensureBrowser();
        const result = await executeCommand(command, args.slice(1));
        console.log(`RESULT:${JSON.stringify(result)}`);
    } catch (error) {
        console.error(`ERROR:${error.message}`);
        process.exit(1);
    }
}

// Keep process alive briefly to allow CDP connection to stabilize
process.on('beforeExit', async () => {
    // Don't close browser on exit - keep it persistent!
});

main().catch(console.error);
