var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme';
var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__';

;(function() {
	var MARKETPLACE_JS_VERSION = 2;

	window.inBeeShop = function (){
		return window.navigator.userAgent.toLowerCase().indexOf("beeshop") != -1;
	};
	
	if (window.navigator.userAgent.indexOf("Beeshop") == -1) {
		return;
	}
	// Note: We check version is because IOS will inject a legacy WebViewJavascriptBridge,
	// which does not have some function like addHook and crash our js
	if (window.WebViewJavascriptBridge && window.WebViewJavascriptBridge._version >= MARKETPLACE_JS_VERSION) {
		return;
	}

	var messagingIframe;
	var sendMessageQueue = [];
	var receiveMessageQueue = [];
	var messageHandlers = {};

	var responseCallbacks = {};
	var uniqueId = 1;

	function reset()
	{
		sendMessageQueue = [];
		receiveMessageQueue = [];
		messageHandlers = {};

		responseCallbacks = {};
		uniqueId = 1;

		window.WebViewJavascriptBridge._messageHandler = null;
	}

	function _createQueueReadyIframe(doc) {
		messagingIframe = doc.createElement('iframe');
		messagingIframe.style.display = 'none';
		messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
		doc.documentElement.appendChild(messagingIframe)
	}

	function init(messageHandler) {
		if (window.WebViewJavascriptBridge._messageHandler) {
			// it's not really an error, fix for many duplicate call cases
			console.warn('WebViewJavascriptBridge.init called twice');
			return null;
		}
		window.WebViewJavascriptBridge._messageHandler = [messageHandler];
		var receivedMessages = receiveMessageQueue;
		receiveMessageQueue = null;
		setTimeout(function(){
			for (var i=0; i<receivedMessages.length; i++) {
				_dispatchMessageFromObjC(receivedMessages[i]);
			}
		}, 0);
	}

	function send(data, responseCallback) {
		_doSend({ data:data }, responseCallback)
	}

	function registerHandler(handlerName, handler, handlerId) {
		if (typeof messageHandlers[handlerName] === 'undefined') {
			messageHandlers[handlerName] = {};
		}
		handlerId = handlerId || handler.toString();
		messageHandlers[handlerName][handlerId] = handler;
		// We have to do this because Android app will only call hasHandler once inside pageFinished(), and thus later handlers won't be able to register with Android if we don't do this.
		hasHandlerCB(handlerName);
	}
	function unregisterHandler(handlerName, handlerId) {
		if(!handlerId) return;
		var handler = messageHandlers[handlerName];
		if(!handler) return;
		delete handler[handlerId];
		var stillHasHandler = false;
		for (var _temp in handler){
			if (handler.hasOwnProperty(_temp)){
				stillHasHandler = true;
				break;
			}
		}
		if (!stillHasHandler){
			delete messageHandlers[handlerName];
			hasHandlerCB(handlerName);
		}
	}

	var hooks_map = {};
	function addHook(hookFunc)
	{
		var key = hookFunc.toString();
		if(hooks_map[key])
			return;
		hooks_map[key] = hookFunc;
	}
	function delHook(hookFunc)
	{
		var key = hookFunc.toString();
		delete hooks_map[key];
	}

	function callHandler(handlerName, data, responseCallback) {
		// for(var i=0; i<hooks.length; i++)
		for(var k in hooks_map)
		{
			if(!hooks_map.hasOwnProperty(k)) continue;
			try{hooks_map[k](handlerName, data);}catch(ex){}
		}

		//!!! Disabled because messing up with the URL caused too many problems in many places. All logics that appending new things to location.href would fail (for example delete credit credit card page).
		////For BI tracking purpose.
		//if(handlerName == 'navigate')
		//{
		//	bridgeCallHandler("save", {key : KEY_SHOW_LESS_CATE, data : show ? '0' : '1', persist : 1}, function() {});
		//	if(data.hasOwnProperty('url'))
		//	{
		//		data['url'] = addSourceToUrl(data['url']);
		//	}
		//	if(data.hasOwnProperty('tabs'))
		//	{
		//		var tabs = data['tabs'];
		//		if(tabs)
		//		{
		//			for(var i=0; i<tabs.length; i++)
		//			{
		//				var tab = tabs[i];
		//				if(tab.hasOwnProperty('url'))
		//					tab['url'] = addSourceToUrl(tab['url']);
		//			}
		//		}
		//	}
		//}

		// !!! When going to full-screen mode, we MUST provide non-webp URL because we do NOT want user to save webp file and share later due to its limited support on iOS devices.
		if(handlerName == 'fullScreenImage' && data)
		{
			var imageUrls = data['imageUrls'];
			if(imageUrls && imageUrls.length)
			{
				try{
					for(var i=0; i<imageUrls.length; i++)
					{
						var url = imageUrls[i];
						if(typeof(url) != 'string' || url.indexOf(window.ITEM_IMAGE_BASE_URL) < 0)
							continue;
						imageUrls[i] = url.replace('/webp/', '/');
					}
				}catch(e){}
			}
		}
		_doSend({ handlerName:handlerName, data:data }, responseCallback)
	}

	function _doSend(message, responseCallback)
	{
		// Skip bridge cmd if app has pending page update.
		if(window.__appHasPendingCacheUpdate__)
			return;
		//if(destroyed) return;
		if (responseCallback) {
			var callbackId = 'cb_'+(uniqueId++)+'_' + curTs();
			responseCallbacks[callbackId] = responseCallback;
			message['callbackId'] = callbackId
		}
		if (window.navigator.userAgent.toLowerCase().indexOf("android") == -1) // ios
		{
			sendMessageQueue.push(message);
			messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
		}
		else
		{
			window.gabridge.sendMsg(JSON.stringify(message));
		}
	}

	function _fetchQueue() {
		var messageQueueString = JSON.stringify(sendMessageQueue);
		sendMessageQueue = [];

		//if(destroyed) return [];
		return messageQueueString;
	}

	function _dispatchMessageFromObjC(messageJSON)
	{
		// Skip bridge cmd if app has pending page update.
		if(window.__appHasPendingCacheUpdate__)
			return;
		//if(destroyed) return;
		try {
			var message = JSON.parse(messageJSON);
		} catch (err) {
			var message = {};
			console.warn('Bridge dispatch failed. err = ' + err);
		}
		var messageHandler;

		if (message.responseId) {
			var responseCallback = responseCallbacks[message.responseId];
			if (!responseCallback) { return; }
			//isCBInProgress = true;
			responseCallback(message.responseData);
			//isCBInProgress = false;
			delete responseCallbacks[message.responseId]
		} else {
			var responseCallback;
			if (message.callbackId) {
				var callbackResponseId = message.callbackId;
				responseCallback = function(responseData) {
					_doSend({ responseId:callbackResponseId, responseData:responseData })
				}
			}

			var handler = window.WebViewJavascriptBridge._messageHandler;
			if (message.handlerName) {
				handler = messageHandlers[message.handlerName]
			}
			if (typeof handler === 'undefined') return;

			if (message.handlerName == "search"){
				// V2 BI tracking require this tracking event
				if (window.BI_ANALYTICS){

					var dataV2 = {
						action: 'searchCMDFromApp',
						data: JSON.stringify(message.data),
					};

					BI_ANALYTICS.trackingActionEventV2({
						info: dataV2,
					}, BI_ANALYTICS.getV2TrackingEventBaseData(TRACKING_V2_EVENT_TYPE.ACTION));
				}
			}

			//isCBInProgress = true;
			for (var k in handler) {
				if(!handler.hasOwnProperty(k)) continue;
				try {
					handler[k](message.data, responseCallback)
				} catch(exception) {
					if (typeof console != 'undefined') {
						console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
					}
				}
			}
			//isCBInProgress = false;
		}
	}

	function _handleMessageFromObjC(messageJSON) {
		if (receiveMessageQueue) {
			receiveMessageQueue.push(messageJSON)
		} else {
			_dispatchMessageFromObjC(messageJSON)
		}
	}

	function hasHandler(handlerName)
	{
		try
		{
			has = (null != messageHandlers[handlerName]);
		}
		catch(exception)
		{
			has = false;
		}
		//On iOS, return String value only!
		return has ? 'true' : 'false';
	}

	function hasHandlerCB(handlerName)
	{
		if(!window.gabridge) return;
		//On Android, DO NOT RETURN ANYTHING!
		//Because Android can only call JS by loadUrl('javascript:....'), returned values WILL BECOME THE WEBPAGE AND WILL BE DISPLAYED!!!
		window.gabridge.onHasHandler(handlerName, hasHandler(handlerName));
	}

	// !!! NOTE: Currently only supported by iOS app >= 2.3. NOT SUPPORTED BY ANDROID APP!!!
	// This is a new way for App to inject handler definitions directly into JS context on page load.
	// In this way, JS won't need to chain callbacks for .callHandler('hasHandler', {handler: <handler>}, function(){.callHandler('<handler>', ... function(){});});
	// Chained JS calls could freeze iOS app if page reloads during the chained calls.
	function appHasHandler(name)
	{
		return window.WebViewJavascriptBridgeiOSNativeAppConfig ?
					(window.WebViewJavascriptBridgeiOSNativeAppConfig._appHandlers
	                    && window.WebViewJavascriptBridgeiOSNativeAppConfig._appHandlers.indexOf(name) >= 0) :
	                (window.WebViewJavascriptBridge._appHandlers && window.WebViewJavascriptBridge._appHandlers.indexOf(name) >= 0)
	}

	// These are failed attempts to avoid iOS deadlock during page reloading. Kept for reference.
	//var destroyed = false;
	//var isCBInProgress = false;
	//function hasPending()
	//{
	//	if(receiveMessageQueue && receiveMessageQueue.length > 0)
	//		return true;
	//	if(!$.isEmptyObject(responseCallbacks))
	//		return true;
	//	return isCBInProgress;
	//
	//}

	//var reloadFromCacheIfNeededCalled = false;
	//function reloadFromCacheIfNeeded()
	//{
	//	if(!window.__appHasPendingCacheUpdate__)
	//		return false;
	//
	//	// Should never happen.
	//	if(!appHasHandler('reloadFromCacheIfNeeded'))
	//	{
	//		window.__appHasPendingCacheUpdate__ = false;
	//		return false;
	//	}
	//
	//	var screenHeight = screen.height;
	//	var scrollTop = scrollTopFunc();
	//	// If user has already scrolled down, don't reload.
	//	if(scrollTop > screenHeight)
	//	{
	//		window.__appHasPendingCacheUpdate__ = false;
	//		return false;
	//	}
	//
	//	// Call this multiple times apparently would freeze iOS app.
	//	if(!reloadFromCacheIfNeededCalled)
	//	{
	//		reloadFromCacheIfNeededCalled = true;
	//
	//		//Just do the raw message sending.
	//		sendMessageQueue.push({ handlerName:'reloadFromCacheIfNeeded', data:{} });
	//		messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
	//		// NOTE: DO NOT callHandler() because it calls reloadFromCacheIfNeeded(), resulting in infinite loop.
	//		//callHandler("reloadFromCacheIfNeeded", {}, null);
	//	}
	//	// Note: Should NOT reset the value of __appHasPendingCacheUpdate__ here because:
	//	// 1) The page will be reloaded soon, and the value will be gone.
	//	// 2) More importantly, we want to let all future callers of reloadFromCacheIfNeeded() to have "true" as returned value so that they won't carry on with possible deadlocking operations.
	//	return true;
	//}

	window.WebViewJavascriptBridge = {
		init: init,
		send: send,
		registerHandler: registerHandler,
		unregisterHandler: unregisterHandler,
		callHandler: callHandler,
		hasHandler: hasHandler,
		hasHandlerCB: hasHandlerCB,
		addHook: addHook,
		delHook: delHook,
		appHasHandler: appHasHandler,
		_fetchQueue: _fetchQueue,
		_handleMessageFromObjC: _handleMessageFromObjC,
		reset: reset,
		_version: MARKETPLACE_JS_VERSION,
		//reloadFromCacheIfNeeded: reloadFromCacheIfNeeded,
		// These are failed attempts to avoid iOS deadlock during page reloading. Kept for reference.
		//hasPending: hasPending,
		//destroy: function(){destroyed = true; responseCallbacks = []; receiveMessageQueue = [];},
	};

	var doc = document;
	_createQueueReadyIframe(doc);
	var readyEvent = doc.createEvent('Events');
	readyEvent.initEvent('WebViewJavascriptBridgeReady');
	readyEvent.bridge = window.WebViewJavascriptBridge;
	doc.dispatchEvent(readyEvent);

	var timerTriggerIOS = null;
	var intervalTriggerIOS = null;
	// !!! Tricky. When network is really slow, the iOS app's didFinishLoad won't be called soon, and all bridge responses will be pending for a long time.
	// So we wait for 100ms and if iOS app didFinishLoad still hasn't been called, we force to trigger it.
	if(isIOS() && isShopeeApp())
	{
		// Wait for 2 secs and start trying.
		timerTriggerIOS = setTimeout(function(){
			timerTriggerIOS = null;
			// This means didFinishLoad has run.
			if(window.__gawindow__) return;
			intervalTriggerIOS = setInterval(function(){
				// This is the magic variable injected by iOS app after didFinishLoad. We use this to detect whether didFinishLoad has been triggered.
				if(window.__gawindow__ || triggerIOSAppPageReInit())
				{
					clearInterval(intervalTriggerIOS);
					intervalTriggerIOS = null;
				}
			}, 300);
		}, 2000);
	}

	var RNCachingUpdateStatus_INITED = 0;
	var RNCachingUpdateStatus_STARTED = 1;
	var RNCachingUpdateStatus_UPDATED = 2;
	var RNCachingUpdateStatus_NOCHANGE = 3;
	function checkCacheStatus()
	{
		//console.log('checkCacheStatus: ' + window.__appCacheStatus__);
		// Keep polling if current page is indeed from cache.
		if(window.__appCacheStatus__ == RNCachingUpdateStatus_STARTED || window.__appCacheStatus__ == RNCachingUpdateStatus_INITED)
		{
			setTimeout(checkCacheStatus, 50);
		}
		else if(window.__appCacheStatus__ == RNCachingUpdateStatus_UPDATED)
		{
			// This is important to avoid reloading twice.
			window.__appCacheStatus__ = RNCachingUpdateStatus_NOCHANGE;
			var htmlstr = window.__appCacheUpdatedData__;
			window.__appCacheUpdatedData__ = null;
			if(scrollTopFunc() > screen.height) return;

			// Remove any potential interferences.
			if(window.GTimeoutAndInterval)
			{
				GTimeoutAndInterval.clearAll();
			}
			window.__appHasPendingCacheUpdate__ = true;
			// To turn off all events.
			offAllEvents();
			// !!!Note: MUST unhook the event funcs!!! When we do document.write(), the hookEventTarget() funcs in base.html will be rerun and will call infinite recursion when it uses the hooked func as the base func.
			try
			{
				unhookEventTarget(Window);
				unhookEventTarget(Document);
				unhookEventTarget(Element);
			}
			catch(e)
			{
				console.log('unhookEventTarget.ex', e);
			}
			// Use JS-only reload. This is the heavy lifting.
			if(htmlstr)
			{
				// Save these two funcs.
				var funcRun = window.runAfterDomReady;
				var funcInit = window.triggerIOSAppPageReInit;
				// Delete all custom fields in global scope.
				_resetGlobals_(window.ORIGINAL_GLOBAL_VARS);
				window.runAfterDomReady = funcRun;
				// The page will go empty right after this line.
				document.open();
				window.__appHasPendingCacheUpdate__ = null;
				document.write(decodeURIComponent(htmlstr.replace(/\\'/g, "'"))); // <-- Here's the magic.
				document.close();
				// MUST run after domready!!! Otherewise won't take effect.
				runAfterDomReady(function(){
					// Tricky: We call this func twice to both flush message queue, and to trigger didFinishLoad.
					// It is essential to trigger iOS didFinishLoad (mimicking a page load from network), which will do all necessary initialization of the whole page.
					funcInit(true, true);
					funcInit(true, false);
				});
			}
			//Fallback to native reload. This is potentially deadlocking.
			//Need to fallback for earlier 2.3.36 iOS apps which had a bug of not always sending htmlstr.
			else
			{
				// Run this after layouting/painting done.
				window.requestAnimationFrame(function(){
					// The following code will trigger iOS app to do things on its main thread, potentialy deadlocking with WebCoreThread.
					// This will sometimes hang iOS app but not always. We use this as temporary solution until we make the htmlstr solution work fine.
					sendMessageQueue.push({ handlerName:'reloadFromCacheIfNeeded', data:{} });
					messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
					window.__appHasPendingCacheUpdate__ = null;
				});
			}
		}
		else if(window.__appCacheStatus__ == RNCachingUpdateStatus_NOCHANGE)
		{
			//Do nothing.
		}
	}
	if(isIOS() && isShopeeApp() && isVer23Plus())
	{
		runAfterDomReady(checkCacheStatus, 50);
	}
})();

// This is to avoid being confused by other Garena apps who natively emit this event but don't handle shopee bridge commands. For example Gas.
function dismissNonShopeeBridge()
{
	if(isShopeeApp())
		return false;
	delete window.WebViewJavascriptBridge;
	return true;
}

// This is to avoid being confused by other Garena apps who natively emit this event but don't handle shopee bridge commands. For example Gas.
// Note: This logic runs inside the global scope, and it doesn't wait for dom-ready/pageload/pageshow or whatever. Reason is to compete with Gas's injection.
try{
	if (isGasApp())
	{
		var __gas_iid__ = null;
		// Note: setTimeout() is not enough. MUST do this forever, because Gas would inject the object any time.
		__gas_iid__ = setInterval(function(){
			if(window.WebViewJavascriptBridge)
			{
				delete window.WebViewJavascriptBridge;
				if(__gas_iid__)
				{
					clearInterval(__gas_iid__);
					__gas_iid__ = null;
				}
			}
		}, 10); //A bit extreme but seems this is the only way to compete with Gas app's injection before it does any damage. Any longer wouldn't cut it. If change it to 50 or larger, then using Gas's "<-" button to go back to batch-item page would result in batch-item thinking window.WebViewJavascriptBridge exist.
		// Here we do first-round prevention.
		if(window.WebViewJavascriptBridge)
			delete window.WebViewJavascriptBridge;
	}
}catch(ex){console.log('GasApp: ' + ex);}

function connectWebViewJavascriptBridge(callback)
{
	// This is to avoid being confused by other Garena apps who natively emit this event but don't handle shopee bridge commands. For example Gas.
	if(dismissNonShopeeBridge())
		return;
	if (window.WebViewJavascriptBridge)
	{
		callback(window.WebViewJavascriptBridge);
	}
	else
	{
		document.addEventListener('WebViewJavascriptBridgeReady', function ()
		{
			// This is to avoid being confused by other Garena apps who natively emit this event but don't handle shopee bridge commands. For example Gas.
			if(dismissNonShopeeBridge())
				return;
			callback(window.WebViewJavascriptBridge);
		}, false);
	}
}

function tapCallBack(e,pattern,bridge){
	var t = $(e.currentTarget);
	var url = t.attr("_href");
	if (url.indexOf(pattern) !== -1)
	{
		url = location.origin + url;
		bridgeCallHandler('navigate', callback(url,t), function(response) {});
	}
}

function bridge_capture_link(pattern,elm,callback)
{


	$.each(elm,function(k,v){
		var $self = $(v);
		var url = $self.attr("href")&&$self.attr("href")!='javascript:void(0)' ? $self.attr("href") : $self.attr("_href");
		if(url && url.indexOf('review-part')>-1){
			return;
		}

		if (url && (!pattern || url.indexOf(pattern) !== -1))
		{
			!$self.attr("_href") ? $self.attr("_href",url) : console.log('pass');
			$self.attr("href","javascript:void(0)");
			$self.off("tap");
			$self.on("tap",function(e){
				//!!! DO NOT USE "this" in this function. Refer to batch-items.js's hack for details.
				if ($self.hasClass('banned')){
					return;
				}

				var navigateData = callback(location.origin + url, $self);
				// the callback should return {reactNative: true, rnParams} if want to go to rn pages.
				if(navigateData.reactNative) {
					BJUtil.navigateReactNative(navigateData.rnParams);
				} else {
					bridgeCallHandler('navigate', navigateData, function(response) {
						console.log("Yeps");
					});
				}

			});
		}
	});
}

// all select must have id
function get_custom_select_handler(select, title, bridge, default_index, additionParams, infoText) {
	//default_index = _defaultFor(default_index, $(select)[0].selectedIndex.toString());
	return function(e) {
		e.preventDefault();

		if (infoText)
			localStorage.setItem('custom_select_info_text', infoText);
		else
			localStorage.removeItem('custom_select_info_text');

		//prepare hash string
		var options = [];
		var $selected = $(select);
		$selected.find("option:not(.disable)").each(function(index, element) {
			options.push($(element).text());
		});
		if (default_index) {
			var selectedIndex = default_index;
		} else {
			var realIndex = options.indexOf(select.find("option:selected").text());
			var selectedIndex = realIndex;
		}
		var hashedText = convertOptionsTextToHashedUri(options, selectedIndex, bridge, additionParams);
		var hashedText = ""; // hashedText is no longer needed, but convertOptionsTextToHashedUri still need to be called
		if (!bridge){
			bridge = window.WebViewJavascriptBridge;
		}

		if (bridge) {
			bridge.callHandler("save", {
				key : "select-name",
				data : $selected.attr("id"),
				persist : 0
			}, function(status) {
				if (status) {
					//navigate to custom-select
					bridgeCallHandler("navigate",{
						url: location.origin + "/func/app_select#" + hashedText,
						navbar: {
							title : title
						},
						config: {disableReload: 1}
					},function(e){});
				}
			});
		}else{
			sessionStorage["select-name"] = $selected.attr("id");
			BJUtil.navigateModally(location.origin + "/func/app_select#selected=" + selectedIndex, "custom_select");
		}
	}
}

// this is a new version of custome select generator.
// the main change is pass the json string of options-value mappings instead of
// maintaining the DOM list and passing query string.

// the disabled/selected items should be handled well by the time this function
// is called.
function getCustomSelectHandlerV2(mapping, title, bridge, selectedIndex, additionParams) {
	return function(e) {
		e.preventDefault();
		var data = {
			'mapping': mapping,
			'selectedIndex': selectedIndex,
			'additionParams': additionParams
		};
		localStorage.setItem("customSelectTextData", JSON.stringify(data));

		if (!bridge) {
			bridge = window.WebViewJavascriptBridge;
		}

		if (bridge) {
			bridgeCallHandler('navigate', {
				url: location.origin + '/func/app_select',
				navbar: {
					title : title
				},
				config: {
					disableReload: 1
				}
			}, function(e) {});
		} else {
			BJUtil.navigateModally(location.origin + "/func/app_select", "custom_select");
		}
	}
}


function bridge_capture_link_shop_callback(url,t)
{
	var username = t.attr('username');
	if(!username)
		username = '';
	var userid = t.attr('userid');
	var items = [ { type:'home' }, { type:'reportuser', userID:userid } ];
	if (USERID && USERID == parseInt(userid))
	{
		items = [{type:'home'}];
	}
	// !!! "{" MUST be on the same line of "return" otherwise will have syntax error because of the optional ";" in JS.
	return {
		url:url,
		navbar:{
			title:'@'+username,
			rightItemsConfig:{
				items:[{
						type:"more",
						items:items
					}
				]
			}
		}
	};
}

function bridge_capture_link_item_callback(url,t){
	keys = parse_hash_general(url);
	var li = t.parents('li');
	var title = li.find('.item-detail .name').text();
	var shopId = keys['shopid'];
	var itemId = keys['itemid'];

	return {url:url, config:{}, navbar: getItemNavBar(shopId == CURRENT_SHOPID, title, shopId, itemId)};
}

function getItemNavBar(isOwner, title, shopId, itemId) {
	if(isOwner)
	{
		return {
				title:title,
				rightItemsConfig:
				{
					items:[
						{type:"more",
							items:[
								{
									type:'button',
									key:'edit',
									text: i18n.t('label_edit_product'),
									iconType:'editProduct'
								},
								{
									type:'home'
								}
							]
						}
					]
				}
		}
	}
	else
	{
		return {
				title:title,
				rightItemsConfig:
				{
					items:[
						{type:"more",
							items:[
								{
									type:'home'
								},
								{
									type:'button',
									itemID:itemId,
									shopID:shopId
								}
							]
						}
					]
				}
		}
	}
}

function getCateNavBar(catid, catname)
{
	return {
				title : catname,
				searchParam:catid,
				searchType : 1,
				showSearch : isVer22Plus() ? 1 : 0,
				searchPlaceholder: isVer22Plus() ? catname : (i18n.t('label_search_in') + ' ' + catname),
				searchPlaceholderActive: i18n.t('label_search_in') + " " + catname,
				searchScope: {0: [
					{text: i18n.t('label_search_within_category').replace('#{categoryName}', encloseColorCode(catname, 'b2'))},
					{text: i18n.t('label_search_within_category').replace('#{categoryName}', encloseColorCode(i18n.t('label_all_categories_s'), 'b2'))},
				]},
				rightItemsConfig:{
					items:[
						{type:'search'},
						{type:'button',
						 key:'filter',
						 iconType:'filter',
						 iconText:i18n.t('label_filter')
						}
					]
				}
	};
}

function handleNavigateCategory(catid, catname, noSub, appBridge) {
	if (noSub) {
		if (isShopeeWeb()) {
			SeoUtil.navigateCategorySeo(catname, catid);
		} else {
			handleNavigateSubCategory(catid, catname, appBridge);
		}
	} else {
		if (isShopeeWeb()) {
			SeoUtil.navigateCategorySeo(catname, catid);
		} else {
			var url = location.origin + "/categories/" + catid + "/";
			bridgeCallHandler("navigate", {
				url : url,
				preloadKey: 'maincate',
				navbar : getCateNavBar(catid, catname)
			}, function() {});
		}
	}
}

// Only used by official_shop_landing_in_tab.html
function getOfficialShopSearchConfig() {
	if (window.i18njs){
		i18n.t('label_official_shops--TW');
		i18n.t('label_official_shops--ID');
		i18n.t('label_official_shops--MY');
		i18n.t('label_official_shops--PH');
		i18n.t('label_official_shops--TH');
		i18n.t('label_official_shops--VN');
		i18n.t('label_official_shops--SG');
	}
	var officialShopLabel = 'label_official_shops--' + window.LOCALE;
	var searchConfig = {
		searchType : 1,
		currentSearchType:0,
		searchPlaceholderColor: "#8e8e93",
		searchPlaceholder:  i18n.t('label_search_in') + ' ' + toTitleCase(i18n.t(officialShopLabel)),
		searchPlaceholderActive:  i18n.t('label_search_in') + ' ' + toTitleCase(i18n.t(officialShopLabel)),
		searchMatchValue: JSON.stringify({'official': 1}),
		searchHotwords: {'0': []},
		isGlobalSearch: true,
	};
	return searchConfig;
}

function getOfficialShopNavBar() {
	var ver = window.APPVER || window.VERSION;

	var officialShopLabel = 'label_official_shops--' + window.LOCALE;
	var navBar = {
		searchType: 1,
		currentSearchType: 0,
		title: i18n.t(officialShopLabel).toUpperCase(),
		searchPlaceholderColor: "#8e8e93",
		searchPlaceholder: i18n.t('label_search_in') + ' ' + toTitleCase(i18n.t(officialShopLabel)),
		searchPlaceholderActive: i18n.t('label_search_in') + ' ' + toTitleCase(i18n.t(officialShopLabel)),
		searchMatchValue: JSON.stringify({'official': 1}),
		searchHotwords: {'0': []},
		// NOTE: Somehow apps do not support all fields inside getOfficialShopSearchConfig() so we still need to configure them above.
		searchConfig: {
			isGlobalSearch: true,
		}
	};

	var menuItems = [
		{
			type:'button',
			key:'nav_cart',
			text: i18n.t('label_cart'),
			iconUrl: BJUtil.getIconUrl('ic_officialshop_cart'),
			iconUrlOnDark: BJUtil.getIconUrl('ic_officialshop_cart_dark'),
		},
	];

	var navBarMenuitems = [
		{type:'search'},
		{type:"more",
			items: menuItems
		}
	];

	if ((isIOS() && ver>=21116) || (isAndroid() && ver>=21100)){
		// From this version, we support set the navbar to red
		navBar['navbarStyle'] = 1;
		navBar['navbarBackgroundColor'] = '#D0011B';
		navBar['statusBarStyle'] = 1;
	}
	if (isIOS() || isAndroid() && ver >= 21205){
		navBar['searchType'] = 0;
	}

	if (!(isAndroid() && ver<21100)) {
		// Android below 2.11 cannot support
		menuItems.push({
			type: 'button',
			key: 'nav_chat',
			// Don't remove the space due to IOS bug, without it, will become "C..."
			// PM requires to add 25 more spaces.
			// For newer version,  app will strip the spaces and use a min-width for the menu.
			text: i18n.t('label_chat') + Array(26).join(" "),
			iconUrl: BJUtil.getIconUrl('ic_officialshop_chats'),
			iconUrlOnDark: BJUtil.getIconUrl('ic_officialshop_chats_dark'),
		});
	}

	// Only 2.11.16 and above apps support these with native tab bar. Old apps would either crash or behave wrongly.
	if(ver >= 21116) {
		navBar['rightItemsConfig'] = {
			items: navBarMenuitems
		};
	}

	return navBar
}

function getSubCateNavBar(catid, catname)
{
	var colorCode = 'b2';
	return {
				title:catname,
				searchParam:catid,
				searchType : 1,
				showSearch : isVer22Plus() ? 1 : 0,
				searchPlaceholder: isVer22Plus() ? catname : (i18n.t('label_search_in') + ' ' + catname),
				searchPlaceholderActive: i18n.t('label_search_in') + " " + catname,
				searchScope: {0: [
					{text: i18n.t('label_search_within_category').replace('#{categoryName}', encloseColorCode(catname, colorCode))},
					{text: i18n.t('label_search_within_category').replace('#{categoryName}', encloseColorCode(i18n.t('label_all_categories_s'), colorCode))},
				]},
				rightItemsConfig:{
					items:[
						{type:'search'},
						{type:'button',
						 key:'filter',
						 iconType:'filter',
						 iconText:i18n.t('label_filter')
						}
					]
				}
			};
}

// Seems no longer used. Refer to shop_v2.3.js.
// function getSearchShopPageNavbar(keyword, username) {
// 	return {
// 		navbar: {
// 			title: getSearchTitle(keyword),
// 			searchPlaceholder: i18n.t('label_search_in') + " " + decodeHTMLEncode(username),
// 			searchPlaceholderColor: "#8e8e93",
// 			showSearch: 1,
// 			searchType: 8,
// 			rightItemsConfig:{
// 				items:[
// 					{type:'search'},
// 					// TODO confirm whether search shop page can do filter
// 					//{type:'button',
// 					// key:'filter',
// 					// iconType:'filter',
// 					// iconText: i18n.t('label_filter')
// 					//}
// 				]
// 			}
// 		}
// 	};
// }

function handleNavigateSubCategory(catid, catname, appBridge) {
	var url = location.origin + "/category-item/?categoryid=" + catid + "&categoryname=" + encodeURIComponent(catname);
	var tabs = getCategoryNativeTabs(url);
	bridgeCallHandler("navigate", {
		url:url,
		/*
		tabs: tabs,
		tabRightButton:{
			"type":"button",
			"key":"filter",
			"iconType":"filter",
			"iconText":i18n.t('label_filter')
		},
		*/
		navbar:getSubCateNavBar(catid, catname)
	}, function() {});
}



function handleNavigateItem(){
	$('.item-href').off('tap');
	bridge_capture_link("/item/",$("a"),bridge_capture_link_item_callback); //removes duplication of identical anonymous callbacks
}

function convertOptionsTextToHashedUri(optionsText, selectedIndex, bridge, additionParams) {
	var hashedString = "";
	optionsText.forEach(function(text, index) {
		var keyValuePair = "option"+index+"="+encodeURIComponent(text);
		hashedString = index == 0 ? hashedString : hashedString + "&";

		hashedString += keyValuePair;
	});
	if (selectedIndex) {
		hashedString += "&selected=" + selectedIndex;
	}
	if (additionParams) {
		hashedString = hashedString + '&params=' + encodeURIComponent(JSON.stringify(additionParams));
	}
	localStorage.setItem("custom_select_text", hashedString);
	//return encodeURIComponent(Base64.encode(hashedString));
	return "";
}

function _gatap_(x, y) {
	var element = document.elementFromPoint(x, y);
	if (element && element.tagName == "IFRAME") {
		// has to get the actual element from iframe, currently only support one level nesting
		var offsetTop = element.offsetTop;
		var scrollTop =  scrollTopFunc();
		//console.log("y in view port", y);
		//console.log("y in iframe", y-(offsetTop-scrollTop));
		var iframeWindow = element.contentWindow;
		var rawElement = iframeWindow.document.elementFromPoint(x,y-(offsetTop-scrollTop));
		console.log(rawElement);
		element = $(rawElement);
		element.trigger("tap");
	} else {
		$(element).tap();
		// Note: element will be null when tapping on an input element.
		console.log("app triggered tap on element " + (element ? element.tagName + " (class=" + element.className + ")" : element));
	}
}

function _android_gatap_temporary_disable_(disable)
{
	if(!window.gabridge || !window.gabridge.onHasHandler) return;
	window.gabridge.onHasHandler('_gatap_', disable ? 'false' : (_gatap_init_(true) ? 'true' : 'false'));
}

var _gatap_disable_override_ = false;

function _gatap_force_disable_(disabled) {
	if(_gatap_disable_override_ == disabled) return;

	_gatap_disable_override_ = disabled;

	if(isAndroid())
	{
		if(window.gabridge)
		{
			var shouldEnable = _gatap_init_(true);
			window.gabridge.onHasHandler('_gatap_', shouldEnable ? 'true' : 'false');
		}
	}
	else if(isIOS())
	{
		triggerIOSAppPageReInit();
	}
}

function triggerIOSAppPageReInit(skipChecking, flushMessageQueue)
{
	try {
		if(!skipChecking && (!isIOS() || !isShopeeApp())) return true;
		if(!window.document || !window.document.documentElement)
			return false;
		var doce = window.document.documentElement;
		// This is to trigger didFinishLoading of iOS, which will in turn re-init gatap.
		var frm = document.createElement('iframe');
		frm.style.display = 'none';
		// This is used after iOS cache invalidation, where we need to both flush message queue and trigger didFinishLoad (this func is called twice in this case).
		if(flushMessageQueue)
			frm.src= CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
		// This is the normal case, where we only need to trigger didFinishLoad.
		else
			frm.src = "javascript:void(0)";
		doce.appendChild(frm);
		setTimeout(function(){doce.removeChild(frm);}, 500);
		return true;
	}
	catch(e) {
		console.log('triggerIOSAppPageReInit_ex', e);
		return true;
	}
}

var _gatap_override_events_done_ = false;

function _gatap_override_events_()
{
	// Only do this once per page.
	if(_gatap_override_events_done_)
		return;
	_gatap_override_events_done_ = true;

	// We need to do enable/disable the jquery mobile's tap handling depending on whether gatap is disabled or enabled on the fly.
	// The following code replaces jQueryMobile's .event.special.tap handler.
	$.event.special.tap = {
		setup: function() {
			var thisObject = this, $this = $( thisObject );

			var downX = -1;
			var downY = -1;
			var MOVE_THRESHOLD = 10;

			$this.bind('vmousedown', function(e){
				downX = e.screenX;
				downY = e.screenY;
			});

			$this.bind('vmouseup', function(e){
				// Do nothing if either gatap is enabled, or not in app.
				if(!_gatap_disable_override_)
				{
					return true;
				}

				// Do nothing if finger moved.
				if(Math.abs(e.screenX - downX) > MOVE_THRESHOLD || Math.abs(e.screenY - downY) > MOVE_THRESHOLD)
				{
					return false;
				}

				// Trigger actual tap handler.
				$this.trigger('tap');

				// These two are very important. They make sure the "vclick" event won't fire (if it fires, there could be double-tap. For example in "Follow/Unfollow" buttons in Shop page.)
				e.stopImmediatePropagation();
				e.preventDefault();

				// !!! SUPER IMPORTANT to return true (I don't know why, but only this works).
				return true;
			});
		},
		teardown: function() {
			$(this).unbind('vmousedown').unbind('vmouseup');
		}
	};


	// Disable whatever mechanism that's being used by JS to detect tap.
	try
	{
		o = $.event.special;

		//$.vmouse =
		//o.tap =
		//o.taphold =
		// These three are important to make sure site.js -> GTapManager works.
		o.scrollstart =
		o.scrollstop =
		o.vclick =
		function(){};

	}
	catch(e)
	{
		console.log('_gatap_override_events_|ex=' + e);
	}
}

function _gatap_init_(checkOnly) {
	if(typeof(_gatap_) == 'undefined' || _gatap_ == null || window._gatap_disable_override_)
		return false;

	// By default, do not enable gatap for pages with inputs.
	// However the page can set _gatap_force_enable_ to true to bypass this (for example cart page which includes horizontal scrolling area of recommended products).
	if(!window._gatap_force_enable_ && (document.getElementsByTagName('input').length > 0 || document.getElementsByTagName('textarea').length > 0))
		return false;

	if(checkOnly)
		return true;

/*
	var UNSUPPORTED_DEVICES = ['ST26i'];
	var ua = navigator.userAgent;
	for(i=0; i<UNSUPPORTED_DEVICES.length; i++) {
		if(ua.indexOf(UNSUPPORTED_DEVICES[i]) >= 0)
			return false;
	}
*/

	// Move all 'click' handlers to 'tap' events.
	$('*').each(function(i, elm) {
		try {
			elm = $(elm);
			events = $._data(elm[0], 'events');
			if (events && ('click' in events)) {
				var handler = events.click[0].handler;
				if(handler)
					elm.off('tap').on('tap', handler).off('click');
			}
		} catch(e){}
	});

	// Turn off FastClick if any.
	if (typeof(FastClick) != 'undefined') {
		try {
			FastClick.prototype.destroy();
		} catch (e) {}
	}

	// DO NOT wait for DOMReady. We should do this as early as possible before any touch events have been registered.
	_gatap_override_events_();

	return true;
}

// Tell iOS app to process bridge commands during scrolling.
var _ga_bridge_on_scroll_ = (isIOS() && isShopeeApp());

function __android_markNodeAsSwipeable__($root)
{
	if(isShopeeApp() && isAndroid())
	{
		// !!! Tricky: Register these two events in order for our Shopee Android app to detect the swipeable regions so that it can do special handling when this page is contained in another horizontally swipeable tabset.
		// !!! Tricky: when registerting swiperight/left, jquery will auto register "touchstart", which will cause Android phone unable to Fling some times so we have to off it.
		// These swipe events are only for Android app to detect the horizontal swiping areas and doesn't do anything specific in this page.
		$root.on('swiperight',function(){}).on('swipeleft',function(){}).off('touchstart');
		// Must trigger this in case android app has already triggered it before we've registered the above.
		setTimeout(function(){__android_getSwipeables__($root);}, 1000);
	}

}

function isdef(t){return t != 'undefined'}
// Horizontal scrolling must be specially handled on Android, when a page is contained inside a screen with swipeable tabs.
function __android_getSwipeables__($items, force)
{
	if(!window.gabridge || !window.gabridge.onAddSwipeableRect)
		return false;
	if(!$items)
		$items = $('*');
	force = (force == undefined ? false : force);
	var found = false;
	$.each($items, function(i, e){
		var events = $._data(e, 'events');
		var overflowX = window.getComputedStyle(e)['overflowX'];
		if(force || overflowX == 'scroll' || (isdef(typeof(events)) && (isdef(typeof(events.swipeleft)) || isdef(typeof(events.swiperight)))))
		{
			found = true;
			//console.log(e);
			var r = e.getBoundingClientRect();
			//Note: getBoundingClientRect() won't give the correct scrolling top so we have to do the following.
			//Note: r is immutable so we cannot just change its value.
			var offset = $(e).offset();
			var top = offset.hasOwnProperty('top') ? offset['top'] : r.top;
			var bottom = top + r.height;

			//console.log(r);
			gabridge.onAddSwipeableRect(r.left, top, r.right, bottom);
		}
	});
	return found;
}
function __ios_didAddOverflow__($items)
{
	if(!isIOS() || !isShopeeApp())
		return;
	// Inform iOS about this for proper horizontal scrolling handling.
	// Note the timeout is to make sure the code only run after DOM has been successfully changed.
	setTimeout(function(){
		var found = false;
		// Optimization for load-more pages such as Timeline, to avoid unnecesasry calls to iOS app.
		if($items)
		{
			$.each($items, function(i, e){
				var overflowX = window.getComputedStyle(e)['overflowX'];
				if(overflowX == 'scroll')
				{
					found = true;
					return false;
				}
			});
		}
		else
		{
			// Leave to iOS to check if no specific $items are given.
			found = true;
		}
		if(found && window.WebViewJavascriptBridge) {
			window.WebViewJavascriptBridge.callHandler("didAddOverflowElement", {}, null);
		}
	}, 0);
}

// this function should be called by app
// @param String action, representing the specific in-app-action to be triggered
var DEEP_LINK_TRIGGER = {
	MAX_ATTEMPT : 50,
	BACK_OFF : 1000,
	CURRENT_ATTEMPT : 0
};

function _deeplink_trigger_(params) {
	if (window.WebViewJavascriptBridge) {
		_deeplink_trigger_handler(window.WebViewJavascriptBridge, JSON.parse(params));
	} else {
		if (DEEP_LINK_TRIGGER.CURRENT_ATTEMPT < DEEP_LINK_TRIGGER.MAX_ATTEMPT) {
			DEEP_LINK_TRIGGER.CURRENT_ATTEMPT++;
			// TODO fix action bug
			setInterval(function() {_deeplink_trigger_(action)}, DEEP_LINK_TRIGGER.BACK_OFF);
		}

	}
}

function _deeplink_trigger_handler(bridge, params) {
	console.log(params);
	switch(params["action"]) {
		case "showChat":
			if (params['shopID'] && params['itemID'] && params['userID']) {
				bridge.callHandler("startChat",
					{'shopID':params['shopID'],'userID':params['userID'],'itemID':params['itemID']},function(e){});
			}
			break;
		case "showComments":
			if (params['shopID'] && params['itemID'])
				bridge.callHandler("showItemComments",{shopID:params['shopID'],itemID:params['itemID']},function(e){});
			break;
		case "showQuantityPanel":
			if (params['shopID'] && params['itemID']) {
				if (parseInt(params['shopID']) != CURRENT_SHOPID) {
					bridge.callHandler("login", {}, function(e) {
						if(e.status == 1) {
							bridge.callHandler("presentAddCart", {
								shopID: params['shopID'],
								itemID: params['itemID'],
								userID: params['userID'],
								itemImage: params['itemImage'],
								variations: params['variations'],
								keepUserChoice: 0,
								stock: params['stock'],
								price: params['price'],
								status: params['status']
							}, function (e) {});
						}
					})
				}
			}
			break;
		case "showMapPage":
			if (params['latitude'] && params['longitude'] && params['shopname'] && params['title']) {
				bridge.callHandler("showMapPage", {
					latitude: params['latitude'],
					longitude: params['longitude'],
					title: params['title'],
					shopname: params['shopname']
				},function(e){});
			}
			break;
		case "showShopSharing":
			bridge.callHandler("share",{
				shopID:params['shopID'],
				shopName:params['shopName'],
				avatar:params['avatar'],
				shopDesc:params['shopDesc'],
				shopImage:params['shopImage'],
				username:params['username'],
				mtime:params['mtime']
			},function(e){});
			break;
		case "likeItem":
			setTimeout(function() { $("#likes").trigger("tap"); }, 1000);
			console.log("like tapped");
			break;
		case "followShop":
			setTimeout(function() { $('.follow').trigger("tap"); }, 1000);
			console.log("follow tapped");
			break;
		case "addHash":
			if (params['hash']){
				setTimeout(function() { location.hash = params['hash']; }, 1000);
			}
			break;
		case "openOfficialShopLanding":
			var catid = params['catid'];
			BJUtil.jumpToOfficialShopLanding(catid);
			break;
		case "editProfile":
			bridgeCallHandler('login', {}, function (e) {
				if (e.status == 1) {
					bridgeCallHandler('jump', {path: 'editProfile'});
				}
			});
			break;
		case "signup":
			bridgeCallHandler('login', {}, function (e) {});
			break;
		default:
			break;
	}
}

// used for shop, item
function app_fetch_json(bridge, url, success, fail) {
	if(!bridge){
		// web version
		getJSON(url,function(e){
			success(e);
		}).fail(fail);
	}else{
		// app version
		var loaded = false;
		setTimeout(function () {
			if( !loaded ){
				getJSON(url,function(e){
					bridge.callHandler('save', {key: url, data: JSON.stringify(e), persist: 0}, function() {});
					success(e);
				}).fail(fail);
			}
		}, 500);
		bridgeCallHandler('load', {key: url}, function(data){
			loaded = true;
			if (data["data"] != null && !JSON.parse(data["data"]).banned) {
				data = JSON.parse(data["data"]);
				success(data);
				// silent update
				getJSON(url,function(e){
					if (data.version != e.version){
						bridge.callHandler('save', {key: url, data: JSON.stringify(e), persist: 0}, function() {});
						success(e);
					}
				});
			}else{
				// first time create data
				getJSON(url,function(e){
					bridge.callHandler('save', {key: url, data: JSON.stringify(e), persist: 0}, function() {});
					success(e);
				}).fail(fail);
			}
		});
	}
}
