<?php

namespace XF\Alert;

use XF\Entity\User;
use XF\Entity\UserAlert;
use XF\Mvc\Entity\AbstractCollection;
use XF\Mvc\Entity\Entity;

/**
 * @template T of Entity
 */
abstract class AbstractHandler
{
	/**
	 * @var string
	 */
	protected $contentType;

	/**
	 * @param string $contentType
	 */
	public function __construct($contentType)
	{
		$this->contentType = $contentType;
	}

	/**
	 * @param T $entity
	 * @param string|\Stringable|null $error
	 *
	 * @return bool
	 */
	public function canViewContent(Entity $entity, &$error = null)
	{
		if (method_exists($entity, 'canView'))
		{
			return $entity->canView($error);
		}

		throw new \LogicException("Could not determine content viewability; please override");
	}

	/**
	 * @param string|\Stringable|null $error
	 *
	 * @return bool
	 */
	public function canViewAlert(UserAlert $alert, &$error = null)
	{
		return true;
	}

	/**
	 * For alerts which are pushed, you can create a push specific template which should be text only.
	 * If no value is provided or the template does not exist, the push contents will come from a
	 * rendered version of the normal alert template with HTML tags stripped.
	 *
	 * @param string $action
	 *
	 * @return string|null
	 */
	public function getPushTemplateName($action)
	{
		return 'public:push_' . $this->contentType . '_' . $action;
	}

	/**
	 * @param string $action
	 *
	 * @return string
	 */
	public function getTemplateName($action)
	{
		return 'public:alert_' . $this->contentType . '_' . $action;
	}

	/**
	 * @param string $action
	 * @param T|null $content
	 *
	 * @return array{
	 *     alert: UserAlert,
	 *     user: User,
	 *     extra: array<mixed>,
	 *     content: T,
	 * }
	 */
	public function getTemplateData($action, UserAlert $alert, ?Entity $content = null)
	{
		if (!$content)
		{
			$content = $alert->Content;
		}

		return [
			'alert' => $alert,
			'user' => $alert->User,
			'extra' => $alert->extra_data,
			'content' => $content,
		];
	}

	/**
	 * @param T|null $content
	 *
	 * @return string
	 */
	public function render(UserAlert $alert, ?Entity $content = null)
	{
		if (!$content)
		{
			$content = $alert->Content;
			if (!$content)
			{
				return '';
			}
		}

		$action = $alert->action;
		$template = $this->getTemplateName($action);
		$data = $this->getTemplateData($action, $alert, $content);

		return \XF::app()->templater()->renderTemplate($template, $data);
	}

	/**
	 * @return bool
	 */
	public function isAlertRenderable(UserAlert $alert)
	{
		$template = $this->getTemplateName($alert->action);
		return \XF::app()->templater()->isKnownTemplate($template);
	}

	/**
	 * @return array{
	 *     text: string,
	 *     url: string,
	 * }
	 */
	public function getApiOutput(UserAlert $alert)
	{
		$templater = \XF::app()->templater();
		$content = $alert->Content;
		if (!$content)
		{
			return null;
		}

		$templateName = $this->getPushTemplateName($alert->action);
		if (!$templater->isKnownTemplate($templateName))
		{
			$templateName = $this->getTemplateName($alert->action);
		}
		$templateData = $this->getTemplateData($alert->action, $alert, $content);

		$pushContent = $templater->renderTemplate($templateName, $templateData);

		if (preg_match('#<push:url>(.*)</push:url>#siU', $pushContent, $match))
		{
			$alertUrl = trim(htmlspecialchars_decode($match[1], ENT_QUOTES));
			$alertUrl = \XF::canonicalizeUrl($alertUrl);
		}
		else
		{
			$alertUrl = null;
		}

		$pushContent = preg_replace('#<(push:[a-z0-9_]+)>.*</\\1>#siU', '', $pushContent);
		$pushContent = strip_tags($pushContent);
		$pushContent = html_entity_decode($pushContent, ENT_QUOTES);
		$pushContent = trim($pushContent);

		return [
			'text' => $pushContent,
			'url' => $alertUrl,
		];
	}

	/**
	 * @return list<string>
	 */
	public function getEntityWith()
	{
		return [];
	}

	/**
	 * @param int|list<int> $id
	 *
	 * @return T|AbstractCollection<T>
	 */
	public function getContent($id)
	{
		return \XF::app()->findByContentType($this->contentType, $id, $this->getEntityWith());
	}

	/**
	 * @return string
	 */
	public function getContentType()
	{
		return $this->contentType;
	}

	/**
	 * An array of alert actions which can be opted out of for this type.
	 *
	 * @return list<string>
	 */
	public function getOptOutActions()
	{
		return [];
	}

	/**
	 * The display order of this type's alert opt outs.
	 *
	 * @return int
	 */
	public function getOptOutDisplayOrder()
	{
		return 0;
	}

	/**
	 * @return array<string, string|\Stringable>
	 */
	public function getOptOutsMap()
	{
		$optOuts = $this->getOptOutActions();
		if (!$optOuts)
		{
			return [];
		}

		return array_combine($optOuts, array_map(function ($action)
		{
			return \XF::phrase('alert_opt_out.' . $this->contentType . '_' . $action);
		}, $optOuts));
	}
}
