
INTERACTIVE_ROLES = {
    "button", "checkbox", "combobox", "link", "listbox", "menuitem", 
    "menuitemcheckbox", "menuitemradio", "option", "radio", "searchbox", 
    "slider", "spinbutton", "switch", "tab", "textbox", "treeitem"
}

CONTENT_ROLES = {
    "heading", "main", "navigation", "region", "search"
}

LANDMARKS = {"main", "navigation", "region", "search", "form", "heading"}

JS_A11Y_EXTRACTOR = """
() => {
    const INTERACTIVE_ROLES = new Set([
      "button", "link", "checkbox", "menuitem", "option", "radio", "switch", "tab", 
      "treeitem", "textbox", "searchbox", "spinbutton", "combobox", "listbox", "slider"
    ]);
    const results = [];

    const isVisible = (el) => {
        if (!el.getClientRects().length) return false;
        const style = window.getComputedStyle(el);
        if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
        if (style.pointerEvents === 'none') return false;
        return true;
    };

    const getAriaLabelledBy = (el) => {
        const ids = el.getAttribute('aria-labelledby');
        if (!ids) return '';
        return ids
            .split(' ')
            .map(id => {
                const target = document.getElementById(id);
                return target ? target.innerText : '';
            })
            .filter(Boolean)
            .join(' ');
    };

    const isInteractiveNode = (node, style, role) => {
        const tagName = node.tagName;
        const isInput = ['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName);
        const isButtonOrLink = tagName === 'BUTTON' || tagName === 'A' || style.cursor === 'pointer';
        const hasOnClick = typeof node.onclick === 'function' || node.getAttribute('onclick');
        const hasTabIndex = node.tabIndex >= 0;
        const hasRole = role && role !== '';
        return (
            INTERACTIVE_ROLES.has(role) ||
            isInput ||
            isButtonOrLink ||
            hasOnClick ||
            hasTabIndex ||
            hasRole
        );
    };

    const walk = (root) => {
        const children = root.children || [];
        for (const node of children) {
            if (node.nodeType !== 1) continue;
            if (!isVisible(node)) continue;

            const rect = node.getBoundingClientRect();
            if (rect.width < 2 || rect.height < 2) continue;

            const style = window.getComputedStyle(node);
            const tagName = node.tagName;
            const role = (node.getAttribute('role') || '').toLowerCase();
            const nameCandidates = [
                node.getAttribute('aria-label'),
                getAriaLabelledBy(node),
                node.placeholder,
                node.value,
                node.innerText
            ];
            const name = (nameCandidates.find(x => x && x.toString().trim()) || tagName).toString().substring(0, 100).trim();

            const interactive = isInteractiveNode(node, style, role);
            if (interactive) {
                results.push({
                    tagName: tagName,
                    role: role || tagName.toLowerCase(),
                    name: name,
                    placeholder: node.placeholder || '',
                    rect: {
                        x: Math.round(rect.x), y: Math.round(rect.y),
                        width: Math.round(rect.width), height: Math.round(rect.height)
                    }
                });
            }

            // Traverse shadow root if present
            if (node.shadowRoot) {
                walk(node.shadowRoot);
            }

            // Continue traversal
            walk(node);
        }
    };

    walk(document.body);
    return results;
}
"""
