<?php
/**
 * Server-side render for blocksify/button.
 *
 * Button 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`)
 * + `Blocksify_CSS_Generator::emit_button_local_rules` for the
 * structural / Outline-preset overrides.
 *
 * Output shape — single class on every output:
 *   - No icon: `<a class="bsy-button-{uid}">text</a>`
 *   - With icon: `<a class="bsy-button-{uid}"><span class="bsy-button__icon">{svg}</span>text</a>`
 *
 * `attributes.icon.svg` is sanitized at commit time by IconLibrary's
 * `sanitizeSvg()` (see `src/shared/icon.js`). 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/button', $attributes );
	echo "\n<!-- Blocksify Button CSS:\n" . esc_html( $debug_css ) . "\n-->\n";
}

$allowed_tags    = array( 'a', 'button', 'div', 'span' );
$tag             = isset( $attributes['tagName'] ) && in_array( $attributes['tagName'], $allowed_tags, true )
	? $attributes['tagName']
	: 'a';
$uid             = isset( $attributes['uid'] ) ? (string) $attributes['uid'] : '';
$preset          = isset( $attributes['preset'] ) ? $attributes['preset'] : 'fill';
$content_type    = isset( $attributes['contentType'] ) ? $attributes['contentType'] : 'text';
$content_text    = isset( $attributes['content'] ) ? (string) $attributes['content'] : '';
$user_class      = isset( $attributes['className'] ) ? trim( (string) $attributes['className'] ) : '';
$anchor          = isset( $attributes['anchor'] ) ? (string) $attributes['anchor'] : '';
$icon            = isset( $attributes['icon'] ) && is_array( $attributes['icon'] ) ? $attributes['icon'] : null;
$icon_position   = isset( $attributes['iconPosition'] ) ? $attributes['iconPosition'] : 'before';

// Anchor-only link attrs.
$href            = ( 'a' === $tag && ! empty( $attributes['url'] ) ) ? (string) $attributes['url'] : '';
$link_target     = ( 'a' === $tag && ! empty( $attributes['linkTarget'] ) ) ? (string) $attributes['linkTarget'] : '';
$rel             = ( 'a' === $tag && ! empty( $attributes['rel'] ) ) ? (string) $attributes['rel'] : '';

$classes = array();
if ( '' !== $uid ) {
	$classes[] = 'bsy-button-' . preg_replace( '/[^a-z0-9_-]/i', '', $uid );
}
if ( $preset && 'fill' !== $preset ) {
	$classes[] = 'is-style-' . preg_replace( '/[^a-z0-9_-]/i', '', $preset );
}
// `bsy-bg-{slug}` — lets a paired theme retint the button text per bg slug via
// the `--blocksify--button--text-color` hook (style.css). A Blocksify-OWN
// class on purpose: the WP-standard `has-{slug}-background-color` would trip WP
// core's `.has-{slug}-background-color { background-color: … !important }` rule,
// which locks the bg and blocks Hover-BG / any bg override. Effective bg:
// explicit palette-slug var → that slug; explicit custom value → no class
// (Blocksify keeps per-instance bg control); no explicit bg → the static
// default (`primary` for Fill, none for Outline). Default Fill has NO
// backgroundColor attr (bg comes from style.css), so the slug is inferred.
// Mirrors the JS twin in edit.js for editor ↔ frontend parity.
$bg_raw  = ( isset( $attributes['backgroundColor'] ) && is_string( $attributes['backgroundColor'] ) )
	? $attributes['backgroundColor']
	: '';
$bg_slug = null;
if ( '' !== $bg_raw ) {
	if ( preg_match( '/--wp--preset--color--([\w-]+)/', $bg_raw, $bg_m ) ) {
		$bg_slug = $bg_m[1];
	}
} elseif ( 'outline' !== $preset ) {
	$bg_slug = 'primary';
}
if ( null !== $bg_slug ) {
	$classes[] = 'bsy-bg-' . preg_replace( '/[^a-z0-9_-]/i', '', $bg_slug );
}
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 ) . '"';
}
if ( '' !== $href ) {
	$attrs_html .= ' href="' . esc_url( $href ) . '"';
}
if ( '' !== $link_target ) {
	$attrs_html .= ' target="' . esc_attr( $link_target ) . '"';
}
if ( '' !== $rel ) {
	$attrs_html .= ' rel="' . esc_attr( $rel ) . '"';
}

// Build inner: icon span (resolved by Blocksify_Icons — handles Lucide
// sparse bag, custom-svg paste, and inline-svg upload via the bag's
// `svg` field short-circuit) + content text.
$icon_svg = Blocksify_Icons::render( $icon );
$icon_url = ( $icon && '' === $icon_svg && isset( $icon['url'] ) && is_string( $icon['url'] ) ) ? $icon['url'] : '';

$icon_html = '';
if ( '' !== $icon_svg ) {
	// SVG sanitized at JS commit time; safe to inline.
	$icon_html = '<span class="bsy-button__icon">' . $icon_svg . '</span>';
} elseif ( '' !== $icon_url ) {
	$icon_html = '<span class="bsy-button__icon"><img class="bsy-icon__img" src="' . esc_url( $icon_url ) . '" alt="" /></span>';
}

// Content — three modes:
//
//   - 'text' (default) / 'html' → use the `content` attribute string
//     authored via `<RichText>` in edit.js. RichText sanitizes formatting
//     marks at commit; safe to inline.
//   - 'blocks' → InnerBlocks variant. `save.js` returns `<InnerBlocks.
//     Content />` for this mode so WP serializes the dropped children
//     inside the parent's comment delimiter; WP renders those children
//     and passes the result via `$content` to this template. We inline
//     `$content` directly. Unlike text/html, the children may include
//     other dynamic blocks whose own `render_block()` already escaped
//     output appropriately.
$content_inner = ( 'blocks' === $content_type )
	? (string) $content
	: $content_text;

$inner_parts = array();
if ( '' !== $icon_html && 'after' !== $icon_position ) {
	$inner_parts[] = $icon_html;
}
$inner_parts[] = $content_inner;
if ( '' !== $icon_html && 'after' === $icon_position ) {
	$inner_parts[] = $icon_html;
}
$inner_html = implode( '', $inner_parts );

// `.bsy-block-root` outer wrapper — block-level box that absorbs the
// theme's content-area treatment. Block themes ship
// `.is-layout-constrained > * { width: var(--theme-block-width);
// margin-inline: auto }` which would otherwise stretch our
// `inline-flex` Button full content-column width when it sits at
// root level of post content. Wrapper takes the stretch; inner
// `<a class="bsy-button-{uid}">` stays content-fit, at LEFT of
// wrapper = at content-column-LEFT (aligned with sibling text /
// heading blocks).
//
// Wrapper is conditional: emitted ONLY when Button is at root level
// of post content. Inside Container / Section / future Pro parents,
// the providesContext signal `blocksify/innerBlock` (declared in each
// parent's `block.json`) resolves to a non-empty uid → `$is_nested`
// becomes truthy → wrapper skipped. Container handles its own flex
// layout with the Button as direct child, no extra DOM hop.
//
// Editor side mirrors via `<BlockRoot>` chrome (`shared/chrome/
// BlockRoot/index.js`), conditional on `getBlockRootClientId`.
$is_nested = ! empty( $block->context['blocksify/innerBlock'] );
$open      = $is_nested ? '' : '<div class="bsy-block-root">';
$close     = $is_nested ? '' : '</div>';

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