<?php
/**
 * Standalone frontend template for faithfully-cloned imported pages.
 *
 * Renders an imported page as a clone of its original document: the original
 * <html>/<head>/<body> attributes and head markup, the page's own merged CSS
 * and per-file JS (the imported-script manifest) emitted in their original
 * positions with their original attributes — with NO theme wrapper and NO
 * wp_head()/wp_footer(). Inline/local author script content is emitted only
 * when the user who saved it still holds the `unfiltered_html` capability;
 * remote references always load.
 *
 * @package Antimanual_Builder
 */

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

$post_id       = get_the_ID();
$rendered_html = (string) get_post_meta( $post_id, '_ambld_rendered_html', true );

$settings = json_decode( (string) get_post_meta( $post_id, '_ambld_page_settings', true ), true );
$settings = is_array( $settings ) ? $settings : array();

$head_markup   = isset( $settings['importedHeadMarkup'] ) ? (string) $settings['importedHeadMarkup'] : '';
$footer_markup = isset( $settings['importedFooterMarkup'] ) ? (string) $settings['importedFooterMarkup'] : '';
$html_attrs    = isset( $settings['importedHtmlAttributes'] ) && is_array( $settings['importedHtmlAttributes'] ) ? $settings['importedHtmlAttributes'] : array();
$body_attrs    = isset( $settings['importedBodyAttributes'] ) && is_array( $settings['importedBodyAttributes'] ) ? $settings['importedBodyAttributes'] : array();

// Assigned header/footer builder templates. Standalone clones honor the same
// per-page header/footer selection as canvas pages, but because this template
// emits no wp_head()/wp_footer(), the templates' generated CSS/JS can't ride
// the normal enqueue pipeline — they're printed inline below instead.
$header_template = AMBLD_Render_Template_Resolver::resolve( $post_id, 'header' );
$footer_template = AMBLD_Render_Template_Resolver::resolve( $post_id, 'footer' );

// Render-time sanitization gate shared with the canvas template (page.php).
$ambld_rendered_allowed_tags = require __DIR__ . '/rendered-allowed-tags.php';

// Per-script manifest, resolved to ready-to-print <script> tags grouped by
// document position. The emitter is shared with the theme-template render
// path (AMBLD_Render_Template_Loader) via AMBLD_Render_Pipeline so both paths
// produce byte-identical scripts — kind, position, attributes, and the
// unfiltered_html capability gate. Author inline/local content is emitted only
// if the saver still holds the capability (mirrors core's unfiltered_html
// enforcement); remote references always load.
$imported_script_tags = AMBLD_Render_Pipeline::imported_script_tags( $post_id );

// Generated merged-CSS asset — emitted AFTER any external <link>s in the head
// markup so the page's own rules win the cascade.
$upload   = wp_upload_dir();
$css_path = trailingslashit( $upload['basedir'] ) . AMBLD_UPLOAD_DIR . '/css/post-' . $post_id . '.css';
$css_url  = trailingslashit( $upload['baseurl'] ) . AMBLD_UPLOAD_DIR . '/css/post-' . $post_id . '.css';
$css_tag  = file_exists( $css_path )
	? '<link rel="stylesheet" href="' . esc_url( $css_url . '?ver=' . filemtime( $css_path ) ) . '">'
	: '';

// StyleMap CSS (visual-editor styles) — shared emitter, so standalone
// clones and the compiled .css file always agree on the output.
$style_map_css = AMBLD_Render_Pipeline::style_map_css( $post_id );

// External resources attached in the editor's Libraries tab (and the AI's
// attach_cdn tool). External JS libraries are unified into the script manifest
// (emitted above with the page's own scripts), so this list now carries the
// page's external stylesheets; a `js` entry only appears on legacy pages saved
// before the unification and is emitted here for back-compat, gated on the same
// unfiltered_html authorization the manifest's author scripts use. This mirrors
// the theme-template enqueue path so a standalone clone and a theme render of
// the same page load the same resources.
$cdn_resources  = json_decode( (string) get_post_meta( $post_id, '_ambld_cdn_resources', true ), true );
$cdn_resources  = is_array( $cdn_resources ) ? $cdn_resources : array();
$cdn_js_allowed = function_exists( 'ambld_unfiltered_authorized' )
	? ambld_unfiltered_authorized( $post_id, '_ambld_cdn_resources_authorized_by' )
	: false;
$cdn_css_tags = array();
$cdn_js_head  = array();
$cdn_js_body  = array();
foreach ( $cdn_resources as $cdn_res ) {
	if ( ! is_array( $cdn_res ) || empty( $cdn_res['url'] ) ) {
		continue;
	}
	$cdn_res_type = isset( $cdn_res['type'] ) ? (string) $cdn_res['type'] : '';
	if ( 'css' === $cdn_res_type ) {
		$cdn_css_tags[] = '<link rel="stylesheet" href="' . esc_url( (string) $cdn_res['url'] ) . '">';
	} elseif ( 'js' === $cdn_res_type && $cdn_js_allowed ) {
		$cdn_js_attr = '';
		if ( ! empty( $cdn_res['async'] ) ) {
			$cdn_js_attr .= ' async';
		}
		if ( ! empty( $cdn_res['defer'] ) ) {
			$cdn_js_attr .= ' defer';
		}
		$cdn_js_tag = '<script src="' . esc_url( (string) $cdn_res['url'] ) . '"' . $cdn_js_attr . '></script>';
		if ( isset( $cdn_res['position'] ) && 'body' === $cdn_res['position'] ) {
			$cdn_js_body[] = $cdn_js_tag;
		} else {
			$cdn_js_head[] = $cdn_js_tag;
		}
	}
}

// Restrictive head allowlist: metadata only (scripts come from the manifest,
// styles from the generated asset).
$head_allowed = array(
	'meta'  => array( 'name' => true, 'content' => true, 'charset' => true, 'property' => true, 'http-equiv' => true, 'itemprop' => true ),
	'link'  => array( 'rel' => true, 'href' => true, 'type' => true, 'media' => true, 'sizes' => true, 'as' => true, 'crossorigin' => true, 'integrity' => true, 'hreflang' => true, 'title' => true ),
	'title' => array(),
	'base'  => array( 'href' => true, 'target' => true ),
);

/**
 * Render an attribute map into an HTML attribute string. Boolean attributes
 * (value `true` or empty string) render bare.
 */
$attr_string = static function ( $attrs ) {
	$out = '';
	foreach ( (array) $attrs as $name => $value ) {
		$name = preg_replace( '/[^A-Za-z0-9_:-]/', '', (string) $name );
		// Drop event-handler attributes (onload, onclick, …): their value is
		// JavaScript, so escaping it does not neutralise the handler. This
		// clone path is served publicly for published imported pages, so a
		// stored `<body onload="…">` would otherwise run for every visitor.
		if ( '' === $name || 0 === stripos( $name, 'on' ) ) {
			continue;
		}
		if ( true === $value || '' === $value ) {
			$out .= ' ' . $name;
		} else {
			$out .= ' ' . $name . '="' . esc_attr( (string) $value ) . '"';
		}
	}
	return $out;
};

/**
 * Build a <link>/<script> tag for an assigned header/footer template's
 * generated asset, or '' when the template has no asset of that type.
 */
$render_template_asset = static function ( $payload, $type ) {
	if ( ! is_array( $payload ) || empty( $payload['id'] ) ) {
		return '';
	}
	$asset = AMBLD_Render_Template_Loader::get_asset_descriptor( absint( $payload['id'] ), $type );
	if ( ! is_array( $asset ) || empty( $asset['url'] ) ) {
		return '';
	}
	$url = empty( $asset['version'] )
		? (string) $asset['url']
		: add_query_arg( 'ver', $asset['version'], (string) $asset['url'] );

	return 'css' === $type
		? '<link rel="stylesheet" href="' . esc_url( $url ) . '">'
		: '<script src="' . esc_url( $url ) . '"></script>';
};

/**
 * Emit one assigned template inside a landmark wrapper (mirrors page.php).
 */
$render_template_block = static function ( $payload, $kind ) use ( $ambld_rendered_allowed_tags ) {
	if ( ! is_array( $payload ) || empty( $payload['main_html'] ) ) {
		return;
	}
	$tag = 'header' === $kind ? 'header' : 'footer';
	echo '<' . $tag . ' class="ambld-template ambld-template--' . esc_attr( $kind ) . '" data-amb-template-id="' . esc_attr( absint( $payload['id'] ) ) . '">' . "\n";
	echo wp_kses( (string) $payload['main_html'], $ambld_rendered_allowed_tags ) . "\n";
	echo '</' . $tag . '>' . "\n";
};

// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- faithful clone: stored markup/JS is sanitized at save and capability-gated here.
echo '<!DOCTYPE html>' . "\n";
echo '<html' . $attr_string( $html_attrs ) . '>' . "\n";
echo '<head>' . "\n";
echo "\t" . '<meta charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">' . "\n";
echo wp_kses( $head_markup, $head_allowed ) . "\n";

if ( ! empty( $imported_script_tags['head'] ) ) {
	echo implode( "\n", $imported_script_tags['head'] ) . "\n";
}

// External stylesheets (Libraries tab) — before the page's own CSS so page
// rules win the cascade, matching the theme path's dependency ordering.
if ( ! empty( $cdn_css_tags ) ) {
	echo implode( "\n", $cdn_css_tags ) . "\n";
}

// Head-loaded external scripts (legacy back-compat — see note above).
if ( ! empty( $cdn_js_head ) ) {
	echo implode( "\n", $cdn_js_head ) . "\n";
}

echo $css_tag . "\n";
if ( '' !== trim( $style_map_css ) ) {
	echo '<style id="ambld-style-map-css">' . "\n" . $style_map_css . '</style>' . "\n";
}

// Assigned header/footer template CSS — printed after the page's own styles so
// the templates inherit, then layer on, the cloned document's cascade.
foreach ( array( $header_template, $footer_template ) as $ambld_tpl_payload ) {
	$ambld_tpl_css = $render_template_asset( $ambld_tpl_payload, 'css' );
	if ( '' !== $ambld_tpl_css ) {
		echo $ambld_tpl_css . "\n";
	}
}
echo '</head>' . "\n";

echo '<body' . $attr_string( $body_attrs ) . '>' . "\n";
$render_template_block( $header_template, 'header' );
// Standalone profile: strips editor artifacts and stray <script> tags and
// resolves shortcode markers / dynamic {{tokens}} (previously these
// leaked raw on standalone pages), but skips kses and the <style> strip
// so the verbatim clone saved under the unfiltered_html gate renders
// unchanged.
echo AMBLD_Render_Pipeline::prepare( $rendered_html, $post_id, 'standalone' ) . "\n";
if ( '' !== trim( $footer_markup ) ) {
	echo $footer_markup . "\n";
}
$render_template_block( $footer_template, 'footer' );

// Body-loaded external scripts (legacy back-compat) — before the manifest's
// own body scripts so libraries are defined when the page's code runs.
if ( ! empty( $cdn_js_body ) ) {
	echo implode( "\n", $cdn_js_body ) . "\n";
}

if ( ! empty( $imported_script_tags['body'] ) ) {
	echo implode( "\n", $imported_script_tags['body'] ) . "\n";
}

// Author/AI-added JS (the `customJs` layer) — emitted last so it runs after the
// page's original scripts. Capability-gated by its own author.
$custom_js          = (string) get_post_meta( $post_id, '_ambld_custom_js', true );
$custom_js_allowed  = function_exists( 'ambld_unfiltered_authorized' )
	? ambld_unfiltered_authorized( $post_id, '_ambld_custom_js_authorized_by' )
	: false;
if ( $custom_js_allowed && '' !== trim( $custom_js ) ) {
	echo '<script id="ambld-custom-js">' . $custom_js . '</script>' . "\n";
}

// Assigned header/footer template JS — emitted last so it runs after the
// cloned page's own scripts, matching the canvas template's footer-of-body order.
foreach ( array( $header_template, $footer_template ) as $ambld_tpl_payload ) {
	$ambld_tpl_js = $render_template_asset( $ambld_tpl_payload, 'js' );
	if ( '' !== $ambld_tpl_js ) {
		echo $ambld_tpl_js . "\n";
	}
}

echo '</body>' . "\n" . '</html>';
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
