<?php
declare( strict_types = 1 );

namespace MediaWiki\Extension\Translate\TranslatorInterface;

use ManualLogEntry;
use MediaWiki\CommentStore\CommentStoreComment;
use MediaWiki\Deferred\DeferredUpdates;
use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroupStatesUpdaterJob;
use MediaWiki\Extension\Translate\MessageGroupProcessing\RevTagStore;
use MediaWiki\Extension\Translate\MessageLoading\MessageHandle;
use MediaWiki\Extension\Translate\PageTranslation\Hooks as PageTranslationHooks;
use MediaWiki\Extension\Translate\Services;
use MediaWiki\Extension\Translate\Statistics\MessageGroupStats;
use MediaWiki\Extension\Translate\TtmServer\TtmServer;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Storage\EditResult;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use MediaWiki\User\UserIdentity;
use ParserOptions;
use TextContent;
use WikiPage;

/**
 * Various editing enhancements to the edit page interface.
 * Partly succeeded by the new ajax-enhanced editor but kept for compatibility.
 * Also has code that is still relevant, like the hooks on save.
 *
 * @author Niklas Laxström
 * @author Siebrand Mazeland
 * @license GPL-2.0-or-later
 */
class TranslateEditAddons {
	/**
	 * Prevent translations to non-translatable languages for the group
	 * Hook: getUserPermissionsErrorsExpensive
	 *
	 * @param Title $title
	 * @param User $user
	 * @param string $action
	 * @param mixed &$result
	 */
	public static function disallowLangTranslations(
		Title $title,
		User $user,
		string $action,
		&$result
	): bool {
		if ( $action !== 'edit' ) {
			return true;
		}

		$handle = new MessageHandle( $title );
		if ( !$handle->isValid() ) {
			return true;
		}

		if ( $user->isAllowed( 'translate-manage' ) ) {
			return true;
		}

		$group = $handle->getGroup();
		$languages = $group->getTranslatableLanguages();
		$langCode = $handle->getCode();
		if ( $languages !== null && $langCode && !isset( $languages[$langCode] ) ) {
			$result = [ 'translate-language-disabled' ];
			return false;
		}

		$groupId = $group->getId();
		$checks = [
			$groupId,
			strtok( $groupId, '-' ),
			'*'
		];

		$disabledLanguages = Services::getInstance()->getConfigHelper()->getDisabledTargetLanguages();
		foreach ( $checks as $check ) {
			if ( isset( $disabledLanguages[$check][$langCode] ) ) {
				$reason = $disabledLanguages[$check][$langCode];
				$result = [ 'translate-page-disabled', $reason ];
				return false;
			}
		}

		return true;
	}

	/**
	 * Runs message checks, adds tp:transver tags and updates statistics.
	 * Hook: PageSaveComplete
	 */
	public static function onSaveComplete(
		WikiPage $wikiPage,
		UserIdentity $userIdentity,
		string $summary,
		int $flags,
		RevisionRecord $revisionRecord,
		EditResult $editResult
	): void {
		global $wgEnablePageTranslation;

		$content = $wikiPage->getContent();

		if ( !$content instanceof TextContent ) {
			// Screw it, not interested
			return;
		}

		$text = $content->getText();
		$title = $wikiPage->getTitle();
		$handle = new MessageHandle( $title );

		if ( !$handle->isValid() ) {
			return;
		}

		// Update it.
		$revId = $revisionRecord->getId();
		$mwServices = MediaWikiServices::getInstance();

		$fuzzy = $handle->needsFuzzy( $text );
		$parentId = $revisionRecord->getParentId();
		if ( $editResult->isNullEdit() || $parentId == 0 ) {
			// In this case the page_latest hasn't changed so we can rely on its fuzzy status
			$wasFuzzy = $handle->isFuzzy();
		} else {
			// In this case the page_latest will (probably) have changed. The above might work by chance
			// since it reads from a replica database which might not have gotten the update yet, but
			// don't trust it and read the fuzzy status of the parent ID from the database instead
			$revTagStore = Services::getInstance()->getRevTagStore();
			$wasFuzzy = $revTagStore->isRevIdFuzzy( $title->getArticleID(), $parentId );
		}
		if ( !$fuzzy && $wasFuzzy ) {
			$title = $mwServices->getTitleFactory()->castFromPageIdentity( $wikiPage );
			$user = $mwServices->getUserFactory()->newFromUserIdentity( $userIdentity );

			if ( !$mwServices->getPermissionManager()->userCan( 'unfuzzy', $user, $title ) ) {
				// No permission to unfuzzy this unit so leave it fuzzy
				$fuzzy = true;
			} elseif ( $editResult->isNullEdit() ) {
				$entry = new ManualLogEntry( 'translationreview', 'unfuzzy' );
				// Generate a log entry and null revision for the otherwise
				// invisible unfuzzying
				$dbw = $mwServices->getDBLoadBalancer()->getConnection( DB_PRIMARY );
				$nullRevision = $mwServices->getRevisionStore()->newNullRevision(
					$dbw,
					$wikiPage,
					CommentStoreComment::newUnsavedComment(
						$summary !== '' ? $summary : wfMessage( "translate-unfuzzy-comment" )
					),
					false,
					$userIdentity
				);
				if ( $nullRevision ) {
					$nullRevision = $mwServices->getRevisionStore()->insertRevisionOn( $nullRevision, $dbw );
					// Overwrite $revId so the revision ID of the null revision rather than the previous parent
					// revision is used for any further edits
					$revId = $nullRevision->getId();
					$wikiPage->updateRevisionOn( $dbw, $nullRevision, $nullRevision->getParentId() );
					$entry->setAssociatedRevId( $revId );
				}

				$entry->setPerformer( $userIdentity );
				$entry->setTarget( $title );
				$logId = $entry->insert();
				$entry->publish( $logId );
			}
		}
		self::updateFuzzyTag( $title, $revId, $fuzzy );

		$group = $handle->getGroup();
		// Update translation stats - source language should always be up to date
		if ( $handle->getCode() !== $group->getSourceLanguage() ) {
			// This will update in-process cache immediately, but the value is saved
			// to the database in a deferred update. See MessageGroupStats::queueUpdates.
			// In case an error happens before that, the stats may be stale, but that
			// would be fixed by the next update or purge.
			MessageGroupStats::clear( $handle );
		}

		// This job asks for stats, however the updated stats are written in a deferred update.
		// To make it less likely that the job would be executed before the updated stats are
		// written, create the job inside a deferred update too.
		DeferredUpdates::addCallableUpdate(
			static function () use ( $handle ) {
				MessageGroupStatesUpdaterJob::onChange( $handle );
			}
		);
		$user = $mwServices->getUserFactory()
			->newFromId( $userIdentity->getId() );

		if ( !$fuzzy ) {
			Services::getInstance()->getHookRunner()
				->onTranslate_newTranslation( $handle, $revId, $text, $user );
		}

		TtmServer::onChange( $handle );

		if ( $wgEnablePageTranslation && $handle->isPageTranslation() ) {
			// Updates for translatable pages only
			$minor = (bool)( $flags & EDIT_MINOR );
			PageTranslationHooks::onSectionSave( $wikiPage, $user, $content,
				$summary, $minor, $flags, $handle );
		}
	}

	/**
	 * @param Title $title
	 * @param int $revision
	 * @param bool $fuzzy Whether to fuzzy or not
	 */
	private static function updateFuzzyTag( Title $title, int $revision, bool $fuzzy ): void {
		$dbw = MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();

		$conds = [
			'rt_page' => $title->getArticleID(),
			'rt_type' => RevTagStore::FUZZY_TAG,
			'rt_revision' => $revision
		];

		// Replace the existing fuzzy tag, if any
		if ( $fuzzy ) {
			$index = array_keys( $conds );
			$dbw->newReplaceQueryBuilder()
				->replaceInto( 'revtag' )
				->uniqueIndexFields( $index )
				->row( $conds )
				->caller( __METHOD__ )
				->execute();
		} else {
			$dbw->newDeleteQueryBuilder()
				->deleteFrom( 'revtag' )
				->where( $conds )
				->caller( __METHOD__ )
				->execute();
		}
	}

	/**
	 * Adds tag which identifies the revision of source message at that time.
	 * This is used to show diff against current version of source message
	 * when updating a translation.
	 * Hook: Translate:newTranslation
	 */
	public static function updateTransverTag(
		MessageHandle $handle,
		int $revision,
		string $text,
		User $user
	): bool {
		if ( $user->isAllowed( 'bot' ) ) {
			return false;
		}

		$group = $handle->getGroup();

		$title = $handle->getTitle();
		$name = $handle->getKey() . '/' . $group->getSourceLanguage();
		$definitionTitle = Title::makeTitleSafe( $title->getNamespace(), $name );
		if ( !$definitionTitle || !$definitionTitle->exists() ) {
			return true;
		}

		$definitionRevision = $definitionTitle->getLatestRevID();
		$revTagStore = Services::getInstance()->getRevTagStore();
		$revTagStore->setTransver( $title, $revision, $definitionRevision );

		return true;
	}

	/** Hook: ArticlePrepareTextForEdit */
	public static function disablePreSaveTransform(
		WikiPage $wikiPage,
		ParserOptions $popts
	): void {
		global $wgTranslateUsePreSaveTransform;

		if ( !$wgTranslateUsePreSaveTransform ) {
			$handle = new MessageHandle( $wikiPage->getTitle() );
			if ( $handle->isMessageNamespace() && !$handle->isDoc() ) {
				$popts->setPreSaveTransform( false );
			}
		}
	}
}
