<?php

namespace SMW\MediaWiki\Hooks;

use MediaWiki\Parser\Parser;
use MediaWiki\Parser\StripState;
use SMW\MediaWiki\HookListener;
use SMW\OptionsAwareTrait;
use SMW\Parser\InTextAnnotationParser;
use SMW\Services\ServicesFactory as ApplicationFactory;

/**
 * The main task for this hook is to parse and replace the Semantic MediaWiki
 * specific annotation syntax (e.g. `[[PropertyA::ValueB]]`) with a corresponding
 * text representation (e.g. wikitext link, reference, URL etc.) of the value
 * and the defined property. Structured information is gathered from a parsed
 * text and attached to the `ParserOutput` for further processing after the
 * parsing has been completed.
 *
 * Hook: InternalParseBeforeLinks is used to process the expanded wiki
 * code after <nowiki>, HTML-comments, and templates have been treated.
 *
 * This method will be called before an article is displayed or previewed.
 * For display and preview we strip out the semantic properties and append them
 * at the end of the article.
 *
 * @note MW 1.20+ see InternalParseBeforeSanitize
 *
 * @see https://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeLinks
 *
 * @ingroup FunctionHook
 *
 * @license GPL-2.0-or-later
 * @since 1.9
 *
 * @author mwjames
 */
class InternalParseBeforeLinks implements HookListener {

	use OptionsAwareTrait;

	/**
	 * @var Parser
	 */
	private $parser;

	/**
	 * @var StripState
	 */
	private $stripState;

	/**
	 * @since 1.9
	 *
	 * @param Parser &$parser
	 * @param StripState $stripState
	 */
	public function __construct( Parser &$parser, $stripState ) {
		$this->parser = $parser;
		$this->stripState = $stripState;
	}

	/**
	 * @since 1.9
	 *
	 * @param string &$text
	 *
	 * @return true
	 */
	public function process( &$text ) {
		if ( !$this->canPerformUpdate( $text, $this->parser->getTitle() ) ) {
			return true;
		}

		return $this->performUpdate( $text );
	}

	private function canPerformUpdate( $text, $title ) {
		if ( $this->getRedirectTarget() !== null ) {
			return true;
		}

		// #2209, #2370 Allow content to be parsed that contain [[SMW::off]]/[[SMW::on]]
		// even in case of MediaWiki messages
		if ( InTextAnnotationParser::hasMarker( $text ) || InTextAnnotationParser::hasPropertyLink( $text ) ) {
			return true;
		}

		// ParserOptions::getInterfaceMessage is being used to identify whether a
		// parse was initiated by `Message::parse`
		if ( $text === '' || $this->parser->getOptions()->getInterfaceMessage() ) {
			return false;
		}

		if ( !$title->isSpecialPage() ) {
			return true;
		}

		// #2529
		foreach ( $this->getOption( 'smwgEnabledSpecialPage', [] ) as $specialPage ) {
			if ( is_string( $specialPage ) && $title->isSpecial( $specialPage ) ) {
				return true;
			}
		}

		return false;
	}

	private function performUpdate( &$text ) {
		$applicationFactory = ApplicationFactory::getInstance();

		/**
		 * @var ParserData $parserData
		 */
		$parserData = $applicationFactory->newParserData(
			$this->parser->getTitle(),
			$this->parser->getOutput()
		);

		/**
		 * Performs [[link::syntax]] parsing and adding of property annotations
		 * to the ParserOutput
		 *
		 * @var InTextAnnotationParser
		 */
		$inTextAnnotationParser = $applicationFactory->newInTextAnnotationParser(
			$parserData
		);

		$stripMarkerDecoder = $applicationFactory->newMwCollaboratorFactory()->newStripMarkerDecoder(
			$this->stripState
		);

		$inTextAnnotationParser->setStripMarkerDecoder(
			$stripMarkerDecoder
		);

		$inTextAnnotationParser->setRedirectTarget(
			$this->getRedirectTarget()
		);

		$inTextAnnotationParser->parse( $text );

		$parserData->markParserOutput();

		return true;
	}

	private function getRedirectTarget() {
		return $this->parser->getOptions()->getRedirectTarget();
	}

}
