/** @module */

'use strict';

const TokenHandler = require('./TokenHandler.js');
const { PipelineUtils } = require('../../utils/PipelineUtils.js');
const { TagTk, EOFTk, SelfclosingTagTk, EndTagTk } = require('../../tokens/TokenTypes.js');

/**
 * @class
 * @extends module:wt2html/tt/TokenHandler
 */
class DOMFragmentBuilder extends TokenHandler {
	constructor(manager, options) {
		super(manager, options);
		this.manager.addTransform(
			(scopeToken, cb) => this.buildDOMFragment(scopeToken, cb),
			'buildDOMFragment',
			DOMFragmentBuilder.scopeRank(),
			'tag',
			'mw:dom-fragment-token'
		);
	}

	static scopeRank() { return 1.99; }

	/**
     * Can/should content represented in 'toks' be processed in its own DOM scope?
	 * 1. No reason to spin up a new pipeline for plain text
	 * 2. In some cases, if templates need not be nested entirely within the
	 *    boundary of the token, we cannot process the contents in a new scope.
	 */
	subpipelineUnnecessary(toks, contextTok) {
		for (let i = 0, n = toks.length; i < n; i++) {
			const t = toks[i];
			const tc = t.constructor;

			// For wikilinks and extlinks, templates should be properly nested
			// in the content section. So, we can process them in sub-pipelines.
			// But, for other context-toks, we back out. FIXME: Can be smarter and
			// detect proper template nesting, but, that can be a later enhancement
			// when dom-scope-tokens are used in other contexts.
			if (contextTok && contextTok.name !== 'wikilink' && contextTok.name !== 'extlink' &&
				tc === SelfclosingTagTk &&
				t.name === 'meta' && t.getAttribute("typeof") === "mw:Transclusion") {
				return true;
			} else if (tc === TagTk || tc === EndTagTk || tc === SelfclosingTagTk) {
				// Since we encountered a complex token, we'll process this
				// in a subpipeline.
				return false;
			}
		}

		// No complex tokens at all -- no need to spin up a new pipeline
		return true;
	}

	buildDOMFragment(scopeToken, cb) {
		const contentKV = scopeToken.getAttributeKV("content");
		const content = contentKV.v;
		if (this.subpipelineUnnecessary(content, scopeToken.getAttribute('contextTok'))) {
			// New pipeline not needed. Pass them through
			cb({ tokens: typeof content === "string" ? [content] : content, async: false });
		} else {
			// First thing, signal that the results will be available asynchronously
			cb({ async: true });

			// Source offsets of content
			const srcOffsets = contentKV.srcOffsets;

			// Without source offsets for the content, it isn't possible to
			// compute DSR and template wrapping in content. So, users of
			// mw:dom-fragment-token should always set offsets on content
			// that comes from the top-level document.
			console.assert(
				this.options.inTemplate || !!srcOffsets,
				"Processing top-level content without source offsets"
			);

			const pipelineOpts = {
				inlineContext: scopeToken.getAttribute('inlineContext'),
				inPHPBlock: scopeToken.getAttribute('inPHPBlock'),
				expandTemplates: this.options.expandTemplates,
				inTemplate: this.options.inTemplate,
			};

			// Process tokens
			PipelineUtils.processContentInPipeline(
				this.manager.env,
				this.manager.frame,
				// Append EOF
				content.concat([new EOFTk()]),
				{
					pipelineType: "tokens/x-mediawiki/expanded",
					pipelineOpts: pipelineOpts,
					srcOffsets: srcOffsets.slice(2, 4),
					documentCB: dom => this.wrapDOMFragment(cb, scopeToken, pipelineOpts, dom),
					sol: true,
				}
			);
		}
	}

	wrapDOMFragment(cb, scopeToken, pipelineOpts, dom) {
		// Pass through pipeline options
		const toks = PipelineUtils.tunnelDOMThroughTokens(this.manager.env, scopeToken, dom.body, {
			pipelineOpts,
		});

		// Nothing more to send cb after this
		cb({ tokens: toks, async: false });
	}
}

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