<?php
/**
 * Markdown zu Gutenberg-Blocks Konverter.
 *
 * Bewusst dependency-frei (kein Composer). Deckt die Block- und Inline-Konstrukte
 * ab, die in Blogartikeln vorkommen. Ziel ist VALIDES Block-Markup, damit
 * WordPress nie still auf den Classic Editor (core/freeform) zurückfällt.
 * Alles Unbekannte degradiert zu einem Absatz, bleibt also innerhalb von
 * Block-Delimitern statt als rohes HTML aus dem Block-Modell zu fallen.
 *
 * Bekannte Grenzen (v0.1): keine Tabellen, keine verschachtelten Listen, keine
 * Fussnoten, keine HTML-Passthrough. Diese werden in späteren Versionen ergänzt.
 *
 * @package WPAgentCompanion
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class WPAgent_Markdown_To_Blocks {

	/**
	 * Convert a Markdown string into serialized Gutenberg block markup.
	 */
	public function convert( string $markdown ): string {
		$markdown = str_replace( array( "\r\n", "\r" ), "\n", $markdown );
		$lines    = explode( "\n", $markdown );
		$n        = count( $lines );
		$i        = 0;
		$blocks   = array();

		while ( $i < $n ) {
			$trimmed = trim( $lines[ $i ] );

			if ( '' === $trimmed ) {
				$i++;
				continue;
			}

			// Fenced code block.
			if ( preg_match( '/^```+/', $trimmed ) ) {
				$code = array();
				$i++;
				while ( $i < $n && ! preg_match( '/^```+\s*$/', trim( $lines[ $i ] ) ) ) {
					$code[] = $lines[ $i ];
					$i++;
				}
				$i++; // Skip the closing fence.
				$blocks[] = $this->code_block( implode( "\n", $code ) );
				continue;
			}

			// ATX heading (# .. ######).
			if ( preg_match( '/^(#{1,6})\s+(.*)$/', $trimmed, $m ) ) {
				$blocks[] = $this->heading_block( strlen( $m[1] ), $this->inline( trim( $m[2] ) ) );
				$i++;
				continue;
			}

			// Horizontal rule.
			if ( preg_match( '/^(\*{3,}|-{3,}|_{3,})$/', $trimmed ) ) {
				$blocks[] = $this->separator_block();
				$i++;
				continue;
			}

			// Standalone image line: ![alt](src).
			if ( preg_match( '/^!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)$/', $trimmed, $m ) ) {
				$blocks[] = $this->image_block( $m[2], $m[1] );
				$i++;
				continue;
			}

			// Blockquote.
			if ( preg_match( '/^>\s?/', $trimmed ) ) {
				$quote = array();
				while ( $i < $n && preg_match( '/^>\s?(.*)$/', trim( $lines[ $i ] ), $qm ) ) {
					$quote[] = $qm[1];
					$i++;
				}
				$blocks[] = $this->quote_block( $quote );
				continue;
			}

			// Unordered list.
			if ( preg_match( '/^[-*+]\s+/', $trimmed ) ) {
				$items = array();
				while ( $i < $n && preg_match( '/^[-*+]\s+(.*)$/', trim( $lines[ $i ] ), $lm ) ) {
					$items[] = $this->inline( trim( $lm[1] ) );
					$i++;
				}
				$blocks[] = $this->list_block( $items, false );
				continue;
			}

			// Ordered list.
			if ( preg_match( '/^\d+[.)]\s+/', $trimmed ) ) {
				$items = array();
				while ( $i < $n && preg_match( '/^\d+[.)]\s+(.*)$/', trim( $lines[ $i ] ), $lm ) ) {
					$items[] = $this->inline( trim( $lm[1] ) );
					$i++;
				}
				$blocks[] = $this->list_block( $items, true );
				continue;
			}

			// Paragraph: gather consecutive lines until a blank line or a new block construct.
			$para = array();
			while ( $i < $n ) {
				$l = trim( $lines[ $i ] );
				if ( '' === $l
					|| preg_match( '/^(#{1,6})\s+/', $l )
					|| preg_match( '/^```+/', $l )
					|| preg_match( '/^>\s?/', $l )
					|| preg_match( '/^[-*+]\s+/', $l )
					|| preg_match( '/^\d+[.)]\s+/', $l )
					|| preg_match( '/^(\*{3,}|-{3,}|_{3,})$/', $l )
				) {
					break;
				}
				$para[] = $l;
				$i++;
			}
			if ( ! empty( $para ) ) {
				$blocks[] = $this->paragraph_block( $this->inline( implode( ' ', $para ) ) );
			}
		}

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

	/**
	 * Convert inline Markdown (bold, italic, code, links) to safe inline HTML.
	 *
	 * Order matters: code spans are extracted first so their content is never
	 * treated as Markdown, then everything is HTML-escaped, then link/emphasis
	 * tokens are turned into tags, then code spans are restored.
	 */
	private function inline( string $text ): string {
		$placeholders = array();

		$text = preg_replace_callback(
			'/`([^`]+)`/',
			function ( $m ) use ( &$placeholders ) {
				$key                  = "\x00CODE" . count( $placeholders ) . "\x00";
				$placeholders[ $key ] = '<code>' . esc_html( $m[1] ) . '</code>';
				return $key;
			},
			$text
		);

		$text = esc_html( $text );

		// Links: [text](url). esc_html turned quotes into &quot; already.
		$text = preg_replace_callback(
			'/\[([^\]]+)\]\(([^)\s]+)(?:\s+&quot;[^&]*&quot;)?\)/',
			function ( $m ) {
				$url = html_entity_decode( $m[2], ENT_QUOTES );
				return '<a href="' . esc_url( $url ) . '">' . $m[1] . '</a>';
			},
			$text
		);

		$text = preg_replace( '/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $text );
		$text = preg_replace( '/__([^_]+)__/', '<strong>$1</strong>', $text );
		$text = preg_replace( '/(?<!\*)\*([^*]+)\*(?!\*)/', '<em>$1</em>', $text );
		$text = preg_replace( '/(?<!_)_([^_]+)_(?!_)/', '<em>$1</em>', $text );

		return strtr( $text, $placeholders );
	}

	private function paragraph_block( string $html ): string {
		return "<!-- wp:paragraph -->\n<p>{$html}</p>\n<!-- /wp:paragraph -->";
	}

	private function heading_block( int $level, string $html ): string {
		$level = max( 1, min( 6, $level ) );
		$attrs = ( 2 === $level ) ? '' : ' {"level":' . $level . '}';
		return "<!-- wp:heading{$attrs} -->\n<h{$level}>{$html}</h{$level}>\n<!-- /wp:heading -->";
	}

	private function list_block( array $items, bool $ordered ): string {
		$tag   = $ordered ? 'ol' : 'ul';
		$attrs = $ordered ? ' {"ordered":true}' : '';
		$out   = "<!-- wp:list{$attrs} -->\n<{$tag} class=\"wp-block-list\">";
		foreach ( $items as $item ) {
			$out .= "<!-- wp:list-item -->\n<li>{$item}</li>\n<!-- /wp:list-item -->";
		}
		$out .= "</{$tag}>\n<!-- /wp:list -->";
		return $out;
	}

	private function quote_block( array $lines ): string {
		$paras = array();
		$cur   = array();
		foreach ( $lines as $l ) {
			if ( '' === trim( $l ) ) {
				if ( $cur ) {
					$paras[] = implode( ' ', $cur );
					$cur     = array();
				}
			} else {
				$cur[] = trim( $l );
			}
		}
		if ( $cur ) {
			$paras[] = implode( ' ', $cur );
		}

		$inner = '';
		foreach ( $paras as $p ) {
			$inner .= "<!-- wp:paragraph -->\n<p>" . $this->inline( $p ) . "</p>\n<!-- /wp:paragraph -->";
		}
		return "<!-- wp:quote -->\n<blockquote class=\"wp-block-quote\">{$inner}</blockquote>\n<!-- /wp:quote -->";
	}

	private function code_block( string $code ): string {
		return "<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>" . esc_html( $code ) . "</code></pre>\n<!-- /wp:code -->";
	}

	private function separator_block(): string {
		return "<!-- wp:separator -->\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"/>\n<!-- /wp:separator -->";
	}

	private function image_block( string $src, string $alt ): string {
		return "<!-- wp:image -->\n<figure class=\"wp-block-image\"><img src=\"" . esc_url( $src ) . '" alt="' . esc_attr( $alt ) . "\"/></figure>\n<!-- /wp:image -->";
	}
}
