<?php
/**
 * Brentwood — Image alt backfill at render time.
 *
 * Part of the "Image Alt Text Migration" (see
 * /srv/apps/brentwood/notes/image-alt-text-migration.md). Many images render
 * with an empty `alt` because the block that outputs them doesn't copy the
 * attachment's alt text:
 *   - the custom `bw/interlinking` card carries `imageId` but emits no alt;
 *   - dynamic blog-thumbnail blocks output the featured image with `alt=""`;
 *   - some Kadence/core image blocks were inserted before the attachment had alt.
 *
 * Rather than editing every block instance, we fill the gap once, at render:
 * for any rendered `<img>` that has NO non-empty alt, resolve its attachment and
 * inject that attachment's `_wp_attachment_image_alt`. The **attachment meta is
 * the single source of truth** — set it once on the media item (durable) and it
 * flows to every place the image renders.
 *
 * Safety:
 *   - Only ever FILLS a blank alt; never overwrites a non-empty one.
 *   - Only acts when an attachment is resolvable AND its meta alt is non-empty,
 *     so decorative theme assets (dash.svg, quote marks, SVG icons — not in the
 *     media library) are left untouched with their empty alt, which is correct.
 *   - Front-end only; leaves the admin/editor markup alone.
 */

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

/**
 * Resolve an attachment ID from a rendered image src, tolerating resized
 * (`-1024x768`) and `.jpg.webp` derivative filenames. Cached per request.
 *
 * @param string $url
 * @return int Attachment ID or 0.
 */
function bw_img_alt_resolve_from_src( $url ) {
	static $cache = array();
	if ( '' === $url ) {
		return 0;
	}
	if ( isset( $cache[ $url ] ) ) {
		return $cache[ $url ];
	}

	// Strip a size suffix that sits before the (possibly double) extension:
	//   large-x.jpg-768x1150.webp -> large-x.jpg.webp
	//   i8300-768x1152.jpg        -> i8300.jpg
	$orig = preg_replace( '/-\d+x\d+(\.[a-z0-9]+)(\.webp)?$/i', '$1$2', $url );

	$id = attachment_url_to_postid( $orig );
	if ( ! $id ) {
		// Fallback: match the file basename against _wp_attached_file.
		global $wpdb;
		$base = basename( wp_parse_url( $orig, PHP_URL_PATH ) );
		if ( '' !== $base ) {
			$id = (int) $wpdb->get_var(
				$wpdb->prepare(
					"SELECT post_id FROM {$wpdb->postmeta}
					  WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s
					  LIMIT 1",
					'%' . $wpdb->esc_like( $base ) . '%'
				)
			);
		}
	}

	$cache[ $url ] = $id ? (int) $id : 0;
	return $cache[ $url ];
}

/**
 * The non-empty attachment alt for an id, cached per request.
 *
 * @param int $id
 * @return string
 */
function bw_img_alt_for( $id ) {
	static $cache = array();
	$id = (int) $id;
	if ( ! $id ) {
		return '';
	}
	if ( isset( $cache[ $id ] ) ) {
		return $cache[ $id ];
	}
	return $cache[ $id ] = trim( (string) get_post_meta( $id, '_wp_attachment_image_alt', true ) );
}

/**
 * Fill blank alts in a rendered block from the images' attachment meta.
 *
 * @param string $html  Rendered block HTML.
 * @param array  $block Parsed block (name + attrs).
 * @return string
 */
function bw_img_alt_render( $html, $block ) {
	if ( is_admin() ) {
		return $html;
	}
	if ( false === stripos( $html, '<img' ) ) {
		return $html;
	}
	// Fast bail if every <img> already has a non-empty alt.
	if ( ! preg_match( '/<img\b(?![^>]*\balt\s*=\s*"[^"]+")[^>]*>/i', $html ) ) {
		return $html;
	}

	// Single-image custom card: trust the block's own imageId.
	$block_img_id = 0;
	if ( isset( $block['blockName'], $block['attrs']['imageId'] )
		&& 'bw/interlinking' === $block['blockName'] ) {
		$block_img_id = (int) $block['attrs']['imageId'];
	}

	return preg_replace_callback(
		'/<img\b[^>]*>/i',
		function ( $m ) use ( $block_img_id ) {
			$tag = $m[0];
			// Already has a non-empty alt → leave it.
			if ( preg_match( '/\balt\s*=\s*"[^"]+"/i', $tag ) ) {
				return $tag;
			}

			$id = $block_img_id;
			if ( ! $id && preg_match( '/wp-image-(\d+)/i', $tag, $c ) ) {
				$id = (int) $c[1];
			}
			if ( ! $id && preg_match( '/\bsrc\s*=\s*"([^"]+)"/i', $tag, $s ) ) {
				$id = bw_img_alt_resolve_from_src( $s[1] );
			}
			if ( ! $id ) {
				return $tag; // unresolvable (e.g. decorative theme asset) — leave blank
			}

			$alt = bw_img_alt_for( $id );
			if ( '' === $alt ) {
				return $tag; // attachment has no alt either — nothing to inject
			}

			$attr = 'alt="' . esc_attr( $alt ) . '"';
			if ( preg_match( '/\balt\s*=\s*"[^"]*"/i', $tag ) ) {
				// Replace an existing empty alt="".
				return preg_replace( '/\balt\s*=\s*"[^"]*"/i', $attr, $tag, 1 );
			}
			// No alt attribute at all — add one.
			return preg_replace( '/<img\b/i', '<img ' . $attr, $tag, 1 );
		},
		$html
	);
}
add_filter( 'render_block', 'bw_img_alt_render', 12, 2 );
