<?php

namespace XF\Repository;

use XF\Entity\TemplateModification;
use XF\Finder\TemplateModificationFinder;
use XF\Finder\TemplateModificationLogFinder;
use XF\Mvc\Entity\AbstractCollection;
use XF\Mvc\Entity\Finder;
use XF\Mvc\Entity\Repository;

class TemplateModificationRepository extends Repository
{
	/**
	 * @return Finder
	 */
	public function findTemplateModificationsForList($type)
	{
		return $this->finder(TemplateModificationFinder::class)
			->with(['AddOn'])
			->where('type', $type)
			->whereAddOnActive()
			->order(['addon_id', 'type', 'template', 'execution_order']);
	}

	public function getModificationTypes()
	{
		$types = [
			'public' => \XF::phrase('public'),
			'email' => \XF::phrase('email'),
		];

		if ($this->canCreateTemplateModification())
		{
			$types['admin'] = \XF::phrase('admin');
		}

		return $types;
	}

	public function canCreateTemplateModification()
	{
		return \XF::$developmentMode;
	}

	public function applyModificationsToTemplate($type, $title, $template, &$status = [])
	{
		$modifications = $this->finder(TemplateModificationFinder::class)
			->where([
				'type' => $type,
				'template' => $title,
				'enabled' => 1,
			])
			->whereAddOnActive()
			->order(['execution_order', 'modification_key'])
			->fetch();
		return $this->applyTemplateModifications($template, $modifications->toArray(), $status);
	}

	public function applyTemplateModifications($template, array $modifications, &$status = [])
	{
		$status = [];

		foreach ($modifications AS $id => $modification)
		{
			$template = str_replace("\r\n", "\n", $template);
			$modification = $modification->toArray();

			switch ($modification['action'])
			{
				case 'str_replace':
					$modification['find'] = str_replace("\r\n", "\n", $modification['find']);
					$modification['replace'] = str_replace('$0', $modification['find'], $modification['replace']);

					$status[$id] = substr_count($template, $modification['find']);
					$template = str_replace($modification['find'], $modification['replace'], $template);
					break;

				case 'preg_replace':
				case 'callback':
					$modification['find'] = str_replace(
						["\r\n", '\r\n'],
						["\n", '\n'],
						trim($modification['find'])
					);

					try
					{
						if (preg_match('/\W[\s\w]*e[\s\w]*$/', $modification['find']))
						{
							// can't run a /e regex
							$status[$id] = 'error_invalid_regex';
						}
						else
						{
							$result = preg_match_all($modification['find'], $template, $null);
							if ($result === false && preg_last_error() !== PREG_NO_ERROR)
							{
								$status[$id] = 'error_invalid_regex';
								break;
							}

							$status[$id] = $result;
						}
					}
					catch (\ErrorException $e)
					{
						$status[$id] = 'error_invalid_regex';
						break;
					}

					if ($modification['action'] == 'callback')
					{
						if ($this->app()->config('enableTemplateModificationCallbacks'))
						{
							if (preg_match('/^([a-z0-9_\\\\]+)::([a-z0-9_]+)$/i', $modification['replace'], $match))
							{
								if (!class_exists($match[1]) || !is_callable([$match[1], $match[2]]))
								{
									$status[$id] = 'error_invalid_callback';
								}
								else
								{
									try
									{
										$template = preg_replace_callback(
											$modification['find'],
											[$match[1], $match[2]],
											$template
										);
									}
									catch (\Exception $e)
									{
										$status[$id] = 'error_callback_failed';
										\XF::logException($e, false, 'Template modification callback error: ');
									}
								}
							}
							else
							{
								$status[$id] = 'error_invalid_callback';
							}
						}
					}
					else
					{
						$template = preg_replace($modification['find'], $modification['replace'], $template);
					}
					break;

				default:
					$status[$id] = 'error_unknown_action';
			}
		}

		return $template;
	}

	/**
	 * @param AbstractCollection<TemplateModification> $modifications
	 *
	 * @return mixed
	 */
	public function addLogsToModifications($modifications)
	{
		$ids = [];
		foreach ($modifications AS $modification)
		{
			$ids[] = $modification->modification_id;
		}

		if ($ids)
		{
			$logsGrouped = $this->finder(TemplateModificationLogFinder::class)
				->where('modification_id', $ids)
				->fetch()
				->groupBy('modification_id');

			foreach ($modifications AS $modification)
			{
				$id = $modification->modification_id;

				$logs = isset($logsGrouped[$id])
					? $this->em->getBasicCollection($logsGrouped[$id])
					: $this->em->getEmptyCollection();

				$modification->hydrateRelation('Logs', $logs);
			}
		}

		return $modifications;
	}
}
