<?php

namespace XF\Repository;

use XF\Entity\AbstractPrompt;
use XF\Mvc\Entity\Repository;

use function count, is_array;

abstract class AbstractPromptMap extends Repository
{
	abstract protected function getMapEntityIdentifier();

	abstract protected function getAssociations(AbstractPrompt $prompt);

	abstract protected function updateAssociationCache(array $cache);

	protected function getStructureData()
	{
		$mapEntity = $this->getMapEntityIdentifier();
		$structure = $this->em->getEntityStructure($mapEntity);
		$className = $this->em->getEntityClassName($mapEntity);

		return [
			'mapEntity' => $mapEntity,
			'table' => $structure->table,
			'key' => $className::getContainerKey(),
		];
	}

	public function updatePromptAssociations(AbstractPrompt $item, array $contentIds)
	{
		$emptyKey = array_search(0, $contentIds);
		if ($emptyKey !== false)
		{
			unset($contentIds[$emptyKey]);
		}
		$contentIds = array_unique($contentIds);

		$structureData = $this->getStructureData();

		$existingAssociations = $this->getAssociations($item);
		if (!count($existingAssociations) && !$contentIds)
		{
			return;
		}

		$db = $this->db();
		$db->beginTransaction();

		$db->delete($structureData['table'], 'prompt_id = ?', $item->prompt_id);

		$map = [];

		foreach ($contentIds AS $id)
		{
			$map[] = [
				$structureData['key'] => $id,
				'prompt_id' => $item->prompt_id,
			];
		}
		if ($map)
		{
			$db->insertBulk($structureData['table'], $map);
		}

		$rebuildIds = $contentIds;

		foreach ($existingAssociations AS $association)
		{
			$rebuildIds[] = $association->getValue($structureData['key']);
		}

		$rebuildIds = array_unique($rebuildIds);
		$this->rebuildContentAssociationCache($rebuildIds);

		$db->commit();
	}

	public function removePromptAssociations(AbstractPrompt $item)
	{
		$structureData = $this->getStructureData();

		$rebuildIds = $this->db()->fetchAllColumn("
			SELECT $structureData[key]
			FROM $structureData[table]
			WHERE prompt_id = ?
		", $item->prompt_id);

		if (!$rebuildIds)
		{
			return;
		}

		$db = $this->db();
		$db->beginTransaction();

		$db->delete($structureData['table'], 'prompt_id = ?', $item->prompt_id);

		$this->rebuildContentAssociationCache($rebuildIds);

		$db->commit();
	}

	public function updateContentAssociations($contentId, array $itemIds)
	{
		$structureData = $this->getStructureData();

		$db = $this->db();
		$db->beginTransaction();

		$db->delete($structureData['table'], $structureData['key'] . ' = ?', $contentId);

		$map = [];

		foreach ($itemIds AS $itemId)
		{
			$map[] = [
				$structureData['key'] => $contentId,
				'prompt_id' => $itemId,
			];
		}

		if ($map)
		{
			$db->insertBulk($structureData['table'], $map);
		}

		$this->rebuildContentAssociationCache($contentId);

		$db->commit();
	}

	public function rebuildContentAssociationCache($contentIds)
	{
		if (!is_array($contentIds))
		{
			$contentIds = [$contentIds];
		}
		if (!$contentIds)
		{
			return;
		}

		$structureData = $this->getStructureData();

		$newCache = [];

		$associations = $this->finder($structureData['mapEntity'])
			->with('Prompt')
			->where($structureData['key'], $contentIds)
			->order('Prompt.materialized_order');
		foreach ($associations->fetch() AS $map)
		{
			$key = $map->get($structureData['key']);
			$newCache[$key][$map->prompt_id] = $map->prompt_id;
		}

		foreach ($contentIds AS $contentId)
		{
			if (!isset($newCache[$contentId]))
			{
				$newCache[$contentId] = [];
			}
		}

		$this->updateAssociationCache($newCache);
	}

	public function getItemIdsInContext($contentIds)
	{
		if (!is_array($contentIds))
		{
			$contentIds = [$contentIds];
		}
		if (!$contentIds)
		{
			return [];
		}

		$db = $this->db();
		$structureData = $this->getStructureData();

		return $db->fetchAllColumn("
			SELECT prompt_id
			FROM $structureData[table]
			WHERE $structureData[key] IN (" . $db->quote($contentIds) . ")
		");
	}
}
