<?php
/**
 * Discover Score v2 — aligned with Google Discover best practices (measurable in editor/server).
 * Server buckets (75 pts): image, entities, timeliness, saved OG meta, links, author.
 * Headline quality (25 pts) is computed in assets/script.js. Not scored: max-image-preview, live CWV.
 *
 * @package Newsroom_AI_Assistant
 */

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

if ( ! function_exists( 'nros_discover_score_option_key' ) ) {
	function nros_discover_score_option_key() {
		return 'nros_trend_keyword_cache';
	}
}

if ( ! function_exists( 'nros_discover_normalize_token' ) ) {
	/**
	 * Lowercase + strip accents when the schema engine helper exists.
	 *
	 * @param string $s Raw token.
	 * @return string Normalized for matching.
	 */
	function nros_discover_normalize_token( $s ) {
		$s = mb_strtolower( trim( (string) $s ), 'UTF-8' );
		if ( function_exists( 'nros_global_unaccent' ) ) {
			$s = nros_global_unaccent( $s );
		}
		return preg_replace( '/[^\p{L}\p{N}]+/u', '', $s );
	}
}

if ( ! function_exists( 'nros_discover_build_trend_tokens' ) ) {
	/**
	 * Extract deduplicated tokens (len >= 4) from trend titles for momentum matching.
	 *
	 * @param string[] $titles Trend titles.
	 * @return string[] Normalized tokens.
	 */
	function nros_discover_build_trend_tokens( array $titles ) {
		$out = [];
		foreach ( $titles as $title ) {
			$parts = preg_split( '/[^\p{L}\p{N}]+/u', (string) $title, -1, PREG_SPLIT_NO_EMPTY );
			if ( ! is_array( $parts ) ) {
				continue;
			}
			foreach ( $parts as $p ) {
				$n = nros_discover_normalize_token( $p );
				if ( mb_strlen( $n, 'UTF-8' ) >= 4 ) {
					$out[ $n ] = true;
				}
			}
		}
		return array_keys( $out );
	}
}

if ( ! function_exists( 'nros_refresh_trend_keyword_cache' ) ) {
	/**
	 * Refresh daily trend token cache from Google Trends RSS (same source as Trend Radar).
	 */
	function nros_refresh_trend_keyword_cache() {
		$geo = strtoupper( preg_replace( '/[^A-Z]/i', '', (string) ( function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US' ) ) );
		if ( $geo === '' ) {
			$geo = function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US';
		}
		$titles = [];
		if ( function_exists( 'newsai_google_trends_get_items' ) ) {
			$result = newsai_google_trends_get_items( $geo, 20 );
			if ( ! is_wp_error( $result ) && ! empty( $result['items'] ) ) {
				foreach ( (array) $result['items'] as $item ) {
					$t = trim( (string) ( $item['title'] ?? '' ) );
					if ( $t !== '' ) {
						$titles[] = $t;
					}
				}
			}
		} elseif ( function_exists( 'newsai_ai_fetch_trends_titles' ) ) {
			$titles = newsai_ai_fetch_trends_titles( $geo );
		}
		if ( ! is_array( $titles ) ) {
			$titles = [];
		}
		$tokens = nros_discover_build_trend_tokens( $titles );
		if ( count( $tokens ) > 80 ) {
			$tokens = array_slice( $tokens, 0, 80 );
		}
		update_option(
			nros_discover_score_option_key(),
			[
				'updated' => time(),
				'geo'     => $geo,
				'tokens'  => $tokens,
			],
			false
		);
	}
}

if ( ! function_exists( 'nros_discover_score_schedule_cron' ) ) {
	function nros_discover_score_schedule_cron() {
		if ( ! wp_next_scheduled( 'nros_refresh_trend_keyword_cache' ) ) {
			wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'nros_refresh_trend_keyword_cache' );
		}
	}
}
add_action( 'admin_init', 'nros_discover_score_schedule_cron' );
add_action( 'nros_refresh_trend_keyword_cache', 'nros_refresh_trend_keyword_cache' );

if ( ! function_exists( 'newsai_discover_score_meta_key' ) ) {
	function newsai_discover_score_meta_key() {
		return '_newsai_discover_score';
	}
}

if ( ! function_exists( 'newsai_discover_score_breakdown_meta_key' ) ) {
	function newsai_discover_score_breakdown_meta_key() {
		return '_newsai_discover_score_breakdown';
	}
}

if ( ! function_exists( 'nros_discover_count_internal_links' ) ) {
	/**
	 * Count same-site links in HTML content.
	 *
	 * @param string $html Post content HTML.
	 * @return int
	 */
	function nros_discover_count_internal_links( $html ) {
		$html = (string) $html;
		if ( $html === '' ) {
			return 0;
		}
		$home = untrailingslashit( home_url( '/' ) );
		$host = (string) wp_parse_url( $home, PHP_URL_HOST );
		if ( $host === '' ) {
			return 0;
		}
		if ( ! preg_match_all( '/<a\s[^>]*href=["\']([^"\']+)["\']/i', $html, $m ) ) {
			return 0;
		}
		$count = 0;
		foreach ( $m[1] as $url ) {
			$url = (string) $url;
			if ( $url === '' || $url[0] === '#' ) {
				continue;
			}
			if ( strpos( $url, '/' ) === 0 && strpos( $url, '//' ) !== 0 ) {
				++$count;
				continue;
			}
			$link_host = (string) wp_parse_url( $url, PHP_URL_HOST );
			if ( $link_host !== '' && strcasecmp( $link_host, $host ) === 0 ) {
				++$count;
			} elseif ( $link_host === '' && strpos( $url, $home ) === 0 ) {
				++$count;
			}
		}
		return $count;
	}
}

if ( ! function_exists( 'nros_discover_content_has_story_cluster' ) ) {
	/**
	 * Detect timeline / story-cluster markup in saved post content.
	 *
	 * @param string $content Post HTML.
	 * @return bool
	 */
	function nros_discover_content_has_story_cluster( $content ) {
		$content = (string) $content;
		if ( $content === '' ) {
			return false;
		}
		return (bool) preg_match( '/\[(newsroom_timeline|nros_timeline)\b/i', $content )
			|| (bool) preg_match( '/(newsai-story-cluster|newsroom-os-story-cluster|nros-timeline|newsroom-timeline|timeline-item|timeline-wrap)/i', $content );
	}
}

if ( ! function_exists( 'nros_discover_is_auto_timeline_active' ) ) {
	/**
	 * Whether Auto-Timeline will inject on publish (Pro + auto mode + not disabled per post).
	 *
	 * @param int      $post_id       Post ID.
	 * @param int|null $editor_flag   1/0 from editor AJAX when set; null = infer from options/meta only.
	 * @return bool
	 */
	function nros_discover_is_auto_timeline_active( $post_id, $editor_flag = null ) {
		if ( ! function_exists( 'newsai_is_pro' ) || ! newsai_is_pro() ) {
			return false;
		}
		if ( get_option( 'newsai_timeline_mode', 'auto' ) !== 'auto' ) {
			return false;
		}
		$post_id = (int) $post_id;
		if ( $post_id > 0 && get_post_meta( $post_id, '_newsai_disable_auto_timeline', true ) === 'yes' ) {
			return false;
		}
		if ( $editor_flag !== null ) {
			return (int) $editor_flag === 1;
		}
		return true;
	}
}

if ( ! function_exists( 'nros_discover_score_momentum' ) ) {
	/**
	 * @param string   $title_norm Normalized title (nros_discover_normalize_token on full title loose — use haystack).
	 * @param string[] $tag_norms    Normalized tag names.
	 * @param string[] $tokens       Cached trend tokens.
	 * @return array{score:int,max:int,matched:string[],title_hit:bool,tag_hit:bool}
	 */
	function nros_discover_score_momentum( $title_norm, array $tag_norms, array $tokens ) {
		$max   = 10;
		$score = 0;
		$matched = [];
		$title_hit = false;
		$tag_hit   = false;
		if ( empty( $tokens ) ) {
			return [
				'score'     => 0,
				'max'       => $max,
				'matched'   => [],
				'title_hit' => false,
				'tag_hit'   => false,
				'reason'    => '',
			];
		}
		$hay = $title_norm;
		foreach ( $tokens as $tok ) {
			if ( $tok === '' || mb_strlen( $tok, 'UTF-8' ) < 4 ) {
				continue;
			}
			if ( mb_stripos( $hay, $tok, 0, 'UTF-8' ) !== false ) {
				$score      = 6;
				$title_hit  = true;
				$matched[]  = $tok;
				break;
			}
		}
		foreach ( $tag_norms as $tn ) {
			if ( $tn === '' ) {
				continue;
			}
			foreach ( $tokens as $tok ) {
				if ( $tok === $tn || mb_stripos( $tn, $tok, 0, 'UTF-8' ) !== false || mb_stripos( $tok, $tn, 0, 'UTF-8' ) !== false ) {
					$add = 4;
					if ( $score + $add > $max ) {
						$add = $max - $score;
					}
					$score     += $add;
					$tag_hit    = true;
					$matched[]  = $tok;
					break 2;
				}
			}
		}
		$matched = array_values( array_unique( array_filter( $matched ) ) );
		$reason  = '';
		if ( $score > 0 ) {
			$reason = __( 'Topic aligns with cached regional trends.', 'newsroom-ai-assistant' );
		}
		return [
			'score'     => min( $max, $score ),
			'max'       => $max,
			'matched'   => array_slice( $matched, 0, 5 ),
			'title_hit' => $title_hit,
			'tag_hit'   => $tag_hit,
			'reason'    => $reason,
		];
	}
}

if ( ! function_exists( 'nros_discover_score_entities' ) ) {
	/**
	 * @param int    $post_id Post ID.
	 * @param string $title   Raw title.
	 * @return array{score:int,max:int,reasons:string[]}
	 */
	function nros_discover_score_entities( $post_id, $title ) {
		$max       = 15;
		$score     = 0;
		$reasons   = [];
		$post_id   = (int) $post_id;
		$tags      = wp_get_post_tags( $post_id, [ 'fields' => 'names' ] );
		$tag_count = is_array( $tags ) ? count( $tags ) : 0;
		if ( $tag_count >= 3 ) {
			$score += 6;
			$reasons[] = __( 'At least three tags set.', 'newsroom-ai-assistant' );
		}
		$cats = get_the_category( $post_id );
		$has_non_default = false;
		if ( is_array( $cats ) ) {
			foreach ( $cats as $c ) {
				if ( $c && isset( $c->slug ) && $c->slug !== 'uncategorized' ) {
					$has_non_default = true;
					break;
				}
			}
		}
		if ( $has_non_default ) {
			$score += 4;
			$reasons[] = __( 'Primary category is set (not Uncategorized).', 'newsroom-ai-assistant' );
		}
		$city_bonus = 0;
		if ( function_exists( 'nros_get_global_cities_graph' ) ) {
			$graph = nros_get_global_cities_graph();
			$blob  = nros_discover_normalize_token( $title );
			foreach ( $tags as $tn ) {
				$blob .= nros_discover_normalize_token( (string) $tn );
			}
			foreach ( array_keys( $graph ) as $city_key ) {
				if ( $city_key !== '' && mb_stripos( $blob, $city_key, 0, 'UTF-8' ) !== false ) {
					$city_bonus = 5;
					$reasons[]  = __( 'Place / city entity detected in title or tags.', 'newsroom-ai-assistant' );
					break;
				}
			}
		}
		$score += $city_bonus;

		if ( function_exists( 'newsai_ai_get_post_analysis_for_discover' ) ) {
			$ai_analysis = newsai_ai_get_post_analysis_for_discover( $post_id );
			if ( is_array( $ai_analysis ) ) {
				$ai_entities = isset( $ai_analysis['entities'] ) && is_array( $ai_analysis['entities'] ) ? count( $ai_analysis['entities'] ) : 0;
				$has_primary = ! empty( $ai_analysis['primary_entity'] ) || ! empty( $ai_analysis['primary_keyword'] );
				if ( $has_primary && $score < $max ) {
					$bonus = min( 4, $max - $score );
					$score += $bonus;
					$reasons[] = __( 'AI SEO entity map saved for this article (AI Assist).', 'newsroom-ai-assistant' );
				} elseif ( $ai_entities >= 2 && $score < $max ) {
					$bonus = min( 2, $max - $score );
					$score += $bonus;
					$reasons[] = __( 'Multiple entities detected in AI SEO analysis.', 'newsroom-ai-assistant' );
				}
			}
		}

		return [
			'score'   => min( $max, $score ),
			'max'     => $max,
			'reasons' => $reasons,
		];
	}
}

if ( ! function_exists( 'nros_discover_score_author_eeat' ) ) {
	/**
	 * @param int $author_id User ID.
	 * @return array{score:int,max:int,missing:string[]}
	 */
	function nros_discover_score_author_eeat( $author_id ) {
		$max     = 5;
		$score   = 0;
		$missing = [];
		$author_id = (int) $author_id;
		if ( $author_id < 1 ) {
			return [ 'score' => 0, 'max' => $max, 'missing' => [ 'author' ] ];
		}
		$bio = (string) get_the_author_meta( 'description', $author_id );
		if ( mb_strlen( wp_strip_all_tags( $bio ), 'UTF-8' ) >= 50 ) {
			$score += 2;
		} else {
			$missing[] = 'bio';
		}
		$job = (string) get_the_author_meta( 'nros_job_title', $author_id );
		if ( trim( $job ) !== '' ) {
			$score += 2;
		} else {
			$missing[] = 'jobTitle';
		}
		$socials = [
			get_the_author_meta( 'nros_social_facebook', $author_id ),
			get_the_author_meta( 'nros_social_twitter', $author_id ),
			get_the_author_meta( 'nros_social_linkedin', $author_id ),
		];
		$has_social = false;
		foreach ( $socials as $u ) {
			if ( is_string( $u ) && filter_var( $u, FILTER_VALIDATE_URL ) ) {
				$has_social = true;
				break;
			}
		}
		if ( $has_social ) {
			$score += 1;
		} else {
			$missing[] = 'sameAs';
		}
		return [
			'score'   => min( $max, $score ),
			'max'     => $max,
			'missing' => $missing,
		];
	}
}

if ( ! function_exists( 'nros_discover_score_image' ) ) {
	/**
	 * @param int $attachment_id Featured image attachment ID.
	 * @return array{score:int,max:int,status:string,width:int,alt_ok:bool,reasons:string[]}
	 */
	function nros_discover_score_image( $attachment_id ) {
		$max     = 35;
		$score   = 0;
		$reasons = [];
		$attachment_id = (int) $attachment_id;
		if ( $attachment_id < 1 ) {
			return [
				'score'      => 0,
				'max'        => $max,
				'status'     => 'missing',
				'width'      => 0,
				'height'     => 0,
				'ratio'      => 0.0,
				'pixels'     => 0,
				'ratio_16x9' => false,
				'alt_ok'     => false,
				'reasons'    => [ __( 'No featured image.', 'newsroom-ai-assistant' ) ],
			];
		}
		$img = wp_get_attachment_image_src( $attachment_id, 'full' );
		if ( ! $img || empty( $img[1] ) || empty( $img[2] ) ) {
			return [
				'score'      => 0,
				'max'        => $max,
				'status'     => 'missing',
				'width'      => 0,
				'height'     => 0,
				'ratio'      => 0.0,
				'pixels'     => 0,
				'ratio_16x9' => false,
				'alt_ok'     => false,
				'reasons'    => [ __( 'Featured image metadata missing.', 'newsroom-ai-assistant' ) ],
			];
		}
		$w      = (int) $img[1];
		$h      = (int) $img[2];
		$r      = $h > 0 ? $w / $h : 0.0;
		$pixels = $w * $h;
		$ratio_16x9 = $r >= 1.72 && $r <= 1.85;
		$status = 'small';
		if ( $w >= 1200 ) {
			$score += 14;
			$status = 'ok';
			$reasons[] = __( 'Hero width is at least 1200px (Google Discover).', 'newsroom-ai-assistant' );
		} else {
			$reasons[] = __( 'Use a hero image at least 1200px wide for Discover.', 'newsroom-ai-assistant' );
		}
		if ( $ratio_16x9 ) {
			$score += 8;
			$reasons[] = __( '16:9 aspect ratio (Discover recommendation).', 'newsroom-ai-assistant' );
		} else {
			$reasons[] = __( 'Prefer a 16:9 landscape hero (e.g. 1280×720).', 'newsroom-ai-assistant' );
		}
		if ( $pixels >= 300000 ) {
			$score += 8;
			$reasons[] = __( 'High resolution (300,000+ pixels).', 'newsroom-ai-assistant' );
		} else {
			$reasons[] = __( 'Use higher resolution (300,000+ total pixels, e.g. 1280×720).', 'newsroom-ai-assistant' );
		}
		$alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
		$alt_ok = is_string( $alt ) && mb_strlen( trim( $alt ), 'UTF-8' ) > 10;
		if ( $alt_ok ) {
			$score += 5;
			$reasons[] = __( 'Image has descriptive alt text.', 'newsroom-ai-assistant' );
		} else {
			$reasons[] = __( 'Add descriptive alt text (10+ characters) on the featured image.', 'newsroom-ai-assistant' );
		}
		return [
			'score'      => min( $max, $score ),
			'max'        => $max,
			'status'     => $status,
			'width'      => $w,
			'height'     => $h,
			'ratio'      => round( $r, 3 ),
			'pixels'     => $pixels,
			'ratio_16x9' => $ratio_16x9,
			'alt_ok'     => $alt_ok,
			'reasons'    => $reasons,
		];
	}
}

if ( ! function_exists( 'newsai_discover_seo_og_plugin_active' ) ) {
	/**
	 * @return bool True when a supported SEO plugin is loaded (outputs og:image on the front end).
	 */
	function newsai_discover_seo_og_plugin_active() {
		return defined( 'WPSEO_VERSION' )
			|| class_exists( 'WPSEO_Options' )
			|| defined( 'RANK_MATH_VERSION' )
			|| class_exists( 'RankMath' )
			|| defined( 'AIOSEO_VERSION' )
			|| function_exists( 'aioseo' )
			|| defined( 'SEOPRESS_VERSION' )
			|| function_exists( 'seopress_get_service' );
	}
}

if ( ! function_exists( 'newsai_discover_og_explicit_from_aioseo' ) ) {
	/**
	 * @param int $post_id Post ID.
	 * @return array{attachment_id:int,url:string,via:string}
	 */
	function newsai_discover_og_explicit_from_aioseo( $post_id ) {
		$empty = [
			'attachment_id' => 0,
			'url'           => '',
			'via'           => '',
		];
		$post_id = (int) $post_id;
		if ( $post_id < 1 ) {
			return $empty;
		}
		if ( function_exists( 'aioseo' ) && is_object( aioseo() ) && isset( aioseo()->helpers ) && is_object( aioseo()->helpers ) && method_exists( aioseo()->helpers, 'getPost' ) ) {
			$aioseo_post = aioseo()->helpers->getPost( $post_id );
			if ( is_object( $aioseo_post ) ) {
				if ( ! empty( $aioseo_post->og_image_custom_fields ) ) {
					$att_id = (int) $aioseo_post->og_image_custom_fields;
					if ( $att_id > 0 ) {
						return [
							'attachment_id' => $att_id,
							'url'           => '',
							'via'           => 'explicit_id',
						];
					}
				}
				if ( ! empty( $aioseo_post->og_image_custom_url ) && is_string( $aioseo_post->og_image_custom_url ) && filter_var( $aioseo_post->og_image_custom_url, FILTER_VALIDATE_URL ) ) {
					return [
						'attachment_id' => 0,
						'url'           => $aioseo_post->og_image_custom_url,
						'via'           => 'explicit_url',
					];
				}
			}
		}
		$legacy = get_post_meta( $post_id, '_aioseop_opengraph_settings', true );
		if ( is_string( $legacy ) ) {
			$legacy = maybe_unserialize( $legacy );
		}
		if ( is_array( $legacy ) ) {
			if ( ! empty( $legacy['image_attachment_id'] ) ) {
				$att_id = (int) $legacy['image_attachment_id'];
				if ( $att_id > 0 ) {
					return [
						'attachment_id' => $att_id,
						'url'           => '',
						'via'           => 'explicit_id',
					];
				}
			}
			if ( ! empty( $legacy['image'] ) && is_string( $legacy['image'] ) && filter_var( $legacy['image'], FILTER_VALIDATE_URL ) ) {
				return [
					'attachment_id' => 0,
					'url'           => $legacy['image'],
					'via'           => 'explicit_url',
				];
			}
		}
		return $empty;
	}
}

if ( ! function_exists( 'newsai_discover_og_explicit_from_seopress' ) ) {
	/**
	 * @param int $post_id Post ID.
	 * @return array{attachment_id:int,url:string,via:string}
	 */
	function newsai_discover_og_explicit_from_seopress( $post_id ) {
		$empty = [
			'attachment_id' => 0,
			'url'           => '',
			'via'           => '',
		];
		$post_id = (int) $post_id;
		if ( $post_id < 1 ) {
			return $empty;
		}
		$og_id = (int) get_post_meta( $post_id, '_seopress_social_fb_img_attachment_id', true );
		if ( $og_id > 0 ) {
			return [
				'attachment_id' => $og_id,
				'url'           => '',
				'via'           => 'explicit_id',
			];
		}
		$url = get_post_meta( $post_id, '_seopress_social_fb_img', true );
		if ( is_string( $url ) && $url !== '' && filter_var( $url, FILTER_VALIDATE_URL ) ) {
			return [
				'attachment_id' => 0,
				'url'           => $url,
				'via'           => 'explicit_url',
			];
		}
		return $empty;
	}
}

if ( ! function_exists( 'newsai_discover_og_explicit_from_meta' ) ) {
	/**
	 * Custom OG image stored in Yoast / Rank Math postmeta (Social tab).
	 *
	 * @param int $post_id Post ID.
	 * @return array{attachment_id:int,url:string,via:string}
	 */
	function newsai_discover_og_explicit_from_meta( $post_id ) {
		$post_id = (int) $post_id;
		$og_id   = (int) get_post_meta( $post_id, '_yoast_wpseo_opengraph-image-id', true );
		if ( $og_id < 1 ) {
			$og_id = (int) get_post_meta( $post_id, 'rank_math_facebook_image_id', true );
		}
		if ( $og_id > 0 ) {
			return [
				'attachment_id' => $og_id,
				'url'           => '',
				'via'           => 'explicit_id',
			];
		}
		$url = get_post_meta( $post_id, '_yoast_wpseo_opengraph-image', true );
		if ( ! is_string( $url ) || $url === '' || ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
			$url = get_post_meta( $post_id, 'rank_math_facebook_image', true );
		}
		if ( is_string( $url ) && filter_var( $url, FILTER_VALIDATE_URL ) ) {
			return [
				'attachment_id' => 0,
				'url'           => $url,
				'via'           => 'explicit_url',
			];
		}
		foreach ( [ 'newsai_discover_og_explicit_from_aioseo', 'newsai_discover_og_explicit_from_seopress' ] as $fn ) {
			if ( ! function_exists( $fn ) ) {
				continue;
			}
			$from = $fn( $post_id );
			if ( $from['via'] !== '' ) {
				return $from;
			}
		}
		return [
			'attachment_id' => 0,
			'url'           => '',
			'via'           => '',
		];
	}
}

if ( ! function_exists( 'newsai_discover_og_featured_fallback_allowed' ) ) {
	/**
	 * Whether the active SEO plugin will typically emit og:image from the featured image when no custom OG is set.
	 *
	 * @param int $post_id Post ID.
	 * @return bool
	 */
	function newsai_discover_og_featured_fallback_allowed( $post_id ) {
		$post_id = (int) $post_id;
		if ( $post_id < 1 || ! newsai_discover_seo_og_plugin_active() ) {
			return false;
		}
		if ( class_exists( 'WPSEO_Options' ) ) {
			$site_og = WPSEO_Options::get( 'opengraph' );
			if ( $site_og === false ) {
				return false;
			}
		}
		if ( class_exists( 'WPSEO_Meta' ) ) {
			$yoast_og = WPSEO_Meta::get_value( 'opengraph', $post_id );
			if ( $yoast_og === '0' || $yoast_og === 0 ) {
				return false;
			}
		}
		if ( function_exists( 'rank_math' ) ) {
			$rm_site = rank_math()->settings->get( 'titles.fb_enable', 'on' );
			if ( $rm_site === 'off' || $rm_site === false ) {
				return false;
			}
		}
		$rm_post = get_post_meta( $post_id, 'rank_math_facebook_enable_image', true );
		if ( $rm_post === 'off' ) {
			return false;
		}
		return true;
	}
}

if ( ! function_exists( 'nros_discover_score_saved_og' ) ) {
	/**
	 * Effective Open Graph image for Discover scoring.
	 *
	 * 1) Explicit social image in Yoast, Rank Math, AIOSEO, or SEOPress postmeta.
	 * 2) Featured image when an SEO plugin is active and uses the standard front-end fallback chain.
	 *
	 * Does not HTTP-fetch the page HTML; aligns with what the SEO plugin outputs via its templates.
	 *
	 * @param int $post_id     Post ID.
	 * @param int $featured_id Featured attachment ID (0 = read from post).
	 * @return array{score:int,max:int,reasons:string[],source:string,attachment_id:int}
	 */
	function nros_discover_score_saved_og( $post_id, $featured_id = 0 ) {
		$max     = 5;
		$score   = 0;
		$reasons = [];
		$source  = 'none';
		$att_id  = 0;
		$post_id = (int) $post_id;
		if ( $post_id < 1 ) {
			return [
				'score'         => 0,
				'max'           => $max,
				'reasons'       => $reasons,
				'source'        => $source,
				'attachment_id' => 0,
			];
		}

		$explicit = newsai_discover_og_explicit_from_meta( $post_id );
		if ( $explicit['via'] === 'explicit_id' ) {
			$score     = 5;
			$source    = 'explicit';
			$att_id    = (int) $explicit['attachment_id'];
			$reasons[] = __( 'Custom Open Graph image in your SEO plugin.', 'newsroom-ai-assistant' );
		} elseif ( $explicit['via'] === 'explicit_url' ) {
			$score     = 5;
			$source    = 'explicit';
			$reasons[] = __( 'Open Graph image URL saved in your SEO plugin.', 'newsroom-ai-assistant' );
		} else {
			$featured_id = (int) $featured_id;
			if ( $featured_id < 1 ) {
				$featured_id = (int) get_post_thumbnail_id( $post_id );
			}
			if ( $featured_id > 0 && newsai_discover_og_featured_fallback_allowed( $post_id ) ) {
				$score     = 4;
				$source    = 'featured_fallback';
				$att_id    = $featured_id;
				$reasons[] = __( 'Open Graph will use the featured image (SEO plugin front-end fallback).', 'newsroom-ai-assistant' );
			} elseif ( ! newsai_discover_seo_og_plugin_active() ) {
				$reasons[] = __( 'Install Yoast SEO, Rank Math, AIOSEO, or SEOPress to manage Open Graph images.', 'newsroom-ai-assistant' );
			} elseif ( $featured_id < 1 ) {
				$reasons[] = __( 'Set a featured image or a custom OG image in your SEO plugin.', 'newsroom-ai-assistant' );
			} else {
				$reasons[] = __( 'Set a custom OG image in your SEO plugin or enable featured-image social fallback.', 'newsroom-ai-assistant' );
			}
		}

		$out = [
			'score'         => min( $max, $score ),
			'max'           => $max,
			'reasons'       => $reasons,
			'source'        => $source,
			'attachment_id' => $att_id,
		];
		return apply_filters( 'nros_discover_score_og_result', $out, $post_id, $featured_id );
	}
}

if ( ! function_exists( 'newsai_ajax_discover_score_partial' ) ) {
	/**
	 * Server-side Discover Score buckets (75 pts). Client merges headline (25 pts).
	 */
	function newsai_ajax_discover_score_partial() {
		newsai_editor_ajax_gate();
		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		if ( $post_id < 1 ) {
			wp_send_json_error( [ 'message' => 'invalid_post' ], 400 );
		}
		newsai_editor_ajax_require_post_edit( $post_id );

		$title   = isset( $_POST['title'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['title'] ) ) : '';
		$content = isset( $_POST['content'] ) ? (string) wp_unslash( $_POST['content'] ) : '';
		if ( $content === '' ) {
			$post = get_post( $post_id );
			$content = $post ? (string) $post->post_content : '';
		}
		if ( $title === '' ) {
			$post = get_post( $post_id );
			$title = $post ? (string) $post->post_title : '';
		}

		$featured_id = isset( $_POST['featured_id'] ) ? (int) $_POST['featured_id'] : 0;
		if ( $featured_id < 1 ) {
			$featured_id = (int) get_post_thumbnail_id( $post_id );
		}
		$auto_timeline_on = nros_discover_is_auto_timeline_active(
			$post_id,
			isset( $_POST['auto_timeline_on'] ) ? (int) $_POST['auto_timeline_on'] : null
		);

		$weights = apply_filters(
			'nros_discover_score_weights',
			[
				'image'    => 35,
				'momentum' => 10,
				'entities' => 15,
				'og'       => 5,
				'author'   => 5,
				'links'    => 5,
			]
		);

		$cache   = get_option( nros_discover_score_option_key(), [] );
		$post_geo = function_exists( 'newsai_get_post_target_geo' ) ? newsai_get_post_target_geo( $post_id ) : ( function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US' );
		$tokens  = function_exists( 'nros_get_trend_tokens_for_geo' )
			? nros_get_trend_tokens_for_geo( $post_geo )
			: ( ( isset( $cache['tokens'] ) && is_array( $cache['tokens'] ) ) ? $cache['tokens'] : [] );
		if ( empty( $tokens ) && ! get_transient( 'nros_trend_keyword_refresh_lock' ) ) {
			set_transient( 'nros_trend_keyword_refresh_lock', 1, 45 );
			if ( function_exists( 'nros_refresh_trend_keyword_cache' ) ) {
				nros_refresh_trend_keyword_cache();
			}
			delete_transient( 'nros_trend_keyword_refresh_lock' );
			$tokens = function_exists( 'nros_get_trend_tokens_for_geo' )
				? nros_get_trend_tokens_for_geo( $post_geo )
				: [];
		}
		$updated = isset( $cache['updated'] ) ? (int) $cache['updated'] : 0;
		$age_h   = $updated > 0 ? max( 0, ( time() - $updated ) / HOUR_IN_SECONDS ) : 999;

		$title_blob = nros_discover_normalize_token( preg_replace( '/\s+/u', '', $title ) );
		$tag_objs   = wp_get_post_tags( $post_id, [ 'fields' => 'names' ] );
		$tag_norms  = [];
		if ( is_array( $tag_objs ) ) {
			foreach ( $tag_objs as $nm ) {
				$tag_norms[] = nros_discover_normalize_token( (string) $nm );
			}
		}

		$img_pack = nros_discover_score_image( $featured_id );
		// Rescale image bucket if weights differ.
		$img_score = (int) round( ( $img_pack['score'] / max( 1, $img_pack['max'] ) ) * (int) $weights['image'] );

		$mom_raw = nros_discover_score_momentum( $title_blob, $tag_norms, $tokens );
		$mom     = (int) round( ( $mom_raw['score'] / max( 1, $mom_raw['max'] ) ) * (int) $weights['momentum'] );
		if ( $auto_timeline_on && $mom < (int) $weights['momentum'] ) {
			$mom = (int) $weights['momentum'];
			$mom_raw['score'] = (int) $mom_raw['max'];
			$mom_raw['reason'] = __( 'Auto Timeline is active, so timeliness receives full points.', 'newsroom-ai-assistant' );
			$mom_raw['auto_timeline'] = true;
		}

		$ent_raw = nros_discover_score_entities( $post_id, $title );
		$ent     = (int) round( ( $ent_raw['score'] / max( 1, $ent_raw['max'] ) ) * (int) $weights['entities'] );

		$post     = get_post( $post_id );
		$author_id = $post ? (int) $post->post_author : 0;
		$auth_raw  = nros_discover_score_author_eeat( $author_id );
		$auth      = (int) round( ( $auth_raw['score'] / max( 1, $auth_raw['max'] ) ) * (int) $weights['author'] );

		$og_raw = nros_discover_score_saved_og( $post_id, $featured_id );
		$og     = (int) round( ( $og_raw['score'] / max( 1, $og_raw['max'] ) ) * (int) $weights['og'] );

		$link_n          = nros_discover_count_internal_links( $content );
		$story           = nros_discover_content_has_story_cluster( $content );
		$links_covered   = $story || $auto_timeline_on;
		$link_score      = 0;
		if ( $link_n >= 2 || $links_covered ) {
			$link_score = (int) $weights['links'];
		} elseif ( $link_n === 1 ) {
			$link_score = (int) max( 1, round( (int) $weights['links'] * 0.4 ) );
		}

		$partial_max = (int) $weights['image'] + (int) $weights['momentum'] + (int) $weights['entities']
			+ (int) $weights['og'] + (int) $weights['author'] + (int) $weights['links'];
		$partial     = min( $partial_max, $img_score + $mom + $ent + $og + $auth + $link_score );

		$structure_pack = function_exists( 'nros_readiness_analyze_structure_pack' )
			? nros_readiness_analyze_structure_pack( $content, $post_id, $auto_timeline_on )
			: ( function_exists( 'nros_readiness_analyze_structure' )
				? nros_readiness_analyze_structure( $content, $post_id, $auto_timeline_on )
				: [ 'score' => 0, 'max' => 100, 'pct' => 0, 'actions' => [], 'checks' => [] ] );
		$entity_pct     = function_exists( 'nros_readiness_entity_pillar_pct' )
			? nros_readiness_entity_pillar_pct( $ent_raw, $post_id )
			: 0;

		wp_send_json_success(
			[
				'partial'               => $partial,
				'partial_max'           => $partial_max,
				'headline_max'          => 25,
				'trend_cache_age_hours' => round( $age_h, 1 ),
				'trend_tokens_count'    => count( $tokens ),
				'entity_pillar_pct'     => $entity_pct,
				'structure'             => $structure_pack,
				'breakdown'             => [
					'image'    => array_merge( $img_pack, [ 'weighted' => $img_score, 'weight_max' => (int) $weights['image'] ] ),
					'momentum' => array_merge( $mom_raw, [ 'weighted' => $mom, 'weight_max' => (int) $weights['momentum'] ] ),
					'entities' => array_merge( $ent_raw, [ 'weighted' => $ent, 'weight_max' => (int) $weights['entities'] ] ),
					'og'       => array_merge( $og_raw, [ 'weighted' => $og, 'weight_max' => (int) $weights['og'] ] ),
					'author'   => array_merge( $auth_raw, [ 'weighted' => $auth, 'weight_max' => (int) $weights['author'] ] ),
					'links'    => [
						'score'         => $link_score,
						'max'           => (int) $weights['links'],
						'count'         => $link_n,
						'story'         => $story,
						'auto_timeline' => $auto_timeline_on,
						'covered'       => $links_covered,
						'weighted'      => $link_score,
					],
				],
			]
		);
	}
}
add_action( 'wp_ajax_newsai_discover_score_partial', 'newsai_ajax_discover_score_partial' );

if ( ! function_exists( 'newsai_ajax_save_discover_score' ) ) {
	function newsai_ajax_save_discover_score() {
		check_ajax_referer( 'newsai_ajax_nonce', 'nonce' );
		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		$score    = isset( $_POST['score'] ) ? (int) $_POST['score'] : -1;
		if ( $post_id < 1 || $score < 0 || $score > 100 ) {
			wp_send_json_error( [ 'message' => 'invalid_input' ], 400 );
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
		}
		$breakdown = isset( $_POST['breakdown'] ) ? wp_unslash( $_POST['breakdown'] ) : '';
		$prev        = (int) get_post_meta( $post_id, newsai_discover_score_meta_key(), true );
		update_post_meta( $post_id, newsai_discover_score_meta_key(), $score );
		if ( is_string( $breakdown ) && $breakdown !== '' ) {
			$decoded = json_decode( $breakdown, true );
			if ( is_array( $decoded ) ) {
				update_post_meta( $post_id, newsai_discover_score_breakdown_meta_key(), wp_json_encode( $decoded ) );
			}
		}
		if ( function_exists( 'newsai_pro_invalidate_author_performance' ) && ( $prev !== $score || ( is_string( $breakdown ) && $breakdown !== '' ) ) ) {
			newsai_pro_invalidate_author_performance();
		}
		wp_send_json_success( [ 'score' => $score ] );
	}
}
add_action( 'wp_ajax_newsai_save_discover_score', 'newsai_ajax_save_discover_score' );
