<?php
/**
 * Server-side render for blocksify/text.
 *
 * Text is a fully dynamic block — `save.js` returns `null`, so saved
 * markup is just the comment delimiter and this file builds the entire
 * frontend HTML from `$attributes`. Per-instance CSS comes from
 * `Blocksify_CSS_Pipeline` (single-handle pool at `wp_enqueue_scripts`).
 * Block-local `text-indent` rules emit via
 * `Blocksify_CSS_Generator::emit_text_indent_rules` which the pipeline
 * picks up through the adapter map.
 *
 * Output shape — single element, no outer wrapper:
 *
 *   Naked  : <{tag} class="bsy-text">content</{tag}>
 *   Styled : <{tag} class="bsy-text bsy-text-{uid}">content</{tag}>
 *
 * The `bsy-text` base class is always present so consumers (Pro,
 * theme integrations, JS animation hooks) can target every Text
 * instance. The per-instance `bsy-text-{uid}` class is added only
 * when the trait pipeline produces CSS for this instance — keeping
 * class presence in lockstep with CSS presence.
 *
 * `$tag` is one of `h1, h2, h3, h4, h5, h6, p, div, span` (default
 * `p`). For h1-h6/p/div tags the element is naturally block-level,
 * so the host theme's content-area treatment lands directly. For
 * the rare `span` variant the element is inline-level and hugs LEFT
 * of its parent's line box (no auto-margin centering applies, no
 * BlockRoot wrapper needed).
 *
 * `$content` was authored via `<RichText>` in edit.js. RichText
 * sanitizes formatting marks at commit time (bold / italic / inline-
 * code / link / strikethrough / etc.); echoed without `wp_kses`
 * because the JS-side allowlist is the single source of truth.
 *
 * @package Blocksify
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Saved block markup (always empty for dynamic).
 * @var WP_Block $block      Parsed block instance.
 */

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

if ( isset( $_GET['blocksify_debug'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
	$debug_css = Blocksify_CSS_Generator::generate_block_css( 'blocksify/text', $attributes );
	echo "\n<!-- Blocksify Text CSS:\n" . esc_html( $debug_css ) . "\n-->\n";
}

$allowed_tags = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'span' );
$tag          = isset( $attributes['tagName'] ) && in_array( $attributes['tagName'], $allowed_tags, true )
	? $attributes['tagName']
	: 'p';

$uid          = isset( $attributes['uid'] ) ? (string) $attributes['uid'] : '';
$content_text = isset( $attributes['content'] ) ? (string) $attributes['content'] : '';
$user_class   = isset( $attributes['className'] ) ? trim( (string) $attributes['className'] ) : '';
$anchor       = isset( $attributes['anchor'] ) ? (string) $attributes['anchor'] : '';

// Naked Text → just `bsy-text` (theme defaults flow through). Styled
// Text → also `bsy-text-{uid}`, the per-instance class carrying trait
// CSS from the pipeline. "Styled" = `generate_block_css` returns a
// non-empty string, which is exactly when the pipeline emits a rule
// for this instance — keeping the class presence in lockstep with CSS
// presence (no orphan classes, no CSS targeting missing classes).
$classes        = array( 'bsy-text' );
$instance_css   = '' !== $uid
	? Blocksify_CSS_Generator::generate_block_css( 'blocksify/text', $attributes )
	: '';
$has_inst_class = '' !== $uid && '' !== trim( $instance_css );
if ( $has_inst_class ) {
	$classes[] = 'bsy-text-' . preg_replace( '/[^a-z0-9_-]/i', '', $uid );
}
if ( '' !== $user_class ) {
	$classes[] = $user_class;
}
$class_attr = esc_attr( implode( ' ', $classes ) );

$attrs_html = ' class="' . $class_attr . '"';
if ( '' !== $anchor ) {
	$attrs_html .= ' id="' . esc_attr( $anchor ) . '"';
}

echo '<' . esc_attr( $tag ) . $attrs_html . '>' . $content_text . '</' . esc_attr( $tag ) . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
