import { ready } from '../../utilities/ready';
import { emit } from '../../utilities/emit';

const tracking = (function (exports) {
	'use strict';

	let user = {},
		queue = [],
		sending = false,
		interval = false,
		isUserKnown = null; // Can be null (maybe unknown), false (unknown) or true. Suppresses tracking submissions when false.

	function generateID() {
		let id = [];

		const array = new Uint32Array(6);
		crypto.getRandomValues(array).forEach(function(i){
			// convert integers to HEX
			id.push(i.toString(16).toUpperCase());
		});
		return id.join('-');
	}

	/**
	 * Detect if browser is sending a doNotTrack property
	 */
	function doNotTrack() {
		const { doNotTrack } = navigator;
		if (doNotTrack !== undefined || window.doNotTrack !== undefined) {
			// if it supports
			// then check for the doNotTrack property in both of them
			if ( window.doNotTrack === "1" || doNotTrack === "1" || doNotTrack === "yes") {
				return true;
			}
		}

		return false;
	}

	/**
	 * Track a page location - adding via persistent tracking queue
	 * @since 3.3 - Renamed from track()
	 * @returns {void}
	 */
	function trackPage() {
		if ( doNotTrack() ) return;

		const { location, referrer } = document;
		const locationTrack = location.pathname + location.search + location.hash;

		let request = {
			url: locationTrack,
			type: 'misc',
			timestamp: new Date().getTime(),
		};
		if (pwpTrack && pwpTrack.type) {
			request.type = pwpTrack.type;
		}
		if (pwpTrack && pwpTrack.obj) {
			request.object = pwpTrack.obj;
		}
		// Add referrer if it's not our own domain
		if ( 0 === referrer.indexOf(locationTrack.protocol + "//" + locationTrack.host) ) {
			request.referrer = referrer;
		}
		// If extra data exists via server side insert into pwpTrack, then add it.
		if (pwpTrack && pwpTrack.metadata) {
			request.data = pwpTrack.metadata; // Key/value pairs of extra data, matching trackEvent
		}
		/**
		 * Filters the request data used to trigger a trackPage request.
		 *
		 * @since 3.4.0
		 *
		 * @param {Object} request      The request data.
		 * @param {string} request.type The trackPage type.
		 */
		request = PersonalizeWP.hooks.applyFilters( 'pwp-tracking-track_page', request, request.type );
		addToQueue( request );
		emit( document, 'tracking', 'trackPage', request );
	}

	/**
	 * Track a custom event - adding via persistent tracking queue
	 * @param {string} eventType Type of event to track, matching registered event type in API
	 * @param {object} eventArgs
	 * @returns {void}
	 */
	function trackEvent( eventType, eventArgs ) {
		if ( doNotTrack() ) return;

		if ( ! eventType || ! eventArgs ) return;

		// Test eventType for disallowed chars. Lowercase alphanumeric characters, dashes, and underscores are allowed.
		const allowedChars = /^[a-z0-9_\-]+$/;
		if ( ! allowedChars.test( eventType ) ) return;

		const { location } = document;
		const locationTrack = location.pathname + location.search + location.hash;

		let request = {
			url: locationTrack,
			type: eventType,
			timestamp: new Date().getTime(),
			data: eventArgs, // Key/value pairs of extra data
		};

		/**
		 * Filters the request data used to trigger a trackEvent request.
		 *
		 * @since 3.4.0
		 *
		 * @param {Object} request   The request data.
		 * @param {string} eventType The custom trackEvent type.
		 */
		request = PersonalizeWP.hooks.applyFilters( 'pwp-tracking-track_event', request, eventType );
		addToQueue( request );
		emit( document, 'tracking', 'trackEvent', request );
	}

	/**
	 * Allow JS loading after tracking.js (e.g. via PWP blocks, post-AJAX submission etc) to trigger a "maybe user known" re-check submission.
	 * Causes the next tracked Page/Event to trigger, and confirm if user is known or not.
	 * @since 3.3
	 * @returns {void}
	 */
	function maybeUserKnown() {
		const { pwpGet, pwpStore } = PersonalizeWP.utils;
		const maybeKnown = pwpGet('tracked_user', true, 'pwp');
		// No user or already known.
		if ( ! maybeKnown || true === maybeKnown.isKnown ) {
			return;
		}
		// Immediately mark that the next submission should be attempted.
		isUserKnown = null;
		// Update stored user that they might be known.
		maybeKnown.isKnown = null;
		pwpStore('tracked_user', maybeKnown, 'pwp');
		user = maybeKnown; // Update locally stored user.
		emit(document, 'tracking', 'maybeUserKnown', user);
	}

	/**
	 * Send the tracking data.
	 */
	async function send() {
		const { pwpStore, callApi } = PersonalizeWP.utils;

		// only send one at a time
		if(sending){
			return;
		}

		sending = true;
		queue.forEach(async(item) => {
			// If we're tracking Anon users, or the user is explicitly not unknown then allow activity recording
			if(pwpTrack.trackAnon || false !== isUserKnown) {
				const { success, known } = await callApi('v1/activity', {
					...item,
					uid: user.id
				});
				// Update storage with known/unknown state
				user.isKnown = known;
				pwpStore('tracked_user', user, 'pwp');
				if (known) {
					// Mark user as known for further calls in same pageload.
					isUserKnown = true;
				} else if (!known && !pwpTrack.trackAnon) {
					// Suppress further sending until some future time.
					isUserKnown = false;
				}
			}
		});
		queue = [];
		sending = false;
		pwpStore('tracking_queue', queue, 'pwp');
		// Since the queue is empty, pause any additional work.
		pauseTracking();
	}

	/**
	 * Initiates the module once PersonaliseWP has loaded.
	 */
	function moduleInit() {
		const { pwpGet, pwpStore } = PersonalizeWP.utils;

		// is tracking enabled?
		if ( doNotTrack() ) return;

		user = pwpGet('tracked_user', true, 'pwp');
		queue = pwpGet('tracking_queue', true, 'pwp');

		// queue should be an array
		if(!queue || (queue && typeof queue.forEach !== 'function')){
			queue = [];
		}

		let usercreation = false;
		// no user stored, create one
		const datetime = new Date().getTime();
		if(!user) {
			usercreation = true;
			// new user object
			user = {
				id:			generateID(),
				firstVisit:	datetime,
				lastVisit:	datetime,
				isKnown:    false, // Clearly not yet known
			};
		}
		else {
			user.lastVisit = datetime;
			// Migrate user obj to newer format
			if (!user.hasOwnProperty('isKnown')) {
				user.isKnown = null; // For now mark as not unknown, allowing next tracking submission
			}
		}
		// Default state to whatever the user has stored
		isUserKnown = user.isKnown;

		// Inline JS object added after a user has logged in as a WP user.
		if (pwpTrack.hasOwnProperty('userIsKnown') && pwpTrack.userIsKnown) {
			isUserKnown = true; // Always send the tracking submission
			user.isKnown = true; // Store for future loads
		}
		pwpStore('tracked_user', user, 'pwp');
		if ( usercreation ){
			emit(document, 'tracking', 'userCreation', user);
		}

		// track an SPA-style location change
		window.addEventListener('locationchange', function () {
			trackPage();
		});

		// Ready
		emit(document, 'tracking', 'ready', user);

		// immediately track the loaded location
		trackPage();
	}

	/**
	 * Add tracking item to queue
	 * @since 3.3
	 * @param {object} args
	 */
	function addToQueue( args ) {
		const { pwpStore } = PersonalizeWP.utils;

		queue.push( args );
		pwpStore('tracking_queue', queue, 'pwp');
		// Since we have something in the queue, trigger sending it.
		sendTracking();
	}

	/**
	 * Sets up an interval for queue sending.
	 */
	function sendTracking() {
		// Already started.
		if (interval) {
			return;
		}
		// set up data-sending interval
		interval = setInterval( () => send(), 500 );
	}

	/**
	 * Turns off the interval for queue sending.
	 */
	function pauseTracking() {
		// Nothing to clear.
		if (!interval) {
			return;
		}
		clearInterval(interval);
		interval = false;
	}

	/**
	 * Called automatically once PersonalizeWP is available for loading this module.
	 */
	function readyInit() {
		// Double check
		if ( window.PersonalizeWP ) {
			PersonalizeWP.module('tracking', this, true); // Note: Immediate load of module, never wait for interaction.

			// Expose public API tracking functions direct on main PWP object.
			PersonalizeWP.tracking = {
				trackPage: this.trackPage,
				trackEvent: this.trackEvent,
				maybeUserKnown: this.maybeUserKnown,
			};
		}
	}

	// Export the public functions of Tracking
	exports.init = moduleInit;
	exports.readyInit = readyInit;
	exports.trackPage = trackPage;
	exports.trackEvent = trackEvent;
	exports.maybeUserKnown = maybeUserKnown;

	return exports;
})({});

// Check and delay until PersonalizeWP object available.
ready(tracking);
