<?php

namespace Planaday\Html\Tags;

use DOMAttr;
use DOMDocument;
use DOMElement;
use DOMNamedNodeMap;
use DOMNode;
use Planaday\Html\HtmlParser;
use Planaday\Html\HtmlParserFactory;
use Planaday\Utility\Debug\Log\Logger;
use Planaday\Utility\Debug\Log\Logging;
use Planaday\Utility\DependencyInjection\Container;
use Planaday\Utility\DependencyInjection\InjectionImpossibleException;

abstract class Tag {
	use Logging;

	public const ATTRIBUTE_PREFIX = 'data-';
	public const RECURSIVE_PREFIX = self::ATTRIBUTE_PREFIX . 'recursive-';
	public const ID_OVERRIDE_ATTRIBUTE = self::ATTRIBUTE_PREFIX . 'override-id';
	public const CLASSES_ATTRIBUTE = self::ATTRIBUTE_PREFIX . 'classes';
	public const TEMPLATE_ATTRIBUTE = self::ATTRIBUTE_PREFIX . 'template';
	private const TEMPLATE_FOLDER = 'Templates/';
	private const STYLE_FOLDER = 'Styles/';
	private const SCRIPT_FOLDER = 'Scripts/';
	private const ERROR_TEMPLATE_FOLDER = 'Error/';

	protected array $parserContext;
	/** @var array<string, mixed> */
	protected array $context = [];
	/** @var array<string, string> */
	protected array $attributes;
	protected HtmlParserFactory $parserFactory;

	/**
	 * @param string $TAG_DIR
	 * @param string $DEFAULT_TEMPLATE
	 * @param Container $registry
	 *
	 * @throws InjectionImpossibleException
	 */
	protected function __construct(
		protected string $TAG_DIR,
		protected string $DEFAULT_TEMPLATE,
		Container $registry
	) {
		$this->setLogger($registry->getInstance(Logger::class));
		$this->parserFactory = $registry->getInstance(HtmlParserFactory::class);
	}

	abstract public function buildTag():string;

	public function buildDomNode( DOMNode $tagNode): DOMNode{
		$this->attributes = [];
		if ( $tagNode->attributes instanceof DOMNamedNodeMap ){
			foreach ( $tagNode->attributes as $attribute ) {
				if ($attribute instanceof DOMAttr)
					$this->attributes['' . $attribute->name] = htmlspecialchars_decode($attribute->value);
			}
		}

		$parsedHtml = $this->buildTag();

		$dom = ($tagNode instanceof DOMDocument ? $tagNode : $tagNode->ownerDocument) ?? new DOMDocument();

		$childDom = new DOMDocument();

		$parsedHtml = "<temp-body-pad>" . $parsedHtml . "</temp-body-pad>";
		$childDom->loadHTML(mb_convert_encoding($parsedHtml, 'HTML-ENTITIES', 'UTF-8'));

		$node = $childDom->getElementsByTagName('temp-body-pad')->item(0);

		if ($node !== null) $newTag = $dom->importNode($node, true);
		else $newTag = $dom->createDocumentFragment();

		if ($newTag instanceof DOMElement)
			$contentSpot = $newTag->getElementsByTagName('enclosed-pad-content')->item(0);

		if (isset($contentSpot) && $contentSpot->parentNode !== null) {
			$content = $dom->createDocumentFragment();
			while ($tagNode->childNodes->length > 0) {
				$child = $tagNode->childNodes->item(0);
				if ($child === null) break;
				$content->appendChild($child);
			}

			$contentSpot->parentNode->replaceChild($content, $contentSpot);
		}

		return $newTag;
	}

	/**
	 * @param string $tag
	 * @param array<string|int, string> $attributes
	 *
	 * @return string
	 */
	public static function getParsableTag(string $tag, array $attributes):string {
		$html = '<pad-' . $tag;
		foreach ($attributes as $attribute => $value){
			if (is_int($attribute)){
				$html .= ' ' . $value;
				continue;
			}
			$html .= ' ' . $attribute . '="' . htmlspecialchars($value) . '"';
		}
		$html .= '/>';

		return $html;
	}

	/**
	 * @param array<mixed> $context
	 *
	 * @return void
	 */
	public function setContext(array $context):void {
		$this->context = $context;
	}

	/**
	 * @param array<mixed> $context
	 *
	 * @return void
	 */
	public function setParserContext(array $context):void {
		$this->parserContext =& $context;
	}

	protected function getNewParser():HtmlParser {
		$parser = $this->parserFactory->getParser($this->parserContext);

		$parser->setBeingParsedName(get_class($this));

		$this->addAndConvertRecursiveAttributes($parser);

		if (isset($this->attributes[self::ID_OVERRIDE_ATTRIBUTE])){
			$parser->setData('uniqueId', $this->attributes[self::ID_OVERRIDE_ATTRIBUTE]);
		}

		if (isset($this->attributes[self::CLASSES_ATTRIBUTE])){
			$parser->setData('classes', $this->attributes[self::CLASSES_ATTRIBUTE]);
		}

		return $parser;
	}

	protected function getTemplate(string $templateType = '', ?string $templateNameOverride = null, ?string $dirOverride = null):string {
		$pathWithoutDir =
		            ('/' . $templateType . self::TEMPLATE_FOLDER) .
		            ( $templateNameOverride ?? $this->attributes[self::TEMPLATE_ATTRIBUTE] ?? $this->DEFAULT_TEMPLATE) .
		            '.html';

		$path =  ($dirOverride ?? $this->TAG_DIR) . $pathWithoutDir;

		if (file_exists($path)) $fileContents = file_get_contents( $path );

		if (!isset($fileContents) || $fileContents === false){
			$fileContents = '<!-- could not load template ' . htmlspecialchars($pathWithoutDir) . ' -->';
			$this->logError('Failed to load template ' . $pathWithoutDir, ['path' => $path]);
		}
		return $fileContents;
	}

	protected function getErrorTemplate(string $templateType = '', ?string $templateNameOverride = null, ?string $dirOverride = null):string {
		$pathWithoutDir =
		        ('/' . $templateType . self::TEMPLATE_FOLDER . self::ERROR_TEMPLATE_FOLDER) .
		        ( $templateNameOverride ?? $this->attributes[self::TEMPLATE_ATTRIBUTE] ?? $this->DEFAULT_TEMPLATE) .
		        '.html';

		$path =  ($dirOverride ?? $this->TAG_DIR) . $pathWithoutDir;

		if (file_exists($path)) $fileContents = file_get_contents( $path );

		if (!isset($fileContents) || $fileContents === false){
			$fileContents = "\n<!-- could not load " . htmlspecialchars($pathWithoutDir) . " -->";
			$this->logError('Failed to load error template ' . $pathWithoutDir, ['path' => $path]);
		}

		return $fileContents;
	}

	protected function getStylePath(?string $templateNameOverride = null, ?string $dirOverride = null):string {
		$folder = ($dirOverride ?? $this->TAG_DIR) .
		          ('/' . self::STYLE_FOLDER);
		$fileName = ( $templateNameOverride ?? $this->attributes[self::TEMPLATE_ATTRIBUTE] ?? $this->DEFAULT_TEMPLATE) . '.css';

		if (!file_exists( $folder . $fileName )) {
			$fileName = $this->DEFAULT_TEMPLATE . '.css';
		}

		return $folder . $fileName;
	}

	protected function getScriptPath(?string $templateNameOverride = null, ?string $dirOverride = null):string {
		$folder = ($dirOverride ?? $this->TAG_DIR) .
		          ('/' . self::SCRIPT_FOLDER);
		$fileName = ( $templateNameOverride ?? $this->attributes[self::TEMPLATE_ATTRIBUTE] ?? $this->DEFAULT_TEMPLATE) . '.js';

		if (!file_exists( $folder . $fileName )) {
			$fileName = $this->DEFAULT_TEMPLATE . '.js';
		}

		return $folder . $fileName;
	}

	public function getRecursiveVersionOfAttribute(string $attribute): string {
		return self::RECURSIVE_PREFIX . substr($attribute, strlen(self::ATTRIBUTE_PREFIX));
	}

	private function addAndConvertRecursiveAttributes(HtmlParser $parser): void {
		foreach ($this->attributes as $key => $value){
			if ( str_starts_with( $key, self::RECURSIVE_PREFIX ) && $value != '') {
				if (!isset($this->attributes[self::ATTRIBUTE_PREFIX . substr($key, strlen(self::RECURSIVE_PREFIX))])) {
					$this->attributes[self::ATTRIBUTE_PREFIX . substr($key, strlen(self::RECURSIVE_PREFIX))] = $value;
				}

				$parser->injectAttribute($key, $value);
			}
		}
	}
}