<?php

namespace CirrusSearch;

use CirrusSearch\Fallbacks\FallbackRunner;
use CirrusSearch\Parser\BasicQueryClassifier;
use CirrusSearch\Parser\NamespacePrefixParser;
use CirrusSearch\Profile\SearchProfileService;
use CirrusSearch\Search\CrossProjectBlockScorerFactory;
use CirrusSearch\Search\FullTextResultsType;
use CirrusSearch\Search\MSearchRequests;
use CirrusSearch\Search\SearchContext;
use CirrusSearch\Search\SearchQuery;
use CirrusSearch\Search\SearchQueryBuilder;
use CirrusSearch\Search\TitleHelper;
use MediaWiki\MediaWikiServices;
use MediaWiki\Status\Status;
use MediaWiki\User\User;
use Wikimedia\Stats\StatsFactory;

/**
 * Performs searches using Elasticsearch -- on interwikis!
 *
 * @license GPL-2.0-or-later
 */
class InterwikiSearcher extends Searcher {

	/**
	 * @param Connection $connection
	 * @param SearchConfig $config
	 * @param int[]|null $namespaces Namespace numbers to search, or null for all of them
	 * @param User|null $user
	 * @param CirrusDebugOptions|null $debugOptions
	 * @param NamespacePrefixParser|null $namespacePrefixParser
	 * @param InterwikiResolver|null $interwikiResolver
	 * @param TitleHelper|null $titleHelper
	 * @param CirrusSearchHookRunner|null $cirrusSearchHookRunner
	 */
	public function __construct(
		Connection $connection,
		SearchConfig $config,
		?array $namespaces = null,
		?User $user = null,
		?CirrusDebugOptions $debugOptions = null,
		?NamespacePrefixParser $namespacePrefixParser = null,
		?InterwikiResolver $interwikiResolver = null,
		?TitleHelper $titleHelper = null,
		?CirrusSearchHookRunner $cirrusSearchHookRunner = null
	) {
		$maxResults = $config->get( 'CirrusSearchNumCrossProjectSearchResults' );
		parent::__construct( $connection, 0, $maxResults, $config, $namespaces, $user, false,
			$debugOptions, $namespacePrefixParser, $interwikiResolver, $titleHelper, $cirrusSearchHookRunner );
	}

	/**
	 * Fetch search results, from caches, if there's any
	 * @param SearchQuery $query original search query
	 * @return Status
	 */
	public function getInterwikiResults( SearchQuery $query ): Status {
		$sources = MediaWikiServices::getInstance()
			->getService( InterwikiResolver::SERVICE )
			->getSisterProjectConfigs();
		if ( !$sources ) {
			// Nothing to search for
			return Status::newGood( [] );
		}

		$iwQueries = [];
		foreach ( $sources as $interwiki => $config ) {
			$iwQueries[$interwiki] = SearchQueryBuilder::forCrossProjectSearch( $config, $query )
				->build();
		}

		$blockScorer = CrossProjectBlockScorerFactory::load( $this->config );
		$msearches = new MSearchRequests();
		foreach ( $iwQueries as $interwiki => $iwQuery ) {
			$context = SearchContext::fromSearchQuery( $iwQuery,
				FallbackRunner::create( $iwQuery, $this->interwikiResolver ), $this->cirrusSearchHookRunner );
			// The local wiki might have some special routing rules that do
			// different things, but we can't assume those exist on other
			// wikis. Always use defaults for interwiki search.
			$context->setProfileContext( SearchProfileService::CONTEXT_DEFAULT );
			$this->searchContext = $context;
			$this->setResultsType( new FullTextResultsType(
				$this->searchContext->getFetchPhaseBuilder(),
				$query->getParsedQuery()->isQueryOfClass( BasicQueryClassifier::COMPLEX_QUERY ),
				$this->titleHelper, [], $this->searchContext->getConfig()->getElement( 'CirrusSearchDeduplicateInMemory' ) === true ) );
			$this->config = $context->getConfig();
			$this->limit = $iwQuery->getLimit();
			$this->offset = $iwQuery->getOffset();
			$this->buildFullTextSearch( $query->getParsedQuery()->getQueryWithoutNsHeader() );
			$this->indexBaseName = $context->getConfig()->get( 'CirrusSearchIndexBaseName' );
			$search = $this->buildSearch();
			if ( $this->searchContext->areResultsPossible() ) {
				$msearches->addRequest( $interwiki, $search );
			}
		}

		$searchDescription = "{$this->searchContext->getSearchType()} search for '{$this->searchContext->getOriginalSearchTerm()}'";
		if ( $this->searchContext->getDebugOptions()->isCirrusDumpQuery() ) {
			return $msearches->dumpQuery( $searchDescription );
		}
		$mresponses = $this->searchMulti( $msearches );
		if ( $mresponses->hasFailure() ) {
			return $mresponses->getFailure();
		}

		if ( $this->searchContext->getDebugOptions()->isReturnRaw() ) {
			return $mresponses->dumpResults( $searchDescription );
		}

		return $mresponses->transformAndGetMulti( $this->searchContext->getResultsType(), array_keys( $iwQueries ),
			static function ( array $v ) use ( $blockScorer ) {
				return $blockScorer->reorder( $v );
			} );
	}

	protected function recordQueryCacheMetrics( StatsFactory $requestStats, string $cacheStatus, ?string $type = null ): void {
		parent::recordQueryCacheMetrics( $requestStats, $cacheStatus, "interwiki" );
	}
}
