<?php
/**
 * /photos/{id}/{filename} — rescue the old site's image URLs.
 *
 * The Laravel site served uploads from `/photos/{numeric-id}/{filename}`. The
 * files came across in the migration and kept their filenames, but they now live
 * under `/wp-content/uploads/{year}/{month}/`, so every old image URL 404s.
 *
 * That matters more than a broken image on our own pages would: the 404 log
 * showed these arriving with EXTERNAL referrers — other sites and image search
 * results linking to them. Unlike the cached-old-site traffic in the same log,
 * this does not decay on its own; those links stay broken until the URL works.
 *
 * A redirect rule per image would be unmaintainable, so the filename is looked
 * up instead: `_wp_attached_file` ends with the same basename, whatever folder
 * it landed in. No match means no redirect — the 404 stands, which is correct
 * for an image that genuinely did not come across.
 *
 * @package Kadence-Child
 */

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

add_action(
	'template_redirect',
	function () {
		if ( ! is_404() ) {
			return;
		}

		$path = isset( $_SERVER['REQUEST_URI'] ) ? wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH ) : '';
		if ( ! $path || ! preg_match( '#^/photos/\d+/([^/]+)$#', $path, $m ) ) {
			return;
		}

		// Only ever a bare filename with a media extension — never a path.
		$file = rawurldecode( $m[1] );
		if ( ! preg_match( '/^[A-Za-z0-9._-]+\.(jpe?g|png|gif|webp|svg|avif)$/i', $file ) ) {
			return;
		}

		$key = 'bw_legacy_photo_' . md5( $file );
		$url = wp_cache_get( $key, 'bw-legacy-photos' );

		if ( false === $url ) {
			$url = bw_legacy_photo_lookup( $file );
			// Cached either way, but misses are held far longer. Roughly half of
			// these images were never migrated, the lookup's last resort is an
			// unindexed LIKE scan of postmeta, and these URLs are hit steadily by
			// crawlers — so an uncached miss is the expensive path, and it is also
			// the one that will never change its answer.
			wp_cache_set( $key, $url, 'bw-legacy-photos', $url ? HOUR_IN_SECONDS : DAY_IN_SECONDS );
		}

		if ( $url ) {
			wp_redirect( $url, 301 );
			exit;
		}
	},
	5
);

/**
 * Resolve an old filename to a migrated attachment URL, or '' if it never came across.
 *
 * The importer did not preserve names exactly, and the 404 log shows three
 * distinct outcomes for the same original image:
 *
 *   small-golf_green.jpg   ->  small-golf_green.jpg.webp     (converted, name kept)
 *   small-dorm_room....jpg ->  medium-dorm_room....jpg.webp  (only another size survived)
 *   small-rabbit.jpg       ->  nothing                       (never migrated)
 *
 * So it tries the exact name, then the name with `.webp` appended, and finally
 * the same image at whatever size did survive — preferring `medium` and `large`
 * over `small`, since serving a larger image than requested degrades better than
 * serving a thumbnail. Anything still unmatched keeps its 404, which is the
 * honest answer for an image that is genuinely gone.
 *
 * @param string $file Bare filename from the legacy URL.
 * @return string Attachment URL, or '' when there is no sensible match.
 */
function bw_legacy_photo_lookup( $file ) {
	global $wpdb;

	$exact = array( $file );
	if ( ! preg_match( '/\.webp$/i', $file ) ) {
		$exact[] = $file . '.webp';
	}
	foreach ( $exact as $candidate ) {
		$id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT post_id FROM {$wpdb->postmeta}
				 WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s
				 ORDER BY post_id DESC LIMIT 1",
				'%/' . $wpdb->esc_like( $candidate )
			)
		);
		if ( $id ) {
			return (string) wp_get_attachment_url( (int) $id );
		}
	}

	// Same image, whatever size survived. esc_like matters here: these filenames
	// are full of underscores, which are single-character wildcards in LIKE.
	if ( ! preg_match( '/^(?:small|medium|large|thumb)-(.+)$/i', $file, $m ) ) {
		return '';
	}
	$core = preg_replace( '/\.webp$/i', '', $m[1] );

	$id = $wpdb->get_var(
		$wpdb->prepare(
			"SELECT post_id FROM {$wpdb->postmeta}
			 WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s
			 ORDER BY
			   CASE
			     WHEN meta_value LIKE %s THEN 1
			     WHEN meta_value LIKE %s THEN 2
			     ELSE 3
			   END,
			   post_id DESC
			 LIMIT 1",
			'%' . $wpdb->esc_like( $core ) . '%',
			'%medium-' . $wpdb->esc_like( $core ) . '%',
			'%large-' . $wpdb->esc_like( $core ) . '%'
		)
	);

	return $id ? (string) wp_get_attachment_url( (int) $id ) : '';
}
