<?php

namespace XF\Api\Docs\Renderer;

use XF\Api\Docs\Annotation\AbstractBlock;
use XF\Api\Docs\Annotation\AbstractValueLine;
use XF\Api\Docs\Annotation\RouteBlock;
use XF\Api\Docs\Annotation\TypeBlock;

use function strval;

class Xf2Html implements RendererInterface
{
	use FileRendererTrait;

	protected $types = [];

	public function renderInternal(array $routeGroupings, array $types)
	{
		$this->types = $types;

		$html = [];

		$html[] = '<xf:sidenav>';
		$html[] = $this->renderTableOfContents($routeGroupings, $types);
		$html[] = '</xf:sidenav>';
		$html[] = '';

		foreach ($routeGroupings AS $group => $routes)
		{
			$html[] = $this->renderRouteGroup($group, $routes);
		}

		foreach ($types AS $type)
		{
			$html[] = $this->renderType($type);
		}

		$this->types = [];

		return implode("\n", $html);
	}

	public function renderTableOfContents(array $routeGroupings, array $types)
	{
		$routesHtml = [];
		foreach ($routeGroupings AS $group => $routes)
		{
			$routesHtml[] = '<h3 class="block-minorHeader">'
				. '<span class="collapseTrigger collapseTrigger--block" data-xf-click="toggle" data-target="< :up:next">'
					. '<span class="block-formSectionHeader-aligner">' . htmlspecialchars($group) . '</span>'
				. '</span>'
				. '</h3><div class="block-body block-body--collapsible">';

			foreach ($routes AS $route)
			{
				/** @var RouteBlock $route */
				$routeId = $this->getRouteId($route);
				$routesHtml[] = '<a href="#' . htmlspecialchars($routeId) . '" class="blockLink u-smaller">'
					. $this->getRouteNameHtml($route)
					. '</a>';
			}

			$routesHtml[] = '</div>';
		}

		$routesHtml = implode("\n", $routesHtml);

		$typesHtml = [];
		foreach ($types AS $type)
		{
			$typeId = $this->getTypeId($type);
			$typesHtml[] = '<a href="#' . htmlspecialchars($typeId) . '" class="blockLink u-smaller">'
				. htmlspecialchars($type->type)
				. '</a>';
		}

		$typesHtml = implode("\n", $typesHtml);

		return <<< FULLHTML
<div class="block">
	<div class="block-container">
		<h2 class="block-header">Routes</h2>
		{$routesHtml}
	</div>
</div>

<div class="block">
	<div class="block-container">
		<h2 class="block-header">Data types</h2>
		<div class="block-body">
			{$typesHtml}
		</div>
	</div>
</div>
FULLHTML;
	}

	public function renderRouteGroup($group, array $routes)
	{
		$html = [];
		foreach ($routes AS $route)
		{
			$html[] = $this->renderRoute($route);
			$html[] = '';
		}

		$html[] = '';

		return implode("\n", $html);
	}

	public function renderRoute(RouteBlock $route)
	{
		if ($route->inputs)
		{
			$inputRowsHtml = [];

			foreach ($route->inputs AS $input)
			{
				$inputRowsHtml[] = $this->renderValueRow($input);
			}

			$inputRowsHtml = implode("\n", $inputRowsHtml);
		}
		else
		{
			$inputRowsHtml = $this->renderEmptyValuesPlaceholder($route);
		}
		if ($route->outputs)
		{
			$outputRowsHtml = [];

			foreach ($route->outputs AS $input)
			{
				$outputRowsHtml[] = $this->renderValueRow($input);
			}

			$outputRowsHtml = implode("\n", $outputRowsHtml);
		}
		else
		{
			$outputRowsHtml = $this->renderEmptyValuesPlaceholder($route);
		}

		if ($route->errors)
		{
			$errorRowsHtml = [];
			foreach ($route->errors AS $errorKey => $description)
			{
				$errorRowsHtml[] = "\t<xf:datarow>"
					. '<xf:cell>' . htmlspecialchars($errorKey) . '</xf:cell>'
					. '<xf:cell>' . htmlspecialchars($description) . '</xf:cell>'
					. '</xf:datarow>';
			}

			$errorRowsHtml = implode("\n", $errorRowsHtml);

			$errorHtml = <<< FULLHTML
	<div class="block">
		<div class="block-container">
			<h4 class="block-header">Errors</h4>
			<div class="block-body">
				<xf:datalist>
					<xf:datarow rowtype="header">
						<xf:cell>Error key</xf:cell>
						<xf:cell>Description</xf:cell>
					</xf:datarow>
					{$errorRowsHtml}
				</xf:datalist>
			</div>
		</div>
	</div>
FULLHTML;
		}
		else
		{
			$errorHtml = '';
		}

		$routeIdHtml = htmlspecialchars($this->getRouteId($route));
		$routeHeaderHtml = $this->getRouteNameHtml($route);

		$descHtml = htmlspecialchars(strval($route->description))
			. ($route->incomplete ? ' [Incomplete]' : '');
		$routeDescHtml = $descHtml ? "<div class=\"blocks-desc\">{$descHtml}</div>" : '';

		return <<< FULLHTML
<div class="blocks blocks--close blocks--separated">
	<h3 class="blocks-header blocks-header--strong">
		<span class="u-anchorTarget" id="{$routeIdHtml}"></span>
		{$routeHeaderHtml}
		{$routeDescHtml}
	</h3>
	<div class="block">
		<div class="block-container">
			<h4 class="block-header">Parameters</h4>
			<div class="block-body">
				<xf:datalist>
					<xf:datarow rowtype="header">
						<xf:cell>Input</xf:cell>
						<xf:cell>Type</xf:cell>
						<xf:cell>Description</xf:cell>
					</xf:datarow>
					{$inputRowsHtml}
				</xf:datalist>
			</div>
		</div>
	</div>
	<div class="block">
		<div class="block-container">
			<h4 class="block-header">Response</h4>
			<div class="block-body">
				<xf:datalist>
					<xf:datarow rowtype="header">
						<xf:cell>Output</xf:cell>
						<xf:cell>Type</xf:cell>
						<xf:cell>Description</xf:cell>
					</xf:datarow>
					{$outputRowsHtml}
				</xf:datalist>
			</div>
		</div>
	</div>

	$errorHtml
</div>
FULLHTML;

		return implode("\n", $html);
	}

	protected function getRouteId(RouteBlock $route)
	{
		$routeId = preg_replace('#[{}]#', '', $route->route);
		$routeId = preg_replace('#[/\-]#', '_', $routeId);
		$routeId = preg_replace('#[^a-z0-9_]#i', '', $routeId);

		return strtolower('route_' . $route->method . '_' . $routeId);
	}

	protected function getRouteNameHtml(RouteBlock $route)
	{
		switch ($route->method)
		{
			case 'GET':
				$method = '<span class="label label--lightGreen">GET</span>';
				break;

			case 'POST':
				$method = '<span class="label label--skyBlue">POST</span>';
				break;

			case 'DELETE':
				$method = '<span class="label label--orange">DELETE</span>';
				break;

			default:
				$method = htmlspecialchars($route->method);
		}

		$name = htmlspecialchars($route->route);

		return $method . ' ' . $name;
	}

	public function renderType(TypeBlock $type)
	{
		if ($type->structure)
		{
			$dataRowHtml = [];

			foreach ($type->structure AS $element)
			{
				$dataRowHtml[] = $this->renderValueRow($element);
			}

			$dataRowHtml = implode("\n", $dataRowHtml);
		}
		else
		{
			$dataRowHtml = $this->renderEmptyValuesPlaceholder($type);
		}

		$typeIdHtml = htmlspecialchars($this->getTypeId($type));
		$typeHeaderHtml = htmlspecialchars($type->type);

		$descHtml = htmlspecialchars($type->description ?? '')
			. ($type->incomplete ? ' [Incomplete]' : '');
		$typeDescHtml = $descHtml ? "<div class=\"block-desc\">{$descHtml}</div>" : '';

		return <<< FULLHTML
<div class="block">
	<div class="block-container">
		<h3 class="block-header">
			<span class="u-anchorTarget" id="{$typeIdHtml}"></span>
			Data type: {$typeHeaderHtml}
			{$typeDescHtml}
		</h3>
		<div class="block-body">
			<xf:datalist>
				<xf:datarow rowtype="header">
					<xf:cell>Column</xf:cell>
					<xf:cell>Type</xf:cell>
					<xf:cell>Description</xf:cell>
				</xf:datarow>
				{$dataRowHtml}
			</xf:datalist>
		</div>
	</div>
</div>
FULLHTML;
	}

	protected function getTypeId(TypeBlock $type)
	{
		return 'type_' . $type->type;
	}

	protected function renderValueRow(AbstractValueLine $value)
	{
		$typeHtml = [];
		foreach ($value->types AS $type)
		{
			if (preg_match('#^([a-z0-9_]+)([\[\{].*)$#i', $type, $match))
			{
				$typeSimple = $match[1];
				$typeExtended = $match[2];
			}
			else
			{
				$typeSimple = $type;
				$typeExtended = '';
			}

			if (isset($this->types[$typeSimple]))
			{
				$typeId = $this->getTypeId($this->types[$typeSimple]);
				$typeHtml[] = '<a href="#' . htmlspecialchars($typeId) . '">'
					. htmlspecialchars($typeSimple)
					. '</a>'
					. htmlspecialchars($typeExtended);
			}
			else
			{
				$typeHtml[] = htmlspecialchars($type);
			}
		}

		$modifiersHtml = [];

		foreach ($value->modifiers AS $modifier)
		{
			if ($modifier === 'req')
			{
				$modifiersHtml[] = '<xf:fa icon="far fa-info-circle u-featuredText" title="Required" data-xf-init="tooltip" />';
			}
			else if ($modifier === 'cond')
			{
				$modifiersHtml[] = '<xf:fa icon="far fa-question-circle u-featuredText" title="Conditionally returned" data-xf-init="tooltip" />';
			}
			else if ($modifier === 'verbose')
			{
				$modifiersHtml[] = '<xf:fa icon="far fa-file-alt u-featuredText" title="Verbose results only" data-xf-init="tooltip" />';
			}
			else if ($modifier === 'perm')
			{
				$modifiersHtml[] = '<xf:fa icon="far fa-lock u-featuredText" title="Returned only if permissions are met" data-xf-init="tooltip" />';
			}
			else
			{
				$modifiersHtml[] = '[' . $modifier . ']';
			}
		}

		return "\t<xf:datarow>"
			. '<xf:cell>' . htmlspecialchars($value->name) . '</xf:cell>'
			. '<xf:cell>' . implode('|', $typeHtml) . '</xf:cell>'
			. '<xf:cell>'
				. ($modifiersHtml ? implode(' ', $modifiersHtml) . ' ' : '')
				. htmlspecialchars($value->description)
				. '</xf:cell>'
			. '</xf:datarow>';
	}

	protected function renderEmptyValuesPlaceholder(AbstractBlock $block)
	{
		return "\t" . '<xf:datarow><xf:cell colspan="3">'
			. ($block->incomplete ? 'Unknown, documentation incomplete' : 'None.')
			. '</xf:cell></xf:datarow>';
	}
}
