<?php
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 6.2.3 - Licence Number L3138846
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2026 MH Sub I, LLC dba vBulletin. All Rights Reserved.  # ||
|| # This file may not be redistributed in whole or significant part.   # ||
|| # ----------------- VBULLETIN IS NOT FREE SOFTWARE ----------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html   # ||
|| ###################################################################### ||
\*========================================================================*/

class vB5_Template_BbCode_DelayedRenderer
{
	private array $cachedPlaceholders = [];

	private array $counters = [
		'urlFetchedCount' => 0,
	];

	private array $renderOptions = [];

	public function __construct(array $counters, array $renderOptions)
	{
		$this->getCache();

		$this->counters = $counters;
		$this->renderOptions = $renderOptions;
	}

	private vB_Cache $cache;
	private function getCache() : vB_Cache
	{
		if (empty($this->cache))
		{

			$api = Api_InterfaceAbstract::instance();
			$this->cache = $api->cacheInstance(vB_Cache::CACHE_STD);
		}

		return $this->cache;
	}

	// todo: should this accept array|string?
	public function doDelayedRender(
		int $nodeid,
		string $cacheKey,
		array $cacheEvents,
		vB5_Template_BbCode_DelayedRenderQueue $queueContainer,
		vB_BbCodeHelper $bbcodeHelper,
		Closure $callback
	) : void
	{
		$cache = $this->getCache();
		$found = $cache->read($cacheKey);

		$queue = $queueContainer->getDelayedRenderQueueForNodeid($nodeid);
		if ($found)
		{
			foreach ($found AS $__placeholder => ['do_render' => $__doRender, 'text' => $__pending_text, 'render_data' => $__data])
			{
				// If we've already saved this node in render cache, we won't have anything deferred from the bbcode parser.
				// Instead, we need to go through our own cache of bbcode render data and see which ones we need to re-render,
				// and which ones we can just replace.
				$queue[$__placeholder] ??= $__data;
				if (!$__doRender)
				{
					// Just replace.
					$this->registerPlaceholder($nodeid, $__placeholder, $__pending_text);
					unset($queue[$__placeholder]);
				}
			}
		}
		else
		{
			// We may fill this up below with new deferred render data.
			$found = [];
		}


		foreach ($queue AS $__placeholder => $__renderData)
		{
			// Ideally we should make this a struct for easier transport.
			[
				/** @var vB_Interface_BbCode */
				'class' => $__class,
				'data' => $__data,
				'option' => $__option,
				//'renderOptions' => $__renderOptions,
				'context' => $__context,
				'usercontextid' => $__usercontextid,
			] = $__renderData;

			$__bbcodeClass = vB_BbCode::determineClassname($__class);
			if (empty($__bbcodeClass))
			{
				// failed to find the relevant bbcode class, ignore this.
				continue;
			}
			$__bbcode = $__bbcodeClass::generateFreshFromDataAndOption($bbcodeHelper, $__data, $__option);

			// Due to get_preview() setting different options than the global render options, we need to track this one by one
			// instead of just using the globally set render...
			// Edit: We're no longer delaying render for previewtext, so we're not doing this -- this was problematic because
			// the perpage option got cached, meaning changes to the option weren't reflected until the cache got cleared or
			// the post was edited. If the delayed renderer gets used for more than just url-previews, something like this could
			// crop back up, in which case we really need to separate the bbcode-renderer instances that need different render
			// options altogether.
			//$__renderOptions = array_merge($this->renderOptions, $__renderOptions);
			$__renderOptions = $this->renderOptions;

			// Currently, limit of -1 means unlimited, and 0 means no fetching ever.
			if ($__renderOptions['url_load_perpage'] >= 0 AND $this->counters['urlFetchedCount'] >= $__renderOptions['url_load_perpage'])
			{
				// We want to still allow any blocks with unexpired cached data to still
				//render, just not fetch new ones.
				$__renderOptions['url_load_allowed'] = false;

				// If we're going to skip the url-preview, let's check to see if we have the non-url-preview render already cached
				// and skip some logic below.
				// This is meant for subsequent pageloads, not the initial page load.
				if (!empty($found[$__placeholder]['text']))
				{
					$this->registerPlaceholder($nodeid, $__placeholder, $found[$__placeholder]['text']);
					continue;
				}

			}


			$__thisUserContext = vB::getUserContext($__usercontextid);

			$__bbcode->setRenderOptionsAndContext($__renderOptions, $__context);
			$__bbcode->setUserContext($__thisUserContext);
			// Seems like renderBbCode() doesn't even need the data & option passed in again, since we
			// could hypothetically set them during construction...
			$__pending_text = $__bbcode->renderBbCode($__data, $__option);
			$this->registerPlaceholder($nodeid, $__placeholder, $__pending_text);

			// URL load limit
			$__check = $__bbcode->getRenderMetaData();
			$__check[$__bbcode::RENDER_META_URL_LOADED] ??= false;
			if ($__check[$__bbcode::RENDER_META_URL_LOADED])
			{
				$this->counters['urlFetchedCount']++;
				// recache the bbcode render so we don't re-do the render unncessarily.
				$found[$__placeholder] = ['do_render' => false, 'text' => $__pending_text, 'render_data' => []];
			}
			else
			{
				$found[$__placeholder] = ['do_render' => true, 'text' => $__pending_text, 'render_data' => $__renderData];
			}
		}

		// Don't bother writing to cache if we didn't render anything.
		if ($found)
		{
			// let's match the node-render lifetime from node text.
			$lifetime = 10080;
			$cache->write($cacheKey, $found, $lifetime, $cacheEvents);
		}

		$queueContainer->clearDelayedRenderQueueForNodeid($nodeid);

		$callback($this->counters);
	}

	private function registerPlaceholder(int $nodeid, string $placeholder, string $content) : void
	{
		$this->cachedPlaceholders[$nodeid][$placeholder] = $content;
	}

	public function replaceCachedPlaceholders(int $nodeid, string &$content)  : void
	{
		$searchToReplace = $this->cachedPlaceholders[$nodeid] ?? [];
		if ($searchToReplace)
		{
			$content = strtr($content, $this->cachedPlaceholders[$nodeid]);
		}
		unset($this->cachedPlaceholders[$nodeid]);
	}
}

/*=========================================================================*\
|| #######################################################################
|| # Downloaded: 13:27, Sun Aug 30th 2026
|| # CVS: $RCSfile$ - $Revision: 105283 $
|| #######################################################################
\*=========================================================================*/
