/**
 * Web Read tool for Clawx.
 *
 * Fetches a URL and extracts readable text content from the HTML.
 * Strips navigation, scripts, styles, and other non-content elements.
 * Works on all platforms without external dependencies (uses built-in fetch + cheerio).
 */
import { Type } from "@sinclair/typebox";
import { log } from "../utils/logger.js";
const WebReadSchema = Type.Object({
    url: Type.String({ description: "URL to fetch and read" }),
    max_length: Type.Optional(Type.Number({ description: "Maximum characters to return (default: 20000)" })),
    selector: Type.Optional(Type.String({ description: "CSS selector to extract specific content (e.g. 'article', 'main', '.content')" })),
});
/** Strip HTML tags and extract readable text */
function htmlToText(html, selector) {
    // Remove script and style blocks entirely
    let cleaned = html
        .replace(/<script[\s\S]*?<\/script>/gi, '')
        .replace(/<style[\s\S]*?<\/style>/gi, '')
        .replace(/<noscript[\s\S]*?<\/noscript>/gi, '')
        .replace(/<!--[\s\S]*?-->/g, '');
    // If a selector is provided, try to extract just that element
    if (selector) {
        // Simple selector extraction for common patterns: tag, .class, #id
        let pattern = null;
        if (selector.startsWith('.')) {
            const cls = selector.slice(1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            pattern = new RegExp(`<[^>]+class="[^"]*\\b${cls}\\b[^"]*"[^>]*>[\\s\\S]*?<\\/`, 'i');
        }
        else if (selector.startsWith('#')) {
            const id = selector.slice(1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            pattern = new RegExp(`<[^>]+id="${id}"[^>]*>[\\s\\S]*?<\\/`, 'i');
        }
        else {
            // Tag name selector — extract the largest matching block
            const tag = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            pattern = new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'i');
        }
        if (pattern) {
            const match = cleaned.match(pattern);
            if (match) {
                cleaned = match[0];
            }
        }
    }
    // Replace block-level elements with newlines
    cleaned = cleaned
        .replace(/<(br|hr)\s*\/?>/gi, '\n')
        .replace(/<\/(p|div|h[1-6]|li|tr|blockquote|pre|section|article|header|footer)>/gi, '\n\n')
        .replace(/<\/td>/gi, '\t')
        .replace(/<li[^>]*>/gi, '  - ');
    // Remove remaining HTML tags
    cleaned = cleaned.replace(/<[^>]+>/g, '');
    // Decode common HTML entities
    cleaned = cleaned
        .replace(/&amp;/g, '&')
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&quot;/g, '"')
        .replace(/&#39;/g, "'")
        .replace(/&nbsp;/g, ' ')
        .replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(parseInt(code)))
        .replace(/&#x([0-9a-fA-F]+);/g, (_m, code) => String.fromCharCode(parseInt(code, 16)));
    // Collapse whitespace
    cleaned = cleaned
        .split('\n')
        .map(line => line.replace(/[ \t]+/g, ' ').trim())
        .join('\n')
        .replace(/\n{3,}/g, '\n\n')
        .trim();
    return cleaned;
}
/** Extract page title from HTML */
function extractTitle(html) {
    const match = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
    return match ? match[1].replace(/<[^>]+>/g, '').trim() : '';
}
export function createWebReadTool() {
    return {
        name: "web_read",
        label: "Web Read",
        description: "Fetch a URL and extract its readable text content. Strips HTML, scripts, styles, and navigation. " +
            "Use the selector parameter to extract specific page sections (e.g. 'article', 'main', '.content').",
        parameters: WebReadSchema,
        async execute(_toolCallId, params) {
            const maxLength = params.max_length || 20000;
            const start = Date.now();
            log.info(`web_read: ${params.url}`);
            try {
                const controller = new AbortController();
                const timeout = setTimeout(() => controller.abort(), 30000);
                const response = await fetch(params.url, {
                    signal: controller.signal,
                    headers: {
                        'User-Agent': 'Mozilla/5.0 (compatible; Clawx/0.7; +https://github.com/stevenmcsorley/clawx)',
                        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                    },
                    redirect: 'follow',
                });
                clearTimeout(timeout);
                const durationMs = Date.now() - start;
                const contentType = response.headers.get('content-type') || 'unknown';
                if (!response.ok) {
                    return {
                        content: [{ type: "text", text: `HTTP ${response.status} ${response.statusText} fetching ${params.url}` }],
                        details: { url: params.url, statusCode: response.status, contentType, contentLength: 0, durationMs },
                    };
                }
                const body = await response.text();
                const title = extractTitle(body);
                let text = htmlToText(body, params.selector);
                // Truncate if needed
                if (text.length > maxLength) {
                    text = text.substring(0, maxLength) + `\n\n[... truncated at ${maxLength} characters. Use max_length parameter to read more.]`;
                }
                const header = title ? `Title: ${title}\nURL: ${params.url}\n\n` : `URL: ${params.url}\n\n`;
                return {
                    content: [{ type: "text", text: header + text }],
                    details: {
                        url: params.url,
                        statusCode: response.status,
                        contentType,
                        contentLength: text.length,
                        durationMs,
                    },
                };
            }
            catch (error) {
                const durationMs = Date.now() - start;
                const message = error instanceof Error ? error.message : String(error);
                return {
                    content: [{ type: "text", text: `Failed to fetch ${params.url}: ${message}` }],
                    details: { url: params.url, statusCode: 0, contentType: '', contentLength: 0, durationMs },
                };
            }
        },
    };
}
//# sourceMappingURL=webRead.js.map