/**
 * Non-IEW (inter-element-whitespace) can only be found in <td> <th> and
 * <caption> tags in a table.  If found elsewhere within a table, such
 * content will be moved out of the table and be "adopted" by the table's
 * sibling ("foster parent"). The content that gets adopted is "fostered
 * content".
 *
 * http://www.w3.org/TR/html5/syntax.html#foster-parent
 * @module
 */

'use strict';

const { DOMDataUtils } = require('../../../utils/DOMDataUtils.js');
const { DOMUtils } = require('../../../utils/DOMUtils.js');
const { Util } = require('../../../utils/Util.js');
const { WTUtils } = require('../../../utils/WTUtils.js');

class MarkFosteredContent {
	/**
	 * Create a new DOM node with attributes.
	 */
	createNodeWithAttributes(document, type, attrs) {
		var node = document.createElement(type);
		DOMDataUtils.addAttributes(node, attrs);
		return node;
	}

	// cleans up transclusion shadows, keeping track of fostered transclusions
	removeTransclusionShadows(node) {
		var sibling;
		var fosteredTransclusions = false;
		if (DOMUtils.isElt(node)) {
			if (DOMUtils.isMarkerMeta(node, "mw:TransclusionShadow")) {
				node.parentNode.removeChild(node);
				return true;
			} else if (DOMDataUtils.getDataParsoid(node).tmp.inTransclusion) {
				fosteredTransclusions = true;
			}
			node = node.firstChild;
			while (node) {
				sibling = node.nextSibling;
				if (this.removeTransclusionShadows(node)) {
					fosteredTransclusions = true;
				}
				node = sibling;
			}
		}
		return fosteredTransclusions;
	}

	// inserts metas around the fosterbox and table
	insertTransclusionMetas(env, fosterBox, table) {
		var aboutId = env.newAboutId();

		// You might be asking yourself, why is table.data.parsoid.tsr[1] always
		// present? The earlier implementation searched the table's siblings for
		// their tsr[0]. However, encapsulation doesn't happen when the foster box,
		// and thus the table, are in the transclusion.
		var s = this.createNodeWithAttributes(fosterBox.ownerDocument, "meta", {
			"about": aboutId,
			"id": aboutId.substring(1),
			"typeof": "mw:Transclusion",
		});
		DOMDataUtils.setDataParsoid(s, {
			tsr: Util.clone(DOMDataUtils.getDataParsoid(table).tsr),
			tmp: { fromFoster: true },
		});
		fosterBox.parentNode.insertBefore(s, fosterBox);

		var e = this.createNodeWithAttributes(table.ownerDocument, "meta", {
			"about": aboutId,
			"typeof": "mw:Transclusion/End",
		});

		var sibling = table.nextSibling;
		var beforeText;

		// Skip past the table end, mw:shadow and any transclusions that
		// start inside the table. There may be newlines and comments in
		// between so keep track of that, and backtrack when necessary.
		while (sibling) {
			if (!WTUtils.isTplStartMarkerMeta(sibling) && (
				WTUtils.hasParsoidAboutId(sibling) ||
				DOMUtils.isMarkerMeta(sibling, "mw:EndTag") ||
				DOMUtils.isMarkerMeta(sibling, "mw:TransclusionShadow")
			)) {
				sibling = sibling.nextSibling;
				beforeText = null;
			} else if (DOMUtils.isComment(sibling) || DOMUtils.isText(sibling)) {
				if (!beforeText) {
					beforeText = sibling;
				}
				sibling = sibling.nextSibling;
			} else {
				break;
			}
		}

		table.parentNode.insertBefore(e, beforeText || sibling);
	}

	getFosterContentHolder(doc, inPTag) {
		var fosterContentHolder = doc.createElement(inPTag ? 'span' : 'p');
		DOMDataUtils.setDataParsoid(fosterContentHolder, { fostered: true, tmp: {} });
		return fosterContentHolder;
	}

	/**
	 * Searches for FosterBoxes and does two things when it hits one:
	 * - Marks all nextSiblings as fostered until the accompanying table.
	 * - Wraps the whole thing (table + fosterbox) with transclusion metas if
	 *   there is any fostered transclusion content.
	 * @param {Node} node
	 * @param {MWParserEnvironment} env
	 */
	markFosteredContent(node, env) {
		var sibling, next, fosteredTransclusions;
		var c = node.firstChild;

		while (c) {
			sibling = c.nextSibling;
			fosteredTransclusions = false;

			if (DOMUtils.hasNameAndTypeOf(c, "TABLE", "mw:FosterBox")) {
				var inPTag = DOMUtils.hasAncestorOfName(c.parentNode, "p");
				var fosterContentHolder = this.getFosterContentHolder(c.ownerDocument, inPTag);

				// mark as fostered until we hit the table
				while (sibling && (!DOMUtils.isElt(sibling) || sibling.nodeName !== "TABLE")) {
					next = sibling.nextSibling;
					if (DOMUtils.isElt(sibling)) {
						// TODO: Note the similarity here with the p-wrapping pass.
						// This can likely be combined in some more maintainable way.
						if (DOMUtils.isBlockNode(sibling) || WTUtils.emitsSolTransparentSingleLineWT(sibling)) {
							// Block nodes don't need to be wrapped in a p-tag either.
							// Links, includeonly directives, and other rendering-transparent
							// nodes dont need wrappers. sol-transparent wikitext generate
							// rendering-transparent nodes and we use that helper as a proxy here.
							DOMDataUtils.getDataParsoid(sibling).fostered = true;

							// If the foster content holder is not empty,
							// close it and get a new content holder.
							if (fosterContentHolder.hasChildNodes()) {
								sibling.parentNode.insertBefore(fosterContentHolder, sibling);
								fosterContentHolder = this.getFosterContentHolder(sibling.ownerDocument, inPTag);
							}
						} else {
							fosterContentHolder.appendChild(sibling);
						}

						if (this.removeTransclusionShadows(sibling)) {
							fosteredTransclusions = true;
						}
					} else {
						fosterContentHolder.appendChild(sibling);
					}
					sibling = next;
				}

				var table = sibling;

				// we should be able to reach the table from the fosterbox
				console.assert(table && DOMUtils.isElt(table) && table.nodeName === "TABLE",
					"Table isn't a sibling. Something's amiss!");

				if (fosterContentHolder.hasChildNodes()) {
					table.parentNode.insertBefore(fosterContentHolder, table);
				}

				// we have fostered transclusions
				// wrap the whole thing in a transclusion
				if (fosteredTransclusions) {
					this.insertTransclusionMetas(env, c, table);
				}

				// remove the foster box
				c.parentNode.removeChild(c);

			} else if (DOMUtils.isMarkerMeta(c, "mw:TransclusionShadow")) {
				c.parentNode.removeChild(c);
			} else if (DOMUtils.isElt(c)) {
				if (c.hasChildNodes()) {
					this.markFosteredContent(c, env);
				}
			}

			c = sibling;
		}
	}

	run(node, env) {
		this.markFosteredContent(node, env);
	}
}

if (typeof module === "object") {
	module.exports.MarkFosteredContent = MarkFosteredContent;
}