<?php
declare( strict_types = 1 );

namespace Cite\Parsoid;

use Cite\Validator;
use MediaWiki\Config\Config;
use Wikimedia\Parsoid\DOM\DocumentFragment;
use Wikimedia\Parsoid\DOM\Element;
use Wikimedia\Parsoid\Ext\DOMDataUtils;
use Wikimedia\Parsoid\Ext\ExtensionTagHandler;
use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;

/**
 * @license GPL-2.0-or-later
 */
class ReferenceListTagHandler extends ExtensionTagHandler {

	public function __construct(
		private readonly Config $mainConfig,
	) {
	}

	/** @inheritDoc */
	public function sourceToDom(
		ParsoidExtensionAPI $extApi, string $txt, array $extArgs
	): DocumentFragment {
		$domFragment = $extApi->extTagToDOM(
			$extArgs,
			$txt,
			[
				'parseOpts' => [ 'extTag' => 'references' ],
			]
		);

		// Detect invalid parameters on the references tag
		$status = Validator::filterReferenceListArguments( $extApi->extArgsToArray( $extArgs ) );
		$refsOpts = $status->getValue();
		foreach ( $status->getMessages() as $msg ) {
			$error = ErrorUtils::fromMessageSpecifier( $msg );
			$extApi->pushError( $error->key, ...$error->params );
		}

		$referenceList = new References( $this->mainConfig );
		$frag = $referenceList->createEmptyReferenceListFragment(
			$extApi,
			$domFragment,
			$refsOpts,
			static function ( $dp ) use ( $extApi ) {
				$dp->src = $extApi->extTag->getSource();
				// Setting redundant info on fragment.
				// $docBody->firstChild info feels cumbersome to use downstream.
				if ( $extApi->extTag->isSelfClosed() ) {
					$dp->selfClose = true;
				}
			}
		);
		$domFragment->appendChild( $frag );

		if ( isset( $error ) ) {
			$errorFragment = ( new ErrorUtils( $extApi ) )->renderParsoidError( $error );
			$frag->appendChild( $errorFragment );
		}

		return $domFragment;
	}

	/** @inheritDoc */
	public function processAttributeEmbeddedDom(
		ParsoidExtensionAPI $extApi, Element $elt, callable $proc
	): void {
		$dataMw = DOMDataUtils::getDataMw( $elt );
		if ( isset( $dataMw->body ) && $dataMw->body->hasHtml() ) {
			$df = $dataMw->body->getHtml( $extApi );
			$changed = $proc( $df );
			if ( $changed ) {
				$dataMw->body->setHtml( $extApi, $df );
			}
		}
	}

	/** @inheritDoc */
	public function domToWikitext(
		ParsoidExtensionAPI $extApi, Element $node, bool $wrapperUnmodified
	) {
		$dataMw = DOMDataUtils::getDataMw( $node );
		$isAutoGenerated = $dataMw->autoGenerated ?? false;
		$isDefaultGroup = ( $dataMw->getExtAttrib( 'group' ) ?? '' ) === '';
		// Autogenerated references aren't considered erroneous (the extension to the legacy
		// parser also generates them) and are not suppressed when serializing because apparently
		// that's the behaviour Parsoid clients want.  However, autogenerated references *with
		// group attributes* are errors (the legacy extension doesn't generate them at all) and
		// are suppressed when serialized since we considered them an error while parsing and
		// don't want them to persist in the content.
		if ( $isAutoGenerated && !$isDefaultGroup ) {
			return '';
		}

		$src = null;
		if ( isset( $dataMw->body ) && $dataMw->body->hasHtml() ) {
			$src = $extApi->domToWikitext( [ 'extName' => $dataMw->name ], $dataMw->body->getHtml( $extApi ) );
			if ( $isAutoGenerated && trim( $src ) === '' ) {
				return '';
			}
		}

		$startTagSrc = $extApi->extStartTagToWikitext( $node );
		if ( str_ends_with( $startTagSrc, '/>' ) ) {
			// We self-closed this already
			return $startTagSrc;
		} else {
			return $startTagSrc . $src . '</' . $dataMw->name . '>';
		}
	}

	/** @inheritDoc */
	public function lintHandler(
		ParsoidExtensionAPI $extApi, Element $refs, callable $defaultHandler
	): bool {
		// Skip the tree.  Content embedded in data-mw will be traversed by linter
		// when processAttributeEmbeddedDom is called
		return true;
	}

	/** @inheritDoc */
	public function diffHandler(
		ParsoidExtensionAPI $extApi, callable $domDiff, Element $origNode,
		Element $editedNode
	): bool {
		$origDataMw = DOMDataUtils::getDataMw( $origNode );
		$editedDataMw = DOMDataUtils::getDataMw( $editedNode );

		if ( isset( $origDataMw->body ) && $origDataMw->body->hasHtml() &&
			 isset( $editedDataMw->body ) && $editedDataMw->body->hasHtml() ) {
			$origFragment = $origDataMw->body->getHtml( $extApi );
			$editedFragment = $editedDataMw->body->getHtml( $extApi );
			return $domDiff( $origFragment, $editedFragment );
		}

		// FIXME: Similar to DOMDiff::subtreeDiffers, maybe $editNode should
		// be marked as inserted to avoid losing any edits, at the cost of
		// more normalization

		return false;
	}

}
