<?php
/**
 * File: modules/header-footer-api.php
 * Endpoint: GET /wp-json/myaio/v1/get-header-footer
 * Optional: ?include_html=1       -> include raw header/footer HTML
 * Optional: ?include_assets=1     -> include parsed links/images arrays
 *
 * Auth:
 * - Header: x-api-key (recommended)
 * - Query/body: key (legacy)
 * - Query/body: api_key (alternate)
 */

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

/**
 * IMPORTANT:
 * - Updated to WP.org-friendly patterns (sanitization, consistent REST permission).
 * - Uses your shared key auth utility: myaio_sac_rest_permission_check().
 * - Output payload shape remains the SAME as your current working response.
 *
 * Ensure your sac-utils.php is loaded before this file, e.g.:
 * require_once plugin_dir_path( __FILE__ ) . '../includes/sac-utils.php';
 */

add_action(
	'rest_api_init',
	function () {
		register_rest_route(
			'myaio/v1',
			'/get-header-footer',
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => 'myaio_sac_get_header_footer',
				'permission_callback' => 'myaio_sac_rest_permission_check',
				'args'                => [
					'include_html'   => [
						'required' => false,
					],
					'include_assets' => [
						'required' => false,
					],
					// kept for backward compatibility; actual auth handled by util
					'key'             => [
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					],
					'api_key'          => [
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					],
				],
			]
		);
	}
);

function myaio_sac_get_header_footer( WP_REST_Request $request ) {
	$include_html   = myaio_sac_bool_param( $request->get_param( 'include_html' ) );
	$include_assets = myaio_sac_bool_param( $request->get_param( 'include_assets' ) );

	// Clean, generic response format for ALL clients (KEEP SAME)
	$payload = [
		'header' => [
			'logo' => [
				'url'    => null,
				'alt'    => null,
				'source' => null,
			],
			'menu'   => [],
			'social' => new stdClass(), // {} instead of []
			'source' => [],
		],
		'footer' => [
			'contact' => [
				'emails'      => [],
				'phones'      => [],
				'address'     => null,
				'footer_text' => null,
			],
			'social' => new stdClass(), // {} instead of []
			'links'  => [],
			'source' => [],
		],
	];

	// Structured first
	$payload['header']['logo'] = myaio_sac_get_logo_structured_first();
	$payload['header']['menu'] = myaio_sac_get_menu_best_effort();

	// Fetch homepage HTML once
	$homepage = home_url( '/' );
	$resp     = wp_remote_get(
		$homepage,
		[
			'timeout'     => 20,
			'redirection' => 5,
			'headers'     => [
				'User-Agent' => 'Mozilla/5.0 (WordPress REST Header/Footer Parser)',
				'Accept'     => 'text/html',
			],
		]
	);

	if ( is_wp_error( $resp ) ) {
		$payload['header']['source'][] = 'structured-only';
		$payload['footer']['source'][] = 'structured-only';

		return new WP_REST_Response(
			[
				'data'    => $payload,
				'message' => 'Could not fetch homepage HTML: ' . $resp->get_error_message(),
			],
			200
		);
	}

	$html = (string) wp_remote_retrieve_body( $resp );
	if ( '' === $html ) {
		$payload['header']['source'][] = 'structured-only';
		$payload['footer']['source'][] = 'structured-only';

		return new WP_REST_Response(
			[
				'data'    => $payload,
				'message' => 'Homepage HTML was empty.',
			],
			200
		);
	}

	// Parse DOM
	$dom = myaio_sac_dom_from_html( $html );
	if ( ! $dom ) {
		return new WP_REST_Response(
			[
				'data'    => $payload,
				'message' => 'Could not parse homepage HTML.',
			],
			200
		);
	}

	$xpath = new DOMXPath( $dom );

	// Detect header/footer nodes
	$header_node = myaio_sac_detect_header_node( $xpath );
	$footer_node = myaio_sac_detect_footer_node( $xpath );

	$header_context = $header_node ? $header_node : $dom->documentElement;
	$footer_context = $footer_node ? $footer_node : $dom->documentElement;

	// Remove noise so text parsing stays clean (Elementor etc.)
	myaio_sac_remove_noise_nodes( $xpath, $header_context );
	myaio_sac_remove_noise_nodes( $xpath, $footer_context );

	// Header links (used for menu + social fallback)
	$header_links = myaio_sac_extract_links_from_node( $xpath, $header_context );

	// Menu fallback
	if ( empty( $payload['header']['menu'] ) ) {
		$payload['header']['menu']     = myaio_sac_links_to_menu_like( $header_links );
		$payload['header']['source'][] = 'menu-from-html';
	} else {
		$payload['header']['source'][] = 'menu-from-wp';
	}

	// Logo fallback
	if ( empty( $payload['header']['logo']['url'] ) ) {
		$payload['header']['logo']     = myaio_sac_detect_logo_from_node( $xpath, $header_context );
		$payload['header']['source'][] = 'logo-from-html';
	} else {
		$payload['header']['source'][] = 'logo-from-structured';
	}

	// Footer links
	$footer_links            = myaio_sac_extract_links_from_node( $xpath, $footer_context );
	$footer_links_normalized = myaio_sac_normalize_links( $footer_links );
	$payload['footer']['links'] = $footer_links_normalized;

	// Social (separate header + footer)
	$header_links_normalized = myaio_sac_normalize_links( $header_links );

	$header_social = myaio_sac_extract_social_links( $header_links_normalized );
	$footer_social = myaio_sac_extract_social_links( $footer_links_normalized );

	$payload['header']['social'] = ! empty( $header_social ) ? $header_social : new stdClass();
	$payload['footer']['social'] = ! empty( $footer_social ) ? $footer_social : new stdClass();

	// Contact
	$payload['footer']['contact'] = myaio_sac_extract_contact_from_footer( $xpath, $footer_context, $payload['footer']['links'] );

	$payload['header']['source'][] = $header_node ? 'header-detected' : 'header-fallback-document';
	$payload['footer']['source'][] = $footer_node ? 'footer-detected' : 'footer-fallback-document';

	// Optional extras
	if ( $include_assets ) {
		$payload['header']['links']  = myaio_sac_normalize_links( $header_links );
		$payload['header']['images'] = myaio_sac_extract_images_from_node( $xpath, $header_context );
		$payload['footer']['images'] = myaio_sac_extract_images_from_node( $xpath, $footer_context );
	}

	if ( $include_html ) {
		$payload['header']['html'] = $header_node ? myaio_sac_outer_html( $header_node ) : null;
		$payload['footer']['html'] = $footer_node ? myaio_sac_outer_html( $footer_node ) : null;
	}

	return new WP_REST_Response(
		[
			'data'   => $payload,
			'source' => 'homepage-html',
		],
		200
	);
}

/* =========================================================================
 * Small helpers
 * ========================================================================= */

/**
 * Treat "1,true,yes,on" as true; anything else false.
 * (So your existing behavior stays predictable for query strings.)
 */
function myaio_sac_bool_param( $v ) {
	if ( is_bool( $v ) ) {
		return $v;
	}
	$v = strtolower( trim( (string) $v ) );
	return in_array( $v, [ '1', 'true', 'yes', 'on' ], true );
}

/* =========================================================================
 * Structured: Logo + Menu
 * ========================================================================= */

function myaio_sac_get_logo_structured_first() {
	$logo_id = get_theme_mod( 'custom_logo' );
	if ( $logo_id ) {
		return [
			'url'    => wp_get_attachment_image_url( $logo_id, 'full' ),
			'alt'    => ( get_post_meta( $logo_id, '_wp_attachment_image_alt', true ) ?: get_bloginfo( 'name' ) ),
			'source' => 'customizer',
		];
	}

	$site_icon_id = get_option( 'site_icon' );
	if ( $site_icon_id ) {
		$src = wp_get_attachment_image_src( $site_icon_id, 'full' );
		if ( ! empty( $src[0] ) ) {
			return [
				'url'    => $src[0],
				'alt'    => get_bloginfo( 'name' ),
				'source' => 'site-icon',
			];
		}
	}

	return [
		'url'    => null,
		'alt'    => null,
		'source' => 'not-found',
	];
}

function myaio_sac_get_menu_best_effort() {
	$locations  = get_nav_menu_locations();
	$registered = function_exists( 'get_registered_nav_menus' ) ? get_registered_nav_menus() : [];
	$candidates = [];

	$try_locations = [ 'primary', 'menu-1', 'main', 'header', 'top', 'primary_menu' ];

	foreach ( $try_locations as $loc ) {
		if ( ! empty( $locations[ $loc ] ) ) {
			$menu_obj = wp_get_nav_menu_object( $locations[ $loc ] );
			if ( $menu_obj ) {
				$items = wp_get_nav_menu_items( $menu_obj->term_id );
				if ( $items ) {
					return myaio_sac_menu_items_to_tree( $items );
				}
			}
		}
	}

	foreach ( $registered as $key => $label ) {
		$hay = strtolower( $key . ' ' . $label );

		if ( false === strpos( $hay, 'header' ) && false === strpos( $hay, 'primary' ) && false === strpos( $hay, 'top' ) ) {
			continue;
		}

		if ( empty( $locations[ $key ] ) ) {
			continue;
		}

		$score = 0;
		if ( false !== strpos( $hay, 'logged out' ) || false !== strpos( $hay, 'logged-out' ) ) {
			$score += 100;
		}
		if ( false !== strpos( $hay, 'header' ) ) {
			$score += 30;
		}
		if ( false !== strpos( $hay, 'primary' ) ) {
			$score += 20;
		}
		if ( false !== strpos( $hay, 'top' ) ) {
			$score += 10;
		}
		if ( false !== strpos( $hay, 'logged in' ) || false !== strpos( $hay, 'logged-in' ) ) {
			$score -= 50;
		}

		$candidates[] = [ 'key' => $key, 'score' => $score ];
	}

	if ( ! empty( $candidates ) ) {
		usort(
			$candidates,
			static function ( $a, $b ) {
				return (int) $b['score'] <=> (int) $a['score'];
			}
		);

		foreach ( $candidates as $c ) {
			$key      = $c['key'];
			$menu_obj = wp_get_nav_menu_object( $locations[ $key ] );
			if ( $menu_obj ) {
				$items = wp_get_nav_menu_items( $menu_obj->term_id );
				if ( $items ) {
					return myaio_sac_menu_items_to_tree( $items );
				}
			}
		}
	}

	$menus = wp_get_nav_menus();
	if ( $menus && ! empty( $menus[0] ) ) {
		$items = wp_get_nav_menu_items( $menus[0]->term_id );
		if ( $items ) {
			return myaio_sac_menu_items_to_tree( $items );
		}
	}

	return [];
}

function myaio_sac_menu_items_to_tree( $items ) {
	$by_id = [];

	foreach ( $items as $item ) {
		$title = isset( $item->title ) ? (string) $item->title : '';
		$by_id[ $item->ID ] = [
			'id'       => (int) $item->ID,
			'title'    => html_entity_decode( $title, ENT_QUOTES | ENT_HTML5, 'UTF-8' ),
			'url'      => isset( $item->url ) ? esc_url_raw( (string) $item->url ) : '',
			'parent'   => (int) $item->menu_item_parent,
			'children' => [],
		];
	}

	$tree = [];
	foreach ( $by_id as $id => &$node ) {
		if ( ! empty( $node['parent'] ) && isset( $by_id[ $node['parent'] ] ) ) {
			$by_id[ $node['parent'] ]['children'][] = &$node;
		} else {
			$tree[] = &$node;
		}
	}
	unset( $node );

	return $tree;
}

/* =========================================================================
 * DOM helpers
 * ========================================================================= */

function myaio_sac_dom_from_html( $html ) {
	if ( ! class_exists( 'DOMDocument' ) ) {
		return null;
	}
	libxml_use_internal_errors( true );
	$dom = new DOMDocument();
	$ok  = @$dom->loadHTML( '<?xml encoding="utf-8" ?>' . (string) $html );
	libxml_clear_errors();
	return $ok ? $dom : null;
}

function myaio_sac_outer_html( DOMNode $node ) {
	$doc = $node->ownerDocument;
	return $doc ? $doc->saveHTML( $node ) : null;
}

function myaio_sac_to_absolute_url( $url ) {
	$url = trim( (string) $url );
	if ( '' === $url ) {
		return null;
	}

	if ( 0 === stripos( $url, 'mailto:' ) || 0 === stripos( $url, 'tel:' ) ) {
		return $url;
	}

	if ( preg_match( '#^https?://#i', $url ) ) {
		return esc_url_raw( $url );
	}

	if ( 0 === strpos( $url, '//' ) ) {
		$scheme = is_ssl() ? 'https:' : 'http:';
		return esc_url_raw( $scheme . $url );
	}

	return esc_url_raw( home_url( '/' ) . ltrim( $url, '/' ) );
}

function myaio_sac_remove_noise_nodes( DOMXPath $xpath, DOMNode $context ) {
	foreach ( [ './/script', './/style', './/noscript' ] as $q ) {
		$nodes = $xpath->query( $q, $context );
		if ( ! $nodes ) {
			continue;
		}

		for ( $i = $nodes->length - 1; $i >= 0; $i-- ) {
			$n = $nodes->item( $i );
			if ( $n && $n->parentNode ) {
				$n->parentNode->removeChild( $n );
			}
		}
	}
}

/* =========================================================================
 * Detect header/footer containers
 * ========================================================================= */

function myaio_sac_detect_header_node( DOMXPath $xpath ) {
	$candidates = [
		'//header',
		"//*[@role='banner']",
		"//*[contains(@id,'masthead')]",
		"//*[contains(concat(' ', normalize-space(@class), ' '), ' site-header ')]",
		"//*[contains(concat(' ', normalize-space(@class), ' '), ' header ')]",
		"//*[contains(concat(' ', normalize-space(@class), ' '), ' elementor-location-header ')]",
		"//*[contains(@id,'header')]",
		"//*[contains(@class,'header')]",
	];

	foreach ( $candidates as $q ) {
		$nodes = $xpath->query( $q );
		if ( $nodes && $nodes->length ) {
			return $nodes->item( 0 );
		}
	}

	return null;
}

function myaio_sac_detect_footer_node( DOMXPath $xpath ) {
	$candidates = [
		'//footer',
		"//*[@role='contentinfo']",
		"//*[@id='colophon']",
		"//*[contains(concat(' ', normalize-space(@class), ' '), ' site-footer ')]",
		"//*[contains(concat(' ', normalize-space(@class), ' '), ' footer ')]",
		"//*[contains(concat(' ', normalize-space(@class), ' '), ' elementor-location-footer ')]",
		"//*[contains(@id,'footer')]",
		"//*[contains(@class,'footer')]",
	];

	foreach ( $candidates as $q ) {
		$nodes = $xpath->query( $q );
		if ( $nodes && $nodes->length ) {
			return $nodes->item( 0 );
		}
	}

	// Content cues
	$cue_queries = [
		"//*[contains(translate(normalize-space(.),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'quick links')]",
		"//*[contains(translate(normalize-space(.),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'contact info')]",
		"//*[contains(translate(normalize-space(.),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'privacy policy')]",
		"//*[contains(normalize-space(.),'©')]",
	];

	foreach ( $cue_queries as $q ) {
		$nodes = $xpath->query( $q );
		if ( $nodes && $nodes->length ) {
			$container = myaio_sac_climb_to_container( $nodes->item( 0 ), 10 );
			if ( $container ) {
				return $container;
			}
		}
	}

	return null;
}

function myaio_sac_climb_to_container( DOMNode $node, $max_levels = 10 ) {
	$cur = $node;

	for ( $i = 0; $i < (int) $max_levels; $i++ ) {
		if ( ! $cur || ! $cur->parentNode ) {
			break;
		}

		if ( $cur instanceof DOMElement ) {
			$tag = strtolower( $cur->tagName );
			$id  = strtolower( (string) $cur->getAttribute( 'id' ) );
			$cls = strtolower( (string) $cur->getAttribute( 'class' ) );

			if ( in_array( $tag, [ 'footer', 'section', 'div' ], true ) ) {
				if ( false !== strpos( $id, 'footer' ) || false !== strpos( $id, 'colophon' ) ) {
					return $cur;
				}
				if ( false !== strpos( $cls, 'footer' ) || false !== strpos( $cls, 'site-footer' ) ) {
					return $cur;
				}
				if ( false !== strpos( $cls, 'elementor-location-footer' ) ) {
					return $cur;
				}
			}
		}

		$cur = $cur->parentNode;
	}

	while ( $node && ! ( $node instanceof DOMElement ) ) {
		$node = $node->parentNode;
	}

	return ( $node instanceof DOMElement ) ? $node : null;
}

/* =========================================================================
 * Extraction (links, images, menu-like, logo fallback)
 * ========================================================================= */

function myaio_sac_extract_links_from_node( DOMXPath $xpath, DOMNode $context ) {
	$links = [];
	$nodes = $xpath->query( ".//a[@href]", $context );

	if ( $nodes ) {
		foreach ( $nodes as $a ) {
			$href = trim( (string) $a->getAttribute( 'href' ) );
			if ( '' === $href || 0 === stripos( $href, 'javascript:' ) ) {
				continue;
			}

			$abs = myaio_sac_to_absolute_url( $href );
			if ( ! $abs ) {
				continue;
			}

			$text  = trim( preg_replace( '/\s+/', ' ', (string) $a->textContent ) );
			$title = trim( (string) $a->getAttribute( 'title' ) );

			$links[] = [
				'href'  => $abs,
				'text'  => $text,
				'title' => $title,
			];
		}
	}

	$seen = [];
	$out  = [];
	foreach ( $links as $l ) {
		$k = ( $l['href'] ?? '' ) . '|' . ( $l['text'] ?? '' ) . '|' . ( $l['title'] ?? '' );
		if ( isset( $seen[ $k ] ) ) {
			continue;
		}
		$seen[ $k ] = true;
		$out[]      = $l;
	}

	return $out;
}

function myaio_sac_extract_images_from_node( DOMXPath $xpath, DOMNode $context ) {
	$images = [];
	$nodes  = $xpath->query( ".//img[@src]", $context );

	if ( $nodes ) {
		foreach ( $nodes as $img ) {
			$src = myaio_sac_to_absolute_url( $img->getAttribute( 'src' ) );
			if ( ! $src ) {
				continue;
			}

			$images[] = [
				'src' => $src,
				'alt' => (string) $img->getAttribute( 'alt' ),
			];
		}
	}

	return $images;
}

function myaio_sac_links_to_menu_like( $links ) {
	$menu = [];
	$i    = 0;

	foreach ( $links as $l ) {
		$text = trim( (string) ( $l['text'] ?? '' ) );
		if ( '' === $text ) {
			continue;
		}

		$menu[] = [
			'id'       => ++$i,
			'title'    => $text,
			'url'      => (string) ( $l['href'] ?? '' ),
			'parent'   => 0,
			'children' => [],
		];
	}

	return $menu;
}

function myaio_sac_normalize_links( $links ) {
	$out = [];
	foreach ( $links as $l ) {
		$href = trim( (string) ( $l['href'] ?? '' ) );
		if ( '' === $href ) {
			continue;
		}

		$out[] = [
			'href'  => $href,
			'text'  => (string) ( $l['text'] ?? '' ),
			'title' => (string) ( $l['title'] ?? '' ),
		];
	}
	return $out;
}

function myaio_sac_detect_logo_from_node( DOMXPath $xpath, DOMNode $header_context ) {
	$queries = [
		".//*[contains(@class,'logo') or contains(@id,'logo')]//img[@src]",
		".//a[contains(@class,'logo') or contains(@id,'logo')]//img[@src]",
		".//img[contains(@class,'logo') or contains(@id,'logo')][@src]",
		".//img[@src]",
	];

	foreach ( $queries as $q ) {
		$imgs = $xpath->query( $q, $header_context );
		if ( ! $imgs || ! $imgs->length ) {
			continue;
		}

		foreach ( $imgs as $img ) {
			$src = myaio_sac_to_absolute_url( $img->getAttribute( 'src' ) );
			if ( ! $src ) {
				continue;
			}

			$alt = trim( (string) $img->getAttribute( 'alt' ) );
			if ( '' === $alt ) {
				$alt = get_bloginfo( 'name' );
			}

			$w = (int) $img->getAttribute( 'width' );
			$h = (int) $img->getAttribute( 'height' );
			if ( ( $w && $w < 40 ) || ( $h && $h < 40 ) ) {
				continue;
			}

			return [
				'url'    => $src,
				'alt'    => $alt,
				'source' => 'header-detected',
			];
		}
	}

	return [
		'url'    => null,
		'alt'    => null,
		'source' => 'not-found',
	];
}

/* =========================================================================
 * Footer: Social + Contact (generic)
 * ========================================================================= */

function myaio_sac_extract_social_links( $links ) {
	$platforms = [
		'facebook'  => 'facebook.com',
		'youtube'   => 'youtube.com',
		'instagram' => 'instagram.com',
		'linkedin'  => 'linkedin.com',
		'twitter'   => 'twitter.com',
		'x'         => 'x.com',
		'tiktok'    => 'tiktok.com',
		'pinterest' => 'pinterest.com',
	];

	$social = [];
	foreach ( $links as $l ) {
		$href = strtolower( (string) ( $l['href'] ?? '' ) );
		foreach ( $platforms as $name => $domain ) {
			if ( $href && false !== strpos( $href, $domain ) ) {
				$social[ $name ] = (string) $l['href'];
			}
		}
	}

	return $social;
}

function myaio_sac_extract_contact_from_footer( DOMXPath $xpath, DOMNode $footer_context, $footer_links ) {
	// Emails/phones from hrefs first (most accurate)
	$emails = [];
	$phones = [];

	foreach ( (array) $footer_links as $l ) {
		$href = (string) ( $l['href'] ?? '' );
		if ( 0 === stripos( $href, 'mailto:' ) ) {
			$emails[] = preg_replace( '/^mailto:/i', '', $href );
		}
		if ( 0 === stripos( $href, 'tel:' ) ) {
			$phones[] = preg_replace( '/^tel:/i', '', $href );
		}
	}

	$emails = array_values( array_unique( array_filter( $emails ) ) );
	$phones = array_values( array_unique( array_filter( $phones ) ) );

	$text_raw  = (string) $footer_context->textContent;
	$text_line = trim( preg_replace( '/\s+/', ' ', $text_raw ) );

	$clean = static function ( $s, $max = 250 ) {
		$s = trim( preg_replace( '/\s+/', ' ', (string) $s ) );
		return ( '' === $s ) ? '' : mb_substr( $s, 0, (int) $max );
	};

	$score_address = static function ( $s ) {
		$s = trim( preg_replace( '/\s+/', ' ', (string) $s ) );
		if ( '' === $s ) {
			return -999;
		}

		$score = 0;

		$street_words = '(street|st\.?|road|rd\.?|avenue|ave\.?|boulevard|blvd\.?|drive|dr\.?|lane|ln\.?|court|ct\.?|circle|cir\.?|way|highway|hwy\.?|place|pl\.?|parkway|pkwy\.?|plaza|square|sq\.?|suite|ste\.?|unit|floor|fl\.?|building|bldg\.?)';
		if ( preg_match( '/\b\d{1,6}\b/i', $s ) ) {
			$score += 2;
		}
		if ( preg_match( '/\b' . $street_words . '\b/i', $s ) ) {
			$score += 4;
		}

		if ( preg_match( '/\b\d{5}(?:-\d{4})?\b/', $s ) ) {
			$score += 5;
		}
		if ( preg_match( '/\b[A-Z]\d[A-Z]\s?\d[A-Z]\d\b/i', $s ) ) {
			$score += 5;
		}
		if ( preg_match( '/\b[A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2}\b/i', $s ) ) {
			$score += 4;
		}
		if ( preg_match( '/\b\d{4}\b/', $s ) ) {
			$score += 1;
		}

		if ( preg_match( '/,\s*[A-Z]{2}\b/', $s ) ) {
			$score += 2;
		}

		if ( preg_match( '/\b(copyright|©|all rights reserved|privacy|terms|newsletter)\b/i', $s ) ) {
			$score -= 6;
		}

		if ( mb_strlen( $s ) > 220 ) {
			$score -= 3;
		}

		if ( ! preg_match( '/\d/', $s )
			&& ! preg_match( '/\b[A-Z]\d[A-Z]\s?\d[A-Z]\d\b/i', $s )
			&& ! preg_match( '/\b[A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2}\b/i', $s )
		) {
			$score -= 3;
		}

		return $score;
	};

	$extract_from_maps_url = static function ( $url ) use ( $clean ) {
		$url = (string) $url;

		if ( preg_match( '/@(-?\d+\.\d+),\s*(-?\d+\.\d+)/', $url, $m ) ) {
			return $clean( $m[1] . ',' . $m[2], 80 );
		}
		if ( preg_match( '/[?&](?:q|query|ll|center)=(-?\d+\.\d+),\s*(-?\d+\.\d+)/', $url, $m ) ) {
			return $clean( $m[1] . ',' . $m[2], 80 );
		}

		if ( preg_match( '/[?&](?:q|query|daddr|destination|address|where)=([^&]+)/i', $url, $m ) ) {
			$p = urldecode( $m[1] );
			$p = str_replace( '+', ' ', $p );
			return $clean( $p, 250 );
		}

		return null;
	};

	$address    = null;
	$best_score = -999;

	$consider = static function ( $cand ) use ( &$address, &$best_score, $clean, $score_address ) {
		$cand = $clean( $cand, 250 );
		if ( '' === $cand ) {
			return;
		}
		$s = $score_address( $cand );
		if ( $s > $best_score ) {
			$best_score = $s;
			$address    = $cand;
		}
	};

	// 1) Maps link-based extraction
	$map_links = $xpath->query( ".//a[@href]", $footer_context );
	if ( $map_links && $map_links->length ) {
		for ( $i = 0; $i < $map_links->length; $i++ ) {
			$href = (string) $map_links->item( $i )->getAttribute( 'href' );
			if ( '' === $href ) {
				continue;
			}

			$is_map =
				( false !== stripos( $href, 'google.com/maps' ) ) ||
				( false !== stripos( $href, 'goo.gl/maps' ) ) ||
				( false !== stripos( $href, 'maps.app.goo.gl' ) ) ||
				( false !== stripos( $href, 'bing.com/maps' ) ) ||
				( false !== stripos( $href, 'apple.com/maps' ) ) ||
				( false !== stripos( $href, 'openstreetmap.org' ) );

			if ( ! $is_map ) {
				continue;
			}

			$from_url = $extract_from_maps_url( $href );
			if ( $from_url ) {
				$consider( $from_url );
			}

			$t = $clean( $map_links->item( $i )->textContent, 250 );
			if ( $t ) {
				$consider( $t );
			}
		}
	}

	// 2) Semantic / class / microdata address nodes
	$addr_selectors = [
		'.//address',
		".//*[@itemprop='address']",
		".//*[contains(translate(@class,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'address')]",
		".//*[contains(translate(@class,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'location')]",
		".//*[contains(translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'address')]",
		".//*[contains(translate(@id,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'location')]",
	];

	foreach ( $addr_selectors as $sel ) {
		$nodes = $xpath->query( $sel, $footer_context );
		if ( ! $nodes || ! $nodes->length ) {
			continue;
		}
		for ( $i = 0; $i < $nodes->length; $i++ ) {
			$consider( $nodes->item( $i )->textContent );
		}
	}

	// 3) Footer HTML -> text with breaks
	if ( ! $address || $best_score < 8 ) {
		$footer_html = myaio_sac_outer_html( $footer_context );

		$h = (string) $footer_html;
		$h = preg_replace( '/<\s*br\s*\/?\s*>/i', "\n", $h );
		$h = preg_replace( '/<\/\s*(p|div|li|tr|section|address)\s*>/i', "\n", $h );
		$h = preg_replace( '/<\s*li\b[^>]*>/i', "\n", $h );

		$footer_text_with_breaks = html_entity_decode( wp_strip_all_tags( $h ), ENT_QUOTES | ENT_HTML5, 'UTF-8' );
		$parts = preg_split( '/\R+|\s*(?:\||•|·|—|–)\s*/u', (string) $footer_text_with_breaks );

		if ( $parts ) {
			foreach ( $parts as $p ) {
				$consider( $p );
			}
		}
	}

	// 4) Windowing around label markers
	if ( ! $address && '' !== $text_line ) {
		$markers = [ 'address', 'location', 'find us', 'visit us', 'office', 'hq', 'headquarters' ];
		foreach ( $markers as $m ) {
			$pos = stripos( $text_line, $m );
			if ( false !== $pos ) {
				$window = mb_substr( $text_line, max( 0, $pos - 90 ), 320 );
				$consider( $window );
			}
		}
	}

	// 5) Last-resort regex patterns
	if ( ! $address && '' !== $text_line ) {
		if ( preg_match( '/(\b\d{1,6}\b[^,]{0,80},\s*[^,]{2,40},\s*[A-Z]{2}\s*\d{5}(?:-\d{4})?)/', $text_line, $m ) ) {
			$consider( $m[1] );
		}
		if ( preg_match( '/(\b\d{1,6}\b[^,]{0,100},\s*[^,]{2,60},\s*[A-Z]{2}\s*[A-Z]\d[A-Z]\s?\d[A-Z]\d)/i', $text_line, $m ) ) {
			$consider( $m[1] );
		}
		if ( preg_match( '/([^,]{2,60},\s*[A-Z]{2}\s*[A-Z]\d[A-Z]\s?\d[A-Z]\d)/i', $text_line, $m ) ) {
			$consider( $m[1] );
		}
	}

	if ( $address && $best_score < 5 ) {
		if ( ! preg_match( '/\d|[A-Z]\d[A-Z]\s?\d[A-Z]\d/i', $address ) ) {
			$address = null;
		}
	}

	$footer_text = trim(
		preg_replace(
			'/\s+/',
			' ',
			html_entity_decode(
				wp_strip_all_tags( (string) $footer_context->textContent ),
				ENT_QUOTES | ENT_HTML5,
				'UTF-8'
			)
		)
	);

	return [
		'emails'      => $emails,
		'phones'      => $phones,
		'address'     => $address,
		'footer_text' => $footer_text ? $footer_text : null,
	];
}
