/*
 * F4 probe #29: paypal.com/ua/home -- hydration runs, throws nothing, and
 * changes nothing.
 *
 * #28 ruled out the two obvious explanations. Writes into a template's
 * content fragment work perfectly (`innerHTML=""` -> 0 kids, appendChild ->
 * 1, textContent reads back), and hydrate() emitted ZERO console lines with
 * our own console installed, so nothing is throwing. Yet
 * `#shadow-content-header kids` is 1 before and 1 after -- and the mount
 * clears that container before it returns:
 *
 *	const a = o.getElementById(e.shadowContentId);
 *	return a ? (a.innerHTML="", {...}) : null;
 *
 * An unchanged child count therefore proves the mount was never entered, so
 * the gate is still answering false. Everything in it has been verified by
 * hand except one step -- turning the NodeList into an array:
 *
 *	const s = document.querySelectorAll("script[data-glnv-bundle-source]");
 *	const i = Array.from(s).find(d => new URL(d.src).href === a);
 *
 * If Array.from() comes back empty for a NodeList -- which it will if the
 * list carries a Symbol.iterator that does not iterate, since that path wins
 * over the array-like length path -- then `i` is undefined, `l` is null, and
 * the gate returns false with everything else about it correct. That is the
 * shape of this bug exactly: no exception, no output, no effect.
 *
 * So: measure the conversion directly. Then force the issue -- run hydrate()
 * with find() rigged to hand back the script element regardless -- and see
 * whether the masthead fills in. If it does, the gate is the whole story.
 */
(function () {
	/* PayPal replaces window.console with a self-XSS banner, so go to the
	 * prototype for a log that reaches NSDBG. */
	var rawlog = null;
	try {
		if (typeof Console === 'function' && Console.prototype &&
		    typeof Console.prototype.log === 'function')
			rawlog = Console.prototype.log;
	} catch (e) { /* - */ }

	function say(s) {
		try {
			if (rawlog !== null)
				rawlog.call(console, 'PROBE29 ' + s);
			else
				console.log('PROBE29 ' + s);
		} catch (e) { /* - */ }
	}

	function flat(v) {
		var s;

		try {
			s = String(v);
		} catch (e) {
			s = '<unstringable>';
		}
		return s.replace(/\s+/g, ' ').slice(0, 240);
	}

	/* ---- the suspect: NodeList -> Array ---- */
	function listConversion() {
		var sels = ['script[data-glnv-bundle-source]', 'script', 'a',
			    'div'];
		var i, nl, viaFrom, viaSlice, viaSpread, iter;

		for (i = 0; i < sels.length; i++) {
			nl = document.querySelectorAll(sels[i]);

			try {
				viaFrom = Array.from(nl).length;
			} catch (e) {
				viaFrom = 'THREW ' + flat(e);
			}
			try {
				viaSlice = Array.prototype.slice.call(nl).length;
			} catch (e) {
				viaSlice = 'THREW ' + flat(e);
			}
			try {
				/* eslint-disable-next-line */
				viaSpread = [].concat(nl.length ?
					Array.prototype.map.call(nl,
						function (x) { return x; }) :
					[]).length;
			} catch (e) {
				viaSpread = 'THREW ' + flat(e);
			}
			try {
				iter = (typeof Symbol === 'function' &&
					Symbol.iterator) ?
					typeof nl[Symbol.iterator] : 'no Symbol';
			} catch (e) {
				iter = 'THREW';
			}

			say('qsa[' + sels[i] + '] length=' + nl.length +
			    ' Array.from=' + viaFrom +
			    ' slice=' + viaSlice +
			    ' map=' + viaSpread +
			    ' Symbol.iterator=' + iter);
		}

		/* Does the iterator, if there is one, actually yield? */
		try {
			nl = document.querySelectorAll('script');
			if (typeof Symbol === 'function' && Symbol.iterator &&
			    typeof nl[Symbol.iterator] === 'function') {
				var it = nl[Symbol.iterator]();
				var n = 0, r;

				for (;;) {
					r = it.next();
					if (!r || r.done) {
						break;
					}
					n++;
					if (n > 200) {
						break;
					}
				}
				say('iterator yielded ' + n + ' of ' +
				    nl.length);
			} else {
				say('no NodeList iterator');
			}
		} catch (e) {
			say('iterator walk THREW ' + flat(e));
		}

		/* Array.from over a plain array-like, to separate a broken
		 * NodeList from a broken Array.from. */
		try {
			say('Array.from({0:1,1:2,length:2})=' +
			    Array.from({ 0: 1, 1: 2, length: 2 }).length);
		} catch (e) {
			say('Array.from(array-like) THREW ' + flat(e));
		}
	}

	function state(when) {
		var host = document.getElementById('shadow-host-header');
		var sr = host ? host.shadowRoot : null;
		var frag = sr ? (sr.content || null) : null;
		var cc = null, w;

		try {
			cc = sr ? sr.getElementById('shadow-content-header') :
				  null;
		} catch (e) { /* - */ }

		say(when + ' header all=' +
		    (frag ? frag.querySelectorAll('*').length : '-') +
		    ' contentKids=' + (cc ? cc.childNodes.length : '-'));

		w = document.querySelector(
			'[class*=segment-control-group-wrapper]');
		say(when + ' hero segment inner=' +
		    (w ? w.querySelectorAll('*').length : 'no wrapper'));
	}

	/* Run hydrate() with find() rigged for the bundle-source lookup only,
	 * and with a console we control so nothing is swallowed. */
	function hydrateForced(name) {
		var reg = window.__GLOBALNAV__;
		var con = window.console;
		var realFind = Array.prototype.find;
		var saved = {};
		var keys = ['error', 'warn', 'log', 'info', 'debug'];
		var lines = 0;
		var i;

		if (!reg || !reg[name] ||
		    typeof reg[name].hydrate !== 'function') {
			say('hydrate(' + name + '): not registered');
			return;
		}

		function grab(kind) {
			return function () {
				var parts = [], j;

				lines++;
				for (j = 0; j < arguments.length; j++) {
					parts.push(flat(arguments[j]));
					if (arguments[j] && arguments[j].stack) {
						parts.push('STACK ' +
						    flat(arguments[j].stack));
					}
				}
				say('  console.' + kind + ': ' +
				    parts.join(' | '));
			};
		}

		for (i = 0; i < keys.length; i++) {
			try {
				saved[keys[i]] = con[keys[i]];
				con[keys[i]] = grab(keys[i]);
			} catch (e) { /* - */ }
		}

		/* Only the SCRIPT lookup is rigged; every other find() during
		 * hydration keeps its real behaviour. */
		try {
			Array.prototype.find = function (pred) {
				var out = realFind.apply(this, arguments);
				var j, r;

				if (out === undefined && this.length > 0 &&
				    this[0] && this[0].nodeName === 'SCRIPT') {
					for (j = 0; j < this.length; j++) {
						try {
							r = pred(this[j], j,
								 this);
						} catch (e) {
							r = 'THREW ' + flat(e);
						}
						say('  script pred[' + j +
						    '] -> ' + r);
					}
					say('  forcing find() to return ' +
					    'script[0]');
					return this[0];
				}
				return out;
			};

			reg[name].hydrate();
			say('hydrate(' + name + '): returned, console lines=' +
			    lines);
		} catch (e) {
			say('hydrate(' + name + ') ESCAPED ' + flat(e) +
			    ' | STACK ' + flat(e && e.stack));
		} finally {
			Array.prototype.find = realFind;
			for (i = 0; i < keys.length; i++) {
				try {
					con[keys[i]] = saved[keys[i]];
				} catch (e) { /* - */ }
			}
		}
	}

	say('==== list conversion ====');
	listConversion();
	say('==== before ====');
	state('before');
	say('==== forced hydrate header ====');
	hydrateForced('header');
	state('after-header');
	say('==== forced hydrate footer ====');
	hydrateForced('footer');
	state('after-footer');
	say('==== done ====');
}());
