<?php

namespace Planaday\Html\Nodes;

use DOMCdataSection;
use DOMElement;
use DOMException;
use Planaday\Html\NodeTags\Tag;
use Planaday\Utility\Debug\Log\Logging;

class StdParserCdataSection extends DOMCdataSection implements ParserNode {

	public function cloneNode( bool $deep = false ): StdParserCdataSection {
		$clonedNode = parent::cloneNode( $deep );
		if ($this->nodeData) $clonedNode->nodeData = clone $this->nodeData;

		// This is important to not lose the data stored, See StdParserNodeLogic for more info
		$this->references[] = $clonedNode;
		$clonedNode->addReference($this);

		return $clonedNode;
	}

	public function addReference(mixed $node): void {
		$this->references[] = $node;
	}

	// START StdParserNodeLogic trait
	use Logging;
	protected Tag|null $tag = null;

	protected NodeData|null $nodeData = null;

	/** @var array
	 * This array is important due to undocumented behavior of DOMNodes:
	 *   When there are no PHP references to a node with a custom tag, it will get cleaned up by the garbage collector.
	 *   The references through the DOM objects do not count towards this.
	 */
	private array $references = [];

	/**
	 * @return DOMElement&ParserNode
	 * @throws NodeCouldNotBeCreatedException
	 */
	public function getUsableDocumentFragment(): DOMElement&ParserNode {
		$ownerDocument = $this->ownerDocument;
		if ($ownerDocument === null) {
			$ownerDocument = $this;
		}

		if (!is_a($ownerDocument, StdParserDocument::class)) {
			$this->logError("ownerDocument is not a StdParserDocument", $ownerDocument);
			throw new NodeCouldNotBeCreatedException("node could not be created, DOMDocument class: " . get_class($ownerDocument));
		}

		try{
			$documentFragment = $ownerDocument->createElement("document-fragment");
		} catch (DOMException $e) {
			$this->logError("documentFragment could not be created", $e);
			throw new NodeCouldNotBeCreatedException("node could not be created", $e->getCode(), $e);
		}

		if ($documentFragment === false) {
			$this->logError("documentFragment could not be created");
			throw new NodeCouldNotBeCreatedException("node could not be created");
		}

		if (!is_a($documentFragment, StdParserElement::class)) {
			$this->logError("documentFragment is not a StdParserElement", $documentFragment);
			throw new NodeCouldNotBeCreatedException("node could not be created, class: " . get_class($documentFragment));
		}

		$this->references[] = $documentFragment;

		return $documentFragment;
	}

	/**
	 * @param string $template
	 *
	 * @return DOMElement&ParserNode
	 * @throws NodeCouldNotBeCreatedException
	 */
	public function getNodeFromTemplate( string $template ): DOMElement&ParserNode {
		$tempDoc = new StdParserDocument();
		libxml_use_internal_errors(true);
		$tempDoc->loadHTML("<root>" . $template . "</root>");
		libxml_use_internal_errors(false);
		$fragment = $this->getUsableDocumentFragment();
		$doc = $fragment->ownerDocument;
		$node = $doc->importNode($tempDoc->getElementsByTagName("root")->item(0), true);

		if ($node === false) {
			throw new NodeCouldNotBeCreatedException("node could not be imported");
		}

		for($i = count($node->childNodes); $i > 0; $i--) {
			$fragment->prepend($node->childNodes->item($i - 1));
		}

		return $fragment;
	}

	public function getExtendedNodeData(): ?NodeData {
		return $this->nodeData;
	}

	public function setExtendedNodeData( ?NodeData $nodeData ): void {
		$this->nodeData = $nodeData;
	}

	public function setAssociatedTag(?Tag $tag): void{
		$this->tag = $tag;
	}

	public function getAssociatedTag(): ?Tag{
		return $this->tag;
	}

	public function destroyPhpReferences(): void {
		unset($this->references);
		unset($this->tag);
		unset($this->nodeData);
		if(isset($this->ownerDocument)) $this->ownerDocument->destroyPhpReferences();
	}
}