/**
 * Perform a web search query and return parsed results.
 * Uses DuckDuckGo HTML search as a free, no-API-key source.
 * Falls back gracefully on network errors.
 */
export async function searchWeb(query) {
    try {
        const encoded = encodeURIComponent(query);
        const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
        const response = await fetch(url, {
            headers: {
                'User-Agent': 'Planu/1.0 (Dynamic Knowledge Engine)',
            },
            signal: AbortSignal.timeout(10000), // 10s timeout
        });
        if (!response.ok) {
            return [];
        }
        const html = await response.text();
        return parseDuckDuckGoResults(html);
    }
    catch {
        return []; // graceful degradation
    }
}
/**
 * Parse DuckDuckGo HTML results into structured data.
 */
function parseDuckDuckGoResults(html) {
    const results = [];
    // Extract result blocks - DuckDuckGo HTML uses class="result__a" for links
    const linkRegex = /class="result__a"[^>]*href="([^"]*)"[^>]*>([^<]*)</g;
    const snippetRegex = /class="result__snippet"[^>]*>([^<]*(?:<[^>]*>[^<]*)*)<\/a>/g;
    let linkMatch;
    const links = [];
    while ((linkMatch = linkRegex.exec(html)) !== null) {
        const rawUrl = linkMatch[1] ?? '';
        const rawTitle = linkMatch[2] ?? '';
        // DuckDuckGo wraps URLs in a redirect - extract actual URL
        const urlMatch = /uddg=([^&]*)/.exec(rawUrl);
        const url = urlMatch?.[1] !== undefined ? decodeURIComponent(urlMatch[1]) : rawUrl;
        links.push({ url, title: rawTitle.trim() });
    }
    let snippetMatch;
    const snippets = [];
    while ((snippetMatch = snippetRegex.exec(html)) !== null) {
        // Strip HTML tags from snippet
        const raw = snippetMatch[1] ?? '';
        snippets.push(raw.replace(/<[^>]*>/g, '').trim());
    }
    for (let i = 0; i < Math.min(links.length, 10); i++) {
        const link = links[i];
        if (!link) {
            continue;
        }
        results.push({
            title: link.title,
            url: link.url,
            snippet: snippets[i] ?? '',
        });
    }
    return results;
}
/**
 * Fetch and extract knowledge items from web search results.
 */
export async function fetchKnowledge(queries, category) {
    const allResults = [];
    // Run queries sequentially to avoid rate limiting (max 3 queries)
    for (const query of queries.slice(0, 3)) {
        const results = await searchWeb(query);
        allResults.push(...results);
    }
    return parseResultsToItems(allResults, category);
}
/**
 * Convert raw web search results into structured KnowledgeItems.
 */
function parseResultsToItems(results, category) {
    const items = [];
    const seen = new Set();
    const now = new Date().toISOString();
    for (const result of results) {
        // Skip duplicates by URL
        if (seen.has(result.url)) {
            continue;
        }
        seen.add(result.url);
        // Skip irrelevant results (ads, unrelated)
        if (!result.snippet || result.snippet.length < 20) {
            continue;
        }
        const id = `${category}-${Buffer.from(result.url).toString('base64url').slice(0, 12)}`;
        items.push({
            id,
            category,
            title: result.title,
            description: result.snippet,
            sourceUrl: result.url,
            applicableTargets: inferTargets(result.snippet, category),
            severity: inferSeverity(result.snippet, category),
            tags: extractTags(result.snippet),
            fetchedAt: now,
        });
    }
    return items;
}
/**
 * Infer which targets (frontend/backend/fullstack) a result applies to.
 */
function inferTargets(snippet, category) {
    const lower = snippet.toLowerCase();
    const targets = [];
    if (/\b(frontend|react|vue|angular|css|dom|browser|ui)\b/.test(lower)) {
        targets.push('frontend');
    }
    if (/\b(backend|server|api|database|sql|endpoint|node)\b/.test(lower)) {
        targets.push('backend');
    }
    if (/\b(fullstack|full.stack|both|universal)\b/.test(lower)) {
        targets.push('fullstack');
    }
    // Security and compliance apply everywhere
    if (targets.length === 0 && (category === 'security' || category === 'compliance')) {
        return ['frontend', 'backend', 'fullstack'];
    }
    return targets.length > 0 ? targets : ['fullstack'];
}
/**
 * Infer severity based on content signals.
 */
function inferSeverity(snippet, category) {
    const lower = snippet.toLowerCase();
    if (category === 'security' && /\b(critical|vulnerability|exploit|owasp|cve)\b/.test(lower)) {
        return 'required';
    }
    if (/\b(must|required|mandatory|essential|always)\b/.test(lower)) {
        return 'required';
    }
    if (/\b(should|recommended|best practice|important)\b/.test(lower)) {
        return 'recommended';
    }
    return 'optional';
}
/**
 * Extract tags from snippet text.
 */
function extractTags(snippet) {
    const lower = snippet.toLowerCase();
    const tags = [];
    const techKeywords = [
        'react',
        'vue',
        'angular',
        'svelte',
        'next',
        'nuxt',
        'express',
        'nestjs',
        'django',
        'fastapi',
        'rails',
        'typescript',
        'python',
        'go',
        'rust',
        'java',
        'postgres',
        'mysql',
        'mongodb',
        'redis',
        'docker',
        'kubernetes',
        'aws',
        'gcp',
        'azure',
        'owasp',
        'csrf',
        'xss',
        'sql injection',
    ];
    for (const kw of techKeywords) {
        if (lower.includes(kw)) {
            tags.push(kw);
        }
    }
    return tags.slice(0, 10); // max 10 tags
}
//# sourceMappingURL=web-researcher.js.map