"""This module contains useful Javascript utility methods for BaseCase."""
import re
import requests
import time
from contextlib import suppress
from r_selenium.common.exceptions import NoSuchElementException
from r_selenium.common.exceptions import WebDriverException
from r_selenium.webdriver.common.by import By
from r_seleniumbase import config as sb_config
from r_seleniumbase.config import settings
from r_seleniumbase.fixtures import constants
from r_seleniumbase.fixtures import css_to_xpath
from r_seleniumbase.fixtures import shared_utils
from r_seleniumbase.fixtures import xpath_to_css


def execute_script(driver, script, *args, **kwargs):
    if shared_utils.is_cdp_swap_needed(driver):
        return driver.cdp.evaluate(script)
    return driver.execute_script(script, *args, **kwargs)


def wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT):
    """The DOM (Document Object Model) has a property called "readyState".
    When the value of this becomes "complete", page resources are considered
      fully loaded (although AJAX and other loads might still be happening).
    This method will wait until document.readyState == "complete".
    This may be redundant, as methods already wait for page elements to load.
    If the timeout is exceeded, the test will still continue
      because readyState == "interactive" may be good enough.
    (Previously, tests would fail immediately if exceeding the timeout.)"""
    if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS:
        return
    start_ms = time.time() * 1000.0
    stop_ms = start_ms + (timeout * 1000.0)
    for x in range(int(timeout * 10)):
        if sb_config.time_limit and not sb_config.recorder_mode:
            shared_utils.check_if_time_limit_exceeded()
        try:
            ready_state = execute_script(driver, "return document.readyState;")
        except WebDriverException:
            # Bug fix for: [Permission denied to access property "document"]
            time.sleep(0.03)
            return True
        if ready_state == "complete":
            time.sleep(0.002)
            return True
        else:
            now_ms = time.time() * 1000.0
            if now_ms >= stop_ms:
                break
            time.sleep(0.1)
    return False  # readyState stayed "interactive" (Not "complete")


def execute_async_script(driver, script, timeout=settings.LARGE_TIMEOUT):
    driver.set_script_timeout(timeout)
    return driver.execute_async_script(script)


def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs):
    if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS:
        return
    with suppress(Exception):
        # This closes pop-up alerts
        execute_script(driver, "")
    if (
        (hasattr(driver, "_is_using_uc") and driver._is_using_uc)
        or not settings.WAIT_FOR_ANGULARJS
    ):
        wait_for_ready_state_complete(driver)
        return
    if timeout == settings.MINI_TIMEOUT:
        timeout = settings.MINI_TIMEOUT / 6.0
    NG_WRAPPER = (
        "%(prefix)s"
        "var $elm=document.querySelector("
        "'[data-ng-app],[ng-app],.ng-scope')||document;"
        "if(window.angular && angular.getTestability){"
        "angular.getTestability($elm).whenStable(%(handler)s)"
        "}else{"
        "var $inj;try{$inj=angular.element($elm).injector()||"
        "angular.injector(['ng'])}catch(ex){"
        "$inj=angular.injector(['ng'])};$inj.get=$inj.get||"
        "$inj;$inj.get('$browser')."
        "notifyWhenNoOutstandingRequests(%(handler)s)}"
        "%(suffix)s"
    )
    def_pre = "var cb=arguments[arguments.length-1];if(window.angular){"
    prefix = kwargs.pop("prefix", def_pre)
    handler = kwargs.pop("handler", "function(){cb(true)}")
    suffix = kwargs.pop("suffix", "}else{cb(false)}")
    script = NG_WRAPPER % {
        "prefix": prefix,
        "handler": handler,
        "suffix": suffix,
    }
    with suppress(Exception):
        execute_async_script(driver, script, timeout=timeout)


def convert_to_css_selector(selector, by=By.CSS_SELECTOR):
    if by == By.CSS_SELECTOR:
        return selector
    elif by == By.ID:
        return "#%s" % selector
    elif by == By.CLASS_NAME:
        return ".%s" % selector
    elif by == By.NAME:
        return '[name="%s"]' % selector
    elif by == By.TAG_NAME:
        return selector
    elif (
        by == By.XPATH
        or (
            selector.startswith("/")
            or selector.startswith("./")
            or selector.startswith("(")
        )
    ):
        return xpath_to_css.convert_xpath_to_css(selector)
    elif by == By.LINK_TEXT:
        return 'a:contains("%s")' % selector
    elif by == By.PARTIAL_LINK_TEXT:
        return 'a:contains("%s")' % selector
    else:
        raise Exception(
            "Exception: Could not convert {%s}(by=%s) to CSS_SELECTOR!"
            % (selector, by)
        )


def is_html_inspector_activated(driver):
    try:
        execute_script(driver, "HTMLInspector;")  # Fails if not defined
        return True
    except Exception:
        return False


def is_jquery_activated(driver):
    try:
        execute_script(driver, "jQuery('html');")  # Fails if jq is not defined
        return True
    except Exception:
        return False


def wait_for_jquery_active(driver, timeout=None):
    if not timeout:
        timeout = 2
    else:
        timeout = int(timeout * 10.0)
    for x in range(timeout):
        # jQuery needs a small amount of time to activate.
        try:
            execute_script(driver, "jQuery('html');")
            wait_for_ready_state_complete(driver)
            wait_for_angularjs(driver)
            return
        except Exception:
            time.sleep(0.1)


def raise_unable_to_load_jquery_exception(driver):
    has_csp_error = False
    csp_violation = "violates the following Content Security Policy directive"
    browser_logs = []
    try:
        browser_logs = driver.get_log("browser")
    except (ValueError, WebDriverException):
        pass
    for entry in browser_logs:
        if entry["level"] == "SEVERE":
            if csp_violation in entry["message"]:
                has_csp_error = True
    if has_csp_error:
        raise Exception(
            """Unable to load jQuery on "%s" due to a violation """
            """of the website's Content Security Policy directive. """
            """To override this policy, add "--disable-csp" on the """
            """command-line when running your tests.""" % driver.current_url
        )
    else:
        raise Exception(
            """Unable to load jQuery on "%s" because this website may be """
            """restricting external JavaScript resources from loading."""
            % driver.current_url
        )


def activate_jquery(driver):
    # If "jQuery is not defined" on a website, use this method to activate it.
    # This method is needed because jQuery is not always defined on web sites.
    with suppress(Exception):
        # Let's first find out if jQuery is already defined.
        execute_script(driver, "jQuery('html');")
        # Since that command worked, jQuery is defined. Let's return.
        return
    # jQuery is not defined. It will be loaded in the next part.
    jquery_js = constants.JQuery.MIN_JS
    add_js_link(driver, jquery_js)
    for x in range(36):
        # jQuery needs a small amount of time to activate.
        try:
            execute_script(driver, "jQuery('html');")
            return
        except Exception:
            if x == 18:
                add_js_link(driver, jquery_js)
            time.sleep(0.1)
    # Since jQuery still isn't activating, give up and raise an exception
    raise_unable_to_load_jquery_exception(driver)


def are_quotes_escaped(string):
    if string.count("\\'") != string.count("'") or (
        string.count('\\"') != string.count('"')
    ):
        return True
    return False


def escape_quotes_if_needed(string):
    """re.escape() works differently in Python 3.7.0 than earlier versions:

    Python 3.6.5:
    >>> import re
    >>> re.escape('"')
    '\\"'

    Python 3.7.0:
    >>> import re
    >>> re.escape('"')
    '"'

    r_seleniumbase needs quotes to be properly escaped for Javascript calls.
    """
    if are_quotes_escaped(string):
        if string.count("'") != string.count("\\'"):
            string = string.replace("'", "\\'")
        if string.count('"') != string.count('\\"'):
            string = string.replace('"', '\\"')
    return string


def is_in_frame(driver):
    # Returns True if the driver has switched to a frame.
    # Returns False if the driver was on default content.
    if shared_utils.is_cdp_swap_needed(driver):
        return False
    in_basic_frame = execute_script(
        driver,
        """
        var frame = window.frameElement;
        if (frame) {
            return true;
        }
        else {
            return false;
        }
        """
    )
    location_href = execute_script(driver, """return window.location.href;""")
    in_external_frame = False
    if driver.current_url != location_href:
        in_external_frame = True
    if in_basic_frame or in_external_frame:
        return True
    return False


def safe_execute_script(driver, script):
    """When executing a script that contains a jQuery command,
    it's important that the jQuery library has been loaded first.
    This method will load jQuery if it wasn't already loaded."""
    try:
        execute_script(driver, script)
    except Exception:
        # The likely reason this fails is because: "jQuery is not defined"
        activate_jquery(driver)  # It's a good thing we can define it here
        execute_script(driver, script)


def remove_extra_slashes(selector):
    if selector.count('\\"') > 0:
        if selector.count('\\"') == selector.count('"'):
            selector = selector.replace('\\"', '"')
        elif selector.count('\\"') == selector[1:-1].count('"') and (
            "'" not in selector[1:-1]
        ):
            selector = "'" + selector[1:-1].replace('\\"', '"') + "'"
        else:
            pass
    if selector.count("\\'") > 0:
        if selector.count("\\'") == selector.count("'"):
            selector = selector.replace("\\'", "'")
        elif selector.count("\\'") == selector[1:-1].count("'") and (
            '"' not in selector[1:-1]
        ):
            selector = '"' + selector[1:-1].replace("\\'", "'") + '"'
        else:
            pass
    return selector


def optimize_selector(selector):
    if (len(selector) > 2 and selector[0] == "'") and (
        selector[-1] == "'" and '"' not in selector[1:-1]
    ):
        selector = '"' + selector[1:-1] + '"'
    if (
        selector.count('"') == 0
        and selector.count("'") >= 2
        and selector.count("'") % 2 == 0
        and "='" in selector
        and "']" in selector
    ):
        swap_char = "*_SWAP_CHAR_*"
        selector = selector.replace("'", swap_char)
        selector = selector.replace('"', "'")
        selector = selector.replace(swap_char, '"')
    return selector


def wait_for_css_query_selector(
    driver, selector, timeout=settings.LARGE_TIMEOUT
):
    element = None
    selector = escape_quotes_if_needed(selector)
    selector = remove_extra_slashes(selector)
    selector = optimize_selector(selector)
    script = """return document.querySelector('%s');""" % selector
    start_ms = time.time() * 1000.0
    stop_ms = start_ms + (timeout * 1000.0)
    for x in range(int(timeout * 10)):
        try:
            element = execute_script(driver, script)
            if element:
                return element
        except Exception:
            element = None
        if not element:
            now_ms = time.time() * 1000.0
            if now_ms >= stop_ms:
                break
            time.sleep(0.1)
    raise NoSuchElementException(
        "Element {%s} was not present after %s seconds!" % (selector, timeout)
    )


def is_valid_by(by):
    return by in [
        "css selector", "class name", "id", "name",
        "link text", "xpath", "tag name", "partial link text",
    ]


def swap_selector_and_by_if_reversed(selector, by):
    if not is_valid_by(by) and is_valid_by(selector):
        selector, by = by, selector
    return (selector, by)


def call_me_later(driver, script, ms):
    """Call script after ms passed."""
    call = "function() {%s}" % script
    execute_script(driver, "window.setTimeout(%s, %s);" % (call, ms))


def highlight(driver, selector, by="css selector", loops=4):
    """For driver.highlight() / driver.page.highlight()"""
    swap_selector_and_by_if_reversed(selector, by)
    if ":contains(" in selector:
        by = "xpath"
        selector = css_to_xpath.convert_css_to_xpath(selector)
    element = None
    try:
        element = driver.find_element(by, selector)
    except Exception:
        time.sleep(1)
        element = driver.find_element(by, selector)
    o_bs = ""  # original_box_shadow
    style = element.get_attribute("style")
    if style and "box-shadow: " in style:
        box_start = style.find("box-shadow: ")
        box_end = style.find(";", box_start) + 1
        original_box_shadow = style[box_start:box_end]
        o_bs = original_box_shadow
    highlight_element_with_js(driver, element, loops=loops, o_bs=o_bs)


def highlight_with_js(driver, selector, loops=4, o_bs=""):
    with suppress(Exception):
        # This closes any pop-up alerts
        execute_script(driver, "")
    if selector == "html":
        selector = "body"
    selector_no_spaces = selector.replace(" ", "")
    early_exit = False
    if '[style=\\"' in selector_no_spaces:
        if "box\\-shadow:" in selector:
            early_exit = True  # Changing the box-shadow changes the selector
        elif '[style=\\"' in selector:
            selector = selector.replace('[style=\\"', '[style\\*=\\"')
        else:
            early_exit = True  # Changing the box-shadow changes the selector
        if early_exit:
            return
    script = (
        """document.querySelector('%s').style.boxShadow =
        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    for n in range(loops):
        script = (
            """document.querySelector('%s').style.boxShadow =
            '0px 0px 6px 6px rgba(255, 0, 0, 1)';"""
            % selector
        )
        try:
            execute_script(driver, script)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """document.querySelector('%s').style.boxShadow =
            '0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
            % selector
        )
        try:
            execute_script(driver, script)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """document.querySelector('%s').style.boxShadow =
            '0px 0px 6px 6px rgba(0, 0, 255, 1)';"""
            % selector
        )
        try:
            execute_script(driver, script)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """document.querySelector('%s').style.boxShadow =
            '0px 0px 6px 6px rgba(0, 255, 0, 1)';"""
            % selector
        )
        try:
            execute_script(driver, script)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """document.querySelector('%s').style.boxShadow =
            '0px 0px 6px 6px rgba(128, 128, 0, 1)';"""
            % selector
        )
        try:
            execute_script(driver, script)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """document.querySelector('%s').style.boxShadow =
            '0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
            % selector
        )
        try:
            execute_script(driver, script)
        except Exception:
            return
        time.sleep(0.0181)
    script = """document.querySelector('%s').style.boxShadow =
        '%s';""" % (
        selector,
        o_bs,
    )
    try:
        execute_script(driver, script)
    except Exception:
        return


def highlight_element_with_js(driver, element, loops=4, o_bs=""):
    with suppress(Exception):
        # This closes any pop-up alerts
        execute_script(driver, "")
    script = (
        """arguments[0].style.boxShadow =
        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';"""
    )
    try:
        execute_script(driver, script, element)
    except Exception:
        return
    for n in range(loops):
        script = (
            """arguments[0].style.boxShadow =
            '0px 0px 6px 6px rgba(255, 0, 0, 1)';"""
        )
        try:
            execute_script(driver, script, element)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """arguments[0].style.boxShadow =
            '0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
        )
        try:
            execute_script(driver, script, element)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """arguments[0].style.boxShadow =
            '0px 0px 6px 6px rgba(0, 0, 255, 1)';"""
        )
        try:
            execute_script(driver, script, element)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """arguments[0].style.boxShadow =
            '0px 0px 6px 6px rgba(0, 255, 0, 1)';"""
        )
        try:
            execute_script(driver, script, element)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """arguments[0].style.boxShadow =
            '0px 0px 6px 6px rgba(128, 128, 0, 1)';"""
        )
        try:
            execute_script(driver, script, element)
        except Exception:
            return
        time.sleep(0.0181)
        script = (
            """arguments[0].style.boxShadow =
            '0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
        )
        try:
            execute_script(driver, script, element)
        except Exception:
            return
        time.sleep(0.0181)
    script = """arguments[0].style.boxShadow = '%s';""" % (o_bs)
    try:
        execute_script(driver, script, element)
    except Exception:
        return


def highlight_with_jquery(driver, selector, loops=4, o_bs=""):
    with suppress(Exception):
        # This closes any pop-up alerts
        execute_script(driver, "")
    if selector == "html":
        selector = "body"
    selector_no_spaces = selector.replace(" ", "")
    early_exit = False
    if '[style=\\"' in selector_no_spaces:
        if "box\\-shadow:" in selector:
            early_exit = True  # Changing the box-shadow changes the selector
        elif '[style=\\"' in selector:
            selector = selector.replace('[style=\\"', '[style\\*=\\"')
        else:
            early_exit = True  # Changing the box-shadow changes the selector
        if early_exit:
            return
    script = (
        """jQuery('%s').css('box-shadow',
        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)');"""
        % selector
    )
    with suppress(Exception):
        safe_execute_script(driver, script)
    for n in range(loops):
        script = (
            """jQuery('%s').css('box-shadow',
            '0px 0px 6px 6px rgba(255, 0, 0, 1)');"""
            % selector
        )
        with suppress(Exception):
            execute_script(driver, script)
        time.sleep(0.0181)
        script = (
            """jQuery('%s').css('box-shadow',
            '0px 0px 6px 6px rgba(128, 0, 128, 1)');"""
            % selector
        )
        with suppress(Exception):
            execute_script(driver, script)
        time.sleep(0.0181)
        script = (
            """jQuery('%s').css('box-shadow',
            '0px 0px 6px 6px rgba(0, 0, 255, 1)');"""
            % selector
        )
        with suppress(Exception):
            execute_script(driver, script)
        time.sleep(0.0181)
        script = (
            """jQuery('%s').css('box-shadow',
            '0px 0px 6px 6px rgba(0, 255, 0, 1)');"""
            % selector
        )
        with suppress(Exception):
            execute_script(driver, script)
        time.sleep(0.0181)
        script = (
            """jQuery('%s').css('box-shadow',
            '0px 0px 6px 6px rgba(128, 128, 0, 1)');"""
            % selector
        )
        with suppress(Exception):
            execute_script(driver, script)
        time.sleep(0.0181)
        script = (
            """jQuery('%s').css('box-shadow',
            '0px 0px 6px 6px rgba(128, 0, 128, 1)');"""
            % selector
        )
        with suppress(Exception):
            execute_script(driver, script)
        time.sleep(0.0181)
    script = """jQuery('%s').css('box-shadow', '%s');""" % (selector, o_bs)
    with suppress(Exception):
        execute_script(driver, script)


def add_css_link(driver, css_link):
    script_to_add_css = """function injectCSS(css) {
          var head_tag=document.getElementsByTagName("head")[0];
          var link_tag=document.createElement("link");
          link_tag.rel="stylesheet";
          link_tag.type="text/css";
          link_tag.href=css;
          link_tag.crossorigin="anonymous";
          head_tag.appendChild(link_tag);
       }
       injectCSS("%s");"""
    css_link = escape_quotes_if_needed(css_link)
    execute_script(driver, script_to_add_css % css_link)


def add_js_link(driver, js_link):
    script_to_add_js = """function injectJS(link) {
          var body_tag=document.getElementsByTagName("body")[0];
          var script_tag=document.createElement("script");
          script_tag.src=link;
          script_tag.type="text/javascript";
          script_tag.crossorigin="anonymous";
          script_tag.defer;
          script_tag.onload=function() { null };
          body_tag.appendChild(script_tag);
       }
       injectJS("%s");"""
    js_link = escape_quotes_if_needed(js_link)
    execute_script(driver, script_to_add_js % js_link)


def add_css_style(driver, css_style):
    add_css_style_script = """function injectStyle(css) {
          var head_tag=document.getElementsByTagName("head")[0];
          var style_tag=document.createElement("style");
          style_tag.type="text/css";
          style_tag.appendChild(document.createTextNode(css));
          head_tag.appendChild(style_tag);
       }
       injectStyle("%s");"""
    css_style = css_style.replace("\n", "")
    css_style = escape_quotes_if_needed(css_style)
    execute_script(driver, add_css_style_script % css_style)


def add_js_code_from_link(driver, js_link):
    if js_link.startswith("//"):
        js_link = "http:" + js_link
    js_code = requests.get(js_link, timeout=5).text
    add_js_code_script = (
        """var body_tag=document.getElementsByTagName('body').item(0);"""
        """var script_tag=document.createElement("script");"""
        """script_tag.type="text/javascript";"""
        """script_tag.onload=function() { null };"""
        """script_tag.appendChild(document.createTextNode("%s"));"""
        """body_tag.appendChild(script_tag);"""
    )
    js_code = js_code.replace("\n", " ")
    js_code = escape_quotes_if_needed(js_code)
    execute_script(driver, add_js_code_script % js_code)


def add_js_code(driver, js_code):
    add_js_code_script = (
        """var body_tag=document.getElementsByTagName('body').item(0);"""
        """var script_tag=document.createElement("script");"""
        """script_tag.type="text/javascript";"""
        """script_tag.onload=function() { null };"""
        """script_tag.appendChild(document.createTextNode("%s"));"""
        """body_tag.appendChild(script_tag);"""
    )
    js_code = js_code.replace("\n", " ")
    js_code = escape_quotes_if_needed(js_code)
    execute_script(driver, add_js_code_script % js_code)


def add_meta_tag(driver, http_equiv=None, content=None):
    if http_equiv is None:
        http_equiv = "Content-Security-Policy"
    if content is None:
        content = (
            "default-src *; style-src 'self' 'unsafe-inline'; "
            "script-src: 'self' 'unsafe-inline' 'unsafe-eval'"
        )
    script_to_add_meta = """function injectMeta() {
           var meta_tag=document.createElement('meta');
           meta_tag.httpEquiv="%s";
           meta_tag.content="%s";
           document.getElementsByTagName('head')[0].appendChild(meta_tag);
        }
        injectMeta();""" % (
        http_equiv,
        content,
    )
    execute_script(driver, script_to_add_meta)


def is_jquery_confirm_activated(driver):
    try:
        execute_script(driver, "jconfirm;")  # Fails if jconfirm is not defined
        return True
    except Exception:
        return False


def activate_jquery_confirm(driver):
    jquery_js = constants.JQuery.MIN_JS
    jq_confirm_css = constants.JqueryConfirm.MIN_CSS
    jq_confirm_js = constants.JqueryConfirm.MIN_JS

    if not is_jquery_activated(driver):
        add_js_link(driver, jquery_js)
        wait_for_jquery_active(driver, timeout=1.2)
    add_css_link(driver, jq_confirm_css)
    add_js_link(driver, jq_confirm_js)

    for x in range(28):
        # jQuery-Confirm needs a small amount of time to load & activate.
        if x == 14:
            add_css_link(driver, jq_confirm_css)
            add_js_link(driver, jq_confirm_js)
        try:
            execute_script(driver, "jconfirm;")
            wait_for_ready_state_complete(driver)
            wait_for_angularjs(driver)
            return
        except Exception:
            time.sleep(0.1)


def activate_html_inspector(driver):
    jquery_js = constants.JQuery.MIN_JS
    html_inspector_js = constants.HtmlInspector.MIN_JS

    if is_html_inspector_activated(driver):
        return
    if not is_jquery_activated(driver):
        add_js_link(driver, jquery_js)
        wait_for_jquery_active(driver, timeout=1.2)
        wait_for_ready_state_complete(driver)
        wait_for_angularjs(driver)
    add_js_link(driver, html_inspector_js)
    wait_for_ready_state_complete(driver)
    wait_for_angularjs(driver)

    for x in range(25):
        # HTML-Inspector needs a small amount of time to load & activate.
        try:
            execute_script(driver, "HTMLInspector;")
            wait_for_ready_state_complete(driver)
            wait_for_angularjs(driver)
            return
        except Exception:
            time.sleep(0.1)
    wait_for_ready_state_complete(driver)
    wait_for_angularjs(driver)


def activate_messenger(driver):
    from r_seleniumbase.core.style_sheet import get_messenger_style

    jquery_js = constants.JQuery.MIN_JS
    messenger_css = constants.Messenger.MIN_CSS
    messenger_js = constants.Messenger.MIN_JS
    msgr_theme_flat_js = constants.Messenger.THEME_FLAT_JS
    msgr_theme_future_js = constants.Messenger.THEME_FUTURE_JS
    msgr_theme_flat_css = constants.Messenger.THEME_FLAT_CSS
    msgr_theme_future_css = constants.Messenger.THEME_FUTURE_CSS
    msgr_theme_block_css = constants.Messenger.THEME_BLOCK_CSS
    msgr_theme_air_css = constants.Messenger.THEME_AIR_CSS
    msgr_theme_ice_css = constants.Messenger.THEME_ICE_CSS
    spinner_css = constants.Messenger.SPINNER_CSS
    underscore_js = constants.Underscore.MIN_JS

    msg_style = (
        "Messenger.options = {'maxMessages': 8, "
        "extraClasses: 'messenger-fixed "
        "messenger-on-bottom messenger-on-right', "
        "theme: 'future'}"
    )

    if not is_jquery_activated(driver):
        add_js_link(driver, jquery_js)
        wait_for_jquery_active(driver, timeout=1.1)
    add_css_link(driver, messenger_css)
    add_css_link(driver, msgr_theme_flat_css)
    add_css_link(driver, msgr_theme_future_css)
    add_css_link(driver, msgr_theme_block_css)
    add_css_link(driver, msgr_theme_air_css)
    add_css_link(driver, msgr_theme_ice_css)
    add_js_link(driver, underscore_js)
    add_css_link(driver, spinner_css)
    add_js_link(driver, messenger_js)
    add_css_style(driver, get_messenger_style())

    for x in range(10):
        # Messenger needs a small amount of time to load & activate.
        try:
            result = execute_script(
                driver,
                """ if (typeof Messenger === 'undefined') { return "U"; } """
            )
            if result == "U":
                time.sleep(0.022)
                continue
            else:
                break
        except Exception:
            time.sleep(0.02)
    try:
        execute_script(driver, msg_style)
        add_js_link(driver, msgr_theme_flat_js)
        add_js_link(driver, msgr_theme_future_js)
        wait_for_ready_state_complete(driver)
        wait_for_angularjs(driver)
        return
    except Exception:
        time.sleep(0.1)


def set_messenger_theme(
    driver, theme="default", location="default", max_messages="default"
):
    if theme == "default":
        theme = "future"
    if location == "default":
        location = "bottom_right"
        if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator:
            location = "top_center"
    if max_messages == "default":
        max_messages = "8"

    valid_themes = ["flat", "future", "block", "air", "ice"]
    if theme not in valid_themes:
        raise Exception("Theme: %s is not in %s!" % (theme, valid_themes))
    valid_locations = [
        "top_left",
        "top_center",
        "top_right",
        "bottom_left",
        "bottom_center",
        "bottom_right",
    ]
    if location not in valid_locations:
        raise Exception(
            "Location: %s is not in %s!" % (location, valid_locations)
        )

    if location == "top_left":
        messenger_location = "messenger-on-top messenger-on-left"
    elif location == "top_center":
        messenger_location = "messenger-on-top"
    elif location == "top_right":
        messenger_location = "messenger-on-top messenger-on-right"
    elif location == "bottom_left":
        messenger_location = "messenger-on-bottom messenger-on-left"
    elif location == "bottom_center":
        messenger_location = "messenger-on-bottom"
    elif location == "bottom_right":
        messenger_location = "messenger-on-bottom messenger-on-right"

    msg_style = (
        "Messenger.options = {'maxMessages': %s, "
        "extraClasses: 'messenger-fixed %s', theme: '%s'}"
        % (max_messages, messenger_location, theme)
    )
    try:
        execute_script(driver, msg_style)
    except Exception:
        time.sleep(0.03)
        activate_messenger(driver)
        time.sleep(0.15)
        with suppress(Exception):
            execute_script(driver, msg_style)
            time.sleep(0.02)
    time.sleep(0.05)


def post_message(driver, message, msg_dur=None, style="info"):
    """A helper method to post a message on the screen with Messenger.
    (Should only be called from post_message() in base_case.py)"""
    if not msg_dur:
        msg_dur = settings.DEFAULT_MESSAGE_DURATION
    msg_dur = float(msg_dur)
    message = re.escape(message)
    message = escape_quotes_if_needed(message)
    messenger_script = (
        """Messenger().post({message: "%s", type: "%s", """
        """hideAfter: %s, hideOnNavigate: true});"""
        % (message, style, msg_dur)
    )
    retry = False
    try:
        execute_script(driver, messenger_script)
    except TypeError as e:
        if (
            shared_utils.is_cdp_swap_needed(driver)
            and "cannot unpack non-iterable" in str(e)
        ):
            pass
        else:
            retry = True
    except Exception:
        retry = True
    if retry:
        activate_messenger(driver)
        set_messenger_theme(driver)
        try:
            execute_script(driver, messenger_script)
        except TypeError:
            pass
        except Exception:
            time.sleep(0.17)
            activate_messenger(driver)
            time.sleep(0.17)
            set_messenger_theme(driver)
            time.sleep(0.27)
            execute_script(driver, messenger_script)


def post_messenger_success_message(driver, message, msg_dur=None):
    if not msg_dur:
        msg_dur = settings.DEFAULT_MESSAGE_DURATION
    msg_dur = float(msg_dur)
    with suppress(Exception):
        theme = "future"
        location = "bottom_right"
        if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator:
            location = "top_right"
        set_messenger_theme(driver, theme=theme, location=location)
        post_message(driver, message, msg_dur, style="success")
        time.sleep(msg_dur + 0.07)


def post_messenger_error_message(driver, message, msg_dur=None):
    if not msg_dur:
        msg_dur = settings.DEFAULT_MESSAGE_DURATION
    msg_dur = float(msg_dur)
    with suppress(Exception):
        set_messenger_theme(driver, theme="block", location="top_center")
        post_message(driver, message, msg_dur, style="error")
        time.sleep(msg_dur + 0.07)


def highlight_with_js_2(driver, message, selector, o_bs, msg_dur):
    with suppress(Exception):
        # This closes any pop-up alerts
        execute_script(driver, "")
    if selector == "html":
        selector = "body"
    selector_no_spaces = selector.replace(" ", "")
    early_exit = False
    if '[style=\\"' in selector_no_spaces:
        if "box\\-shadow:" in selector:
            early_exit = True  # Changing the box-shadow changes the selector
        elif '[style=\\"' in selector:
            selector = selector.replace('[style=\\"', '[style\\*=\\"')
        else:
            early_exit = True  # Changing the box-shadow changes the selector
        if early_exit:
            with suppress(Exception):
                activate_jquery(driver)
                post_messenger_success_message(driver, message, msg_dur)
            return
    script = (
        """document.querySelector('%s').style.boxShadow =
        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """document.querySelector('%s').style.boxShadow =
        '0px 0px 6px 6px rgba(205, 30, 0, 1)';"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """document.querySelector('%s').style.boxShadow =
        '0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """document.querySelector('%s').style.boxShadow =
        '0px 0px 6px 6px rgba(50, 50, 128, 1)';"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """document.querySelector('%s').style.boxShadow =
        '0px 0px 6px 6px rgba(50, 205, 50, 1)';"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    with suppress(Exception):
        activate_jquery(driver)
        post_messenger_success_message(driver, message, msg_dur)
    script = """document.querySelector('%s').style.boxShadow = '%s';""" % (
        selector,
        o_bs,
    )
    try:
        execute_script(driver, script)
    except Exception:
        return


def highlight_element_with_js_2(driver, message, element, o_bs, msg_dur):
    with suppress(Exception):
        # This closes any pop-up alerts
        execute_script(driver, "")
    script = (
        """arguments[0].style.boxShadow =
        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)';"""
    )
    try:
        execute_script(driver, script, element)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """arguments[0].style.boxShadow =
        '0px 0px 6px 6px rgba(205, 30, 0, 1)';"""
    )
    try:
        execute_script(driver, script, element)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """arguments[0].style.boxShadow =
        '0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
    )
    try:
        execute_script(driver, script, element)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """arguments[0].style.boxShadow =
        '0px 0px 6px 6px rgba(50, 50, 128, 1)';"""
    )
    try:
        execute_script(driver, script, element)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """arguments[0].style.boxShadow =
        '0px 0px 6px 6px rgba(50, 205, 50, 1)';"""
    )
    try:
        execute_script(driver, script, element)
    except Exception:
        return
    time.sleep(0.0181)
    with suppress(Exception):
        activate_jquery(driver)
        post_messenger_success_message(driver, message, msg_dur)
    script = """arguments[0].style.boxShadow = '%s';""" % (o_bs)
    try:
        execute_script(driver, script, element)
    except Exception:
        return


def highlight_with_jquery_2(driver, message, selector, o_bs, msg_dur):
    if selector == "html":
        selector = "body"
    selector_no_spaces = selector.replace(" ", "")
    early_exit = False
    if '[style=\\"' in selector_no_spaces:
        if "box\\-shadow:" in selector:
            early_exit = True  # Changing the box-shadow changes the selector
        elif '[style=\\"' in selector:
            selector = selector.replace('[style=\\"', '[style\\*=\\"')
        else:
            early_exit = True  # Changing the box-shadow changes the selector
        if early_exit:
            with suppress(Exception):
                activate_jquery(driver)
                post_messenger_success_message(driver, message, msg_dur)
            return
    script = (
        """jQuery('%s').css('box-shadow',
        '0px 0px 6px 6px rgba(128, 128, 128, 0.5)');"""
        % selector
    )
    try:
        safe_execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """jQuery('%s').css('box-shadow',
        '0px 0px 6px 6px rgba(205, 30, 0, 1)');"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """jQuery('%s').css('box-shadow',
        '0px 0px 6px 6px rgba(128, 0, 128, 1)');"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """jQuery('%s').css('box-shadow',
        '0px 0px 6px 6px rgba(50, 50, 200, 1)');"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)
    script = (
        """jQuery('%s').css('box-shadow',
        '0px 0px 6px 6px rgba(50, 205, 50, 1)');"""
        % selector
    )
    try:
        execute_script(driver, script)
    except Exception:
        return
    time.sleep(0.0181)

    with suppress(Exception):
        activate_jquery(driver)
        post_messenger_success_message(driver, message, msg_dur)

    script = """jQuery('%s').css('box-shadow', '%s');""" % (selector, o_bs)
    try:
        execute_script(driver, script)
    except Exception:
        return


def get_active_element_css(driver):
    from r_seleniumbase.js_code import active_css_js

    if shared_utils.is_cdp_swap_needed(driver):
        return driver.cdp.get_active_element_css()
    return execute_script(driver, active_css_js.get_active_element_css)


def get_locale_code(driver):
    script = "return navigator.language || navigator.languages[0];"
    return execute_script(driver, script)


def get_screen_rect(driver):
    return execute_script(driver, "return window.screen;")


def get_origin(driver):
    return execute_script(driver, "return window.location.origin;")


def get_user_agent(driver):
    return execute_script(driver, "return navigator.userAgent;")


def get_cookie_string(driver):
    return execute_script(driver, "return document.cookie;")


def get_scroll_distance_to_element(driver, element):
    try:
        scroll_position = execute_script(driver, "return window.scrollY;")
        element_location = None
        element_location = element.location["y"]
        element_location = element_location - constants.Scroll.Y_OFFSET
        if element_location < 0:
            element_location = 0
        distance = element_location - scroll_position
        return distance
    except Exception:
        return 0


def scroll_to_element(driver, element):
    element_location_y = None
    element_location_x = None
    element_width = 0
    screen_width = 0
    try:
        element_location_y = element.location["y"]
    except Exception:
        return False
    try:
        element_location_x = element.location["x"]
        element_width = element.size["width"]
        screen_width = driver.get_window_size()["width"]
    except Exception:
        element_location_x = 0
    element_location_y = element_location_y - constants.Scroll.Y_OFFSET
    if element_location_y < 0:
        element_location_y = 0
    element_location_x_fix = element_location_x - 400
    if element_location_x_fix < 0:
        element_location_x_fix = 0
    if element_location_x + element_width <= screen_width:
        element_location_x_fix = 0
    scroll_script = "window.scrollTo(%s, %s);" % (
        element_location_x_fix, element_location_y
    )
    # The old jQuery scroll_script required by=By.CSS_SELECTOR
    # scroll_script = "jQuery('%s')[0].scrollIntoView()" % selector
    # This other scroll_script does not centralize the element
    # execute_script(driver, "arguments[0].scrollIntoView();", element)
    try:
        execute_script(driver, scroll_script)
        return True
    except Exception:
        return False


def slow_scroll_to_element(driver, element, *args, **kwargs):
    if driver.capabilities["browserName"] == "internet explorer":
        # IE breaks on slow-scrolling. Do a fast scroll instead.
        scroll_to_element(driver, element)
        return
    scroll_position = execute_script(driver, "return window.scrollY;")
    element_location_y = None
    try:
        if shared_utils.is_cdp_swap_needed(driver):
            element.get_position().y
        else:
            element_location_y = element.location["y"]
    except Exception:
        element.location_once_scrolled_into_view
        return
    try:
        element_location_x = element.location["x"]
        element_width = element.size["width"]
        screen_width = driver.get_window_size()["width"]
    except Exception:
        element_location_x = 0
    element_location_y = element_location_y - constants.Scroll.Y_OFFSET
    if element_location_y < 0:
        element_location_y = 0
    element_location_x_fix = element_location_x - 400
    if element_location_x_fix < 0:
        element_location_x_fix = 0
    if element_location_x + element_width <= screen_width:
        element_location_x_fix = 0
    distance = element_location_y - scroll_position
    if distance != 0:
        total_steps = int(abs(distance) / 50.0) + 2.0
        step_value = float(distance) / total_steps
        new_position = scroll_position
        for y in range(int(total_steps)):
            time.sleep(0.011)
            new_position += step_value
            scroll_script = "window.scrollTo(0, %s);" % new_position
            execute_script(driver, scroll_script)
    time.sleep(0.01)
    scroll_script = "window.scrollTo(%s, %s);" % (
        element_location_x_fix, element_location_y
    )
    execute_script(driver, scroll_script)
    time.sleep(0.01)
    if distance > 430 or distance < -300:
        # Add small recovery time for long-distance slow-scrolling
        time.sleep(0.162)
    else:
        time.sleep(0.045)


def get_drag_and_drop_script():
    # This script uses jQuery to perform a Drag-and-Drop action.
    # (Requires the Drag-selector and the Drop-selector to work)
    script = r"""(function( $ ) {
        $.fn.simulateDragDrop = function(options) {
                return this.each(function() {
                        new $.simulateDragDrop(this, options);
                });
        };
        $.simulateDragDrop = function(elem, options) {
                this.options = options;
                this.simulateEvent(elem, options);
        };
        $.extend($.simulateDragDrop.prototype, {
                simulateEvent: function(elem, options) {
                        /*Simulating drag start*/
                        var type = 'dragstart';
                        var event = this.createEvent(type);
                        this.dispatchEvent(elem, type, event);

                        /*Simulating drop*/
                        type = 'drop';
                        var dropEvent = this.createEvent(type, {});
                        dropEvent.dataTransfer = event.dataTransfer;
                        this.dispatchEvent(
                            $(options.dropTarget)[0], type, dropEvent);

                        /*Simulating drag end*/
                        type = 'dragend';
                        var dragEndEvent = this.createEvent(type, {});
                        dragEndEvent.dataTransfer = event.dataTransfer;
                        this.dispatchEvent(elem, type, dragEndEvent);
                },
                createEvent: function(type) {
                        var event = document.createEvent("CustomEvent");
                        event.initCustomEvent(type, true, true, null);
                        event.dataTransfer = {
                                data: {
                                },
                                setData: function(type, val){
                                        this.data[type] = val;
                                },
                                getData: function(type){
                                        return this.data[type];
                                }
                        };
                        return event;
                },
                dispatchEvent: function(elem, type, event) {
                        if(elem.dispatchEvent) {
                                elem.dispatchEvent(event);
                        }else if( elem.fireEvent ) {
                                elem.fireEvent("on"+type, event);
                        }
                }
        });
        })(jQuery);"""
    return script


def get_js_drag_and_drop_script():
    # HTML5 Drag-and-Drop script (Requires extra parameters to work)
    # param1 (WebElement): Source element to drag
    # param2 (WebElement): Target element for the drop (Optional)
    # param3 (int): Optional - Drop offset x relative to the target
    # param4 (int): Optional - Drop offset y relative to the target
    # param4 (int): Optional - Delay in milliseconds (default = 1ms)
    # param5 (string): Optional - Key pressed (ALT or CTRL or SHIFT)
    script = """var t=arguments,e=t[0],n=t[1],i=t[2]||0,o=t[3]||0,r=t[4]||1,
        a=t[5]||'',s='alt'===a||'\ue00a'===a,l='ctrl'===a||'\ue009'===a,
        c='shift'===a||'\ue008'===a,u=e.ownerDocument,
        f=e.getBoundingClientRect(),g=n?n.getBoundingClientRect():f,
        p=f.left+f.width/2,d=f.top+f.height/2,h=g.left+(i||g.width/2),
        m=g.top+(o||g.height/2),v=u.elementFromPoint(p,d),
        y=u.elementFromPoint(h,m);if(!v||!y){
        var E=new Error('source or target element is not interactable');
        throw E.code=15,E}var _={constructor:DataTransfer,effectAllowed:null,
        dropEffect:null,types:[],files:Object.setPrototypeOf([],null),
        _items:Object.setPrototypeOf([],{add:function(t,e){
        this[this.length]={_data:''+t,kind:'string',
        type:e,getAsFile:function(){},getAsString:function(t){t(this._data)}},
        _.types.push(e)},remove:function(t){
        Array.prototype.splice.call(this,65535&t,1),_.types.splice(65535&t,1)},
        clear:function(t,e){this.length=0,_.types.length=0}}),
        setData:function(t,e){this.clearData(t),this._items.add(e,t)},
        getData:function(t){for(var e=this._items.length;
        e--&&this._items[e].type!==t;);return e>=0?this._items[e]._data:null},
        clearData:function(t){for(var e=this._items.length;
        e--&&this._items[e].type!==t;);this._items.remove(e)},
        setDragImage:function(t){}};function w(t,e,n,i){
        for(var o=0;o<e.length;++o){var r=u.createEvent('MouseEvent');
        r.initMouseEvent(e[o],!0,!0,u.defaultView,0,0,0,p,d,l,s,c,!1,0,null),
        t.dispatchEvent(r)}i&&setTimeout(i,n)}function D(t,e,n,i){
        var o=u.createEvent('DragEvent');o.initMouseEvent(
        e,!0,!0,u.defaultView,0,0,0,p,d,l,s,c,!1,0,null),Object.setPrototypeOf(
        o,null),o.dataTransfer=_,Object.setPrototypeOf(o,DragEvent.prototype),
        t.dispatchEvent(o),i&&setTimeout(i,n)}
        'items'in DataTransfer.prototype&&(_.items=_._items),
        w(v,['pointerdown','mousedown'],1,function(){
        for(var t=v;t&&!t.draggable;)t=t.parentElement;if(t&&t.contains(v)){
        var e=y.getBoundingClientRect();D(v,'dragstart',r,function(){
        var t=y.getBoundingClientRect();p=t.left+h-e.left,d=t.top+m-e.top,D(
        y,'dragenter',1,function(){D(y,'dragover',r,
        function(){D(u.elementFromPoint(p,d),'drop',1,function(){D(v,'dragend',
        1,function(){w(u.elementFromPoint(p,d),
        ['mouseup','pointerup'])})})})})})}})"""
    return script


def get_drag_and_drop_with_offset_script(selector, x, y):
    # This script uses pure JS (No jQuery)
    script_a = """
        var source = document.querySelector("%s");
        var offsetX = %f;
        var offsetY = %f;
        """ % (
        selector,
        x,
        y,
    )
    script_b = r"""
        var rect = source.getBoundingClientRect();
        var dragPt = {x: rect.left + (rect.width >> 1),
                      y: rect.top + (rect.height >> 1)};
        var dropPt = {x: dragPt.x + offsetX, y: dragPt.y + offsetY};
        var target = document.elementFromPoint(dropPt.x, dropPt.y);
        var dataTransfer = {
          dropEffect: '',
          effectAllowed: 'all',
          files: [],
          items: {},
          types: [],
          setData: function (format, data) {
            this.items[format] = data;
            this.types.push(format);
          },
          getData: function (format) {
            return this.items[format];
          },
          clearData: function (format) { }
        };
        var emit = function (event, target, pt) {
          var evt = document.createEvent('MouseEvent');
          evt.initMouseEvent(event, true, true, window, 0, 0, 0, pt.x, pt.y,
                             false, false, false, false, 0, null);
          evt.dataTransfer = dataTransfer;
          target.dispatchEvent(evt);
        };
        emit('mousedown', source, dragPt);
        emit('mousemove', source, dragPt);
        emit('mousemove', source, dropPt);
        emit('mouseup',   source, dropPt);"""
    script = script_a + script_b
    return script


def clear_out_console_logs(driver):
    with suppress(Exception):
        # Clear out the current page log before navigating to a new page
        # (To make sure that assert_no_js_errors() uses current results)
        driver.get_log("browser")


def _jq_format(code):
    """
    DEPRECATED - Use re.escape() instead, which performs the intended action.
    Use before throwing raw code such as 'div[tab="advanced"]' into jQuery.
    Selectors with quotes inside of quotes would otherwise break jQuery.
    If you just want to escape quotes, there's escape_quotes_if_needed().
    This is similar to "json.dumps(value)", but with one less layer of quotes.
    """
    code = code.replace("\\", "\\\\").replace("\t", "\\t").replace("\n", "\\n")
    code = code.replace('"', '\\"').replace("'", "\\'")
    code = code.replace("\v", "\\v").replace("\a", "\\a").replace("\f", "\\f")
    code = code.replace("\b", "\\b").replace(r"\u", "\\u").replace("\r", "\\r")
    return code
