/**
 * WebSearch capability — search the web via BlockRun API or DuckDuckGo fallback.
 */
import { VERSION } from '../config.js';
import { frameUntrusted } from './untrusted.js';
const MAX_RESULTS_CAP = 8;
const MAX_SNIPPET_CHARS = 220;
const MAX_OUTPUT_CHARS = 3_200;
async function execute(input, _ctx) {
    const { query, max_results } = input;
    if (!query) {
        return { output: 'Error: query is required', isError: true };
    }
    const maxResults = Math.min(Math.max(max_results ?? 5, 1), MAX_RESULTS_CAP);
    // Try DuckDuckGo HTML search (no API key needed)
    try {
        const encoded = encodeURIComponent(query);
        const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 15_000);
        const response = await fetch(url, {
            signal: controller.signal,
            headers: {
                'User-Agent': `franklin/${VERSION} (coding-agent)`,
            },
        });
        clearTimeout(timeout);
        if (!response.ok) {
            return { output: `Search failed: HTTP ${response.status}`, isError: true };
        }
        const html = await response.text();
        const results = parseDuckDuckGoResults(html, maxResults);
        if (results.length === 0) {
            return { output: `No results found for: ${query}` };
        }
        const lines = [];
        let totalChars = `Search results for "${query}":\n\n`.length;
        for (let i = 0; i < results.length; i++) {
            const r = results[i];
            const snippet = r.snippet.length > MAX_SNIPPET_CHARS
                ? r.snippet.slice(0, MAX_SNIPPET_CHARS - 3) + '...'
                : r.snippet;
            const block = `${i + 1}. ${r.title}\n   ${r.url}\n   ${snippet}`;
            if (lines.length > 0 && totalChars + block.length + 2 > MAX_OUTPUT_CHARS) {
                lines.push(`... (${results.length - i} more results omitted)`);
                break;
            }
            lines.push(block);
            totalChars += block.length + 2;
        }
        return { output: frameUntrusted('Web search results', `Search results for "${query}":\n\n${lines.join('\n\n')}`) };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        if (msg.includes('abort')) {
            return { output: `Search timed out after 15s for: ${query}`, isError: true };
        }
        return { output: `Search error: ${msg}`, isError: true };
    }
}
function parseDuckDuckGoResults(html, maxResults) {
    const results = [];
    const seenUrls = new Set();
    // Primary parser: match result blocks by class names
    const linkRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
    const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
    let links = [...html.matchAll(linkRegex)];
    let snippets = [...html.matchAll(snippetRegex)];
    // Fallback parser if primary finds nothing (DDG may have updated HTML)
    if (links.length === 0) {
        const fallbackLink = /<a[^>]*class="[^"]*result[^"]*"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
        links = [...html.matchAll(fallbackLink)];
    }
    for (let i = 0; i < Math.min(links.length, maxResults); i++) {
        const link = links[i];
        const snippet = snippets[i];
        let url = link[1] || '';
        // DuckDuckGo wraps URLs in redirect — extract the actual URL
        const uddgMatch = url.match(/uddg=([^&]+)/);
        if (uddgMatch) {
            url = decodeURIComponent(uddgMatch[1]);
        }
        // Skip internal DDG links
        if (url.startsWith('/') || url.includes('duckduckgo.com'))
            continue;
        if (seenUrls.has(url))
            continue;
        seenUrls.add(url);
        results.push({
            title: stripTags(link[2] || '').trim(),
            url,
            snippet: stripTags(snippet?.[1] || '').trim(),
        });
    }
    // Last resort: if both parsers failed, extract ANY external links from the page
    // Partial results are better than "No results found" when the page loaded OK
    if (results.length === 0) {
        const allLinks = /<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
        let match;
        while ((match = allLinks.exec(html)) !== null && results.length < maxResults) {
            let url = match[1] || '';
            const text = stripTags(match[2]).trim();
            // Must be a real external URL with meaningful text
            if (!text || text.length < 4)
                continue;
            if (url.startsWith('/') || url.includes('duckduckgo.com'))
                continue;
            // Extract from DDG redirect wrapper
            const uddg = url.match(/uddg=([^&]+)/);
            if (uddg)
                url = decodeURIComponent(uddg[1]);
            if (!url.startsWith('http'))
                continue;
            if (seenUrls.has(url))
                continue;
            seenUrls.add(url);
            results.push({ title: text, url, snippet: '' });
        }
    }
    return results;
}
function stripTags(html) {
    return html
        .replace(/<[^>]+>/g, '')
        .replace(/&amp;/g, '&')
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&quot;/g, '"')
        .replace(/&#39;/g, "'")
        .replace(/&nbsp;/g, ' ')
        .replace(/\s+/g, ' ');
}
export const webSearchCapability = {
    spec: {
        name: 'WebSearch',
        description: `Search the web and use the results to inform responses. Returns titles, URLs, and snippets.

Usage:
- Provides up-to-date information beyond training data cutoff
- Cannot access X.com content (use SearchX for X posts)
- Do NOT rephrase and retry the same search — if results are empty, stop. Max 3-5 searches per topic.

CRITICAL REQUIREMENT — After answering, you MUST include a "Sources:" section at the end of your response listing all relevant URLs as markdown hyperlinks:

Sources:
- [Source Title 1](https://example.com/1)
- [Source Title 2](https://example.com/2)

This is MANDATORY — never skip including sources when using web search results.

IMPORTANT — The current date is ${new Date().toISOString().slice(0, 7)} (${new Date().toLocaleString('en-US', { month: 'long', year: 'numeric' })}). Use the current year when searching for recent information, documentation, or current events.`,
        input_schema: {
            type: 'object',
            properties: {
                query: { type: 'string', description: 'The search query' },
                max_results: { type: 'number', description: 'Max number of results. Default: 5' },
            },
            required: ['query'],
        },
    },
    execute,
    concurrent: true,
};
