<?php
declare( strict_types = 1 );

namespace MediaWiki\Extension\Translate\TranslatorInterface\Aid;

use Content;
use ContentHandler;
use MediaWiki\Extension\Translate\MessageGroupProcessing\RevTagStore;
use MediaWiki\Extension\Translate\MessageLoading\MessageHandle;
use MediaWiki\Extension\Translate\TranslatorInterface\TranslationHelperException;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\SlotRecord;
use MessageGroup;
use TextContent;
use Wikimedia\Rdbms\IDatabase;

/**
 * @author Niklas Laxström
 * @license GPL-2.0-or-later
 * @since 2018.01
 */
class TranslationAidDataProvider {
	/** @var MessageHandle */
	private $handle;
	/** @var MessageGroup */
	private $group;
	/** @var string|null */
	private $definition;
	/** @var array */
	private $translations;

	public function __construct( MessageHandle $handle ) {
		$this->handle = $handle;
		$this->group = $handle->getGroup();
	}

	/**
	 * Get the message definition. Cached for performance.
	 * @return string
	 */
	public function getDefinition(): string {
		if ( $this->definition !== null ) {
			return $this->definition;
		}

		// Optional performance optimization
		if ( method_exists( $this->group, 'getMessageContent' ) ) {
			// @phan-suppress-next-line PhanUndeclaredMethod
			$this->definition = $this->group->getMessageContent( $this->handle );
		} else {
			$this->definition = $this->group->getMessage(
				$this->handle->getKey(),
				$this->group->getSourceLanguage()
			);
		}

		if ( $this->definition === null ) {
			throw new TranslationHelperException(
				'Did not find message definition for ' . $this->handle->getTitle()->getPrefixedText() .
				' in group ' . $this->group->getId()
			);
		}
		return $this->definition;
	}

	public function hasDefinition(): bool {
		try {
			$this->getDefinition();
			return true;
		} catch ( TranslationHelperException $e ) {
			return false;
		}
	}

	public function getDefinitionContent(): Content {
		return ContentHandler::makeContent( $this->getDefinition(), $this->handle->getTitle() );
	}

	/**
	 * Get the translations in all languages. Cached for performance.
	 * Fuzzy translation are not included.
	 * @return array Language code => Translation
	 */
	public function getGoodTranslations(): array {
		if ( $this->translations !== null ) {
			return $this->translations;
		}

		$mwServices = MediaWikiServices::getInstance();
		$data = self::loadTranslationData(
			$mwServices->getDBLoadBalancer()->getConnection( DB_REPLICA ),
			$this->handle
		);
		$translations = [];
		$prefixLength = strlen( $this->handle->getTitleForBase()->getDBkey() . '/' );
		$languageNameUtils = $mwServices->getLanguageNameUtils();

		foreach ( $data as $page => $translation ) {
			// Could use MessageHandle here, but that queries the message index.
			// Instead, we can get away with simple string manipulation.
			$code = substr( $page, $prefixLength );
			if ( !$languageNameUtils->isKnownLanguageTag( $code ) ) {
				continue;
			}

			$translations[ $code ] = $translation;
		}

		$this->translations = $translations;

		return $translations;
	}

	private static function loadTranslationData( IDatabase $db, MessageHandle $handle ): array {
		$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
		$conditions = [];

		// The list of pages we want to select, and their latest versions
		$conditions['page_namespace'] = $handle->getTitle()->getNamespace();
		$base = $handle->getKey();
		$conditions[] = 'page_title ' . $db->buildLike( "$base/", $db->anyString() );
		$conditions[] = 'rev_id=page_latest';

		// For fuzzy tags we need the join with revtag and also:
		$conditions[ 'rt_type' ] = null;

		$rows = $revisionStore->newSelectQueryBuilder( $db )
			->joinPage()
			->joinComment()
			->leftJoin( 'revtag', null, [
				'page_id=rt_page',
				'page_latest=rt_revision',
				'rt_type' => RevTagStore::FUZZY_TAG
			] )
			->where( $conditions )
			->caller( __METHOD__ )
			->fetchResultSet();

		$pages = [];
		$revisions = $revisionStore->newRevisionsFromBatch( $rows, [ 'slots' => [ SlotRecord::MAIN ] ] )
			->getValue();
		foreach ( $rows as $row ) {
			/** @var RevisionRecord|null $rev */
			$rev = $revisions[$row->rev_id];
			if ( $rev && $rev->getContent( SlotRecord::MAIN ) instanceof TextContent ) {
				$pages[$row->page_title] = $rev->getContent( SlotRecord::MAIN )->getText();
			}
		}

		return $pages;
	}
}
