"""Utilities for using sizzle (jQuery-style) selectors with Selenium."""
import json
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support.wait import WebDriverWait
from .has_driver import exception_indicates_stale_element
SIZZLE_LOAD_TIMEOUT = 5
SIZZLE_URL = "//cdnjs.cloudflare.com/ajax/libs/sizzle/1.10.18/sizzle.js"
[docs]
def sizzle_selector_clickable(selector):
    def ec(driver):
        elements = find_elements_by_sizzle(driver, selector)
        if not elements:
            return False
        element = elements[0]
        try:
            clickable = element.is_displayed() and element.is_enabled()
        except Exception as e:
            # Handle the case where the element is detached between when it is
            # discovered and when it is checked - it is likely changing quickly
            # and the next pass will be the final state. If not, this should be
            # wrapped in a wait anyway - so no problems there. For other
            # non-custom selectors I believe this all happens on the Selenium
            # server and so there is likely no need to handle this case - they
            # are effectively atomic.
            if exception_indicates_stale_element(e):
                return None
            raise
        if clickable:
            return element
        else:
            return None
    return ec 
[docs]
def sizzle_presence_of_selector(selector):
    def ec(driver):
        elements = find_elements_by_sizzle(driver, selector)
        if not elements:
            return False
        element = elements[0]
        try:
            displayed = element.is_displayed()
        except Exception as e:
            # See note above insizzle_selector_clickable about this exception.
            if exception_indicates_stale_element(e):
                return None
            raise
        if displayed:
            return element
        else:
            return None
    return ec 
[docs]
def find_element_by_sizzle(driver, sizzle_selector: str):
    """
    Finds an element by sizzle selector.
    :param sizzle_selector: The sizzle selector to use when finding element.
    """
    if elements := driver.find_elements_by_sizzle(sizzle_selector):
        return elements[0]
    else:
        raise NoSuchElementException(f"Unable to locate element by Sizzle: {sizzle_selector}") 
[docs]
def find_elements_by_sizzle(driver, sizzle_selector: str):
    """
    Finds elements by sizzle selector.
    :param sizzle_selector: The sizzle selector to use when finding elements.
    """
    if not _is_sizzle_loaded(driver):
        _inject_sizzle(driver, SIZZLE_URL, SIZZLE_LOAD_TIMEOUT)
    elements = driver.execute_script(_make_sizzle_string(sizzle_selector))
    return elements 
def _inject_sizzle(driver, sizzle_url, timeout):
    script = f"""
        if(typeof(window.$) != "undefined") {{
            // Just reuse jQuery if it is available, avoids potential amd problems
            // that have cropped up with Galaxy for instance.
            window.Sizzle = window.$;
        }} else {{
            var _s = document.createElement("script");
            _s.type = "text/javascript";
            _s.src = "{sizzle_url}";
            var _h = document.getElementsByTagName("head")[0];
            _h.appendChild(_s);
        }}
    """
    driver.execute_script(script)
    wait = WebDriverWait(driver, timeout)
    wait.until(lambda d: _is_sizzle_loaded(d), f"Can't inject Sizzle in {timeout} seconds")
def _is_sizzle_loaded(driver):
    script = 'return typeof(Sizzle) != "undefined";'
    return driver.execute_script(script)
def _make_sizzle_string(sizzle_selector):
    # Use json.dumps to escape quotes
    selector = json.dumps(sizzle_selector)
    return f"return Sizzle({selector});"
__all__ = (
    "find_element_by_sizzle",
    "find_elements_by_sizzle",
    "sizzle_selector_clickable",
    "sizzle_presence_of_selector",
)