<?php
/**
 * Server-side render for blocksify/section.
 *
 * Section is fully server-rendered per FOUNDATION §9.5 — `save.js`
 * returns bare `<InnerBlocks.Content />` and this file builds the
 * entire frontend markup from `$attributes`:
 *
 *   <{tag} class="bsy-section-{uid} [alignwide|alignfull]" [id="…"]>
 *     [image scroll layer]
 *     [per-device video layers]
 *     [overlay div]
 *     [shape edge top]
 *     [shape edge bottom]
 *     <div class="bsy-section__inner bsy-has-{count}-cols">
 *       {inner blocks via $content}
 *     </div>
 *   </{tag}>
 *
 * Background DOM layers come from `Blocksify_Background_Render` — the
 * PHP twin of `src/shared/controls/BackgroundControl/render.js` which
 * the editor still uses for canvas preview. JS↔PHP parity per
 * FOUNDATION §9.1 keeps editor preview and frontend byte-for-byte
 * identical for equal input.
 *
 * Per-instance CSS emit lives in `Blocksify_CSS_Pipeline` (single-
 * handle inline `<style>` pool at `wp_enqueue_scripts`). This file
 * owns DOM only — no CSS rules emitted here.
 *
 * @package Blocksify
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Serialized inner blocks (from
 *                           `<InnerBlocks.Content />` slot).
 * @var WP_Block $block      Parsed block instance.
 */

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

// Lazy-enqueue the Motion.dev runtime ONLY when this Section instance
// has an active scroll effect. Gate is the `effect` value (parallax /
// zoom) — mirrors `Blocksify_Background_Render::is_scroll_active()` and
// JS `isScrollEffectActive()`. Multiple sections share the handle —
// `wp_enqueue_script` is no-op on subsequent calls. Sections without
// scroll effect pay zero JS cost (no-JS performance default).
$scroll_bag = isset( $attributes['backgroundScrollEffect'] ) && is_array( $attributes['backgroundScrollEffect'] )
	? $attributes['backgroundScrollEffect']
	: null;
if ( $scroll_bag
	&& isset( $scroll_bag['effect'] )
	&& in_array( $scroll_bag['effect'], array( 'parallax', 'zoom' ), true )
	&& wp_script_is( 'blocksify-section-scroll-effect', 'registered' ) ) {
	wp_enqueue_script( 'blocksify-section-scroll-effect' );
}

// Resolve wrapper tag — whitelist mirrors `ALLOWED_SECTION_TAGS` in
// `src/blocks/section/save.js` (defensive resolve so a stale attribute
// from before the whitelist tightening still serializes to a valid
// element). Default 'section'.
$allowed_tags = array( 'div', 'section', 'article', 'header', 'footer', 'main', 'aside', 'nav' );
$tag          = isset( $attributes['tagName'] ) && in_array( $attributes['tagName'], $allowed_tags, true )
	? $attributes['tagName']
	: 'section';

// Per-instance class only — FOUNDATION §11 forbids hand-emitting
// `wp-block-blocksify-*` on frontend. Editor still gets that class
// via `useBlockProps()` for editor-only chrome targeting; the block's
// `supports.className: false` flag strips it from saved markup +
// frontend output declaratively. Static CSS anchors via
// `[class*="bsy-section-"]` attribute selector — no shared base class
// needed on the wrapper. `align{wide|full}` added when set so theme
// content-width rules (`.entry-content > *:not(.alignwide):not
// (.alignfull)`) can gate on them — alignment is user-controlled,
// not silently emitted.
//
// `attributes.className` carries user-authored "Additional CSS
// classes" from Inspector → Advanced. `supports.className: false`
// disables the wp-block-* auto-class but does NOT disable the
// Additional CSS classes field (that's `supports.customClassName`,
// which defaults to true and is not opted out here). In hybrid save
// the field was auto-merged by `useBlockProps.save`; pure-dynamic
// save has no wrapper so render.php must merge manually — cf. Button's
// render.php for the same pattern. Skipping this drops the user's
// custom classes silently on frontend (regression confirmed empirically).
$uid        = isset( $attributes['uid'] ) ? $attributes['uid'] : '';
$user_class = isset( $attributes['className'] ) ? trim( (string) $attributes['className'] ) : '';
$class_set  = array();
if ( '' !== $uid ) {
	$class_set[] = 'bsy-section-' . $uid;
}
if ( isset( $attributes['align'] ) && in_array( $attributes['align'], array( 'wide', 'full' ), true ) ) {
	$class_set[] = 'align' . $attributes['align'];
}
if ( '' !== $user_class ) {
	$class_set[] = $user_class;
}
$class_attr = implode( ' ', $class_set );

// Custom HTML id — block `supports.anchor: true`. WP stores in
// `attributes.anchor`; render to `id=""` attribute.
$id_attr = isset( $attributes['anchor'] ) && '' !== $attributes['anchor']
	? ' id="' . esc_attr( $attributes['anchor'] ) . '"'
	: '';

// Inner-blocks container — `bsy-section__inner` + `bsy-has-{N}-cols`.
// `count` defaults to 2 (matches block.json default + save.js default).
$count       = isset( $attributes['count'] ) ? (int) $attributes['count'] : 2;
$inner_class = 'bsy-section__inner bsy-has-' . $count . '-cols';

// Background DOM layer order mirrors save.js (siblings of the inner
// div, all `position: absolute` so DOM order doesn't affect visual
// stacking; z-index decides):
//   1. Image scroll-effect `<img>` (only when scrollEffect enabled).
//   2. Per-device `<video>` / `<iframe>` per override.
//   3. Overlay `<div>` (when any device state has overlay content).
//   4. Shape top + bottom edges (absolute positioned each end).
$scroll_effect = isset( $attributes['backgroundScrollEffect'] ) ? $attributes['backgroundScrollEffect'] : null;
$bg_image      = Blocksify_Background_Render::image(
	isset( $attributes['backgroundImage'] ) ? $attributes['backgroundImage'] : null,
	array( 'scrollEffect' => $scroll_effect )
);
$bg_videos = Blocksify_Background_Render::videos(
	$attributes,
	array( 'scrollEffect' => $scroll_effect )
);
$show_overlay =
	Blocksify_Background_Render::has_overlay_content( isset( $attributes['backgroundOverlay'] ) ? $attributes['backgroundOverlay'] : null )
	|| Blocksify_Background_Render::has_overlay_content( isset( $attributes['backgroundOverlayTablet'] ) ? $attributes['backgroundOverlayTablet'] : null )
	|| Blocksify_Background_Render::has_overlay_content( isset( $attributes['backgroundOverlayMobile'] ) ? $attributes['backgroundOverlayMobile'] : null );
$bg_overlay = Blocksify_Background_Render::overlay( $show_overlay );
$shape      = isset( $attributes['backgroundShape'] ) && is_array( $attributes['backgroundShape'] )
	? $attributes['backgroundShape']
	: array();
$shape_top    = Blocksify_Background_Render::shape_edge( 'top', isset( $shape['top'] ) ? $shape['top'] : null );
$shape_bottom = Blocksify_Background_Render::shape_edge( 'bottom', isset( $shape['bottom'] ) ? $shape['bottom'] : null );

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

// Build markup. `$content` is the serialized inner-blocks markup from
// the `<InnerBlocks.Content />` slot in save.js. Already safe HTML —
// WP rendered it through the block parser + sanitization pipeline.
echo '<' . $tag . ' class="' . esc_attr( $class_attr ) . '"' . $id_attr . '>';
echo $bg_image; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $bg_videos; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $bg_overlay; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $shape_top; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $shape_bottom; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="' . esc_attr( $inner_class ) . '">';
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo '</' . $tag . '>';
