<?php

namespace XF\Mvc\Renderer;

use XF\Mvc\Reply\AbstractReply;
use XF\Util\File;

use function array_key_exists, count, is_array, is_object, strval;

class Json extends AbstractRenderer
{
	protected $encodeModifiers = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT;

	protected function initialize()
	{
		$this->response->contentType('application/json');
	}

	public function getResponseType()
	{
		return 'json';
	}

	public function renderRedirect($url, $type, $message = '')
	{
		return [
			'status' => 'ok',
			'message' => $message,
			'redirect' => $url,
		];
	}

	public function renderMessage($message)
	{
		return [
			'status' => 'ok',
			'message' => $message,
		];
	}

	public function renderErrors(array $errors)
	{
		$app = \XF::app();
		$params = [
			'errors' => $errors,
			'error' => count($errors) == 1 ? reset($errors) : false,
			'forJson' => true,
		];
		$html = $this->getTemplate($app['app.defaultType'] . ':error', $params)->render();

		return [
			'status' => 'error',
			'errors' => $errors,
			'errorHtml' => $this->getHtmlOutputStructure($html),
		];
	}

	public function renderView($viewName, $templateName, array $params = [])
	{
		if (isset($params['innerContent']))
		{
			return $params['innerContent'];
		}

		$output = $this->renderViewObject($viewName, $templateName, $params);
		if ($output === null)
		{
			$output = [
				'status' => 'ok',
				'html' => $this->renderHtmlFallback($viewName, $templateName, $params),
			];
		}
		else if (is_array($output) && array_key_exists('html', $output) && $output['html'] === null)
		{
			$output['html'] = $this->renderHtmlFallback($viewName, $templateName, $params);
		}

		return $output;
	}

	public function renderHtmlFallback($viewName, $templateName, array $params = [])
	{
		$htmlOutput = $this->renderViewObject($viewName, $templateName, $params, 'html');
		if ($htmlOutput === null && $templateName)
		{
			$htmlOutput = $this->getTemplate($templateName, $params)->render();
		}

		return $this->getHtmlOutputStructure($htmlOutput);
	}

	public function getHtmlOutputStructure($html)
	{
		$output = [
			'content' => strval($html),
		];

		$templater = $this->templater;
		$pageParams = $templater->pageParams;
		$templateErrors = $templater->getTemplateErrors();

		if ($output['content'] === '' && $templateErrors)
		{
			$output['content'] = \XF::phrase('no_content_returned_try_again_later');
		}

		if (\XF::$debugMode && $templateErrors)
		{
			$output['templateErrors'] = true;

			$templateErrorDetails = [];
			foreach ($templateErrors AS $error)
			{
				$templateErrorDetails[] = sprintf(
					'Template %s: %s (%s:%d)',
					$error['template'],
					$error['error'],
					File::stripRootPathPrefix($error['file']),
					$error['line']
				);
			}
			$output['templateErrorDetails'] = $templateErrorDetails;
		}

		if (isset($pageParams['pageTitle']))
		{
			$output['title'] = $pageParams['pageTitle'];
		}
		if (isset($pageParams['pageH1']))
		{
			$output['h1'] = $pageParams['pageH1'];
		}

		$includedCss = $templater->getIncludedCss();
		if ($includedCss)
		{
			$output['css'] = $includedCss;
		}

		$inlineCss = $templater->getInlineCss();
		if ($inlineCss)
		{
			$output['cssInline'] = $inlineCss;
		}

		$includedJs = array_keys($templater->getIncludedJs());
		if ($includedJs)
		{
			$output['js'] = $includedJs;
		}

		$inlineJs = array_column($templater->getInlineJs(), 'js');
		if ($inlineJs)
		{
			$output['jsInline'] = $inlineJs;
		}

		if (isset($templater->pageParams['jsState']) && is_array($templater->pageParams['jsState']))
		{
			$output['jsState'] = $templater->pageParams['jsState'];
		}

		return $output;
	}

	public function postFilter($content, AbstractReply $reply)
	{
		if (!is_array($content))
		{
			$e = new \LogicException('JSON response output must be an array.');
			if (\XF::$debugMode)
			{
				throw $e;
			}
			else
			{
				\XF::logException($e);
			}
			$content = [];
		}

		$replyJson = $reply->getJsonParams();
		if ($replyJson)
		{
			foreach ($replyJson AS $k => $v)
			{
				if (!array_key_exists($k, $content))
				{
					$content[$k] = $v;
				}
			}
		}

		if (!$reply->getViewOption('skipDefaultJsonParams'))
		{
			$content = $this->addDefaultJsonParams($content);
		}

		return json_encode($this->prepareJsonEncode($content), $this->encodeModifiers);
	}

	protected function addDefaultJsonParams(array $content)
	{
		$app = \XF::app();
		$visitor = \XF::visitor();
		$language = \XF::language();
		$container = $app->container();

		if ($visitor->user_id)
		{
			$conversations = $visitor->conversations_unread;
			$alerts = $visitor->alerts_unviewed;

			$content['visitor'] = [
				'conversations_unread' => $language->numberFormat($conversations),
				'alerts_unviewed' => $language->numberFormat($alerts),
				'total_unread' => $language->numberFormat($conversations + $alerts),
			];
		}

		if ($container->isCached('job.manager'))
		{
			$jobManager = $app->jobManager();
			$manualJobs = $jobManager->hasManualEnqueued();
			$autoJobs = $jobManager->hasAutoEnqueued();
			$autoBlocking = $jobManager->hasAutoBlocking();

			if ($manualJobs || $autoJobs)
			{
				if ($autoBlocking)
				{
					$autoBlockingMessage = $jobManager->getAutoBlockingMessage();
					if ($autoBlockingMessage)
					{
						$autoBlockingMessage = strval($autoBlockingMessage);
					}
				}
				else
				{
					$autoBlockingMessage = null;
				}

				if ($autoJobs && \XF::options()->jobRunTrigger == 'activity')
				{
					$runAutoJobs = true;
				}
				else
				{
					$runAutoJobs = false;
				}

				$content['job'] = [
					'manual' => $manualJobs ? array_keys($jobManager->getManualEnqueued()) : null,
					'autoBlocking' => $autoBlocking ? array_keys($jobManager->getAutoBlocking()) : null,
					'autoBlockingMessage' => $autoBlockingMessage,
					'auto' => $runAutoJobs,
				];
			}
		}

		if (\XF::$debugMode)
		{
			if ($container->isCached('db'))
			{
				$queryCount = \XF::db()->getQueryCount();
			}
			else
			{
				$queryCount = null;
			}

			$content['debug'] = [
				'time' => round(microtime(true) - \XF::app()->container('time.granular'), 4),
				'queries' => $queryCount,
				'memory' => round(memory_get_peak_usage() / 1024 / 1024, 2),
			];
		}

		return $content;
	}

	protected function prepareJsonEncode($value)
	{
		if (is_array($value))
		{
			foreach ($value AS &$innerValue)
			{
				$innerValue = $this->prepareJsonEncode($innerValue);
			}
		}
		else if (is_object($value) && method_exists($value, 'jsonSerialize'))
		{
			$value = $value->jsonSerialize();
		}
		else if (is_object($value) && method_exists($value, '__toString'))
		{
			$value = $value->__toString();
		}

		return $value;
	}
}
