"use strict";

// Object.assign() isn't available in IE11
if (typeof Object.assign !== "function") {
	// Must be writable: true, enumerable: false, configurable: true
	Object.defineProperty(Object, "assign", {
		value: function assign(target, varArgs) { // .length of function is 2
			"use strict";
			if (target === null || target === undefined) {
				throw new TypeError("Cannot convert undefined or null to object");
			}

			var to = Object(target);

			for (var index = 1; index < arguments.length; index++) {
				var nextSource = arguments[index];

				if (nextSource !== null && nextSource !== undefined) {
					for (var nextKey in nextSource) {
						// Avoid bugs when hasOwnProperty is shadowed
						if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
							to[nextKey] = nextSource[nextKey];
						}
					}
				}
			}
			return to;
		},
		writable: true,
		configurable: true
	});
}

/**
 * @lends GuidanceSteplist
 * @external GuidanceSteplist
 * @typedef {Object} GuidanceSteplist
 * @type {GuidanceSteplist} GuidanceSteplist
 * @constructs
 */

if (!(GuidanceSteplist.prototype instanceof Object)) {
	GuidanceSteplist.prototype = {
		loadJS: function () {
			// Intentionally left blank
		}
	};
}

/* @type {{title: String, steps: Array}} saveJSONData */
var saveJSONData = {
	"title": "",
	"steps": []
};

/**
 * jslint unused: false, jshint unused: false, sonarlint unused: false
 *
 * ATTENTION: This function will be called by every "data.js" file.
 * DO NOT REMOVE THIS FUNCTION!
 *
 * @public
 * @param {Object} data
 */
function saveJSON(data) {
	saveJSONData = data;
}

(function () {
	/**
	 * @private
	 */
	var options = {
			"cancelEventTimeout": 250,
			"animationDuration": 400,
			"animationEasing": "swing",
			"screenshotDuration": 400,
			"screenshotEasing": "swing",
			"playIconDelay": 500,
			"useJSONData": false
		},
		self,
		name = "GuidanceSteplist",
		clickedOnScreenshot = false,
		eventCancellingInProgress = false,
		browserName,
		browserVersion,
		qaContentView;

	/**
	 * @private
	 */
	function logError() {
		if (typeof console === "undefined") {
			return;
		}
		var newArray = [].slice.call(arguments);
		newArray.unshift("ERROR in " + name + ": ");
		console.log.apply(console, newArray);
	}

	/**
	 * Initialize steplist
	 * @private
	 * @param {Object} [opts]
	 */
	function init(opts) {
		try {
			opts = (typeof opts === "object") ? opts : {};
			options = Object.assign({}, options, opts);
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 */
	function loadCustomStyles() {
		try {
			var customCSSArray = options["customCSS"],
				len = customCSSArray.length,
				i = 0;
			for (; i < len; i++) {
				loadCSS(document, customCSSArray[i]);
			}
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 */
	function loadGuidanceStyles() {
		try {
			loadPlayerCSS(document, "font-awesome/css/font-awesome.min.css");
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * This creates two CSS class selectors in the html element.
	 * The first is the browser name, the second with the version number
	 * added to the browser name.
	 *
	 * Examples:
	 *        <html class="ie ie7">...</html>
	 *        <html class="firefox firefox35">...</html>
	 *        <html class="chrome chrome40">...</html>
	 *
	 * @private
	 */
	function addUserAgentClass() {
		var browser = getBrowserVersion();

		browserName = browser["name"];
		browserVersion = browser["version"];

		$("html").addClass(browserName + " " + browserName + browserVersion);
	}

	/**
	 * @private
	 * @returns {{name: String, version: String}}
	 */
	function getBrowserVersion() {
		var userAgent = window.navigator.userAgent,
			temp,
			matchResultEdge = userAgent.match(/(edge)\/?\s*(\d+)/i) || [],
			matchResult = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [],
			improveIEVersionNumber = function () {
				try {
					var docMode = document && typeof document.documentMode !== "undefined" ? document.documentMode : 0;
					if (docMode) {
						return docMode + "";
					}
				} catch (e) {
					// Intentionally left blank
				}
				return "";
			};

		if (matchResultEdge.length > 0) {
			matchResult = matchResultEdge;
		}
		if (/trident/i.test(matchResult[1])) {
			return {
				"name": "ie",
				"version": improveIEVersionNumber()
			};
		}
		if (matchResult[1].toLowerCase() === "msie") {
			return {
				"name": "ie",
				"version": improveIEVersionNumber()
			};
		}
		if (matchResult[1].toLowerCase() === "chrome") {
			temp = userAgent.match(/\bOPR\/(\d+)/);
			if (temp !== null) {
				return {
					"name": "opera",
					"version": temp[1]
				};
			}
		}
		matchResult = matchResult[2] ? [matchResult[1], matchResult[2]] : [navigator.appName, navigator.appVersion, "-?"];
		if ((temp = userAgent.match(/version\/(\d+)/i)) != null) {
			matchResult.splice(1, 1, temp[1]);
		}
		return {
			"name": matchResult[0].toLowerCase(),
			"version": matchResult[1]
		};
	}

	/**
	 *
	 * @private
	 * @returns {Array}
	 */
	function cacheStepPositions() {
		var arr = [];
		try {
			// Cache current top positions and anchor ids of every step
			$(".step:visible").each(function (index, element) {
				var jqElement = $(element),
					elementTop = jqElement[0].offsetTop - (parseInt(jqElement.css("margin-top"), 10) || 0),
					anchorId = $(".selector", jqElement).data("stepId");
				arr.push({
					"posY": elementTop,
					"id": anchorId
				});
			});
		} catch (e) {
			logError(e);
		}
		return arr;
	}

	/**
	 *
	 * @private
	 * @param {String} direction
	 * @returns {String}
	 */
	function getScrollTargetAnchorId(direction) {
		// Get current vertical scroll offset
		var scrTop = $(".scroller").scrollTop(),
			arr,
			targetAnchorId = "";

		try {
			arr = cacheStepPositions();
			arr = $.grep(arr, function (n/*, index*/) {
				if (direction === "next") {
					return (scrTop < n["posY"]);
				}
				return (scrTop > (n["posY"]));
			});

			if (arr.length > 0) {
				if (direction === "next") {
					targetAnchorId = arr[0]["id"];
				} else {
					targetAnchorId = arr[arr.length - 1]["id"];
				}
			}
		} catch (e) {
			logError(e);
		}
		return targetAnchorId;
	}

	/**
	 * Scrolls the .scroller element to the anchor with the target anchorId
	 *
	 * @private
	 * @param {String} anchorId
	 * @returns {HTMLElement|undefined} Returns the "step" element of the anchor, or undefined if no step was found.
	 */
	function scrollToAnchor(anchorId) {
		var anchorTag,
			step;

		try {
			adjustHeader();
			if (!anchorId) {
				return undefined;
			}
			anchorTag = $("div.selector").filter(function () {
				return $(this).data("stepId") === anchorId;
			});
			if (anchorTag.length > 0) {
				step = anchorTag.parent(".step");
				if (step.length > 0) {
					$(".scroller")
						.animate({scrollTop: (step[0].offsetTop - (parseInt(step.css("margin-top"), 10) || 0))}, "fast");
					return step.get(0);
				}
			}
		} catch (e) {
			logError(e);
		}
		return undefined;
	}

	/**
	 *
	 * @private
	 * @param {Event} e
	 * @returns {boolean}
	 */
	function cancelEvent(e) {
		var isHyperlink = $(e.target).is("a");
		if (e.preventDefault && !isHyperlink) {
			e.preventDefault();
		}
		if (e.stopPropagation) {
			e.stopPropagation();
		}
		e.returnValue = false;
		return !!isHyperlink;
	}

	/**
	 * @private
	 */
	function resetEventCancelling() {
		setTimeout(function () {
			eventCancellingInProgress = false;
		}, options["cancelEventTimeout"]);
	}

	/**
	 * @private
	 * @returns {boolean}
	 */
	function setEventCancelling() {
		if (eventCancellingInProgress === true) {
			return true;
		}
		eventCancellingInProgress = true;
		return false;
	}

	/**
	 * Creates an URL parameter <code>r</code> with a random number.
	 * @private
	 * @returns {String}
	 */
	function createRandomUrlParameter() {
		return "r=" + Math.floor(Math.random() * Date.now());
	}

	/**
	 * @private
	 */
	function hideAllAnimations() {
		try {
			$(".step").each(function (i, stepElement) {
				var step = $(stepElement),
					jqAnimationElement = $(".animation", step);
				jqAnimationElement.data("img-animate", true);
				if (!jqAnimationElement.hasClass("noHeight")) {
					animateHeight(step, 0);
				}
			});
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 * @param {{stepId: String, opened: Boolean}} data
	 */
	function onAnimationFinished(data) {
		if (data && data["opened"]) {
			scrollToAnchor("#" + data["stepId"]);
		}
	}

	/**
	 * @private
	 * @param {Object} jqStep
	 * @param {Number} [animationHeight]
	 */
	function animateHeight(jqStep, animationHeight) {
		var jqAnimationElement = $(".animation", jqStep),
			jqScreenshotElement = $(".screenshot", jqStep),
			oldSRC,
			screenshotHeight,
			iconsHeight,
			iconsHeightReverse,
			screenshotOriginalWidth = parseInt(jqScreenshotElement.data("img-width"), 10),
			screenshotOriginalHeight = parseInt(jqScreenshotElement.data("img-height"), 10),
			jqAnimationLayer = jqAnimationElement.parents(".animationLayer"),
			jqScreenshotLayer = jqScreenshotElement.parents(".screenshotContainer"),
			jqScreenshotIcons = $(".screenshotPlayIcon,.screenshotLoupeIcon", jqStep),
			jqScreenshotIconsReverse = $(".screenshotCloseIcon", jqStep),
			animationOriginalWidth = parseInt(jqAnimationElement.data("img-width"), 10),
			animationOriginalHeight = parseInt(jqAnimationElement.data("img-height"), 10);

		screenshotOriginalWidth = !isNaN(screenshotOriginalWidth) ? screenshotOriginalWidth : 0;
		screenshotOriginalHeight = !isNaN(screenshotOriginalHeight) ? screenshotOriginalHeight : 0;
		animationOriginalWidth = !isNaN(animationOriginalWidth) ? animationOriginalWidth : 0;
		animationOriginalHeight = !isNaN(animationOriginalHeight) ? animationOriginalHeight : 0;

		animationHeight = !isNaN(animationHeight) ? animationHeight : animationOriginalHeight;
		if (typeof jqScreenshotIcons.data("height") === "undefined") {
			jqScreenshotIcons.data("height", jqScreenshotIcons.outerHeight());
		}
		if (typeof jqScreenshotIconsReverse.data("height") === "undefined") {
			jqScreenshotIconsReverse.data("height", jqScreenshotIconsReverse.outerHeight());
		}
		if (animationHeight > 0) {
			animationHeight = animationOriginalWidth !== 0 ? (animationOriginalHeight * jqAnimationLayer.outerWidth() / animationOriginalWidth) : 0;
			screenshotHeight = "0px";
			iconsHeight = 0;
			iconsHeightReverse = parseInt(jqScreenshotIconsReverse.data("height"), 10);
		} else {
			screenshotHeight = screenshotOriginalWidth !== 0 ? (screenshotOriginalHeight * jqScreenshotLayer.outerWidth() / screenshotOriginalWidth) : 0;
			screenshotHeight += "px";
			iconsHeight = parseInt(jqScreenshotIcons.data("height"), 10);
			iconsHeightReverse = 0;
		}
		if (jqAnimationElement.data("img-animate") !== true) {
			return;
		}
		if (animationHeight !== 0) {
			hideAllAnimations();
			oldSRC = jqAnimationElement.attr("src");
			oldSRC = updateRandomParameter(oldSRC);
			jqAnimationElement
				.data("img-animate", false)
				.attr("src", "")
				.attr("src", oldSRC);
			jqStep.addClass("animationOn");

			if (jqAnimationElement.data("css-animation") === true) {
				jqAnimationElement.attr("visible", true);
			}
		} else {
			jqStep.removeClass("animationOn");
		}

		$(function () {
			jqScreenshotElement.animate({
					"height": screenshotHeight
				},
				options["screenshotDuration"],
				options["screenshotEasing"],
				function () {
					if (screenshotHeight !== "0px") {
						jqScreenshotElement.css("height", "auto");
					}
					clickedOnScreenshot = false;
				});

			jqAnimationElement.animate({
					"height": (animationHeight + "px")
				},
				options["animationDuration"],
				options["animationEasing"],
				function () {
					var opened = true;
					if (animationHeight === 0) {
						jqAnimationElement
							.addClass("noHeight")
							.css("height", "");

						if (jqAnimationElement.data("css-animation") === true) {
							jqAnimationElement.attr("visible", false);
						}

						opened = false;
					} else {
						jqAnimationElement
							.removeClass("noHeight")
							.css("height", "auto");
					}
					jqAnimationElement.parents("div.selector").focus();
					onAnimationFinished({
						"stepId": $(".selector", jqStep).attr("id"),
						"opened": opened
					});
				});

			if (iconsHeight !== 0) {
				jqScreenshotIcons
					.delay(0.8 * options["screenshotDuration"]);
			}
			if (iconsHeightReverse !== 0) {
				jqScreenshotIconsReverse
					.delay(0.8 * options["screenshotDuration"]);
			}
			jqScreenshotIcons
				.animate({
					"height": (iconsHeight + "px")
				}, 0.25 * options["screenshotDuration"]);
			jqScreenshotIconsReverse
				.animate({
					"height": (iconsHeightReverse + "px")
				}, 0.25 * options["screenshotDuration"]);
		});
	}

	function updateRandomParameter(animationSourceUrl) {
		var newSrc,
			browser = getBrowserVersion();

		if(browser.name !== "ie" && browser.name !== "edge") {
			return animationSourceUrl;
		}
		var regexRandomParam = /(\?r=[0-9]+)/g,
			randomParam = "?" + createRandomUrlParameter();
		if (regexRandomParam.test(animationSourceUrl)) {
			newSrc = animationSourceUrl.replace(regexRandomParam, randomParam);
		} else {
			newSrc = animationSourceUrl + randomParam;
		}
		return newSrc;
	}

	/**
	 * Loads and opens the animation.
	 *
	 * Info:
	 * For QuickGuides the animation has to be requested every time.
	 * This isn't necessary for EditorialGuides.
	 *
	 * @private
	 * @param {Element} step
	 */
	function loadAnimation(step) {
		var newSrc,
			jqAnimation = $(".animation", step),
			animationSourceUrl = jqAnimation.data("img-src"),
			isEditorialGuide = !!$("html").hasClass("editorial");

		if (jqAnimation.data("css-animation") && isEditorialGuide) {
			animateHeight(step);
			return;
		}

		if (!animationSourceUrl) {
			return;
		}

		if (jqAnimation.data("img-loaded") && isEditorialGuide) {
			animateHeight(step);
			return;
		}

		newSrc = updateRandomParameter(animationSourceUrl);
		jqAnimation
			.on("load", function (e) {
				$(e.currentTarget)
					.data("img-loaded", true)
					.data("img-width", e.currentTarget.naturalWidth)
					.data("img-height", e.currentTarget.naturalHeight);
				animateHeight(step);
			})
			.on("error", function (e) {
				$(e.currentTarget).data("img-loaded", false);
			})
			.attr("src", newSrc);
	}

	/**
	 * Adjustments to step selector anchor tags
	 * @private
	 */
	function adjustAnchorSelectors() {
		try {
			$(".steplist .stage div.selector").each(function (anchorIndex, anchorElement) {
				$(anchorElement)
					.data("stepId", "#step" + (anchorIndex + 1))
					.on("click", function () {
						var textSelected = isTextSelected();
						if (textSelected.result && $(textSelected.step).is($(this).parents(".step").get(0))) {
							return;
						}
						window.location.hash = "#step" + (anchorIndex + 1);
						scrollToAnchor("#step" + (anchorIndex + 1));
						collapseRange();
					});

				$(anchorElement)
					.attr("id", "step" + (anchorIndex + 1))
			});
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 * @param {String} eventType
	 * @param {Object | String} data
	 * @returns {Boolean}
	 */
	function sendEvent(eventType, data) {
		if(!eventType) {
			return false;
		}
		var sendData = "";
		switch(typeof data) {
			case "object":
				sendData = Object.assign({}, data);
				break;
			case "string":
				sendData = data;
				break;
			case "undefined":
			default:
		}
		return $.event.trigger({
			type: ("steplist:" + eventType),
			message: sendData,
			time: new Date()
		});
	}

	/**
	 * @private
	 */
	function setAnchorTitleListeners() {
		var sendData = {
			isQuickAccessContentView: true
		};
		qaContentView = findQuickAccessContentView(parent.window);
		if(qaContentView === null) {
			qaContentView = findQuickAccessContentView(window.top);
		}

		if (qaContentView === null) {
			sendData.isQuickAccessContentView = false;
			// Don't show back button
			$("body.steplist").addClass("noqa");
		}

		if(sendEvent("before:setTitleListeners", sendData)) {
			return;
		}

		// TG-70
		// IE9 only issue for QuickGuide
		$(".ie9:not(.editorial) .steplist .header table")
			.on("click", function (e) {
				var jqAnchor = $(this).parents("a"),
					href;
				if (jqAnchor.length > 0) {
					href = jqAnchor.get(0).href;
					scrollToAnchor(href);
					window.location.href = href;
					e.stopPropagation();
					e.preventDefault();
				}
			});

		// Only for Editorial Guides (won't be handled by QuickGuide)
		$(".editorial .steplist .header a.title").on("click", function () {
			scrollToAnchor("#step1");
			try {
				// Notify QA
				if (qaContentView !== null && typeof qaContentView.close === "function") {
					qaContentView.close();
				}
			} catch (e) {
				logError(e);
			}
		});
		sendEvent("after:setTitleListeners", sendData);
	}

	/**
	 * @private
	 * @param {Window} win
	 * @returns {Element | null}
	 */
	function findQuickAccessContentView(win) {
		var framesLength,
			i = 0,
			qaContentView;

		try {
			if (typeof window["QuickAccessContentView"] !== "undefined") {
				return window["QuickAccessContentView"];
			}
			if (win && typeof win["QuickAccessContentView"] !== "undefined") {
				return win["QuickAccessContentView"];
			}
			if (typeof window["quickAccessContentViewport"] !== "undefined") {
				return window["quickAccessContentViewport"];
			}
			if (win && typeof win["quickAccessContentViewport"] !== "undefined") {
				return win["quickAccessContentViewport"];
			}
			framesLength = win.length;
			for (; i < framesLength; i++) {
				qaContentView = findQuickAccessContentView(win.frames[i]);
				if (qaContentView !== null) {
					return qaContentView;
				}
			}
		} catch (e) {
			logError(e);
		}
		return null;
	}

	/**
	 * Set event handlers to step tags
	 * @private
	 */
	function setEventHandlerSteps() {
		try {
			$(".steplist .step").each(function (index, element) {
				var step = $(element);

				$("div.selector", step)
					.on("mouseenter", function () {
						$(".step.hoverStep").removeClass("hoverStep");
						step.addClass("hoverStep");
					})
					.on("mouseleave", function () {
						step.removeClass("hoverStep");
					})
					.on("mousedown", function () {
						// Remove hover selector
						step.removeClass("hoverStep");
						if (step.hasClass("activeStep")) {
							// Remove active selector in current active step
							step.removeClass("activeStep");
						} else {
							// Remove active selector in all active steps
							$(".step").removeClass("activeStep");

							// Set current step active
							step.addClass("activeStep");
						}
					});

				// Don't select step after clicking on text
				$(".explanation, .interaction", step)
					.on("click", function (e) {
						return cancelEvent(e);
					});
			});
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * Set event handlers to screenshots
	 * @private
	 */
	function setEventHandlerScreenshots() {
		try {
			$(".steplist .step").each(function (index, element) {
				var step = $(element),
					showAnimation = function () {
						var jqAnimation = $(".animation", step);
						clickedOnScreenshot = jqAnimation.length > 0;
						if (jqAnimation.length > 0 && jqAnimation.hasClass("noHeight")) {
							jqAnimation.data("img-animate", true);
							loadAnimation(step);
						}
					},
					jqImage,
					dataImage,
					showTimeout = -1;

				$(".screenshot", step)
					.on("mousedown", showAnimation)
					.on("click", function (e) {
						e.stopPropagation();
					});

				$(".screenshot-button", step)
					.on("click", function (e) {
						showAnimation();
						e.stopPropagation();
					});

				$(".screenshotPlayIcon", step)
					.on("mouseover", function () {
						if (showTimeout === -1) {
							showTimeout = setTimeout(showAnimation, options["playIconDelay"]);
						}
					})
					.on("mouseout", function () {
						if (showTimeout !== -1) {
							clearTimeout(showTimeout);
						}
						showTimeout = -1;
					});


				jqImage = $("img.screenshot", step);
				dataImage = jqImage.data();
				if (typeof dataImage !== "object") {
					return;
				}
				if (!dataImage["imgWidth"]) {
					jqImage.data("img-width", jqImage.width());
				}
				if (!dataImage["imgHeight"]) {
					jqImage.data("img-height", jqImage.height());
				}
			});
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * Set event handlers to animations
	 * @private
	 */
	function setEventHandlerAnimations() {
		try {
			$(".steplist .step").each(function (index, element) {
				var step = $(element),
					hideAnimation = function () {
						var jqAnimation = $(".animation", step);
						if (!jqAnimation.hasClass("noHeight")) {
							jqAnimation.data("img-animate", true);
							animateHeight(step, 0);
						}
					};

				$(".animation,.animationCloseIcon", step)
					.on("mousedown", hideAnimation);

				$(".screenshot-button", step)
					.on("click", hideAnimation);

				$(".screenshotCloseIcon", step)
					.on("mouseover", hideAnimation);
			});
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * Returns the hash value of the URL or an empty string.
	 * @private
	 * @returns {String}
	 */
	function getHash() {
		var url = window.location.href,
			pos = url.indexOf("#");
		try {
			if (typeof window.location.hash !== "undefined" && window.location.hash !== "") {
				return window.location.hash;
			}
			return (pos === -1) ? "" : url.substring(pos + 1);
		} catch (e) {
			logError(e);
		}
		return "";
	}

	/**
	 * @private
	 * @returns {String}
	 */
	function onHashChange() {
		var hashValue = "";
		try {
			if (clickedOnScreenshot === true) {
				return hashValue;
			}
			hashValue = getHash();
			if (hashValue) {
				scrollToAnchor(hashValue);
			}
		} catch (e) {
			logError(e);
		}
		return hashValue;
	}

	/**
	 * @private
	 */
	function setScrollListeners() {
		try {
			// Add mousewheel listener for step-wise scrolling
			addWheelListener($(".scroller").get(0), function (e) {
				if (setEventCancelling() === true) {
					return cancelEvent(e);
				}
				var dir = e.deltaY > 0 ? "next" : "prev",
					scrollToId = getScrollTargetAnchorId(dir);
				if (scrollToId) {
					scrollToAnchor(scrollToId);
					resetEventCancelling();
					return cancelEvent(e);
				}
				resetEventCancelling();
			});

			$(window.document).on("keydown", function (e) {
				var scrollToId;
				switch (e.keyCode) {
					// arrow up
					case 38:
					// page up
					case 33:
						if (setEventCancelling() === true) {
							return cancelEvent(e);
						}
						scrollToId = getScrollTargetAnchorId("prev");
						if (scrollToId) {
							scrollToAnchor(scrollToId);
							resetEventCancelling();
							return cancelEvent(e);
						}
						break;
					// arrow down
					case 40:
					// page down
					case 34:
						if (setEventCancelling() === true) {
							return cancelEvent(e);
						}
						scrollToId = getScrollTargetAnchorId("next");
						if (scrollToId) {
							scrollToAnchor(scrollToId);
							resetEventCancelling();
							return cancelEvent(e);
						}
						break;
					default:
				}
				resetEventCancelling();
			});

			$(window).on("hashchange", onHashChange);

			$(window).on("resize", function () {
				adjustHeader();
			});

			$(window).on("load", afterContentRendered);

			if (browserName === "edge" || browserName === "ie") {
				setTimeout(afterContentRendered, 100);
			}

		} catch (ex) {
			logError(ex);
		}
	}

	/**
	 * @private
	 */
	function afterContentRendered() {
		var hashValue = onHashChange();
		if (hashValue) {
			$(hashValue).mousedown();
		}
		adjustHeader();
	}

	/**
	 * @private
	 * @param {Document} doc
	 * @param {String} file
	 */
	function loadCSS(doc, file) {
		try {
			if (!file) {
				return;
			}
			var linkElement = doc.createElement("link");
			linkElement.setAttribute("type", "text/css");
			linkElement.setAttribute("rel", "stylesheet");
			linkElement.setAttribute("href", fixSourcePath(file));
			doc.getElementsByTagName("head")[0].appendChild(linkElement);
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 * @param {Document} doc
	 * @param {string} file
	 */
	function loadPlayerCSS(doc, file) {
		try {
			loadCSS(doc, options["playerPath"] + file);
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 */
	function adjustKeySymbolPaths() {
		try {
			$("img.ttclasskey").each(function (index, element) {
				var jqElement = $(element),
					dataBgImage = jqElement.data("background-image");
				// Always refer to one.gif in player folder
				jqElement
					.attr("src", options["playerPath"] + "one.gif")
					.css("background-image", "url(\"" + options["rootPath"] + dataBgImage + "\")");
			});
		} catch (ex) {
			logError(ex);
		}
	}

	/**
	 * @private
	 * @returns {{result: boolean, step: HTMLElement | undefined}}
	 */
	function isTextSelected() {
		var userSelection,
			selectedText,
			step,
			result = {
				result: false,
				step: undefined
			};

		try {
			if (window.getSelection) {
				userSelection = window.getSelection();
			} else if (document.selection) {
				// should come last; Opera!
				userSelection = document.selection.createRange();
			} else {
				return result;
			}

			selectedText = typeof userSelection.text !== "undefined" ? ("" + userSelection.text) : userSelection.toString();

			if (typeof userSelection.baseNode !== "undefined") {
				step = $(userSelection.baseNode).parents(".step").get(0);
			} else if (typeof userSelection.anchorNode !== "undefined") {
				step = $(userSelection.anchorNode).parents(".step").get(0);
			} else {
				step = $(document.selection.createRange().parentElement()).parents(".step").get(0);
			}
			result = {
				result: (selectedText !== ""),
				step: step
			};
		} catch (err) {
			logError(err);
		}
		return result;
	}

	/**
	 * @private
	 */
	function collapseRange() {
		var userSelection;

		try {
			if (window.getSelection) {
				userSelection = window.getSelection();
			} else if (document.selection) {
				userSelection = document.selection.createRange();
			} else {
				return;
			}
			if (typeof userSelection.collapseToStart !== "undefined") {
				if (!userSelection.isCollapsed) {
					userSelection.collapseToStart();
				}
			} else if (typeof userSelection.collapse !== "undefined") {
				userSelection.collapse(true);
				userSelection.select();
			}
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 */
	function adjustHeader() {
		var headerOuterHeight,
			marginTopStyle = "0";

		try {
			headerOuterHeight = $(".steplist .header").outerHeight();

			if (headerOuterHeight && !isNaN(headerOuterHeight)) {
				marginTopStyle = headerOuterHeight + "px";
			}

			$(".steplist .scroller").css({
				"margin-top": marginTopStyle
			});
		} catch (e) {
			logError(e);
		}
	}

	/**
	 * @private
	 * @param {Object} jqElement
	 * @param {String} textHTML
	 */
	function setTextContent(jqElement, textHTML) {
		var textElement = $(".text_content", jqElement);
		if (!textHTML) {
			// Hide element
			jqElement.addClass("hiddenElement");
		} else {
			jqElement.removeClass("hiddenElement");
			var sanitizedElement = sanitizeHTML( textHTML );
			if( sanitizedElement ) {
				textElement.html( $(sanitizedElement).html() );
			}
		}
	}

	/**
	 * @private
	 * @param {Object} jqElement
	 * @param {String} type
	 */
	function setInteractionIcon(jqElement, type) {
		if (!type) {
			return;
		}
		type += "";
		jqElement.addClass("interaction_type_" + type.toLowerCase());
	}

	/**
	 * Adjusts a path.
	 *
	 * @private
	 * @param {String} path
	 * @returns {String}
	 */
	function setContentPath(path) {
		var base = window.location.href,
			urlParameters = window.location.search,
			protocol = window.location.protocol,
			res,
			pathPattern = /(.*?)([^/\\]*?)$/gi,
			newPath = path;

		try {
			if (urlParameters) {
				// Cut the url parameters from origin URL
				base = base.substring(0, base.indexOf(urlParameters));
				res = pathPattern.exec(base);
				if (res && res.length >= 3) {
					base = res[1] ? res[1] : base;
				}
			}
			if (path.indexOf(".") === 0) {
				// Also allow relative paths in "content" parameter
				newPath = makeAbsolutePath(base, path);
			} else {
				// ...if "content" parameter was used with Windows path syntax:
				if (newPath.indexOf(protocol) !== 0) {
					newPath = protocol + "//" + newPath;
				}
			}

			newPath = fixSourcePath(newPath);

			// Is the last character not a slash?
			if (newPath.split("").pop() !== "/") {
				newPath = newPath + "/";
			}
		} catch (e) {
			logError(e);
		}
		return newPath;
	}

	/**
	 * Converts some umlauts/special characters in URLs
	 *
	 * @private
	 * @param {String} url
	 * @returns {String}
	 */
	function fixSourcePath(url) {
		var result = url;
		try {
			if (url && typeof url === "string") {
				result = url.replace(/#/g, "%23");
				result = result.replace(/\\/g, "/");
			}
		} catch (e) {
			logError(e);
		}
		return result;
	}

	/**
	 *
	 * @private
	 * @param {String} base
	 * @param {String} relative
	 * @returns {String}
	 */
	function makeAbsolutePath(base, relative) {
		var stack = base.split("/"),
			parts = relative.split("/"),
			i = 0,
			len = parts.length;

		stack.pop(); // remove current file name (or empty string)
					 // (omit if "base" is the current folder without trailing slash)
		for (; i < len; i++) {
			if (parts[i] === ".") {
				continue;
			}
			if (parts[i] === "..") {
				stack.pop();
			} else {
				stack.push(parts[i]);
			}
		}
		return stack.join("/");
	}

	/**
	 * @private
	 */
	function sanitizeHTML( value ) {
		if( !value ) {
			return null;
		}
		var element = document.createElement("p");
		element.setHTML(value);
		return element;
	}


		/**
	 * @private
	 */
	function createHTMLByJSON() {
		if (!saveJSONData || typeof saveJSONData !== "object") {
			return;
		}
		var contentPath = setContentPath(options["contentPath"]),
			title,
			templateStep,
			templateClear,
			stage,
			steps;

		if (saveJSONData["title"]) {
			var sanitizedElement = sanitizeHTML( saveJSONData["title"] );
			if( sanitizedElement ) {
				title = $(sanitizedElement).text();
				$(".steplist .header .title td.content").html(title);
			}
		}

		templateStep = $(".steplist .template_step .stepTEMPLATE");
		templateClear = $(".steplist .template_clear .clearTEMPLATE");
		stage = $(".steplist .stage");
		steps = saveJSONData["steps"].length;

		$.each(saveJSONData["steps"], function (index, elementJSON) {
			var step = templateStep.clone(true),
				img,
				clear,
				afterLoadImage;

			step
				.removeClass("stepTEMPLATE")
				.addClass("step");

			setTextContent($(".explanation", step), elementJSON["explanation"]);
			setTextContent($(".interaction", step), elementJSON["interaction"]);
			setInteractionIcon($(".interaction", step), elementJSON["type"]);

			afterLoadImage = function () {
				// NOTE: load handler does not work in IE 7 and IE 8!
				$(this)
					.data("img-width", this.naturalWidth)
					.data("img-height", this.naturalHeight);
			};

			var jsonElement = elementJSON["screenshot"] || elementJSON["image"];
			if (jsonElement) {
				$(".screenshotContainer", step).removeClass("hiddenElement");
				$(".numberColumn", step).removeClass("noPaddingBottom");
				$(".contentColumn", step).removeClass("noPaddingBottom");
				$(".screenshot", step)
					.attr("src", fixSourcePath(contentPath + jsonElement))
					.one("load", afterLoadImage)
					.on("error", function(e) {
						$(e.currentTarget).attr("src", "one.gif");
					});
				$(".screenshotPlayIcon", step).removeClass("hiddenElement");
				if (elementJSON["image"]) {
					$(".screenshotContainer", step).addClass("baseImage");
					$(".screenshotPlayIcon", step).addClass("hiddenElement");
				}
			} else {
				$(".screenshotContainer", step).addClass("hiddenElement");
				$(".numberColumn", step).addClass("noPaddingBottom");
				$(".contentColumn", step).addClass("noPaddingBottom");
			}


			if (elementJSON["cssanimation"]) {

				$(".animationLayer .gifAnimation", step).each(function () {
					$(this).remove();
				});

				var animationJSON = elementJSON["cssanimation"];
				$(".cssAnimation", step).each(function () {
					$(this).data({
						"css-animation": true,
						"img-loaded": true,
						"img-width": animationJSON["screenshotwidth"],
						"img-height": animationJSON["screenshotheight"]
					});

					$(this).attr({
						"visible": false,
						"screenshotsrc": contentPath + animationJSON["screenshotsrc"],
						"screenshotwidth": animationJSON["screenshotwidth"],
						"screenshotheight": animationJSON["screenshotheight"],
						"detailsrc": contentPath + animationJSON["detailsrc"],
						"detailx": animationJSON["detailx"],
						"detaily": animationJSON["detaily"],
						"detailwidth": animationJSON["detailwidth"],
						"detailheight": animationJSON["detailheight"],
						"interactionobjectx": animationJSON["interactionobjectx"],
						"interactionobjecty": animationJSON["interactionobjecty"],
						"interactionobjectwidth": animationJSON["interactionobjectwidth"],
						"interactionobjectheight": animationJSON["interactionobjectheight"]
					});
				});
			}
			else {

				$(".animationLayer .cssAnimation", step).each(function () {
					$(this).remove();
				});

				if (elementJSON["animation"]) {
					// using selector ".animation" instead of ".gifAnimation" due to legacy issues
					$(".animationLayer .animation", step).each(function() {
						$(this).data("img-src", contentPath + elementJSON["animation"]);
					});
				}
			}
			stage.append(step);

			// Resize a.selector after step is appended in DOM (IE 7 and 8 only)
			if ((browserName + browserVersion) === "ie7" || (browserName + browserVersion) === "ie8") {
				img = new Image();
				img.onload = afterLoadImage;
				img.src = fixSourcePath(contentPath + elementJSON["screenshot"]);
			}

			// Insert a clear element after all step but the last step
			if (index < steps - 1) {
				clear = templateClear.clone(true);
				clear
					.removeClass("clearTEMPLATE")
					.addClass("clear");
				stage.append(clear);
			}
		});
	}

	/**
	 * @private
	 */
	function setStepNumbers() {
		$(".step", ".steplist .stage").each(function (index, step) {
			// Insert the step number
			$(".number", step).html(index + 1);
		});
	}

	/**
	 * @private
	 * @param {Element} elem
	 * @returns {Boolean}
	 */
	function hasMultipleClickListeners(elem) {
		try {
			// $._data was tested with jQuery 1.12.4 and 3.3.1
			var numberOfClickListeners = $._data(elem, "events").click.length;
			if (numberOfClickListeners > 1) {
				return true;
			}
		} catch (err) {
			logError(err);
		}
		return false;
	}

	/**
	 * @private
	 * @returns {Boolean}
	 */
	function isEmbeddedBrowserQA() {
		var isIEWebBrowserControl,
			isChromiumControl = false;

		var isChromiumBased = !!(window && window.chrome);
		if (isChromiumBased) {
			// The runtime API is an extension API that's not supported in Chromium browser
			var isExternalChrome = !!window.chrome.runtime;
			isChromiumControl = !isExternalChrome;
		}
		var patternIE = /rv\s*:\s*([0-9]+\.?[0-9]*)/,
			isIE = !!patternIE.exec(window.navigator.userAgent.toLowerCase()),
			// If Editorial guide is displayed in QA 19.0: external IE
			isExternalIE = typeof window.external === "object";
		isIEWebBrowserControl = isIE && !isExternalIE;

		return !(!isIEWebBrowserControl && !isChromiumControl);
	}

	/**
	 * @private
	 * @param {Window} fromWindow
	 * @returns {null|Object}
	 */
	function getOTPHandler(fromWindow) {
		try {
			if (!fromWindow) {
				return null;
			}
			if (fromWindow["oneTimePasswordHandler"] &&
				"function" === typeof fromWindow["oneTimePasswordHandler"]["appendTokenAndCallURLInExternalBrowser"]) {
				return fromWindow["oneTimePasswordHandler"];
			}
			if (fromWindow.parent && fromWindow.parent !== fromWindow) {
				return getOTPHandler(fromWindow.parent);
			}
		} catch(err) {
			console.error(err);
		}
		return null;
	}

	/**
	 * @private
	 */
	function modifyContentHyperlinks() {
		if (!isEmbeddedBrowserQA()) {
			return;
		}

		var externalWindowParamPattern = /[&?]externalwindow/g,
			currentHost = location.host.toLowerCase(),
			otp = getOTPHandler(parent),
			hasOTPHandler = !!otp,
			f = hasOTPHandler ? otp.appendTokenAndCallURLInExternalBrowser : null;
		$(".step .content a.ttclasshyperlink").each(function (i, anchorNode) {
			var jqAnchorNode = $(anchorNode),
				href = jqAnchorNode.attr("href"),
				hrefLC = href.toLowerCase() || "",
				isTTPSDeeplink = -1 !== hrefLC.indexOf("usecase=deeplink") && -1 !== hrefLC.indexOf(currentHost);
			// Modify hyperlinks only for deep links
			// The hyperlink must have the same host as the current one
			// The link will only be modified if the OTP handler exists
			if (hasOTPHandler && hrefLC && isTTPSDeeplink) {
				jqAnchorNode.on("click", function (e) {
					if (hasMultipleClickListeners(this)) {
						return;
					}
					var urlForOTP = this.href.slice(this.href.indexOf(location.host) + location.host.length);
					// The 'externalwindow' parameter is added because the QA would open the link in a new
					// Chromium window instead (from 19.1).
					externalWindowParamPattern.lastIndex = 0;
					// Test if the param doesn't exists yet
					if (!externalWindowParamPattern.test(urlForOTP)) {
						urlForOTP += "&externalwindow=1";
					}
					f.call(otp, urlForOTP, "_blank");
					e.preventDefault();
					return false;
				});
			}
		});
	}

	/**
	 * @private
	 * @param {object} opts
	 */
	function createSteplist(opts) {
		sendEvent("before:createSteplist", opts);
		init(opts);
		addUserAgentClass();
		loadCustomStyles();
		loadGuidanceStyles();
		if (options["useJSONData"]) {
			createHTMLByJSON();
		}
		setStepNumbers();
		setAnchorTitleListeners();
		adjustAnchorSelectors();
		setEventHandlerSteps();
		setEventHandlerScreenshots();
		setEventHandlerAnimations();
		setScrollListeners();
		adjustKeySymbolPaths();
		modifyContentHyperlinks();
		adjustHeader();
		sendEvent("after:createSteplist", options);
	}

	/**
	 * @public
	 * @param {object} opts
	 */
	GuidanceSteplist.prototype.run = function(opts) {
		self = this;
		// Load jQuery
		self.loadJS(document, opts["playerPath"] + "jquery-3.6.0.min.js", function () {
			try {
				if(opts["customJS"]) {
					self.loadJS(document, opts["customJS"], function () {
						createSteplist(opts);
					});
				} else {
					createSteplist(opts);
				}
			} catch (ex) {
				logError(ex);
			}
		});
	};
})();


/**
 * Creates a global "addWheelListener" method
 *
 * Example:
 *        addWheelListener(elem, function(e) {
 * 			console.log(e.deltaY);
 * 			e.preventDefault();
 * 		});
 *
 * Source: https://developer.mozilla.org/en-US/docs/Web/Events/wheel
 * @param {Window} win
 * @param {Document} doc
 */
(function (win, doc) {
	var prefix = "",
		_addEventListener,
		support;

	// detect event model
	if (win.addEventListener) {
		_addEventListener = "addEventListener";
	} else {
		_addEventListener = "attachEvent";
		prefix = "on";
	}

	// detect available wheel event
	// Modern browsers support "wheel"
	support = "onwheel" in doc.createElement("div") ? "wheel" :
		// Webkit and IE support at least "mousewheel"
		doc.onmousewheel !== undefined ? "mousewheel" :
			// let's assume that remaining browsers are older Firefox
			"DOMMouseScroll";

	function addWheelListenerImpl(elem, eventName, callback, useCapture) {
		elem[_addEventListener](prefix + eventName, support === "wheel" ? callback : function (originalEvent) {
			!originalEvent && ( originalEvent = win.event );

			// create a normalized event object
			var event = {
				// keep a ref to the original event object
				originalEvent: originalEvent,
				target: originalEvent.target || originalEvent.srcElement,
				type: "wheel",
				deltaMode: originalEvent.type === "MozMousePixelScroll" ? 0 : 1,
				deltaX: 0,
				deltaZ: 0,
				preventDefault: function () {
					originalEvent.preventDefault ?
						originalEvent.preventDefault() :
						originalEvent.returnValue = false;
				}
			};

			// calculate deltaY (and deltaX) according to the event
			if (support === "mousewheel") {
				event.deltaY = -1 / 40 * originalEvent.wheelDelta;
				// Webkit also support wheelDeltaX
				originalEvent.wheelDeltaX && ( event.deltaX = -1 / 40 * originalEvent.wheelDeltaX );
			} else {
				event.deltaY = originalEvent.detail;
			}

			// it's time to fire the callback
			return callback(event);

		}, (useCapture || false));
	}

	win.addWheelListener = function (elem, callback, useCapture) {
		addWheelListenerImpl(elem, support, callback, useCapture);

		// handle MozMousePixelScroll in older Firefox
		if (support === "DOMMouseScroll") {
			addWheelListenerImpl(elem, "MozMousePixelScroll", callback, useCapture);
		}
	};
})(window, document);
