/**
 * HTML Content Extractor
 *
 * Extracts JavaScript content from HTML files for analysis:
 * - Inline <script> blocks with line offset tracking
 * - Inline event handler attributes (onclick, onerror, etc.)
 * - External script src references (informational)
 */
/** Known inline event handler attribute names */
const EVENT_HANDLER_ATTRS = new Set([
    'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover',
    'onmousemove', 'onmouseout', 'onmouseenter', 'onmouseleave',
    'onkeydown', 'onkeyup', 'onkeypress',
    'onfocus', 'onblur', 'onchange', 'oninput', 'onsubmit', 'onreset',
    'onload', 'onerror', 'onabort', 'onresize', 'onscroll',
    'oncontextmenu', 'ondrag', 'ondrop', 'oncopy', 'onpaste', 'oncut',
    'ontouchstart', 'ontouchend', 'ontouchmove',
    'onanimationend', 'onanimationstart', 'ontransitionend',
]);
/**
 * Extract JavaScript content from an HTML AST.
 *
 * Walks the tree-sitter-html AST to find:
 * 1. <script> elements — extracts inline code or notes external src
 * 2. Elements with on* event handler attributes
 */
export function extractHtmlContent(rootNode) {
    const scriptBlocks = [];
    const eventHandlers = [];
    walkNode(rootNode, scriptBlocks, eventHandlers);
    return { scriptBlocks, eventHandlers };
}
function walkNode(node, scriptBlocks, eventHandlers) {
    if (node.type === 'script_element') {
        extractScriptBlock(node, scriptBlocks);
    }
    // Check for event handler attributes on any element or self-closing tag
    if (node.type === 'element' || node.type === 'self_closing_tag') {
        extractEventHandlers(node, eventHandlers);
    }
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child) {
            walkNode(child, scriptBlocks, eventHandlers);
        }
    }
}
/**
 * Extract a <script> block — either inline code or external src reference.
 */
function extractScriptBlock(scriptNode, scriptBlocks) {
    const startTag = scriptNode.childForFieldName('start_tag') ?? findChildByType(scriptNode, 'start_tag');
    // Check for src attribute (external script)
    const src = getAttributeValue(startTag, 'src');
    if (src) {
        scriptBlocks.push({
            code: '',
            lineOffset: scriptNode.startPosition.row + 1,
            kind: 'external-src',
            src,
            scriptType: getAttributeValue(startTag, 'type') ?? getAttributeValue(startTag, 'lang'),
        });
        return;
    }
    // Look for inline script content (raw_text child)
    const rawText = findChildByType(scriptNode, 'raw_text');
    if (rawText && rawText.text.trim()) {
        scriptBlocks.push({
            code: rawText.text,
            lineOffset: rawText.startPosition.row + 1,
            kind: 'inline',
            scriptType: getAttributeValue(startTag, 'type') ?? getAttributeValue(startTag, 'lang'),
        });
    }
}
/**
 * Extract inline event handler attributes from an element.
 */
function extractEventHandlers(elementNode, eventHandlers) {
    // Get the element's tag name
    const tagName = getTagName(elementNode);
    // Find the start_tag (or the self_closing_tag itself)
    const tag = elementNode.type === 'self_closing_tag'
        ? elementNode
        : findChildByType(elementNode, 'start_tag');
    if (!tag)
        return;
    // Iterate attributes looking for event handlers
    for (let i = 0; i < tag.childCount; i++) {
        const child = tag.child(i);
        if (!child || child.type !== 'attribute')
            continue;
        const nameNode = findChildByType(child, 'attribute_name');
        if (!nameNode)
            continue;
        const attrName = nameNode.text.toLowerCase();
        if (!EVENT_HANDLER_ATTRS.has(attrName))
            continue;
        const valueNode = findChildByType(child, 'quoted_attribute_value') ?? findChildByType(child, 'attribute_value');
        if (!valueNode)
            continue;
        const code = stripQuotes(valueNode.text);
        if (code) {
            eventHandlers.push({
                code,
                eventName: attrName,
                line: child.startPosition.row + 1,
                element: tagName,
            });
        }
    }
}
/**
 * Get the tag name from an element or self-closing tag node.
 */
function getTagName(node) {
    if (node.type === 'self_closing_tag') {
        const tagNameNode = findChildByType(node, 'tag_name');
        return tagNameNode?.text ?? 'unknown';
    }
    const startTag = findChildByType(node, 'start_tag');
    if (startTag) {
        const tagNameNode = findChildByType(startTag, 'tag_name');
        return tagNameNode?.text ?? 'unknown';
    }
    return 'unknown';
}
/**
 * Get the value of a named attribute from a start_tag or self_closing_tag.
 */
function getAttributeValue(tag, name) {
    if (!tag)
        return undefined;
    for (let i = 0; i < tag.childCount; i++) {
        const child = tag.child(i);
        if (!child || child.type !== 'attribute')
            continue;
        const nameNode = findChildByType(child, 'attribute_name');
        if (nameNode?.text.toLowerCase() === name) {
            const valueNode = findChildByType(child, 'quoted_attribute_value') ?? findChildByType(child, 'attribute_value');
            return valueNode ? stripQuotes(valueNode.text) : '';
        }
    }
    return undefined;
}
/**
 * Find the first child node of a given type.
 */
function findChildByType(node, type) {
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child?.type === type)
            return child;
    }
    return null;
}
/**
 * Strip surrounding quotes from an attribute value.
 */
function stripQuotes(text) {
    if ((text.startsWith('"') && text.endsWith('"')) ||
        (text.startsWith("'") && text.endsWith("'"))) {
        return text.slice(1, -1);
    }
    return text;
}
// Re-export getAttributeValue and getTagName for use by security pass
export { getAttributeValue, getTagName, findChildByType, stripQuotes };
//# sourceMappingURL=html-extractor.js.map