<?php

namespace XF\DesignerOutput;

use XF\Finder\TemplateFinder;
use XF\Mvc\Entity\Entity;
use XF\Template\Templater;
use XF\Template\WatcherInterface;

use function count;

class Template extends AbstractHandler implements WatcherInterface
{
	protected $templatesWatched = [];
	protected $templatesActioned = [];

	protected function getTypeDir()
	{
		return 'templates';
	}

	public function export(Entity $template)
	{
		$metadata = [
			'addon_id' => $template->addon_id,
			'version_id' => $template->version_id,
			'version_string' => $template->version_string,
		];

		$fileName = $this->getFileName($template);
		return $this->designerOutput->writeFile($this->getTypeDir(), $template->Style, $fileName, $template->template, $metadata, true);
	}

	protected function getEntityForImport($parts, $styleId, $json, array $options)
	{
		[$type, $title] = $parts;

		$template = \XF::em()->getFinder(TemplateFinder::class)
			->where([
				'type' => $type,
				'title' => $title,
				'style_id' => $styleId,
			])->fetchOne();
		if (!$template)
		{
			$template = \XF::em()->create(\XF\Entity\Template::class);

			$template->style_id = $styleId;
			$template->type = $type;
			$template->title = $title;

			$parent = $template->ParentTemplate;
			if ($parent)
			{
				$template->addon_id = $parent->addon_id;
			}
		}

		$this->prepareEntityForImport($template, $options);

		return $template;
	}

	public function import($name, $styleId, $contents, array $metadata, array $options = [])
	{
		$parts = preg_split('#[:/\\\\]#', $name, 2);
		if (count($parts) == 1)
		{
			throw new \InvalidArgumentException("Template $name does not contain a type component");
		}

		[$type, $title] = $parts;

		$template = $this->getEntityForImport($parts, $styleId, null, $options);
		$template->setOption('check_duplicate', false);
		$template->setOption('report_modification_errors', false);

		$template->set('title', $title, ['forceSet' => true]);
		$template->set('type', $type, ['forceSet' => true]);
		$template->set('template', $contents, ['forceSet' => true]);

		// ignoring the meta version can be used when you want to just
		// update the version naturally if needed; requires writing back to the
		// designer output so import mode must be disabled
		if (empty($options['ignore_meta_version']))
		{
			if (isset($metadata['addon_id']))
			{
				$template->addon_id = $metadata['addon_id'];
			}
			if (isset($metadata['version_id']))
			{
				$template->version_id = $metadata['version_id'];
			}
			if (isset($metadata['version_string']))
			{
				$template->version_string = $metadata['version_string'];
			}
		}

		$template->save();
		// this will update the metadata itself

		return $template;
	}

	public function getFileName(Entity $template, $new = true)
	{
		$title = $new ? $template->getValue('title') : $template->getExistingValue('title');
		$type = $new ? $template->getValue('type') : $template->getExistingValue('type');

		return $this->convertTemplateNameToFile($type, $title);
	}

	public function convertTemplateFileToName($name)
	{
		if (substr($name, -5) == '.html')
		{
			$name = substr($name, 0, -5);
		}

		$name = str_replace('/', ':', $name);

		return $name;
	}

	public function convertTemplateNameToFile($type, $name)
	{
		if (!strpos($name, '.'))
		{
			$name = "$name.html";
		}

		return $type . '/' . $name;
	}

	protected $metadataWatchCache = [];

	public function watchTemplate(Templater $templater, $type, $name)
	{
		$styleId = $templater->getStyleId();
		if (!$styleId)
		{
			return false;
		}

		$fileName = $this->convertTemplateNameToFile($type, $name);

		if (isset($this->templatesWatched[$styleId][$fileName]))
		{
			return false;
		}

		$typeDir = $this->getTypeDir();

		if (!isset($this->metadataWatchCache[$styleId]))
		{
			$styleCache = \XF::app()->container('style.cache');
			$styleMap = [];
			foreach ($styleCache[$styleId]['parent_list'] AS $styleId)
			{
				if (!$styleId || !$styleCache[$styleId]['designer_mode'])
				{
					continue;
				}
				$styleMap[$styleId] = $styleCache[$styleId]['designer_mode'];
			}

			$metadata = [];
			foreach ($styleMap AS $styleId => $designerMode)
			{
				$metadata[$designerMode] = [
					'style_id' => $styleId,
					'metadata' => $this->designerOutput->getMetadata($typeDir, $designerMode),
				];
			}

			$this->metadataWatchCache[$styleId] = $metadata;
		}

		$actionTaken = false;

		foreach ($this->metadataWatchCache[$styleId] AS $designerMode => $designerMeta)
		{
			$fullPath = $this->designerOutput->getFilePath($typeDir, $designerMode, $fileName);
			if (file_exists($fullPath))
			{
				$contents = file_get_contents($fullPath);
				$hash = $this->designerOutput->hashContents($contents);

				$templateMeta = $designerMeta['metadata'][$fileName] ?? [];
				$metaHash = $templateMeta ? $templateMeta['hash'] : null;

				if (!$metaHash || $hash !== $metaHash)
				{
					// metadata doesn't exist or has a different hash - template has been added
					// or edited so the version needs to be updated
					$recompile = true;
					$ignoreMetaVersion = true;
				}
				else
				{
					// metadata matches -- we can accept the metadata hash but might still need to recompile
					$recompile = false;
					$ignoreMetaVersion = false;

					$compiledFile = $templater->getTemplateFilePath($type, $name, $designerMeta['style_id']);
					if (!file_exists($compiledFile))
					{
						$recompile = true;
					}
					else
					{
						$compiledData = file_get_contents($compiledFile);
						if (preg_match('#<?php\s+// FROM HASH: ([^\s]+)#s', $compiledData, $match))
						{
							if ($match[1] !== $hash)
							{
								$recompile = true;
							}
						}
						else
						{
							$recompile = true;
						}
					}
				}

				if ($recompile)
				{
					$this->import(
						"$type:$name",
						$designerMeta['style_id'],
						$contents,
						$templateMeta,
						['ignore_meta_version' => $ignoreMetaVersion]
					);
					$actionTaken = true;
				}
			}
			else if (isset($designerMeta['metadata'][$fileName]))
			{
				$template = \XF::em()->findOne(\XF\Entity\Template::class, [
					'style_id' => $designerMeta['style_id'],
					'type' => $type,
					'title' => $name,
				]);
				if ($template)
				{
					$template->delete();
				}
				else
				{
					$this->designerOutput->removeMetadata($typeDir, $designerMode, $fileName);
				}

				$actionTaken = true;
			}
		}

		$this->templatesWatched[$styleId][$fileName] = true;

		if ($actionTaken)
		{
			$this->templatesActioned[$styleId][$fileName] = true;
		}

		return $actionTaken;
	}

	public function hasActionedTemplates()
	{
		return !empty($this->templatesActioned);
	}
}
