"""This module contains methods for running website tours.
These helper methods SHOULD NOT be called directly from tests."""
import os
import re
import textwrap
import time
from seleniumbase import config as sb_config
from seleniumbase.config import settings
from seleniumbase.core import style_sheet
from seleniumbase.fixtures import constants
from seleniumbase.fixtures import js_utils
from seleniumbase.fixtures import page_actions

EXPORTED_TOURS_FOLDER = constants.Tours.EXPORTED_TOURS_FOLDER


def activate_bootstrap(driver):
    """Allows you to use Bootstrap Tours with SeleniumBase
    http://bootstraptour.com/
    """
    bootstrap_tour_css = constants.BootstrapTour.MIN_CSS
    bootstrap_tour_js = constants.BootstrapTour.MIN_JS

    verify_script = """// Verify Bootstrap Tour activated
                     var tour2 = new Tour({
                     });"""

    backdrop_style = style_sheet.get_bt_backdrop_style()
    js_utils.add_css_style(driver, backdrop_style)
    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, bootstrap_tour_css)
        js_utils.add_js_link(driver, bootstrap_tour_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # Bootstrap needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_bootstrap_activated(driver):
    verify_script = """// Verify Bootstrap Tour activated
                     var tour2 = new Tour({
                     });"""
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_driverjs(driver):
    """Allows you to use DriverJS Tours with SeleniumBase
    https://kamranahmed.info/driver.js/
    """
    backdrop_style = style_sheet.get_dt_backdrop_style()
    driverjs_css = constants.DriverJS.MIN_CSS
    driverjs_js = constants.DriverJS.MIN_JS

    verify_script = """// Verify DriverJS activated
                     var driverjs2 = Driver.name;
                     """

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, driverjs_css)
        js_utils.add_js_link(driver, driverjs_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # DriverJS needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_driverjs_activated(driver):
    verify_script = """// Verify DriverJS activated
                     var driverjs2 = Driver.name;
                     """
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_hopscotch(driver):
    """Allows you to use Hopscotch Tours with SeleniumBase
    http://linkedin.github.io/hopscotch/
    """
    hopscotch_css = constants.Hopscotch.MIN_CSS
    hopscotch_js = constants.Hopscotch.MIN_JS
    backdrop_style = style_sheet.get_hops_backdrop_style()

    verify_script = """// Verify Hopscotch activated
                     var hops = hopscotch.isActive;
                     """

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, hopscotch_css)
        js_utils.add_js_link(driver, hopscotch_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # Hopscotch needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_hopscotch_activated(driver):
    verify_script = """// Verify Hopscotch activated
                     var hops = hopscotch.isActive;
                     """
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_introjs(driver):
    """Allows you to use IntroJS Tours with SeleniumBase
    https://introjs.com/
    """
    intro_css = constants.IntroJS.MIN_CSS
    intro_js = constants.IntroJS.MIN_JS

    theme_color = sb_config.introjs_theme_color
    hover_color = sb_config.introjs_hover_color
    backdrop_style = style_sheet.get_introjs_style() % (
        theme_color,
        hover_color,
        hover_color,
        hover_color,
        theme_color,
    )

    verify_script = """// Verify IntroJS activated
                     var intro2 = introJs();
                     """

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, intro_css)
        js_utils.add_js_link(driver, intro_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # IntroJS needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_introjs_activated(driver):
    verify_script = """// Verify IntroJS activated
                     var intro2 = introJs();
                     """
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_shepherd(driver):
    """Allows you to use Shepherd Tours with SeleniumBase
    https://cdnjs.com/libraries/shepherd/1.8.1
    """
    shepherd_js = constants.Shepherd.MIN_JS
    sh_theme_arrows_css = constants.Shepherd.THEME_ARROWS_CSS
    sh_theme_arrows_fix_css = constants.Shepherd.THEME_ARR_FIX_CSS
    sh_theme_default_css = constants.Shepherd.THEME_DEFAULT_CSS
    sh_theme_dark_css = constants.Shepherd.THEME_DARK_CSS
    sh_theme_sq_css = constants.Shepherd.THEME_SQ_CSS
    sh_theme_sq_dark_css = constants.Shepherd.THEME_SQ_DK_CSS
    tether_js = constants.Tether.MIN_JS
    spinner_css = constants.Messenger.SPINNER_CSS
    sh_style = style_sheet.get_sh_style_test()
    backdrop_style = style_sheet.get_sh_backdrop_style()

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    for x in range(4):
        js_utils.add_css_link(driver, spinner_css)
        js_utils.add_css_link(driver, sh_theme_arrows_css)
        js_utils.add_css_link(driver, sh_theme_arrows_fix_css)
        js_utils.add_css_link(driver, sh_theme_default_css)
        js_utils.add_css_link(driver, sh_theme_dark_css)
        js_utils.add_css_link(driver, sh_theme_sq_css)
        js_utils.add_css_link(driver, sh_theme_sq_dark_css)
        js_utils.add_js_link(driver, tether_js)
        js_utils.add_js_link(driver, shepherd_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # Shepherd needs a small amount of time to load & activate.
            try:
                driver.execute_script(sh_style)  # Verify Shepherd has loaded
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                driver.execute_script(sh_style)  # Need it twice for ordering
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_shepherd_activated(driver):
    sh_style = style_sheet.get_sh_style_test()
    try:
        driver.execute_script(sh_style)  # Verify Shepherd has loaded
        return True
    except Exception:
        return False


def play_shepherd_tour(driver, tour_steps, msg_dur, name=None, interval=0):
    """Plays a Shepherd tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """
        // Start the tour
        tour.start();
        $tour = tour;"""
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp' || event.key === 'ArrowLeft') {
            Shepherd.activeTour.back(); }
        if (event.key === 'PageDown' || event.key === 'ArrowRight') {
            Shepherd.activeTour.next(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_shepherd_activated(driver):
        instructions += extra
        activate_shepherd(driver)

    if len(tour_steps[name]) > 1:
        try:
            selector = re.search(
                r"[\S\s]+{element: '([\S\s]+)', on: [\S\s]+",
                tour_steps[name][1],
            ).group(1)
            selector = selector.replace("\\", "")
            page_actions.wait_for_element_present(
                driver,
                selector,
                by="css selector",
                timeout=settings.SMALL_TIMEOUT,
            )
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )
    driver.execute_script(instructions)
    try:
        page_actions.wait_for_element_visible(
            driver, "a.tour-button-right", by="css selector", timeout=1.2
        )
    except Exception:
        pass
    try:
        driver.execute_script('document.activeElement.blur();')
    except Exception:
        pass
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_element = None
        latest_text = None
    while tour_on:
        try:
            time.sleep(0.01)
            result = driver.execute_script(
                "return Shepherd.activeTour.currentStep.isOpen()"
            )
        except Exception:
            tour_on = False
            result = None
        if result:
            tour_on = True
            if autoplay:
                try:
                    element = driver.execute_script(
                        "return Shepherd.activeTour.currentStep"
                        ".options.attachTo.element"
                    )
                    shep_text = driver.execute_script(
                        "return Shepherd.activeTour.currentStep"
                        ".options.text"
                    )
                except Exception:
                    continue
                if element != latest_element or shep_text != latest_text:
                    latest_element = element
                    latest_text = shep_text
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if (element == latest_element) and (
                        shep_text == latest_text
                    ):
                        driver.execute_script("Shepherd.activeTour.next()")
                        try:
                            latest_element = driver.execute_script(
                                "return Shepherd.activeTour.currentStep"
                                ".options.attachTo.element"
                            )
                            latest_text = driver.execute_script(
                                "return Shepherd.activeTour.currentStep"
                                ".options.text"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                selector = driver.execute_script(
                    "return Shepherd.activeTour"
                    ".currentStep.options.attachTo.element"
                )
                try:
                    js_utils.wait_for_css_query_selector(
                        driver, selector, timeout=settings.SMALL_TIMEOUT
                    )
                except Exception:
                    remove_script = (
                        "jQuery('%s').remove()" % "div.shepherd-content"
                    )
                    driver.execute_script(remove_script)
                    js_utils.post_messenger_error_message(
                        driver,
                        "Tour Error: {'%s'} was not found!" % selector,
                        msg_dur,
                    )
                    time.sleep(0.1)
                driver.execute_script("Shepherd.activeTour.next()")
                if autoplay:
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                tour_on = True
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_bootstrap_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays a Bootstrap tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]);
        // Initialize the tour
        tour.init();
        // Start the tour
        tour.start();
        // Fix timing issue by restarting tour immediately
        tour.restart();
        // Save for later
        $tour = tour;"""
    if interval and interval > 0:
        if interval < 1:
            interval = 1
        interval = str(float(interval) * 1000.0)
        instructions = instructions.replace(
            "duration: 0,", "duration: %s," % interval
        )
    if not is_bootstrap_activated(driver):
        activate_bootstrap(driver)
    if len(tour_steps[name]) > 1:
        try:
            if "element: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+element: '([\S\s]+)',[\S\s]+title: '",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "").replace(":first", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )
    driver.execute_script(instructions)
    tour_on = True
    try:
        page_actions.wait_for_element_visible(
            driver, ".tour-tour", by="css selector", timeout=1.2
        )
    except Exception:
        pass
    try:
        driver.execute_script('document.activeElement.blur();')
    except Exception:
        pass
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = driver.execute_script("return $tour.ended()")
            else:
                page_actions.wait_for_element_present(
                    driver, ".tour-tour", by="css selector", timeout=0.48
                )
                result = False
        except Exception:
            tour_on = False
            result = None
        if result is False:
            tour_on = True
            time.sleep(0.05)
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = driver.execute_script("return $tour.ended()")
                else:
                    page_actions.wait_for_element_present(
                        driver, ".tour-tour", by="css selector", timeout=0.48
                    )
                    result = False
                if result is False:
                    time.sleep(0.05)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_driverjs_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays a DriverJS tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]
        );
        // Start the tour!
        tour.start();
        $tour = tour;"""
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp') { $tour.movePrevious(); }
        if (event.key === 'PageDown') { $tour.moveNext(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_driverjs_activated(driver):
        instructions += extra
        activate_driverjs(driver)

    if len(tour_steps[name]) > 1:
        try:
            if "element: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+element: '([\S\s]+)',[\S\s]+popover: {",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "").replace(":first", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )

    driver.execute_script(instructions)
    driver.execute_script(
        'document.querySelector(".driver-next-btn").focus();'
    )
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_step = 0
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = not driver.execute_script("return $tour.isActivated")
            else:
                page_actions.wait_for_element_visible(
                    driver,
                    "#driver-popover-item",
                    by="css selector",
                    timeout=1.1,
                )
                result = False
        except Exception:
            tour_on = False
            result = None
        if result is False:
            tour_on = True
            driver.execute_script(
                'document.querySelector(".driver-next-btn").focus();'
            )
            if autoplay:
                try:
                    current_step = driver.execute_script(
                        "return $tour.currentStep"
                    )
                except Exception:
                    continue
                if current_step != latest_step:
                    latest_step = current_step
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if current_step == latest_step:
                        driver.execute_script("$tour.moveNext()")
                        try:
                            latest_step = driver.execute_script(
                                "return $tour.currentStep"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = not driver.execute_script(
                        "return $tour.isActivated"
                    )
                else:
                    page_actions.wait_for_element_visible(
                        driver,
                        "#driver-popover-item",
                        by="css selector",
                        timeout=1.1,
                    )
                    result = False
                if result is False:
                    time.sleep(0.1)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_hopscotch_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays a Hopscotch tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]
        };
        // Start the tour!
        hopscotch.startTour(tour);
        $tour = hopscotch;"""
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp' || event.key === 'ArrowLeft') {
            $tour.prevStep(); }
        if (event.key === 'PageDown' || event.key === 'ArrowRight') {
            $tour.nextStep(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_hopscotch_activated(driver):
        instructions += extra
        activate_hopscotch(driver)

    if len(tour_steps[name]) > 1:
        try:
            if "target: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+target: '([\S\s]+)',[\S\s]+title: '",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "").replace(":first", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )

    driver.execute_script(instructions)
    try:
        page_actions.wait_for_element_visible(
            driver, "button.hopscotch-next", by="css selector", timeout=1.2
        )
    except Exception:
        pass
    try:
        driver.execute_script('document.activeElement.blur();')
    except Exception:
        pass
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_step = 0
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = not driver.execute_script("return $tour.isActive")
            else:
                page_actions.wait_for_element_present(
                    driver,
                    ".hopscotch-bubble",
                    by="css selector",
                    timeout=0.4,
                )
                result = False
        except Exception:
            tour_on = False
            result = None
        if result is False:
            tour_on = True
            if autoplay:
                try:
                    current_step = driver.execute_script(
                        "return $tour.getCurrStepNum()"
                    )
                except Exception:
                    continue
                if current_step != latest_step:
                    latest_step = current_step
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if current_step == latest_step:
                        driver.execute_script("$tour.nextStep()")
                        try:
                            latest_step = driver.execute_script(
                                "return $tour.getCurrStepNum()"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = not driver.execute_script("return $tour.isActive")
                else:
                    page_actions.wait_for_element_present(
                        driver,
                        ".hopscotch-bubble",
                        by="css selector",
                        timeout=0.4,
                    )
                    result = False
                if result is False:
                    time.sleep(0.1)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_introjs_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays an IntroJS tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]
        });
        intro.setOption("disableInteraction", true);
        intro.setOption("overlayOpacity", .29);
        intro.setOption("scrollToElement", true);
        intro.setOption("keyboardNavigation", true);
        intro.setOption("exitOnEsc", true);
        intro.setOption("hidePrev", true);
        intro.setOption("nextToDone", true);
        intro.setOption("exitOnOverlayClick", false);
        intro.setOption("showStepNumbers", false);
        intro.setOption("showProgress", false);
        intro.start();
        $tour = intro;
        };
        // Start the tour
        startIntro();
        """
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp') { $tour.previousStep(); }
        if (event.key === 'PageDown') { $tour.nextStep(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_introjs_activated(driver):
        instructions += extra
        activate_introjs(driver)

    if len(tour_steps[name]) > 1:
        try:
            if "element: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+element: '([\S\s]+)',[\S\s]+intro: '",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )
    driver.execute_script(instructions)
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_step = 0
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = driver.execute_script("return $tour._currentStep")
            else:
                page_actions.wait_for_element_present(
                    driver, ".introjs-tooltip", by="css selector", timeout=0.4
                )
                result = True
        except Exception:
            tour_on = False
            result = None
        if result is not None:
            tour_on = True
            if autoplay:
                try:
                    current_step = driver.execute_script(
                        "return $tour._currentStep"
                    )
                except Exception:
                    continue
                if current_step != latest_step:
                    latest_step = current_step
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if current_step == latest_step:
                        try:
                            driver.execute_script("$tour.nextStep()")
                        except Exception:
                            driver.execute_script("$tour.exit()")
                        try:
                            latest_step = driver.execute_script(
                                "return $tour._currentStep"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = driver.execute_script("return $tour._currentStep")
                else:
                    page_actions.wait_for_element_present(
                        driver,
                        ".introjs-tooltip",
                        by="css selector",
                        timeout=0.4,
                    )
                    result = True
                if result is not None:
                    time.sleep(0.1)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def export_tour(tour_steps, name=None, filename="my_tour.js", url=None):
    """Exports a tour as a JS file.
    It will include necessary resources as well, such as jQuery.
    You'll be able to copy the tour directly into the Console of
    any web browser to play the tour outside of SeleniumBase runs."""
    if not name:
        name = "default"
    if name not in tour_steps:
        raise Exception("Tour {%s} does not exist!" % name)
    if not filename.endswith(".js"):
        raise Exception('Tour file must end in ".js"!')
    if not url:
        url = "data:,"

    tour_type = None
    if "Bootstrap" in tour_steps[name][0]:
        tour_type = "bootstrap"
    elif "DriverJS" in tour_steps[name][0]:
        tour_type = "driverjs"
    elif "Hopscotch" in tour_steps[name][0]:
        tour_type = "hopscotch"
    elif "IntroJS" in tour_steps[name][0]:
        tour_type = "introjs"
    elif "Shepherd" in tour_steps[name][0]:
        tour_type = "shepherd"
    else:
        raise Exception("Unknown tour type!")

    instructions = (
        """////////  Load Tour Start Page (if not there now)  ////////\n\n"""
        """if (window.location.href != "%s") {\n"""
        """    window.location.href="%s";\n"""
        """}\n\n"""
        """////////  Resources  ////////\n\n"""
        """function injectCSS(css_link) {"""
        """var head = document.getElementsByTagName("head")[0];"""
        """var link = document.createElement("link");"""
        """link.rel = "stylesheet";"""
        """link.type = "text/css";"""
        """link.href = css_link;"""
        """link.crossorigin = "anonymous";"""
        """head.appendChild(link);"""
        """};\n"""
        """function injectJS(js_link) {"""
        """var head = document.getElementsByTagName("head")[0];"""
        """var script = document.createElement("script");"""
        """script.src = js_link;"""
        """script.defer;"""
        """script.type="text/javascript";"""
        """script.crossorigin = "anonymous";"""
        """script.onload = function() { null };"""
        """head.appendChild(script);"""
        """};\n"""
        """function injectStyle(css) {"""
        """var head = document.getElementsByTagName("head")[0];"""
        """var style = document.createElement("style");"""
        """style.type = "text/css";"""
        """style.appendChild(document.createTextNode(css));"""
        """head.appendChild(style);"""
        """};\n""" % (url, url)
    )

    if tour_type == "bootstrap":
        jquery_js = constants.JQuery.MIN_JS
        bootstrap_tour_css = constants.BootstrapTour.MIN_CSS
        bootstrap_tour_js = constants.BootstrapTour.MIN_JS
        backdrop_style = style_sheet.get_bt_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectJS("%s");\n' % jquery_js
        instructions += "\n"
        instructions += "function loadResources() { "
        instructions += 'if ( typeof jQuery !== "undefined" ) {\n'
        instructions += 'injectCSS("%s");\n' % bootstrap_tour_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % bootstrap_tour_js
        instructions += '} else { window.setTimeout("loadResources();",100); '
        instructions += "} }\n"
        instructions += "loadResources()"

    elif tour_type == "driverjs":
        driverjs_css = constants.DriverJS.MIN_CSS
        driverjs_js = constants.DriverJS.MIN_JS
        backdrop_style = style_sheet.get_dt_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % driverjs_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % driverjs_js

    elif tour_type == "hopscotch":
        hopscotch_css = constants.Hopscotch.MIN_CSS
        hopscotch_js = constants.Hopscotch.MIN_JS
        backdrop_style = style_sheet.get_hops_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % hopscotch_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % hopscotch_js

    elif tour_type == "introjs":
        intro_css = constants.IntroJS.MIN_CSS
        intro_js = constants.IntroJS.MIN_JS
        theme_color = sb_config.introjs_theme_color
        hover_color = sb_config.introjs_hover_color
        backdrop_style = style_sheet.get_introjs_style() % (
            theme_color,
            hover_color,
            hover_color,
            hover_color,
            theme_color,
        )
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % intro_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % intro_js

    elif tour_type == "shepherd":
        jquery_js = constants.JQuery.MIN_JS
        shepherd_js = constants.Shepherd.MIN_JS
        sh_theme_arrows_css = constants.Shepherd.THEME_ARROWS_CSS
        sh_theme_arrows_fix_css = constants.Shepherd.THEME_ARR_FIX_CSS
        sh_theme_default_css = constants.Shepherd.THEME_DEFAULT_CSS
        sh_theme_dark_css = constants.Shepherd.THEME_DARK_CSS
        sh_theme_sq_css = constants.Shepherd.THEME_SQ_CSS
        sh_theme_sq_dark_css = constants.Shepherd.THEME_SQ_DK_CSS
        tether_js = constants.Tether.MIN_JS
        spinner_css = constants.Messenger.SPINNER_CSS
        backdrop_style = style_sheet.get_sh_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % spinner_css
        instructions += 'injectJS("%s");\n' % jquery_js
        instructions += 'injectJS("%s");\n' % tether_js
        instructions += "\n"
        instructions += "function loadResources() { "
        instructions += 'if ( typeof jQuery !== "undefined" ) {\n'
        instructions += 'injectCSS("%s");' % sh_theme_arrows_css
        instructions += 'injectCSS("%s");' % sh_theme_arrows_fix_css
        instructions += 'injectCSS("%s");' % sh_theme_default_css
        instructions += 'injectCSS("%s");' % sh_theme_dark_css
        instructions += 'injectCSS("%s");' % sh_theme_sq_css
        instructions += 'injectCSS("%s");\n' % sh_theme_sq_dark_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");\n' % shepherd_js
        instructions += '} else { window.setTimeout("loadResources();",100); '
        instructions += "} }\n"
        instructions += "loadResources()"

    instructions += "\n\n////////  Tour Code  ////////\n\n"
    first_instructions = instructions
    instructions = "        "
    if tour_type == "bootstrap":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof Tour !== "undefined" ) {\n'
    elif tour_type == "driverjs":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof Driver !== "undefined" ) {\n'
    elif tour_type == "hopscotch":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof hopscotch !== "undefined" ) {\n'
    elif tour_type == "introjs":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof introJs !== "undefined" ) {\n'
    elif tour_type == "shepherd":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof Shepherd !== "undefined" ) {\n'

    for tour_step in tour_steps[name]:
        instructions += tour_step

    if tour_type == "bootstrap":
        instructions += """]);
            // Initialize the tour
            tour.init();
            // Start the tour
            tour.start();
            $tour = tour;
            $tour.restart();\n"""
    elif tour_type == "driverjs":
        instructions += """]
            );
            // Start the tour!
            tour.start();
            $tour = tour;\n"""
    elif tour_type == "hopscotch":
        instructions += """]
            };
            // Start the tour!
            hopscotch.startTour(tour);
            $tour = hopscotch;\n"""
    elif tour_type == "introjs":
        instructions += """]
            });
            intro.setOption("disableInteraction", true);
            intro.setOption("overlayOpacity", .29);
            intro.setOption("scrollToElement", true);
            intro.setOption("keyboardNavigation", true);
            intro.setOption("exitOnEsc", true);
            intro.setOption("hidePrev", true);
            intro.setOption("nextToDone", true);
            intro.setOption("exitOnOverlayClick", false);
            intro.setOption("showStepNumbers", false);
            intro.setOption("showProgress", false);
            intro.start();
            $tour = intro;
            };
            startIntro();\n"""
    elif tour_type == "shepherd":
        instructions += """
            tour.start();
            $tour = tour;\n"""
    else:
        pass
    instructions = textwrap.dedent(instructions)
    instructions = first_instructions + instructions
    instructions += '\n} else { window.setTimeout("loadTour();",100); } '
    instructions += "}\n"
    instructions += "loadTour()\n"

    exported_tours_folder = EXPORTED_TOURS_FOLDER
    if exported_tours_folder.endswith("/"):
        exported_tours_folder = exported_tours_folder[:-1]
    if not os.path.exists(exported_tours_folder):
        try:
            os.makedirs(exported_tours_folder)
        except Exception:
            pass

    file_path = exported_tours_folder + "/" + filename
    out_file = open(file_path, mode="w+", encoding="utf-8")
    out_file.writelines(instructions)
    out_file.close()
    print("\n>>> [%s] was saved!\n" % file_path)
