<?php
/**
 * Converts TipTap HTML output to Notion block objects.
 *
 * @package Sequensy\Integrations\Notion
 * @since 1.0.0
 */

declare(strict_types=1);

namespace Sequensy\Integrations\Notion\Helpers;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Converts TipTap HTML output to Notion block objects.
 *
 * Parses HTML from the rich text editor and produces an array of Notion API
 * block structures suitable for page creation or content appending.
 */
class HtmlToBlocks {

	/**
	 * Convert HTML string to an array of Notion block objects.
	 *
	 * @param string $html HTML content (typically from TipTap editor).
	 * @return array<int, array> Notion block objects.
	 */
	public static function convert( string $html ): array {
		$html = trim( $html );

		if ( '' === $html ) {
			return array();
		}

		// Plain text with no HTML tags — wrap in a single paragraph.
		if ( wp_strip_all_tags( $html ) === $html ) {
			return array(
				self::make_block(
					'paragraph',
					array(
						array(
							'type' => 'text',
							'text' => array( 'content' => $html ),
						),
					)
				),
			);
		}

		$doc = new \DOMDocument();

		// Suppress warnings from HTML5 tags; wrap in root to handle fragments.
		libxml_use_internal_errors( true );
		$doc->loadHTML(
			'<html><body>' . $html . '</body></html>',
			LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
		);
		libxml_clear_errors();

		$body   = $doc->getElementsByTagName( 'body' )->item( 0 );
		$blocks = array();

		if ( null === $body ) {
			return array();
		}

		foreach ( $body->childNodes as $node ) {
			$block = self::node_to_block( $node );
			if ( null !== $block ) {
				if ( isset( $block[0] ) ) {
					// Array of blocks (e.g. list items).
					foreach ( $block as $b ) {
						$blocks[] = $b;
					}
				} else {
					$blocks[] = $block;
				}
			}
		}

		return $blocks;
	}

	/**
	 * Convert a DOM node to a Notion block (or array of blocks for lists).
	 *
	 * @param \DOMNode $node DOM node to convert.
	 * @return array|null Block array, indexed array of blocks, or null.
	 */
	private static function node_to_block( \DOMNode $node ): ?array {
		if ( $node instanceof \DOMText ) {
			$text = $node->textContent;
			if ( '' === trim( $text ) ) {
				return null;
			}
			return self::make_block(
				'paragraph',
				array(
					array(
						'type' => 'text',
						'text' => array( 'content' => $text ),
					),
				)
			);
		}

		if ( ! ( $node instanceof \DOMElement ) ) {
			return null;
		}

		$tag = strtolower( $node->tagName );

		switch ( $tag ) {
			case 'p':
				return self::make_block( 'paragraph', self::extract_rich_text( $node ) );

			case 'h2':
				return self::make_block( 'heading_2', self::extract_rich_text( $node ) );

			case 'h3':
				return self::make_block( 'heading_3', self::extract_rich_text( $node ) );

			case 'ul':
				return self::extract_list_items( $node, 'bulleted_list_item' );

			case 'ol':
				return self::extract_list_items( $node, 'numbered_list_item' );

			default:
				// Unknown block-level element — treat children as paragraph.
				$rich_text = self::extract_rich_text( $node );
				if ( ! empty( $rich_text ) ) {
					return self::make_block( 'paragraph', $rich_text );
				}
				return null;
		}
	}

	/**
	 * Extract list items from a <ul> or <ol> element.
	 *
	 * @param \DOMElement $list_node  List element to extract items from.
	 * @param string      $block_type 'bulleted_list_item' or 'numbered_list_item'.
	 * @return array Indexed array of blocks.
	 */
	private static function extract_list_items( \DOMElement $list_node, string $block_type ): array {
		$blocks = array();

		foreach ( $list_node->childNodes as $child ) {
			if ( $child instanceof \DOMElement && 'li' === strtolower( $child->tagName ) ) {
				$blocks[] = self::make_block( $block_type, self::extract_rich_text( $child ) );
			}
		}

		return $blocks;
	}

	/**
	 * Build a Notion block array.
	 *
	 * @param string $type      Block type (paragraph, heading_2, etc.).
	 * @param array  $rich_text Array of rich_text objects.
	 * @return array Notion block.
	 */
	private static function make_block( string $type, array $rich_text ): array {
		return array(
			'object' => 'block',
			'type'   => $type,
			$type    => array(
				'rich_text' => $rich_text,
			),
		);
	}

	/**
	 * Recursively extract inline children into a rich_text array.
	 *
	 * @param \DOMNode $node        Parent node to extract from.
	 * @param array    $annotations Accumulated annotations from parent inline tags.
	 * @return array Array of Notion rich_text objects.
	 */
	private static function extract_rich_text( \DOMNode $node, array $annotations = array() ): array {
		$result = array();

		foreach ( $node->childNodes as $child ) {
			if ( $child instanceof \DOMText ) {
				$text = $child->textContent;
				if ( '' === $text ) {
					continue;
				}

				$rt = array(
					'type' => 'text',
					'text' => array( 'content' => $text ),
				);

				if ( ! empty( $annotations ) ) {
					$rt['annotations'] = $annotations;
				}

				$result[] = $rt;
				continue;
			}

			if ( ! ( $child instanceof \DOMElement ) ) {
				continue;
			}

			$tag            = strtolower( $child->tagName );
			$new_annotations = $annotations;

			switch ( $tag ) {
				case 'strong':
				case 'b':
					$new_annotations['bold'] = true;
					break;

				case 'em':
				case 'i':
					$new_annotations['italic'] = true;
					break;

				case 'u':
					$new_annotations['underline'] = true;
					break;

				case 's':
				case 'del':
					$new_annotations['strikethrough'] = true;
					break;

				case 'a':
					// Links are represented via the text object's link field, not annotations.
					$href       = $child->getAttribute( 'href' );
					$link_texts = self::extract_rich_text( $child, $new_annotations );

					foreach ( $link_texts as &$lt ) {
						if ( ! empty( $href ) ) {
							$lt['text']['link'] = array( 'url' => $href );
						}
					}
					unset( $lt );

					foreach ( $link_texts as $lt ) {
						$result[] = $lt;
					}
					continue 2;

				case 'span':
					// data-variable spans are treated as plain text (variable already resolved).
					break;

				default:
					break;
			}

			$children = self::extract_rich_text( $child, $new_annotations );
			foreach ( $children as $c ) {
				$result[] = $c;
			}
		}

		return $result;
	}
}
