<?php
/**
 * Brentwood Interlinking — front-end render.
 *
 * A single image tile: full-bleed image, a bottom-right label pill (label text +
 * chevron), an optional subtext line below it, and a hover border-reveal at the
 * bottom. Shape is driven by the aspect-ratio attribute so the same block works
 * at any size inside a Columns / Row layout.
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Inner content (unused — dynamic block).
 * @var WP_Block $block      Block instance.
 */

defined( 'ABSPATH' ) || exit;

/**
 * Accept a CSS colour only if it is a hex, rgb()/rgba() or CSS var() value.
 * Anything else falls back to the supplied default.
 */
if ( ! function_exists( 'bw_il_color' ) ) {
	function bw_il_color( $value, $default = '' ) {
		$value = trim( (string) $value );
		if ( '' === $value ) {
			return $default;
		}
		if ( preg_match( '/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i', $value )
			|| preg_match( '/^rgba?\(\s*[0-9.,%\s\/]+\)$/i', $value )
			|| preg_match( '/^var\(\s*--[a-z0-9\-]+\s*\)$/i', $value ) ) {
			return $value;
		}
		return $default;
	}
}

// ── sanitize attributes ─────────────────────────────────────────────────────

$behavior     = $attributes['behavior'] ?? 'link';
if ( ! in_array( $behavior, array( 'accordion', 'modal', 'link' ), true ) ) {
	$behavior = 'link';
}

// Modal anchor (only meaningful in modal mode). When set, the block becomes a
// HEADLESS popup: no visible tile — it is opened solely via a text link to
// "#<anchor>". A virtual anchor matched by the view script (see render below).
$modal_anchor = ( 'modal' === $behavior )
	? sanitize_title( (string) ( $attributes['modalAnchor'] ?? '' ) )
	: '';
$image_url    = esc_url_raw( $attributes['imageUrl'] ?? '' );
$image_alt    = sanitize_text_field( $attributes['imageAlt'] ?? '' );
$label        = sanitize_text_field( $attributes['label'] ?? '' );
$link_url     = esc_url_raw( $attributes['linkUrl'] ?? '' );
$link_new_tab = ! empty( $attributes['linkNewTab'] );
$show_subtext = ! empty( $attributes['showSubtext'] );
$subtext      = sanitize_text_field( $attributes['subtext'] ?? '' );

$label_color   = bw_il_color( $attributes['labelColor']   ?? '', '#231f20' );
$chevron_color = bw_il_color( $attributes['chevronColor'] ?? '', '#cc0000' );

// Aspect ratio — only "<num> / <num>" is accepted, else fall back to 3 / 2.
$ratio = trim( (string) ( $attributes['ratio'] ?? '3 / 2' ) );
if ( ! preg_match( '/^[0-9]+(\.[0-9]+)?\s*\/\s*[0-9]+(\.[0-9]+)?$/', $ratio ) ) {
	$ratio = '3 / 2';
}

// "Fill section height" is a responsive-only option: on the selected breakpoints
// the tile drops its fixed ratio and stretches to the parent section/column's
// full height (see the .bw-il--fill-* media queries). Desktop always keeps the
// aspect ratio above. Tablet = 768–1024px, Mobile = ≤767px, independent bands.
$fill_tablet = ! empty( $attributes['fillTablet'] );
$fill_mobile = ! empty( $attributes['fillMobile'] );

// Corner radius in px (0–200). When linked, one value drives all four corners;
// when unlinked, each corner has its own value. Falls back to $radius / 8.
$clamp_radius = static function ( $v ) {
	return max( 0, min( 200, (int) $v ) );
};
$radius      = isset( $attributes['radius'] ) ? $clamp_radius( $attributes['radius'] ) : 8;
$radius_link = ! isset( $attributes['radiusLink'] ) || ! empty( $attributes['radiusLink'] );

if ( $radius_link ) {
	$r_tl = $r_tr = $r_br = $r_bl = $radius;
} else {
	$r_tl = isset( $attributes['radiusTL'] ) ? $clamp_radius( $attributes['radiusTL'] ) : $radius;
	$r_tr = isset( $attributes['radiusTR'] ) ? $clamp_radius( $attributes['radiusTR'] ) : $radius;
	$r_br = isset( $attributes['radiusBR'] ) ? $clamp_radius( $attributes['radiusBR'] ) : $radius;
	$r_bl = isset( $attributes['radiusBL'] ) ? $clamp_radius( $attributes['radiusBL'] ) : $radius;
}

// border-radius shorthand order: top-left top-right bottom-right bottom-left.
$radius_css = $r_tl . 'px ' . $r_tr . 'px ' . $r_br . 'px ' . $r_bl . 'px';

// Focal point → object-position percentage.
$fp   = is_array( $attributes['focalPoint'] ?? null ) ? $attributes['focalPoint'] : array();
$fx   = isset( $fp['x'] ) ? max( 0, min( 1, (float) $fp['x'] ) ) : 0.5;
$fy   = isset( $fp['y'] ) ? max( 0, min( 1, (float) $fp['y'] ) ) : 0.5;
$obj  = round( $fx * 100, 2 ) . '% ' . round( $fy * 100, 2 ) . '%';

// A headless (anchor) modal has no tile, so it is allowed to have no image/label.
if ( ! $image_url && ! $label && '' === $modal_anchor ) {
	return; // nothing to show
}

// ── chevron (FontAwesome angle-right) ─────────────────────────────────────────
$chevron = '<svg class="bw-il__chevron" viewBox="0 0 320 512" role="img" aria-hidden="true" focusable="false">'
	. '<path fill="currentColor" d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"></path>'
	. '</svg>';

// ── inner markup ──────────────────────────────────────────────────────────────
$img_html = $image_url
	? sprintf(
		'<img class="bw-il__img" src="%s" alt="%s" loading="lazy" style="object-position:%s">',
		esc_url( $image_url ),
		esc_attr( $image_alt ),
		esc_attr( $obj )
	)
	: '<span class="bw-il__img bw-il__img--empty" aria-hidden="true"></span>';

$pill = '';
if ( '' !== $label ) {
	$pill = '<span class="bw-il__pill" style="color:' . esc_attr( $label_color ) . '">'
		. '<span class="bw-il__label">' . esc_html( $label ) . '</span>'
		. '<span class="bw-il__chevron-wrap" style="color:' . esc_attr( $chevron_color ) . '">' . $chevron . '</span>'
		. '</span>';
}

$sub = ( $show_subtext && '' !== $subtext )
	? '<span class="bw-il__subtext">' . esc_html( $subtext ) . '</span>'
	: '';

$overlay = '<span class="bw-il__overlay">' . $pill . $sub . '</span>';

$inner = $img_html . $overlay;

// ── wrapper ──────────────────────────────────────────────────────────────────
$style = '--bw-il-ratio:' . esc_attr( $ratio ) . ';--bw-il-radius:' . esc_attr( $radius_css ) . ';';

// Responsive fill-height classes (scoped to their breakpoints in CSS).
$fill_class = '';
if ( $fill_tablet ) { $fill_class .= ' bw-il--fill-tablet'; }
if ( $fill_mobile ) { $fill_class .= ' bw-il--fill-mobile'; }

// Accordion mode: the tile is a <button> that toggles a panel rendered below it.
// $content holds the rendered InnerBlocks (the panel content).
if ( 'accordion' === $behavior ) {
	$uid      = wp_unique_id( 'bw-il-' );
	$tab_id   = $uid . '-tab';
	$panel_id = $uid . '-panel';

	// Panel content width (% of the block) and horizontal alignment.
	$panel_width = max( 20, min( 100, (int) ( $attributes['panelWidth'] ?? 63 ) ) );
	$panel_align = $attributes['panelAlign'] ?? 'right';
	if ( ! in_array( $panel_align, array( 'left', 'center', 'right' ), true ) ) {
		$panel_align = 'right';
	}

	$aria_label = ( '' !== $label ) ? ' aria-label="' . esc_attr( $label ) . '"' : '';

	$button = '<button type="button" class="bw-il bw-il--button' . $fill_class . '" style="' . esc_attr( $style ) . '"'
		. ' id="' . esc_attr( $tab_id ) . '" aria-controls="' . esc_attr( $panel_id ) . '" aria-expanded="false"'
		. $aria_label . '>' . $inner . '</button>';

	$close = '<button type="button" class="bw-il__close" aria-label="Close">'
		. '<svg viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false">'
		. '<path fill="currentColor" d="M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7A1 1 0 1 0 5.7 7.11L10.59 12 5.7 16.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z"></path>'
		. '</svg></button>';

	$panel = '<div class="bw-il__panel bw-il__panel--' . esc_attr( $panel_align ) . '" id="' . esc_attr( $panel_id ) . '"'
		. ' role="region" aria-labelledby="' . esc_attr( $tab_id ) . '"'
		. ' style="--bw-il-panel-w:' . $panel_width . '%" hidden>'
		. '<div class="bw-il__panel-inner">' . $close . $content . '</div>'
		. '</div>';

	printf(
		'<div %s>%s%s</div>',
		get_block_wrapper_attributes( array( 'class' => 'bw-il-acc' ) ), // phpcs:ignore WordPress.Security.EscapeOutput
		$button, // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above
		$panel   // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above / core blocks self-escape
	);
	return;
}

// Modal mode: the tile is a <button> that opens a centered popup dialog. The
// dialog content is the rendered InnerBlocks ($content), so each tile can have
// its own layout. A native <dialog> gives us Escape, focus-trapping and a
// backdrop for free; the view script adds backdrop-click + body scroll-lock.
if ( 'modal' === $behavior ) {
	$uid       = wp_unique_id( 'bw-il-modal-' );
	$modal_size = $attributes['modalSize'] ?? 'medium';
	if ( ! in_array( $modal_size, array( 'small', 'medium', 'large', 'xlarge', 'full' ), true ) ) {
		$modal_size = 'medium';
	}

	// The anchor (matched by the view script, NOT the dialog's DOM id — so it
	// can't collide with the trigger wiring or scroll the browser to a hidden
	// <dialog>) mirrors the Team Modal's #staff-<slug> pattern. Computed above.
	$is_headless = ( '' !== $modal_anchor );
	$anchor_attr = $is_headless ? ' data-bw-il-anchor="' . esc_attr( $modal_anchor ) . '"' : '';

	$aria_label = ( '' !== $label ) ? ' aria-label="' . esc_attr( $label ) . '"' : '';

	// Headless mode: no visible tile — the popup is opened only via the #anchor
	// text link. Otherwise the tile <button> is the click trigger, as before.
	$button = $is_headless
		? ''
		: '<button type="button" class="bw-il bw-il--button bw-il--modal-trigger' . $fill_class . '" style="' . esc_attr( $style ) . '"'
			. ' aria-haspopup="dialog" data-bw-il-modal="' . esc_attr( $uid ) . '"'
			. $aria_label . '>' . $inner . '</button>';

	$close = '<button type="button" class="bw-il-modal__close" aria-label="Close" data-bw-il-modal-close>'
		. '<svg viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false">'
		. '<path fill="currentColor" d="M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7A1 1 0 1 0 5.7 7.11L10.59 12 5.7 16.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z"></path>'
		. '</svg></button>';

	$dialog = '<dialog id="' . esc_attr( $uid ) . '" class="bw-il-modal bw-il-modal--' . esc_attr( $modal_size ) . '"'
		. $anchor_attr
		. ( ( '' !== $label ) ? ' aria-label="' . esc_attr( $label ) . '"' : '' ) . '>'
		. '<div class="bw-il-modal__inner">'
		. $close
		. '<div class="bw-il-modal__content">' . $content . '</div>'
		. '</div></dialog>';

	$wrap_class = 'bw-il-modal-block' . ( $is_headless ? ' bw-il-modal-block--headless' : '' );

	printf(
		'<div %s>%s%s</div>',
		get_block_wrapper_attributes( array( 'class' => $wrap_class ) ), // phpcs:ignore WordPress.Security.EscapeOutput
		$button, // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above
		$dialog  // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above / core blocks self-escape
	);
	return;
}

if ( $link_url ) {
	$tag   = 'a';
	$attrs = array(
		'class' => 'bw-il bw-il--link' . $fill_class,
		'style' => $style,
	);
	$extra = ' href="' . esc_url( $link_url ) . '"';
	if ( $link_new_tab ) {
		$extra .= ' target="_blank" rel="noopener noreferrer"';
	}
	if ( '' !== $label ) {
		$extra .= ' aria-label="' . esc_attr( $label ) . '"';
	}
} else {
	$tag   = 'div';
	$attrs = array(
		'class' => 'bw-il' . $fill_class,
		'style' => $style,
	);
	$extra = '';
}

printf(
	'<%1$s %2$s%3$s>%4$s</%1$s>',
	$tag, // phpcs:ignore WordPress.Security.EscapeOutput -- literal a|div
	get_block_wrapper_attributes( $attrs ), // phpcs:ignore WordPress.Security.EscapeOutput -- escaped by core
	$extra, // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above
	$inner // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above
);
