<?php
/**
 * Dynamic Region Fallback (free build).
 *
 * The full dynamic-content render engine (AMBLD_Pro\Render\Dynamic_Resolver)
 * ships only in Antimanual Builder Pro. When Pro is not active, pages that were
 * built with dynamic bindings must still render cleanly — this lightweight
 * fallback strips the binding markers (data-amb-dynamic / data-amb-loop and the
 * empty-state directives) and removes any leftover {{token}} placeholders so a
 * dynamic section degrades to its static template instead of leaking raw
 * template markup to the browser.
 *
 * It never fetches data or expands loops — that is a premium capability.
 *
 * @package Antimanual_Builder
 * @since   0.7.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class AMBLD_Render_Dynamic_Fallback.
 *
 * @since 0.7.0
 */
class AMBLD_Render_Dynamic_Fallback {

	/**
	 * Binding/marker attributes stripped from a dynamic region.
	 *
	 * @since 0.7.0
	 * @var   string[]
	 */
	const MARKER_ATTRS = array( 'data-amb-dynamic', 'data-amb-loop', 'data-amb-hide-if-empty', 'data-amb-fallback-src' );

	/**
	 * Strip dynamic binding markup so a section renders as static content.
	 *
	 * Mirrors AMBLD_Pro\Render\Dynamic_Resolver::resolve()'s fast-path bail and
	 * DOM-loading behaviour, but only removes markers/tokens — it does not run
	 * any query, repeat any loop, or substitute real data.
	 *
	 * @since  0.7.0
	 * @param  string $html Rendered builder HTML.
	 * @return string HTML with binding markers and {{tokens}} removed.
	 */
	public static function strip( $html ) {
		if ( ! is_string( $html ) || '' === $html ) {
			return is_string( $html ) ? $html : '';
		}

		$has_marker = false !== strpos( $html, 'data-amb-dynamic' );
		$has_tokens = false !== strpos( $html, '{{' );

		// Nothing to do — no marked regions and no orphan tokens.
		if ( ! $has_marker && ! $has_tokens ) {
			return $html;
		}

		// Remove the marker attributes via a DOM round-trip (only when a region
		// is actually present and DOMDocument is available).
		if ( $has_marker && class_exists( 'DOMDocument' ) ) {
			$html = self::strip_markers( $html );
		}

		// Guarantee no raw template tokens ever reach the browser. Scoped by the
		// caller's guard (dynamic regions present), matching the resolver's own
		// orphan-token safety net.
		if ( false !== strpos( $html, '{{' ) ) {
			$html = preg_replace( '/\{\{[^{}]*\}\}/', '', $html );
		}

		return $html;
	}

	/**
	 * Remove binding marker attributes from every dynamic region in the HTML.
	 *
	 * @since  0.7.0
	 * @param  string $html Rendered builder HTML.
	 * @return string
	 */
	private static function strip_markers( $html ) {
		$wrapper_id = 'ambld-dyn-fallback-root';

		$doc                   = new DOMDocument( '1.0', 'UTF-8' );
		$previous_libxml_state = libxml_use_internal_errors( true );

		// LIBXML_HTML_NOIMPLIED / NODEFDTD keep DOMDocument from injecting
		// <html>/<body>/<!DOCTYPE> chrome around the fragment.
		$loaded = $doc->loadHTML(
			'<?xml encoding="UTF-8"?><div id="' . $wrapper_id . '">' . $html . '</div>',
			LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
		);
		libxml_clear_errors();
		libxml_use_internal_errors( $previous_libxml_state );

		if ( ! $loaded ) {
			return $html;
		}

		$root = $doc->getElementById( $wrapper_id );
		if ( ! $root ) {
			return $html;
		}

		$xpath = new DOMXPath( $doc );

		// Any element carrying a binding marker attribute (region roots + the
		// loop template child + empty-state hosts).
		$nodes = $xpath->query( './/*[@data-amb-dynamic or @data-amb-loop or @data-amb-hide-if-empty or @data-amb-fallback-src]', $root );
		if ( $nodes ) {
			foreach ( $nodes as $node ) {
				if ( ! $node instanceof DOMElement ) {
					continue;
				}
				foreach ( self::MARKER_ATTRS as $attr ) {
					if ( $node->hasAttribute( $attr ) ) {
						$node->removeAttribute( $attr );
					}
				}
			}
		}

		$output = '';
		foreach ( $root->childNodes as $child ) {
			$output .= $doc->saveHTML( $child );
		}

		return $output;
	}
}
