<?php

namespace XF\AdminSearch;

use XF\App;
use XF\Finder\PhraseMapFinder;
use XF\Mvc\Entity\AbstractCollection;

use function count;

class Searcher
{
	/**
	 * @var App
	 */
	protected $app;

	/**
	 * @var AbstractHandler[]
	 */
	protected $handlers = [];

	protected $typeLimit = 10;

	public function __construct(App $app, array $handlerClasses = [], $allowExclusions = true)
	{
		$this->app = $app;

		if ($allowExclusions)
		{
			foreach ($app->options()->acpSearchExclude AS $type => $value)
			{
				if (isset($handlerClasses[$type]))
				{
					unset($handlerClasses[$type]);
				}
			}
		}

		$this->addHandlerClasses($handlerClasses);
	}

	/**
	 * @param string $text
	 *
	 * @return TypeResultSet[]
	 */
	public function search($text)
	{
		if (!$text)
		{
			return [];
		}

		$matchedPhrases = $this->searchPhrases($text);

		$displayOrder = [];

		$resultTypes = [];
		foreach ($this->handlers AS $contentType => $handler)
		{
			if (!$handler->isSearchable())
			{
				continue;
			}

			$typePhraseIds = $matchedPhrases[$contentType] ?? [];
			$maxResults = $handler->getMaxTypeResults($this->typeLimit);

			$results = $handler->search($text, $maxResults, $typePhraseIds);
			if ($results && count($results))
			{
				if ($results instanceof AbstractCollection)
				{
					$results = $results->toArray();
				}

				$displayOrder[$contentType] = $handler->getDisplayOrder();
				$resultTypes[$contentType] = new TypeResultSet($contentType, $handler, $results);
			}
		}

		asort($displayOrder);

		$output = [];
		foreach (array_keys($displayOrder) AS $contentType)
		{
			$output[$contentType] = $resultTypes[$contentType];
		}

		return $output;
	}

	protected function searchPhrases($text)
	{
		$phraseGroupContentMap = [];

		foreach ($this->handlers AS $contentType => $handler)
		{
			if (!$handler->isSearchable())
			{
				continue;
			}

			foreach ($handler->getRelatedPhraseGroups() AS $phraseGroup)
			{
				$phraseGroupContentMap[$phraseGroup] = $contentType;
			}
		}

		$matchedPhraseIds = [];
		if ($phraseGroupContentMap)
		{
			$phraseFinder = $this->app->finder(PhraseMapFinder::class);

			$groupPossibilities = [];
			foreach (array_keys($phraseGroupContentMap) AS $phraseGroup)
			{
				$groupPossibilities[] = $phraseFinder->escapeLike($phraseGroup, '?.%');
			}

			$phraseFinder
				->where('language_id', \XF::language()->getId())
				->where('title', 'like', $groupPossibilities)
				->where('Phrase.phrase_text', 'like', $phraseFinder->escapeLike($text, '%?%'));

			foreach ($phraseFinder->fetchRaw(['fetchOnly' => ['title']]) AS $match)
			{
				$title = $match['title'];
				[$group, $id] = explode('.', $title, 2);

				if (isset($phraseGroupContentMap[$group]))
				{
					$contentType = $phraseGroupContentMap[$group];
					$matchedPhraseIds[$contentType][] = $id;
				}
			}
		}

		return $matchedPhraseIds;
	}

	public function addHandler($contentType, AbstractHandler $handler)
	{
		$this->handlers[$contentType] = $handler;
	}

	public function addHandlerClass($contentType, $handlerClass)
	{
		if (!class_exists($handlerClass))
		{
			return false;
		}

		$class = \XF::extendClass($handlerClass);
		$this->handlers[$contentType] = new $class($contentType, $this->app);
		return true;
	}

	public function addHandlerClasses(array $classes)
	{
		$output = [];
		foreach ($classes AS $contentType => $class)
		{
			$output[$contentType] = $this->addHandlerClass($contentType, $class);
		}

		return $output;
	}
}
