<?php

namespace CirrusSearch\Search;

use CirrusSearch\Searcher;
use CirrusSearch\Util;
use LogicException;
use MediaWiki\FileRepo\File\File;
use MediaWiki\MediaWikiServices;
use MediaWiki\Search\SearchResult;
use MediaWiki\Search\SearchResultTrait;
use MediaWiki\Title\Title;

/**
 * Base class for SearchResult
 */
abstract class CirrusSearchResult extends SearchResult {
	use SearchResultTrait;

	/**
	 * @var string Counter title for identified missing revisions
	 */
	private const MISSING_REVISION_TOTAL = 'missing_revision_total';

	/**
	 * @var Title
	 */
	private $title;

	/**
	 * @var ?File
	 */
	private $file;

	/**
	 * @var bool
	 */
	private $checkedForFile = false;

	public function __construct( Title $title ) {
		$this->title = $title;
	}

	/**
	 * Initialize from a Title and if possible initializes a corresponding
	 * File.
	 *
	 * @param Title $title
	 */
	final protected function initFromTitle( $title ) {
		// Everything is done in the constructor.
		// XXX: we do not call the SearchResultInitFromTitle hook
		// this hook is designed to fetch a particular revision
		// but the way cirrus works does not allow to vary the revision
		// text being displayed at query time.
	}

	/**
	 * Check if this is result points to an invalid title
	 *
	 * @return bool
	 */
	final public function isBrokenTitle() {
		// Title is mandatory in the constructor it would have failed earlier if the Title was broken
		return false;
	}

	/**
	 * Check if target page is missing, happens when index is out of date
	 *
	 * @return bool
	 */
	final public function isMissingRevision() {
		global $wgCirrusSearchDevelOptions;
		if ( isset( $wgCirrusSearchDevelOptions['ignore_missing_rev'] ) ) {
			return false;
		}
		if ( !$this->getTitle()->isKnown() ) {
			$this->increment( self::MISSING_REVISION_TOTAL, 'title' );
			return true;
		}
		// Similarly if we matched due to a redirect
		if ( $this->getRedirectTitle() && !$this->getRedirectTitle()->isKnown() ) {
			// There may be other reasons this result matched, for now keep it in the results
			// but clear the redirect.
			$redirectIsOnlyMatch = $this->clearRedirectTitle();
			$this->increment(
				self::MISSING_REVISION_TOTAL,
				$redirectIsOnlyMatch ? 'only_redirect' : 'redirect'
			);
		}

		return false;
	}

	private function increment( string $counter, string $problem ) {
		Util::getStatsFactory()
			->getCounter( $counter )
			->setLabel( 'problem', $problem )
			->increment();
	}

	/**
	 * @return Title
	 */
	final public function getTitle() {
		return $this->title;
	}

	/**
	 * Get the file for this page, if one exists
	 * @return File|null
	 */
	final public function getFile() {
		if ( !$this->checkedForFile && $this->getTitle()->getNamespace() === NS_FILE ) {
			$this->checkedForFile = true;
			$this->file = MediaWikiServices::getInstance()->getRepoGroup()
				->findFile( $this->title );
		}
		return $this->file;
	}

	/**
	 * Lazy initialization of article text from DB
	 * @return never
	 */
	final protected function initText() {
		throw new LogicException( "initText() should not be called on CirrusSearchResult, " .
			"content must be fetched directly from the backend at query time." );
	}

	/**
	 * @param string $text A snippet from the search highlighter
	 * @return bool True when the string contains highlight markers
	 */
	protected function containsHighlight( string $text ): bool {
		return strpos( $text, Searcher::HIGHLIGHT_PRE ) !== false;
	}

	/**
	 * @return string
	 */
	abstract public function getDocId();

	/**
	 * @return float
	 */
	abstract public function getScore();

	/**
	 * @return array|null
	 */
	abstract public function getExplanation();

	/**
	 * Clear any redirect match so it won't be part of the result.
	 *
	 * @return bool True if the redirect was the only snippet available
	 *  for this result.
	 */
	abstract protected function clearRedirectTitle(): bool;
}
