<?php
/**
 * Pro AI features (BYOK): SEO analysis, titles, optimize, trends, meta description, tags, key points.
 *
 * All endpoints check the Pro license + provider config, validate nonce, sanitize the
 * post payload, and return uniform JSON. AI calls are routed through newsai_ai_call().
 *
 * @package Newsroom_AI_Assistant
 */

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

if ( ! function_exists( 'newsai_ai_pro_lang' ) ) {
	function newsai_ai_pro_lang() {
		return function_exists( 'newsai_get_ui_lang' ) ? newsai_get_ui_lang() : 'en';
	}
}

if ( ! function_exists( 'newsai_ai_geo_to_locale' ) ) {
	/**
	 * Map the configured geo target to the language code AI providers should answer in.
	 * Falls back to the UI language when geo is unknown.
	 *
	 * @return array{code:string, label:string, native:string}
	 */
	function newsai_ai_geo_to_locale( $geo_override = '' ) {
		$geo = newsai_normalize_geo_code( (string) $geo_override );
		if ( $geo === '' ) {
			$geo = strtoupper( (string) ( function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US' ) );
		}
		// Geo code → [lang code, English label, native name].
		$map = [
			'GR' => [ 'el',    'Greek',                 'Ελληνικά' ],
			'CY' => [ 'el',    'Greek',                 'Ελληνικά' ],
			'US' => [ 'en',    'English (US)',          'English' ],
			'GB' => [ 'en',    'English (UK)',          'English' ],
			'UK' => [ 'en',    'English (UK)',          'English' ],
			'IE' => [ 'en',    'English (IE)',          'English' ],
			'AU' => [ 'en',    'English (AU)',          'English' ],
			'CA' => [ 'en',    'English (CA)',          'English' ],
			'NZ' => [ 'en',    'English (NZ)',          'English' ],
			'IN' => [ 'en',    'English (Indian)',      'English' ],
			'BR' => [ 'pt-BR', 'Brazilian Portuguese',  'Português brasileiro' ],
			'PT' => [ 'pt',    'European Portuguese',   'Português' ],
			'MX' => [ 'es-MX', 'Mexican Spanish',       'Español mexicano' ],
			'ES' => [ 'es',    'Castilian Spanish',     'Español' ],
			'AR' => [ 'es-AR', 'Rioplatense Spanish',   'Español rioplatense' ],
			'CL' => [ 'es-CL', 'Chilean Spanish',       'Español chileno' ],
			'CO' => [ 'es-CO', 'Colombian Spanish',     'Español colombiano' ],
			'PE' => [ 'es-PE', 'Peruvian Spanish',      'Español peruano' ],
			'FR' => [ 'fr',    'French',                'Français' ],
			'BE' => [ 'fr',    'French (BE)',           'Français' ],
			'IT' => [ 'it',    'Italian',               'Italiano' ],
			'DE' => [ 'de',    'German',                'Deutsch' ],
			'AT' => [ 'de',    'Austrian German',       'Deutsch' ],
			'CH' => [ 'de',    'Swiss German',          'Deutsch' ],
			'NL' => [ 'nl',    'Dutch',                 'Nederlands' ],
			'PL' => [ 'pl',    'Polish',                'Polski' ],
			'RO' => [ 'ro',    'Romanian',              'Română' ],
			'BG' => [ 'bg',    'Bulgarian',             'Български' ],
			'CZ' => [ 'cs',    'Czech',                 'Čeština' ],
			'SK' => [ 'sk',    'Slovak',                'Slovenčina' ],
			'HU' => [ 'hu',    'Hungarian',             'Magyar' ],
			'SE' => [ 'sv',    'Swedish',               'Svenska' ],
			'NO' => [ 'no',    'Norwegian',             'Norsk' ],
			'DK' => [ 'da',    'Danish',                'Dansk' ],
			'FI' => [ 'fi',    'Finnish',               'Suomi' ],
			'TR' => [ 'tr',    'Turkish',               'Türkçe' ],
			'RU' => [ 'ru',    'Russian',               'Русский' ],
			'UA' => [ 'uk',    'Ukrainian',             'Українська' ],
			'IL' => [ 'he',    'Hebrew',                'עברית' ],
			'JP' => [ 'ja',    'Japanese',              '日本語' ],
			'KR' => [ 'ko',    'Korean',                '한국어' ],
			'CN' => [ 'zh',    'Simplified Chinese',    '简体中文' ],
			'TW' => [ 'zh-TW', 'Traditional Chinese',   '繁體中文' ],
			'TH' => [ 'th',    'Thai',                  'ไทย' ],
			'VN' => [ 'vi',    'Vietnamese',            'Tiếng Việt' ],
			'ID' => [ 'id',    'Indonesian',            'Bahasa Indonesia' ],
			'PH' => [ 'tl',    'Filipino',              'Filipino' ],
			'MY' => [ 'ms',    'Malay',                 'Bahasa Melayu' ],
			'EG' => [ 'ar',    'Arabic (EG)',           'العربية' ],
			'AE' => [ 'ar',    'Arabic (AE)',           'العربية' ],
			'SA' => [ 'ar',    'Arabic (SA)',           'العربية' ],
		];
		$row = isset( $map[ $geo ] ) ? $map[ $geo ] : null;
		if ( ! $row ) {
			// Fall back to UI language if the geo isn't mapped explicitly.
			$ui = newsai_ai_pro_lang();
			$row = $ui === 'el' ? [ 'el', 'Greek', 'Ελληνικά' ] : [ 'en', 'English', 'English' ];
		}
		/**
		 * Allow integrations to override the AI output language per geo (e.g. multilingual newsrooms).
		 */
		return apply_filters(
			'newsai_ai_geo_to_locale',
			[ 'code' => $row[0], 'label' => $row[1], 'native' => $row[2] ],
			$geo
		);
	}
}

if ( ! function_exists( 'newsai_ai_map_wp_locale_to_ai' ) ) {
	/**
	 * Map a WordPress / WPML / Polylang locale code to newsai_ai_geo_to_locale row shape.
	 *
	 * @param string $locale e.g. el, de_DE, es-MX.
	 * @return array{code:string,label:string,native:string}
	 */
	function newsai_ai_map_wp_locale_to_ai( $locale ) {
		$locale = strtolower( str_replace( '_', '-', trim( (string) $locale ) ) );
		$map    = [
			'el'    => [ 'el', 'Greek', 'Ελληνικά' ],
			'de'    => [ 'de', 'German', 'Deutsch' ],
			'de-de' => [ 'de', 'German', 'Deutsch' ],
			'fr'    => [ 'fr', 'French', 'Français' ],
			'fr-fr' => [ 'fr', 'French', 'Français' ],
			'es'    => [ 'es', 'Castilian Spanish', 'Español' ],
			'es-es' => [ 'es', 'Castilian Spanish', 'Español' ],
			'es-mx' => [ 'es-MX', 'Mexican Spanish', 'Español mexicano' ],
			'it'    => [ 'it', 'Italian', 'Italiano' ],
			'pt'    => [ 'pt', 'European Portuguese', 'Português' ],
			'pt-br' => [ 'pt-BR', 'Brazilian Portuguese', 'Português brasileiro' ],
			'nl'    => [ 'nl', 'Dutch', 'Nederlands' ],
			'pl'    => [ 'pl', 'Polish', 'Polski' ],
			'ro'    => [ 'ro', 'Romanian', 'Română' ],
			'tr'    => [ 'tr', 'Turkish', 'Türkçe' ],
			'ru'    => [ 'ru', 'Russian', 'Русский' ],
			'uk'    => [ 'uk', 'Ukrainian', 'Українська' ],
			'ja'    => [ 'ja', 'Japanese', '日本語' ],
			'zh'    => [ 'zh', 'Simplified Chinese', '简体中文' ],
			'zh-tw' => [ 'zh-TW', 'Traditional Chinese', '繁體中文' ],
			'ar'    => [ 'ar', 'Arabic', 'العربية' ],
			'en'    => [ 'en', 'English', 'English' ],
			'en-us' => [ 'en', 'English (US)', 'English' ],
			'en-gb' => [ 'en', 'English (UK)', 'English' ],
		];
		if ( isset( $map[ $locale ] ) ) {
			$row = $map[ $locale ];
			return [ 'code' => $row[0], 'label' => $row[1], 'native' => $row[2] ];
		}
		$short = strtok( $locale, '-' );
		if ( is_string( $short ) && isset( $map[ $short ] ) ) {
			$row = $map[ $short ];
			return [ 'code' => $row[0], 'label' => $row[1], 'native' => $row[2] ];
		}
		return newsai_ai_geo_to_locale();
	}
}

if ( ! function_exists( 'newsai_ai_locale_for_post' ) ) {
	/**
	 * Resolve AI output language for a post: WPML/Polylang locale → post geo → site geo.
	 *
	 * @param int $post_id Post ID.
	 * @return array{code:string,label:string,native:string}
	 */
	function newsai_ai_locale_for_post( $post_id = 0 ) {
		$post_id = (int) $post_id;

		if ( $post_id > 0 ) {
			$wpml = apply_filters( 'wpml_post_language_details', null, $post_id );
			if ( is_array( $wpml ) && ! empty( $wpml['language_code'] ) ) {
				$mapped = newsai_ai_map_wp_locale_to_ai( (string) $wpml['language_code'] );
				return (array) apply_filters( 'newsai_ai_post_locale', $mapped, $post_id );
			}
			if ( function_exists( 'pll_get_post_language' ) ) {
				$pll_locale = pll_get_post_language( $post_id, 'locale' );
				if ( is_string( $pll_locale ) && $pll_locale !== '' ) {
					$mapped = newsai_ai_map_wp_locale_to_ai( $pll_locale );
					return (array) apply_filters( 'newsai_ai_post_locale', $mapped, $post_id );
				}
				$pll_lang = pll_get_post_language( $post_id, 'slug' );
				if ( is_string( $pll_lang ) && $pll_lang !== '' ) {
					$mapped = newsai_ai_map_wp_locale_to_ai( $pll_lang );
					return (array) apply_filters( 'newsai_ai_post_locale', $mapped, $post_id );
				}
			}
		}

		$geo = '';
		if ( $post_id > 0 && function_exists( 'newsai_get_post_target_geo' ) ) {
			$geo = newsai_get_post_target_geo( $post_id );
		}
		return newsai_ai_geo_to_locale( $geo );
	}
}

if ( ! function_exists( 'newsai_ai_check_request' ) ) {
	/**
	 * Common gate for all AI ajax endpoints. Returns null on success or sends a JSON error.
	 *
	 * @return null|void
	 */
	function newsai_ai_check_request() {
		check_ajax_referer( 'newsai_ajax_nonce', 'nonce' );
		if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => 'unauthorized' ], 403 );
		}
		if ( ! function_exists( 'newsai_is_pro' ) || ! newsai_is_pro() ) {
			wp_send_json_error( [ 'message' => 'pro_required' ], 402 );
		}
		if ( ! newsai_ai_is_active() ) {
			wp_send_json_error( [ 'message' => 'ai_not_configured' ], 400 );
		}
		return null;
	}
}

if ( ! function_exists( 'newsai_ai_read_post_context' ) ) {
	/**
	 * Build a trimmed payload for prompts: title + plain content, validated for permissions.
	 *
	 * @return array{post_id:int, title:string, content:string}|null
	 */
	function newsai_ai_read_post_context() {
		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		if ( $post_id < 1 ) {
			return null;
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return null;
		}
		$post = get_post( $post_id );
		if ( ! $post ) {
			return null;
		}
		$title = (string) $post->post_title;
		if ( isset( $_POST['title'] ) ) {
			$title = sanitize_text_field( wp_unslash( (string) $_POST['title'] ) );
		}
		if ( isset( $_POST['content'] ) ) {
			$content = newsai_ai_prepare_plain_content( (string) wp_unslash( $_POST['content'] ) );
		} else {
			$content = newsai_ai_prepare_plain_content( (string) $post->post_content );
		}
		$content = newsai_ai_truncate_for_prompt( $content );
		return [
			'post_id' => $post_id,
			'title'   => $title,
			'content' => $content,
		];
	}
}

if ( ! function_exists( 'newsai_ai_lang_label' ) ) {
	/**
	 * Human-readable language label that AI prompts can reference. Derived from the
	 * configured geo target so that e.g. GR returns "Greek", BR returns "Brazilian
	 * Portuguese", MX returns "Mexican Spanish", regardless of the WP admin UI language.
	 *
	 * @param int $post_id Optional post for multilingual / per-post geo prompts.
	 * @return string
	 */
	function newsai_ai_lang_label( $post_id = 0 ) {
		$loc = $post_id > 0 ? newsai_ai_locale_for_post( $post_id ) : newsai_ai_geo_to_locale();
		return $loc['label'] . ' (' . $loc['native'] . ')';
	}
}

if ( ! function_exists( 'newsai_ai_language_directive' ) ) {
	/**
	 * Aggressive language directive for AI providers (especially Llama/Groq which tend to
	 * default to English unless told repeatedly). Includes the locale code, English label
	 * and native name to remove any ambiguity.
	 *
	 * @param int $post_id Optional post for multilingual / per-post geo prompts.
	 * @return string
	 */
	function newsai_ai_language_directive( $post_id = 0 ) {
		$loc    = $post_id > 0 ? newsai_ai_locale_for_post( $post_id ) : newsai_ai_geo_to_locale();
		$code   = $loc['code'];
		$label  = $loc['label'];
		$native = $loc['native'];
		return sprintf(
			'CRITICAL LANGUAGE RULE: You MUST write EVERY single output field — including "heading", "title", "why", "suggestion", "tip", reasons, explanations and labels — in %1$s (%2$s, ISO code "%3$s"). Do NOT translate to English under any circumstance. Do NOT mix languages. If you produce English, the answer is invalid and will be rejected. Brand names, proper nouns and technical acronyms may stay in their original spelling, but every full sentence and explanation must be in %2$s.',
			$label,
			$native,
			$code
		);
	}
}

if ( ! function_exists( 'newsai_geo_summary_default_heading' ) ) {
	/**
	 * Localized fallback H2 when the model omits "heading" (matches article locale, not admin UI).
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	function newsai_geo_summary_default_heading( $post_id = 0 ) {
		$loc  = newsai_ai_locale_for_post( (int) $post_id );
		$code = isset( $loc['code'] ) ? (string) $loc['code'] : 'en';
		$map  = [
			'el'    => 'Γρήγορες απαντήσεις',
			'de'    => 'Schnelle Antworten',
			'fr'    => 'Réponses rapides',
			'es'    => 'Respuestas rápidas',
			'es-MX' => 'Respuestas rápidas',
			'it'    => 'Risposte rapide',
			'pt'    => 'Respostas rápidas',
			'pt-BR' => 'Respostas rápidas',
			'en'    => 'Quick answers',
		];
		$heading = isset( $map[ $code ] ) ? $map[ $code ] : 'Quick answers';
		return (string) apply_filters( 'newsai_geo_summary_default_heading', $heading, $post_id, $loc );
	}
}

// ============================================================
// Phase A — Shared SEO payload, article analysis, prompt helpers
// ============================================================

if ( ! function_exists( 'newsai_ai_prepare_plain_content' ) ) {
	/**
	 * Plain text for prompts: strip HTML, keep paragraph breaks for lede extraction.
	 *
	 * @param string $raw Post content or editor HTML.
	 * @return string
	 */
	function newsai_ai_prepare_plain_content( $raw ) {
		$text = wp_strip_all_tags( (string) $raw );
		$text = str_replace( [ "\r\n", "\r" ], "\n", $text );
		$chunks = preg_split( '/\n\s*\n+/u', $text );
		$paras  = [];
		if ( is_array( $chunks ) ) {
			foreach ( $chunks as $chunk ) {
				$p = trim( (string) preg_replace( '/[ \t]+/u', ' ', $chunk ) );
				if ( $p !== '' ) {
					$paras[] = $p;
				}
			}
		}
		if ( ! empty( $paras ) ) {
			return implode( "\n\n", $paras );
		}
		return trim( (string) preg_replace( '/\s+/u', ' ', $text ) );
	}
}

if ( ! function_exists( 'newsai_ai_extract_lede_paragraphs' ) ) {
	/**
	 * First N paragraphs for focused LLM input (Discover / News lede signals).
	 *
	 * @param string $content Plain text with optional \n\n paragraph breaks.
	 * @param int    $max     Max paragraphs.
	 * @return string
	 */
	function newsai_ai_extract_lede_paragraphs( $content, $max = 8 ) {
		$max = max( 3, min( 12, (int) apply_filters( 'newsai_ai_lede_paragraph_count', $max ) ) );
		$paras = preg_split( '/\n\s*\n+/u', trim( (string) $content ) );
		if ( ! is_array( $paras ) || empty( $paras ) ) {
			$lede = trim( (string) $content );
			return newsai_ai_truncate_for_prompt( $lede );
		}
		$take = array_slice( array_filter( array_map( 'trim', $paras ) ), 0, $max );
		$lede = implode( "\n\n", $take );
		$cap  = (int) apply_filters( 'newsai_ai_lede_max_chars', 6000 );
		if ( $cap > 2000 && function_exists( 'mb_strlen' ) && mb_strlen( $lede, 'UTF-8' ) > $cap ) {
			$lede = mb_substr( $lede, 0, $cap, 'UTF-8' );
		} elseif ( $cap > 2000 && strlen( $lede ) > $cap ) {
			$lede = substr( $lede, 0, $cap );
		}
		return $lede;
	}
}

if ( ! function_exists( 'newsai_ai_enrich_context' ) ) {
	/**
	 * @param array{post_id:int,title:string,content:string} $ctx
	 * @return array{post_id:int,title:string,content:string,content_lede:string,content_hash:string}
	 */
	function newsai_ai_enrich_context( $ctx ) {
		$lede = newsai_ai_extract_lede_paragraphs( $ctx['content'] );
		$ctx['content_lede']  = $lede;
		$ctx['content_hash']  = md5( (string) $ctx['title'] . '|' . $lede );
		return $ctx;
	}
}

if ( ! function_exists( 'newsai_ai_normalize_analysis' ) ) {
	/**
	 * @param array<string,mixed> $data Raw model JSON.
	 * @return array<string,mixed>
	 */
	function newsai_ai_normalize_analysis( $data ) {
		if ( ! is_array( $data ) ) {
			return [];
		}
		$allowed_types = [
			'breaking', 'analysis', 'liveblog', 'sports', 'politics', 'finance',
			'entertainment', 'disaster', 'crime', 'opinion', 'general',
		];
		$allowed_intent = [ 'breaking', 'explainer', 'analysis', 'feature', 'opinion', 'evergreen' ];
		$cat = isset( $data['article_type'] ) ? sanitize_key( (string) $data['article_type'] ) : 'general';
		if ( ! in_array( $cat, $allowed_types, true ) ) {
			$cat = 'general';
		}
		$intent = isset( $data['search_intent'] ) ? sanitize_key( (string) $data['search_intent'] ) : '';
		if ( ! in_array( $intent, $allowed_intent, true ) ) {
			$intent = '';
		}
		$primary = isset( $data['primary_keyword'] ) ? sanitize_text_field( (string) $data['primary_keyword'] ) : '';
		$secondary = [];
		if ( ! empty( $data['secondary_keywords'] ) && is_array( $data['secondary_keywords'] ) ) {
			foreach ( $data['secondary_keywords'] as $kw ) {
				$kw = sanitize_text_field( (string) $kw );
				if ( $kw !== '' && $kw !== $primary ) {
					$secondary[] = $kw;
				}
				if ( count( $secondary ) >= 6 ) {
					break;
				}
			}
		}
		$entities = [];
		if ( ! empty( $data['entities'] ) && is_array( $data['entities'] ) ) {
			foreach ( $data['entities'] as $ent ) {
				if ( is_array( $ent ) ) {
					$name = isset( $ent['name'] ) ? sanitize_text_field( (string) $ent['name'] ) : '';
					$type = isset( $ent['type'] ) ? sanitize_key( (string) $ent['type'] ) : 'thing';
				} else {
					$name = sanitize_text_field( (string) $ent );
					$type = 'thing';
				}
				if ( $name === '' ) {
					continue;
				}
				$entities[] = [ 'name' => $name, 'type' => $type ];
				if ( count( $entities ) >= 12 ) {
					break;
				}
			}
		}
		$topics = [];
		if ( ! empty( $data['topics'] ) && is_array( $data['topics'] ) ) {
			foreach ( $data['topics'] as $t ) {
				$t = sanitize_text_field( (string) $t );
				if ( $t !== '' ) {
					$topics[] = $t;
				}
				if ( count( $topics ) >= 6 ) {
					break;
				}
			}
		}
		$breaking = ! empty( $data['is_breaking'] );
		return [
			'primary_keyword'     => $primary,
			'secondary_keywords'  => $secondary,
			'entities'            => $entities,
			'topics'              => $topics,
			'search_intent'       => $intent,
			'article_type'        => $cat,
			'is_breaking'         => $breaking,
			'primary_entity'      => isset( $data['primary_entity'] ) ? sanitize_text_field( (string) $data['primary_entity'] ) : '',
		];
	}
}

if ( ! function_exists( 'newsai_ai_format_analysis_block' ) ) {
	/**
	 * @param array<string,mixed>|null $analysis
	 * @return string
	 */
	function newsai_ai_format_analysis_block( $analysis ) {
		if ( empty( $analysis ) || ! is_array( $analysis ) ) {
			return '';
		}
		$lines = [ 'PRE-EXTRACTED SEO ANALYSIS (use as ground truth; do not invent beyond the article):' ];
		if ( ! empty( $analysis['primary_keyword'] ) ) {
			$lines[] = 'Primary keyword: ' . $analysis['primary_keyword'];
		}
		if ( ! empty( $analysis['primary_entity'] ) ) {
			$lines[] = 'Primary entity: ' . $analysis['primary_entity'];
		}
		if ( ! empty( $analysis['secondary_keywords'] ) && is_array( $analysis['secondary_keywords'] ) ) {
			$lines[] = 'Secondary keywords: ' . implode( ', ', $analysis['secondary_keywords'] );
		}
		if ( ! empty( $analysis['entities'] ) && is_array( $analysis['entities'] ) ) {
			$ent_str = [];
			foreach ( $analysis['entities'] as $e ) {
				if ( is_array( $e ) && ! empty( $e['name'] ) ) {
					$ent_str[] = $e['name'] . ( ! empty( $e['type'] ) ? ' (' . $e['type'] . ')' : '' );
				}
			}
			if ( $ent_str ) {
				$lines[] = 'Named entities: ' . implode( '; ', $ent_str );
			}
		}
		if ( ! empty( $analysis['topics'] ) && is_array( $analysis['topics'] ) ) {
			$lines[] = 'Topics: ' . implode( ', ', $analysis['topics'] );
		}
		if ( ! empty( $analysis['search_intent'] ) ) {
			$lines[] = 'Search intent: ' . $analysis['search_intent'];
		}
		if ( ! empty( $analysis['article_type'] ) ) {
			$lines[] = 'Article type: ' . $analysis['article_type'];
		}
		if ( ! empty( $analysis['is_breaking'] ) ) {
			$lines[] = 'Breaking/developing: yes';
		}
		return implode( "\n", $lines );
	}
}

if ( ! function_exists( 'newsai_ai_article_type_editorial_rules' ) ) {
	/**
	 * Extra system-prompt guidance keyed off stored article_type (1C).
	 *
	 * @param array<string,mixed>|null $analysis Normalized SEO analysis.
	 * @return string
	 */
	function newsai_ai_article_type_editorial_rules( $analysis ) {
		if ( ! is_array( $analysis ) || empty( $analysis['article_type'] ) ) {
			return '';
		}
		$type = sanitize_key( (string) $analysis['article_type'] );
		$map  = [
			'breaking'      => 'ARTICLE TYPE: BREAKING — Lead with the newest verified fact. Short paragraphs. Avoid evergreen framing. Headlines must signal immediacy without hype.',
			'liveblog'      => 'ARTICLE TYPE: LIVE / DEVELOPING — Timestamped updates matter. Suggest lede that states what is happening now; flag missing time context or unclear chronology.',
			'sports'        => 'ARTICLE TYPE: SPORTS — Include score, competition, venue, and key players when present. Avoid generic fan engagement clichés.',
			'politics'      => 'ARTICLE TYPE: POLITICS — Balance actors, institution, and policy impact. Prefer neutral wire tone; flag missing attribution on claims.',
			'finance'       => 'ARTICLE TYPE: FINANCE — Quantify market moves (%, currency, index). Explain who is affected; avoid sensational “crash/soars” unless numbers support it.',
			'entertainment' => 'ARTICLE TYPE: ENTERTAINMENT — Name the work, creators, and release context. Avoid spoiler-heavy suggestions unless the lede already covers them.',
			'disaster'      => 'ARTICLE TYPE: DISASTER — Prioritize location, scale, official sources, and safety guidance. Do not speculate on casualties or causes.',
			'crime'         => 'ARTICLE TYPE: CRIME — Respect “innocent until proven guilty”. Prefer police/court attribution; avoid sensational victim details.',
			'opinion'       => 'ARTICLE TYPE: OPINION — Frame suggestions for clear author stance and evidence; do not rewrite as neutral news.',
			'analysis'      => 'ARTICLE TYPE: ANALYSIS — Emphasize context, data, and “why it matters”. Headlines may be longer if they carry explanatory value.',
			'evergreen'     => 'ARTICLE TYPE: EVERGREEN — Favor durable how/why framing, entity clarity, and internal linking; avoid date-stamped urgency unless justified.',
			'general'       => '',
		];
		$rule = isset( $map[ $type ] ) ? $map[ $type ] : '';
		if ( $rule === '' ) {
			return '';
		}
		return "\n\n" . $rule;
	}
}

if ( ! function_exists( 'newsai_ai_build_task_user_prompt' ) ) {
	/**
	 * @param array{title:string,content_lede:string} $ctx
	 * @param array<string,mixed>|null                 $analysis
	 * @param string                                   $extra Optional trailing instructions.
	 * @return string
	 */
	function newsai_ai_build_task_user_prompt( $ctx, $analysis, $extra = '' ) {
		$loc  = newsai_ai_geo_to_locale();
		$body = newsai_ai_format_analysis_block( $analysis );
		if ( $body !== '' ) {
			$body .= "\n\n";
		}
		$body .= sprintf(
			"Original headline:\n%s\n\nArticle lede (first paragraphs, plain text):\n%s",
			$ctx['title'],
			$ctx['content_lede']
		);
		if ( $extra !== '' ) {
			$body .= "\n\n" . $extra;
		}
		$body .= "\n\nRespond entirely in " . $loc['native'] . '.';
		return $body;
	}
}

if ( ! function_exists( 'newsai_ai_build_selection_user_prompt' ) ) {
	/**
	 * Compact editor payload for inline selection edits (keeps Groq JSON mode stable).
	 *
	 * @param array{title:string,content_lede:string}      $ctx
	 * @param array<string,mixed>|null                     $analysis
	 * @param string                                       $selection Selected plain text.
	 * @return string
	 */
	function newsai_ai_build_selection_user_prompt( $ctx, $analysis, $selection ) {
		$seo_lines = [];
		if ( is_array( $analysis ) ) {
			if ( ! empty( $analysis['primary_keyword'] ) ) {
				$seo_lines[] = 'Primary keyword: ' . $analysis['primary_keyword'];
			}
			if ( ! empty( $analysis['primary_entity'] ) ) {
				$seo_lines[] = 'Primary entity: ' . $analysis['primary_entity'];
			}
			if ( ! empty( $analysis['search_intent'] ) ) {
				$seo_lines[] = 'Search intent: ' . $analysis['search_intent'];
			}
		}
		$seo = $seo_lines !== [] ? "SEO TARGETS:\n" . implode( "\n", $seo_lines ) . "\n\n" : '';
		$loc = newsai_ai_geo_to_locale();
		return $seo
			. "Headline:\n" . (string) $ctx['title'] . "\n\n"
			. "Article context (opening paragraphs):\n"
			. newsai_ai_truncate_for_prompt( (string) ( $ctx['content_lede'] ?? '' ) )
			. "\n\nSELECTED PASSAGE TO EDIT:\n" . (string) $selection
			. "\n\nRespond entirely in " . $loc['native'] . '.';
	}
}

if ( ! function_exists( 'newsai_ai_merge_remaining' ) ) {
	/**
	 * @param int   $task_remaining
	 * @param array $analysis_result Result from newsai_ai_get_article_analysis.
	 * @return int
	 */
	function newsai_ai_merge_remaining( $task_remaining, $analysis_result ) {
		if ( is_array( $analysis_result ) && isset( $analysis_result['remaining'] ) ) {
			return min( (int) $task_remaining, (int) $analysis_result['remaining'] );
		}
		return (int) $task_remaining;
	}
}

if ( ! function_exists( 'newsai_ai_task_analyze_article' ) ) {
	/**
	 * Shared SEO extraction: keywords, entities, intent, article type.
	 *
	 * @param array{title:string,content_lede:string,content_hash:string} $ctx Enriched context.
	 * @return array{ok:bool,data:array|null,error:string,cached:bool,remaining:int}
	 */
	function newsai_ai_task_analyze_article( $ctx ) {
		$language  = newsai_ai_lang_label();
		$lang_rule = newsai_ai_language_directive();
		$loc       = newsai_ai_geo_to_locale();
		$system    = newsai_ai_seo_editor_preamble() . ' '
			. 'Read the headline and article lede. Extract ONLY what is supported by the text — never invent facts, names, or events. '
			. 'All string values in the JSON must be in ' . $language . '. '
			. $lang_rule . ' '
			. newsai_ai_json_output_rules(
				'{"primary_keyword":"main topic phrase","secondary_keywords":["kw1"],"primary_entity":"Name","entities":[{"name":"Name","type":"person|organization|place|event|product|thing"}],"topics":["topic"],"search_intent":"breaking|explainer|analysis|feature|opinion|evergreen","article_type":"breaking|analysis|liveblog|sports|politics|finance|entertainment|disaster|crime|opinion|general","is_breaking":false}'
			);
		$user = sprintf(
			"Headline:\n%s\n\nArticle lede:\n%s\n\nRespond entirely in %s.",
			$ctx['title'],
			$ctx['content_lede'],
			$loc['native']
		);
		$seed = 'analyze-v3|' . $loc['code'] . '|' . $ctx['content_hash'];
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		return [
			'ok'        => true,
			'data'      => newsai_ai_normalize_analysis( $res['data'] ),
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => (int) $res['remaining'],
		];
	}
}

if ( ! function_exists( 'newsai_ai_get_article_analysis' ) ) {
	/**
	 * Cached article analysis (30 min via newsai_ai_call). Non-fatal on failure.
	 *
	 * @param array{title:string,content:string} $ctx
	 * @return array{ok:bool,analysis:array|null,error:string,cached:bool,remaining:int}
	 */
	function newsai_ai_get_article_analysis( $ctx ) {
		$ctx = newsai_ai_enrich_context( $ctx );
		$res = newsai_ai_task_analyze_article( $ctx );
		if ( empty( $res['ok'] ) ) {
			return [
				'ok'        => false,
				'analysis'  => null,
				'error'     => (string) $res['error'],
				'cached'    => false,
				'remaining' => isset( $res['remaining'] ) ? (int) $res['remaining'] : 0,
			];
		}
		$analysis = $res['data'];
		if ( ! empty( $ctx['post_id'] ) && is_array( $analysis ) ) {
			newsai_ai_store_post_analysis( (int) $ctx['post_id'], $analysis, (string) $ctx['content_hash'] );
		}
		return [
			'ok'        => true,
			'analysis'  => $analysis,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => (int) $res['remaining'],
		];
	}
}

if ( ! function_exists( 'newsai_ai_analysis_meta_key' ) ) {
	function newsai_ai_analysis_meta_key() {
		return '_newsai_ai_seo_analysis';
	}
}

if ( ! function_exists( 'newsai_ai_store_post_analysis' ) ) {
	/**
	 * Persist SEO analysis on the post for Discover Score and later tasks.
	 *
	 * @param int                  $post_id Post ID.
	 * @param array<string,mixed>  $analysis Normalized analysis.
	 * @param string               $content_hash Hash of title+lede.
	 */
	function newsai_ai_store_post_analysis( $post_id, $analysis, $content_hash ) {
		$post_id = (int) $post_id;
		if ( $post_id < 1 || ! is_array( $analysis ) ) {
			return;
		}
		update_post_meta(
			$post_id,
			newsai_ai_analysis_meta_key(),
			[
				'updated'      => time(),
				'content_hash' => sanitize_key( (string) $content_hash ),
				'analysis'     => $analysis,
			]
		);
	}
}

if ( ! function_exists( 'newsai_ai_get_post_analysis_for_discover' ) ) {
	/**
	 * @param int $post_id Post ID.
	 * @return array<string,mixed>|null
	 */
	function newsai_ai_get_post_analysis_for_discover( $post_id ) {
		$post_id = (int) $post_id;
		if ( $post_id < 1 ) {
			return null;
		}
		$stored = get_post_meta( $post_id, newsai_ai_analysis_meta_key(), true );
		if ( ! is_array( $stored ) || empty( $stored['analysis'] ) || ! is_array( $stored['analysis'] ) ) {
			return null;
		}
		$max_age = (int) apply_filters( 'newsai_ai_post_analysis_max_age', 7 * DAY_IN_SECONDS );
		$updated = isset( $stored['updated'] ) ? (int) $stored['updated'] : 0;
		if ( $max_age > 0 && $updated > 0 && ( time() - $updated ) > $max_age ) {
			return null;
		}
		return $stored['analysis'];
	}
}

if ( ! function_exists( 'newsai_ai_seo_plugin_targets' ) ) {
	/**
	 * @return string[] yoast|rankmath|excerpt
	 */
	function newsai_ai_seo_plugin_targets() {
		$targets = [];
		if ( defined( 'WPSEO_VERSION' ) ) {
			$targets[] = 'yoast';
		}
		if ( defined( 'RANK_MATH_VERSION' ) ) {
			$targets[] = 'rankmath';
		}
		if ( empty( $targets ) ) {
			$targets[] = 'excerpt';
		}
		return apply_filters( 'newsai_ai_seo_plugin_targets', $targets );
	}
}

if ( ! function_exists( 'newsai_ai_save_meta_description' ) ) {
	/**
	 * Save meta description to Yoast, Rank Math, and/or post excerpt.
	 *
	 * @param int    $post_id Post ID.
	 * @param string $text    Meta description.
	 * @return array{ok:bool,targets:string[]}
	 */
	function newsai_ai_save_meta_description( $post_id, $text ) {
		$post_id = (int) $post_id;
		$text    = trim( sanitize_textarea_field( (string) $text ) );
		if ( $post_id < 1 || $text === '' || ! current_user_can( 'edit_post', $post_id ) ) {
			return [ 'ok' => false, 'targets' => [] ];
		}
		$targets = [];
		foreach ( newsai_ai_seo_plugin_targets() as $target ) {
			if ( $target === 'yoast' ) {
				update_post_meta( $post_id, '_yoast_wpseo_metadesc', $text );
				$targets[] = 'yoast';
			} elseif ( $target === 'rankmath' ) {
				update_post_meta( $post_id, 'rank_math_description', $text );
				$targets[] = 'rankmath';
			} elseif ( $target === 'excerpt' ) {
				wp_update_post(
					[
						'ID'           => $post_id,
						'post_excerpt' => $text,
					]
				);
				$targets[] = 'excerpt';
			}
		}
		/**
		 * @param int    $post_id Post ID.
		 * @param string $text    Meta description saved.
		 * @param string[] $targets Targets written.
		 */
		do_action( 'newsai_ai_meta_description_saved', $post_id, $text, $targets );
		return [
			'ok'      => ! empty( $targets ),
			'targets' => $targets,
		];
	}
}

// ============================================================
// Task — Meta description (Phase B)
// ============================================================

if ( ! function_exists( 'newsai_ai_task_meta_description' ) ) {
	/**
	 * @param array{post_id:int,title:string,content:string} $ctx
	 * @return array{ok:bool,data:array,error:string,cached:bool,remaining:int}
	 */
	function newsai_ai_task_meta_description( $ctx ) {
		$ctx             = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$language        = newsai_ai_lang_label();
		$lang_rule       = newsai_ai_language_directive();
		$system          = newsai_ai_seo_editor_preamble() . ' '
			. 'Write exactly ONE meta description in ' . $language . '. '
			. "Rules:\n"
			. "- Length: 140–160 characters (count spaces). Never under 130 or over 165.\n"
			. "- Factual, entity-rich, action-oriented; include the primary keyword naturally in the first half.\n"
			. "- Optimized for Google Discover cards and search snippets (standalone, complete thought).\n"
			. "- No clickbait, no ALL-CAPS, no emojis, no quotes around the text.\n"
			. "- Use only facts from the article and PRE-EXTRACTED SEO ANALYSIS.\n"
			. $lang_rule . ' '
			. newsai_ai_json_output_rules( '{"meta_description":"snippet text","chars":155}' )
			. newsai_ai_article_type_editorial_rules( $analysis );
		$user = newsai_ai_build_task_user_prompt( $ctx, $analysis );
		$loc  = newsai_ai_geo_to_locale();
		$seed = 'meta-desc-v3|' . $loc['code'] . '|' . md5( (string) $ctx['content_hash'] . '|' . wp_json_encode( $analysis ) );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		$data = is_array( $res['data'] ) ? $res['data'] : [];
		$desc = isset( $data['meta_description'] ) ? trim( sanitize_textarea_field( (string) $data['meta_description'] ) ) : '';
		if ( $desc === '' ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'empty_meta_description',
			];
		}
		$len = function_exists( 'mb_strlen' ) ? (int) mb_strlen( $desc, 'UTF-8' ) : strlen( $desc );
		if ( $len > 165 ) {
			$desc = function_exists( 'mb_substr' ) ? mb_substr( $desc, 0, 160, 'UTF-8' ) : substr( $desc, 0, 160 );
			$len  = function_exists( 'mb_strlen' ) ? (int) mb_strlen( $desc, 'UTF-8' ) : strlen( $desc );
		}
		$out_data = [
			'meta_description' => $desc,
			'chars'            => $len,
			'seo_targets'      => newsai_ai_seo_plugin_targets(),
		];
		if ( $analysis ) {
			$out_data['analysis'] = $analysis;
		}
		return [
			'ok'        => true,
			'data'      => $out_data,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_meta_description' ) ) {
	function newsai_ai_ajax_meta_description() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_post_context();
		if ( ! $ctx || $ctx['content'] === '' ) {
			wp_send_json_error( [ 'message' => 'empty_content' ], 400 );
		}
		$res = newsai_ai_task_meta_description( $ctx );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_meta_description', 'newsai_ai_ajax_meta_description' );

if ( ! function_exists( 'newsai_ai_ajax_apply_meta_description' ) ) {
	function newsai_ai_ajax_apply_meta_description() {
		newsai_ai_check_request();
		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		$text    = isset( $_POST['meta_description'] ) ? sanitize_textarea_field( wp_unslash( (string) $_POST['meta_description'] ) ) : '';
		if ( $post_id < 1 || $text === '' ) {
			wp_send_json_error( [ 'message' => 'invalid_request' ], 400 );
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( [ 'message' => 'unauthorized' ], 403 );
		}
		$saved = newsai_ai_save_meta_description( $post_id, $text );
		if ( empty( $saved['ok'] ) ) {
			wp_send_json_error( [ 'message' => 'save_failed' ], 500 );
		}
		wp_send_json_success(
			[
				'post_id'          => $post_id,
				'meta_description' => $text,
				'targets'          => $saved['targets'],
			]
		);
	}
}
add_action( 'wp_ajax_newsai_ai_apply_meta_description', 'newsai_ai_ajax_apply_meta_description' );

// ============================================================
// Task 1 — Title suggestions
// ============================================================

if ( ! function_exists( 'newsai_ai_task_title_suggest' ) ) {
	function newsai_ai_task_title_suggest( $ctx ) {
		$ctx            = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$language        = newsai_ai_lang_label();
		$lang_rule       = newsai_ai_language_directive();
		$system          = "You are a senior news editor with 15+ years of experience writing headlines for Google Discover and Google News top-stories carousels. "
			. 'Your job: propose FIVE alternative headlines for the article below, in ' . $language . '. '
			. "Headlines must follow EVERY rule below — no exceptions:\n\n"
			. "STRICT HEADLINE RULES (Google Discover & News optimized):\n"
			. "1. LENGTH: each headline must be 55–70 characters (count spaces). NEVER shorter than 50 or longer than 75. A 60–65 char headline is the sweet spot.\n"
			. "2. FRONT-LOAD THE NEWS: the most important entity (who/where/what number) goes in the FIRST 40 characters so it survives mobile truncation in Discover. The first five words must be unambiguous.\n"
			. "3. CONCRETE DETAILS: include at least one of: a specific number, place, named person, organization, date, or quantified outcome. Vague headlines are rejected.\n"
			. "4. NATURAL FULL-SENTENCE STRUCTURE: write a complete proposition (subject + verb + object), not telegraphic noun-stacks.\n"
			. "5. ACTIVE VOICE, PRESENT/PAST TENSE matched to the news cycle. Avoid passive voice.\n"
			. "6. NO CLICKBAIT, NO ALL-CAPS, NO EMOJIS, NO QUESTION MARKS unless the news itself is a question.\n"
			. "7. NO TRAILING COLON, NO TRAILING DASH, NO ELLIPSIS. Punctuation only where it carries meaning.\n"
			. "8. PROPER NOUNS keep their original spelling. Acronyms stay as in the source.\n"
			. "9. EACH of the 5 headlines must take a DIFFERENT angle: (a) facts-first / wire style, (b) human-impact, (c) consequence/aftermath, (d) authority/source, (e) Discover-curiosity (still factual).\n"
			. "10. Preserve the original meaning. Never invent facts not present in the article or PRE-EXTRACTED SEO ANALYSIS.\n"
			. "11. Include the primary keyword from the analysis naturally in each headline.\n"
			. "12. Prefer recognizable entities (country, person, company, city, event) over generic nouns; repeat the primary entity where natural.\n"
			. "13. If the article is breaking/developing, reflect immediacy naturally (e.g. live, new, latest, today) ONLY when supported by the text.\n"
			. "14. Avoid vague emotional adjectives (shocking, dramatic, unbelievable, exclusive).\n"
			. "15. Optimize for high CTR while remaining fully factual — publishable by wire services or major newspapers.\n"
			. "16. Headlines must sound publishable by Reuters, AP, BBC, or leading national outlets in the target language.\n\n"
			. "PROCESS: Before returning JSON, internally score each candidate headline 1–10 on: SEO clarity, Discover CTR potential, entity prominence, specificity, mobile truncation quality. "
			. 'Return only the five highest-scoring headlines.' . "\n"
			. $lang_rule . ' '
			. 'For each headline return: title, why (max 90 chars), chars, angle, score (1–10 integer). '
			. newsai_ai_json_output_rules( '{"suggestions":[{"title":"headline","why":"reason","chars":63,"angle":"facts|impact|aftermath|authority|curiosity","score":8}]}' )
			. newsai_ai_article_type_editorial_rules( $analysis );
		$loc  = newsai_ai_geo_to_locale();
		$user = newsai_ai_build_task_user_prompt( $ctx, $analysis );
		$seed = 'title-discover-v5|' . $loc['code'] . '|' . md5( (string) $ctx['content_hash'] . '|' . wp_json_encode( $analysis ) );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		$data        = $res['data'];
		$suggestions = isset( $data['suggestions'] ) && is_array( $data['suggestions'] ) ? $data['suggestions'] : [];
		$allowed_ang = [ 'facts', 'impact', 'aftermath', 'authority', 'curiosity' ];
		$clean       = [];
		foreach ( $suggestions as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$t = isset( $row['title'] ) ? sanitize_text_field( (string) $row['title'] ) : '';
			$w = isset( $row['why'] ) ? sanitize_text_field( (string) $row['why'] ) : '';
			if ( $t === '' ) {
				continue;
			}
			$t = rtrim( $t, " \t\n\r\0\x0B:–-—…" );
			$len = function_exists( 'mb_strlen' ) ? (int) mb_strlen( $t ) : strlen( $t );
			if ( $len < 45 || $len > 80 ) {
				continue;
			}
			$ang = isset( $row['angle'] ) ? sanitize_key( (string) $row['angle'] ) : '';
			if ( ! in_array( $ang, $allowed_ang, true ) ) {
				$ang = '';
			}
			$score = isset( $row['score'] ) ? max( 1, min( 10, (int) $row['score'] ) ) : 0;
			$clean[] = [
				'title' => $t,
				'why'   => $w,
				'chars' => $len,
				'angle' => $ang,
				'score' => $score,
			];
		}
		usort(
			$clean,
			static function ( $a, $b ) {
				return ( (int) $b['score'] ) <=> ( (int) $a['score'] );
			}
		);
		$clean = array_slice( $clean, 0, 5 );
		$out_data = [ 'suggestions' => $clean ];
		if ( $analysis ) {
			$out_data['analysis'] = $analysis;
		}
		return [
			'ok'        => true,
			'data'      => $out_data,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_title_suggest' ) ) {
	function newsai_ai_ajax_title_suggest() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_post_context();
		if ( ! $ctx || $ctx['content'] === '' ) {
			wp_send_json_error( [ 'message' => 'empty_content' ], 400 );
		}
		$res = newsai_ai_task_title_suggest( $ctx );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_title_suggest', 'newsai_ai_ajax_title_suggest' );

// ============================================================
// Task 2 — Content optimization (targeted suggestions, NOT rewrite)
// ============================================================

if ( ! function_exists( 'newsai_ai_task_optimize_content' ) ) {
	function newsai_ai_task_optimize_content( $ctx ) {
		$ctx             = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$language        = newsai_ai_lang_label();
		$lang_rule       = newsai_ai_language_directive();
		$system          = newsai_ai_seo_editor_preamble() . ' '
			. 'Return targeted, NON-REWRITE editorial and SEO suggestions in ' . $language . '. Do NOT rewrite the article. '
			. 'Each suggestion must be small, actionable, and reference a paragraph number or a global issue. '
			. "Focus especially on:\n"
			. "- missing search intent coverage\n"
			. "- missing named entities from the analysis\n"
			. "- weak introductory paragraphs (lede)\n"
			. "- absent contextual keywords\n"
			. "- internal linking opportunities (describe anchor topic, not URLs)\n"
			. "- missing E-E-A-T signals (sources, expertise, datelines)\n"
			. "- paragraph length for mobile readability\n"
			. "- quote attribution clarity\n"
			. "Check whether the first two paragraphs answer who, what, where, when, and why; if not, flag it as high-impact.\n"
			. "Categories: readability, seo, eeat, structure, factcheck. "
			. $lang_rule . ' '
			. 'Never invent facts. Use only the article and PRE-EXTRACTED SEO ANALYSIS. '
			. newsai_ai_json_output_rules( '{"suggestions":[{"category":"readability|seo|eeat|structure|factcheck","paragraph":1,"suggestion":"action","why":"reason"}]}' )
			. "Limit to 6 suggestions, ordered by impact. Use paragraph=0 if global."
			. newsai_ai_article_type_editorial_rules( $analysis );
		$loc  = newsai_ai_geo_to_locale();
		$user = newsai_ai_build_task_user_prompt( $ctx, $analysis );
		$seed = 'optimize-v4|' . $loc['code'] . '|' . md5( (string) $ctx['content_hash'] . '|' . wp_json_encode( $analysis ) );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		$data        = $res['data'];
		$suggestions = isset( $data['suggestions'] ) && is_array( $data['suggestions'] ) ? $data['suggestions'] : [];
		$allowed_cat = [ 'readability', 'seo', 'eeat', 'structure', 'factcheck' ];
		$clean       = [];
		foreach ( $suggestions as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$cat = isset( $row['category'] ) ? sanitize_key( (string) $row['category'] ) : 'readability';
			if ( ! in_array( $cat, $allowed_cat, true ) ) {
				$cat = 'readability';
			}
			$para = isset( $row['paragraph'] ) ? max( 0, (int) $row['paragraph'] ) : 0;
			$s    = isset( $row['suggestion'] ) ? sanitize_textarea_field( (string) $row['suggestion'] ) : '';
			$w    = isset( $row['why'] ) ? sanitize_textarea_field( (string) $row['why'] ) : '';
			if ( $s === '' ) {
				continue;
			}
			$clean[] = [
				'category'   => $cat,
				'paragraph'  => $para,
				'suggestion' => $s,
				'why'        => $w,
			];
			if ( count( $clean ) >= 10 ) {
				break;
			}
		}
		$out_data = [ 'suggestions' => $clean ];
		if ( $analysis ) {
			$out_data['analysis'] = $analysis;
		}
		return [
			'ok'        => true,
			'data'      => $out_data,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_optimize_content' ) ) {
	function newsai_ai_ajax_optimize_content() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_post_context();
		if ( ! $ctx || $ctx['content'] === '' ) {
			wp_send_json_error( [ 'message' => 'empty_content' ], 400 );
		}
		$res = newsai_ai_task_optimize_content( $ctx );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_optimize_content', 'newsai_ai_ajax_optimize_content' );

// ============================================================
// Task 3 — Trend-aware keyword suggestions
// ============================================================

if ( ! function_exists( 'newsai_ai_fetch_trends_titles' ) ) {
	/**
	 * Lightweight fetch of trend titles from the Google Trends RSS we already use elsewhere.
	 *
	 * @param string $geo Two-letter geo code.
	 * @return string[] Trend titles (up to 20).
	 */
	function newsai_ai_fetch_trends_titles( $geo ) {
		if ( function_exists( 'newsai_google_trends_normalize_geo' ) ) {
			$geo = newsai_google_trends_normalize_geo( $geo );
		} else {
			$geo = strtoupper( preg_replace( '/[^A-Z]/i', '', (string) $geo ) );
			if ( $geo === '' ) {
				$geo = function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US';
			}
		}

		$cache_key = 'newsai_ai_trends_titles_' . $geo;
		$cached    = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$out = [];
		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 ( $result['items'] as $item ) {
					$t = trim( (string) ( $item['title'] ?? '' ) );
					if ( $t !== '' ) {
						$out[] = $t;
					}
				}
			}
		}

		$ttl = empty( $out ) ? 2 * MINUTE_IN_SECONDS : 5 * MINUTE_IN_SECONDS;
		set_transient( $cache_key, $out, $ttl );
		return $out;
	}
}

if ( ! function_exists( 'newsai_ai_task_trend_keywords' ) ) {
	function newsai_ai_task_trend_keywords( $ctx ) {
		$ctx             = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$language        = newsai_ai_lang_label();
		$lang_rule       = newsai_ai_language_directive();
		$geo             = (string) ( function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US' );
		$trends          = newsai_ai_fetch_trends_titles( $geo );
		$system          = newsai_ai_seo_editor_preamble() . ' Given an article and CURRENT trending searches in the same region, '
			. 'select only trends that are editorially and semantically relevant enough to reference naturally in ' . $language . '. '
			. 'Do NOT suggest keyword stuffing. Reject trends that would feel forced, misleading, or unrelated. '
			. 'For each accepted trend, classify search intent relation: direct (same story angle), adjacent (related context), or unrelated (reject — do not include). '
			. 'Propose a short integration tip only when natural (headline, lede, body, or tag idea). '
			. $lang_rule . ' '
			. 'Never invent connections not supported by the article. '
			. newsai_ai_json_output_rules( '{"keywords":[{"term":"trend term","fit":"high|medium|low","intent_relation":"direct|adjacent","placement":"headline|lede|paragraph|tag","tip":"integration tip"}]}' )
			. "Limit to 5 entries, ranked by editorial value. Exclude unrelated trends entirely."
			. newsai_ai_article_type_editorial_rules( $analysis );
		$loc   = newsai_ai_geo_to_locale();
		$extra = sprintf(
			"Geo: %s\nTrending searches now:\n- %s",
			$geo,
			implode( "\n- ", array_slice( $trends, 0, 20 ) )
		);
		$user = newsai_ai_build_task_user_prompt( $ctx, $analysis, $extra );
		$seed = 'trends-v4|' . $geo . '|' . $loc['code'] . '|' . md5( (string) $ctx['content_hash'] . '|' . implode( '|', $trends ) );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		$data     = $res['data'];
		$keywords = isset( $data['keywords'] ) && is_array( $data['keywords'] ) ? $data['keywords'] : [];
		$placements = [ 'headline', 'lede', 'paragraph', 'tag' ];
		$fits       = [ 'high', 'medium', 'low' ];
		$relations  = [ 'direct', 'adjacent' ];
		$clean      = [];
		foreach ( $keywords as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$term = isset( $row['term'] ) ? sanitize_text_field( (string) $row['term'] ) : '';
			if ( $term === '' ) {
				continue;
			}
			$relation = isset( $row['intent_relation'] ) ? sanitize_key( (string) $row['intent_relation'] ) : 'adjacent';
			if ( $relation === 'unrelated' || ! in_array( $relation, $relations, true ) ) {
				if ( $relation === 'unrelated' ) {
					continue;
				}
				$relation = 'adjacent';
			}
			$fit       = isset( $row['fit'] ) ? sanitize_key( (string) $row['fit'] ) : 'medium';
			$placement = isset( $row['placement'] ) ? sanitize_key( (string) $row['placement'] ) : 'paragraph';
			if ( ! in_array( $fit, $fits, true ) ) {
				$fit = 'medium';
			}
			if ( ! in_array( $placement, $placements, true ) ) {
				$placement = 'paragraph';
			}
			$tip = isset( $row['tip'] ) ? sanitize_text_field( (string) $row['tip'] ) : '';
			$clean[] = [
				'term'             => $term,
				'fit'              => $fit,
				'intent_relation'  => $relation,
				'placement'        => $placement,
				'tip'              => $tip,
			];
			if ( count( $clean ) >= 8 ) {
				break;
			}
		}
		$out_data = [
			'keywords'    => $clean,
			'geo'         => $geo,
			'trend_count' => count( $trends ),
		];
		if ( $analysis ) {
			$out_data['analysis'] = $analysis;
		}
		return [
			'ok'        => true,
			'data'      => $out_data,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_trend_keywords' ) ) {
	function newsai_ai_ajax_trend_keywords() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_post_context();
		if ( ! $ctx || $ctx['content'] === '' ) {
			wp_send_json_error( [ 'message' => 'empty_content' ], 400 );
		}
		$res = newsai_ai_task_trend_keywords( $ctx );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_trend_keywords', 'newsai_ai_ajax_trend_keywords' );

// ============================================================
// Task 4 — Key takeaways / bullet summary for Quick Actions block
// ============================================================

if ( ! function_exists( 'newsai_ai_read_editor_payload' ) ) {
	/**
	 * Post context for sidebar quick blocks; allows unsaved posts (post_id 0) with live editor fields.
	 *
	 * @return array{post_id:int, title:string, content:string}|null
	 */
	function newsai_ai_read_editor_payload() {
		$ctx = newsai_ai_read_post_context();
		if ( $ctx ) {
			return $ctx;
		}
		$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		if ( $post_id > 0 || ! current_user_can( 'edit_posts' ) ) {
			return null;
		}
		$title = isset( $_POST['title'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['title'] ) ) : '';
		$raw     = isset( $_POST['content'] ) ? (string) wp_unslash( $_POST['content'] ) : '';
		$content = newsai_ai_prepare_plain_content( $raw );
		$content = newsai_ai_truncate_for_prompt( $content );
		if ( $content === '' ) {
			return null;
		}
		return [
			'post_id' => 0,
			'title'   => $title,
			'content' => $content,
		];
	}
}

if ( ! function_exists( 'newsai_ai_task_key_points' ) ) {
	/**
	 * @param array{post_id:int, title:string, content:string} $ctx
	 */
	function newsai_ai_task_key_points( $ctx ) {
		$ctx             = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$post_id         = (int) ( $ctx['post_id'] ?? 0 );
		$language        = newsai_ai_lang_label( $post_id );
		$lang_rule       = newsai_ai_language_directive( $post_id );
		$system          = newsai_ai_seo_editor_preamble() . ' Write 3 to 5 concise key takeaways for readers who skim the story. Language: ' . $language . '. '
			. "Rules:\n"
			. "- Each bullet is ONE clear sentence, max 120 characters.\n"
			. "- Summarize and paraphrase; do NOT copy full sentences from the article.\n"
			. "- At least one bullet must include the primary entity and a concrete outcome or detail.\n"
			. "- Bullets must be understandable standalone (Discover / AI overview snippet style).\n"
			. "- Lead with the most newsworthy facts (who, what, where, impact).\n"
			. "- No clickbait, no emojis, no duplicate ideas. Never invent facts.\n"
			. $lang_rule . ' '
			. newsai_ai_json_output_rules( '{"points":["bullet one","bullet two","bullet three"]}' )
			. newsai_ai_article_type_editorial_rules( $analysis );
		$loc  = newsai_ai_locale_for_post( $post_id );
		$user = newsai_ai_build_task_user_prompt( $ctx, $analysis );
		$seed = 'keypoints-v4|' . $loc['code'] . '|' . md5( (string) $ctx['content_hash'] );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		$data   = $res['data'];
		$points = isset( $data['points'] ) && is_array( $data['points'] ) ? $data['points'] : [];
		$clean  = [];
		foreach ( $points as $p ) {
			$p = sanitize_text_field( (string) $p );
			$p = trim( $p );
			if ( $p === '' ) {
				continue;
			}
			$len = function_exists( 'mb_strlen' ) ? (int) mb_strlen( $p ) : strlen( $p );
			if ( $len > 160 ) {
				$p = function_exists( 'mb_substr' ) ? mb_substr( $p, 0, 157 ) . '…' : substr( $p, 0, 157 ) . '…';
			}
			$clean[] = $p;
			if ( count( $clean ) >= 5 ) {
				break;
			}
		}
		if ( count( $clean ) < 2 ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'insufficient_points',
			];
		}
		$out_data = [ 'points' => $clean ];
		if ( $analysis ) {
			$out_data['analysis'] = $analysis;
		}
		return [
			'ok'        => true,
			'data'      => $out_data,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_key_points' ) ) {
	function newsai_ai_ajax_key_points() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_editor_payload();
		if ( ! $ctx || $ctx['content'] === '' ) {
			wp_send_json_error( [ 'message' => 'empty_content' ], 400 );
		}
		$plain_len = function_exists( 'mb_strlen' ) ? mb_strlen( $ctx['content'] ) : strlen( $ctx['content'] );
		if ( $plain_len < 50 ) {
			wp_send_json_error( [ 'message' => 'content_too_short' ], 400 );
		}
		$res = newsai_ai_task_key_points( $ctx );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_key_points', 'newsai_ai_ajax_key_points' );

if ( ! function_exists( 'newsai_ai_task_geo_summary' ) ) {
	/**
	 * GEO / AI Overviews summary block: lead paragraph + 2–4 Q&A pairs.
	 *
	 * @param array{post_id:int, title:string, content:string} $ctx
	 */
	function newsai_ai_task_geo_summary( $ctx ) {
		$ctx             = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$post_id         = (int) ( $ctx['post_id'] ?? 0 );
		$language        = newsai_ai_lang_label( $post_id );
		$lang_rule       = newsai_ai_language_directive( $post_id );
		$system          = newsai_ai_seo_editor_preamble() . ' Optimize for Google AI Overviews and generative search. Language: ' . $language . '. '
			. "Rules:\n"
			. "- Write a short section heading (3–6 words) for the Q&A block in the article language (e.g. Greek equivalent of \"Quick answers\").\n"
			. "- Write a 2-sentence factual summary (max 280 characters total) with primary entity and keyword.\n"
			. "- Add 2 to 4 question-and-answer pairs that a reader might search for about this story.\n"
			. "- Questions must be natural search queries; answers 1–2 sentences, max 220 characters each.\n"
			. "- Use only facts from the article. No speculation, no clickbait, no emojis.\n"
			. "- Lead with who/what/where/impact.\n"
			. $lang_rule . ' '
			. newsai_ai_json_output_rules( '{"heading":"short section title","summary":"two sentences","qa":[{"q":"question","a":"answer"}]}' )
			. newsai_ai_article_type_editorial_rules( $analysis );
		$loc  = newsai_ai_locale_for_post( $post_id );
		$user = newsai_ai_build_task_user_prompt( $ctx, $analysis );
		$seed = 'geo-summary-v3|' . $loc['code'] . '|' . md5( (string) $ctx['content_hash'] );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		$data    = $res['data'];
		$summary = isset( $data['summary'] ) ? sanitize_text_field( (string) $data['summary'] ) : '';
		$summary = trim( $summary );
		$qa_out  = [];
		if ( ! empty( $data['qa'] ) && is_array( $data['qa'] ) ) {
			foreach ( $data['qa'] as $row ) {
				if ( ! is_array( $row ) ) {
					continue;
				}
				$q = trim( sanitize_text_field( (string) ( $row['q'] ?? $row['question'] ?? '' ) ) );
				$a = trim( sanitize_text_field( (string) ( $row['a'] ?? $row['answer'] ?? '' ) ) );
				if ( $q === '' || $a === '' ) {
					continue;
				}
				$qa_out[] = [ 'q' => $q, 'a' => $a ];
				if ( count( $qa_out ) >= 4 ) {
					break;
				}
			}
		}
		if ( $summary === '' || count( $qa_out ) < 2 ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'insufficient_geo_summary',
			];
		}
		$heading = isset( $data['heading'] ) ? trim( sanitize_text_field( (string) $data['heading'] ) ) : '';
		if ( $heading === '' ) {
			$heading = newsai_geo_summary_default_heading( $post_id );
		}
		$out_data = [
			'heading' => $heading,
			'summary' => $summary,
			'qa'      => $qa_out,
		];
		if ( $analysis ) {
			$out_data['analysis'] = $analysis;
		}
		return [
			'ok'        => true,
			'data'      => $out_data,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_geo_summary' ) ) {
	function newsai_ai_ajax_geo_summary() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_editor_payload();
		if ( ! $ctx || $ctx['content'] === '' ) {
			wp_send_json_error( [ 'message' => 'empty_content' ], 400 );
		}
		$plain_len = function_exists( 'mb_strlen' ) ? mb_strlen( $ctx['content'] ) : strlen( $ctx['content'] );
		if ( $plain_len < 80 ) {
			wp_send_json_error( [ 'message' => 'content_too_short' ], 400 );
		}
		$res = newsai_ai_task_geo_summary( $ctx );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_geo_summary', 'newsai_ai_ajax_geo_summary' );

if ( ! function_exists( 'newsai_ai_task_selection_edit' ) ) {
	/**
	 * Edit a highlighted text fragment inside the article (floating toolbar).
	 *
	 * @param array  $ctx         Post context.
	 * @param string $selection   Selected plain text.
	 * @param string $edit_action rewrite|shorten|expand|simplify.
	 */
	function newsai_ai_task_selection_edit( $ctx, $selection, $edit_action ) {
		$selection = trim( sanitize_textarea_field( (string) $selection ) );
		if ( $selection === '' ) {
			return [ 'ok' => false, 'data' => [], 'error' => 'empty_selection' ];
		}
		$allowed = [ 'rewrite', 'shorten', 'expand', 'simplify' ];
		$edit_action = sanitize_key( (string) $edit_action );
		if ( ! in_array( $edit_action, $allowed, true ) ) {
			$edit_action = 'rewrite';
		}

		$ctx             = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$post_id         = (int) ( $ctx['post_id'] ?? 0 );
		$language        = newsai_ai_lang_label( $post_id );
		$lang_rule       = newsai_ai_language_directive( $post_id );

		$action_lines = [
			'rewrite'  => 'REWRITE for news SEO: clearer, tighter prose; active voice; front-load named entities and primary keyword when natural. Keep the same facts and tone. Target similar length (±15%).',
			'shorten'  => 'SHORTEN for mobile/Discover: keep all essential facts, entities, and numbers. Target 65–85% of original length.',
			'expand'   => 'EXPAND with context supported ONLY by the article (no new facts): clarify who/what/why for SEO. Target 115–135% of original length.',
			'simplify' => 'SIMPLIFY for a general audience: plain language, short sentences, preserve factual precision and named entities.',
		];
		$instruction = $action_lines[ $edit_action ];

		$system = newsai_ai_seo_editor_preamble() . ' Language: ' . $language . '. '
			. 'TASK: ' . $instruction . ' '
			. 'The "text" value must be publishable article prose only — no HTML, markdown, labels, or wrapping quotes. '
			. newsai_ai_json_output_rules( '{"text":"replacement passage as one JSON string"}' )
			. ' ' . $lang_rule;

		$user = newsai_ai_build_selection_user_prompt( $ctx, $analysis, $selection );
		if ( is_array( $analysis ) && ! empty( $analysis['article_type'] ) ) {
			$user .= newsai_ai_article_type_editorial_rules( $analysis );
		}

		$loc  = newsai_ai_locale_for_post( $post_id );
		$seed = 'selection-' . $edit_action . '-v2|' . $loc['code'] . '|' . md5( $selection . '|' . (string) $ctx['content_hash'] );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}

		$text = isset( $res['data']['text'] ) ? trim( sanitize_textarea_field( (string) $res['data']['text'] ) ) : '';
		if ( $text === '' ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'empty_result',
			];
		}

		$max = (int) apply_filters( 'newsai_ai_selection_max_chars', 2500 );
		if ( $max < 400 ) {
			$max = 400;
		}
		if ( function_exists( 'mb_strlen' ) && mb_strlen( $text ) > $max ) {
			$text = mb_substr( $text, 0, $max );
		} elseif ( strlen( $text ) > $max ) {
			$text = substr( $text, 0, $max );
		}

		return [
			'ok'        => true,
			'data'      => [ 'text' => $text ],
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_selection_edit' ) ) {
	function newsai_ai_ajax_selection_edit() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_editor_payload();
		if ( ! $ctx ) {
			wp_send_json_error( [ 'message' => 'invalid_context' ], 400 );
		}
		$selection = isset( $_POST['selection'] ) ? sanitize_textarea_field( wp_unslash( (string) $_POST['selection'] ) ) : '';
		$action    = isset( $_POST['edit_action'] ) ? sanitize_key( wp_unslash( (string) $_POST['edit_action'] ) ) : 'rewrite';
		$sel_len   = function_exists( 'mb_strlen' ) ? mb_strlen( $selection ) : strlen( $selection );
		if ( $sel_len < 12 ) {
			wp_send_json_error( [ 'message' => 'selection_too_short' ], 400 );
		}
		if ( $sel_len > 1400 ) {
			wp_send_json_error( [ 'message' => 'selection_too_long' ], 400 );
		}
		$res = newsai_ai_task_selection_edit( $ctx, $selection, $action );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_selection_edit', 'newsai_ai_ajax_selection_edit' );

// ============================================================
// Task — AI Tag Match (semantic tag suggestions, Pro BYOK)
// ============================================================

if ( ! function_exists( 'newsai_ai_tag_pool_for_match' ) ) {
	/**
	 * @param int $limit Max tags sent to the model.
	 * @return array<int, array{id:int,name:string}>
	 */
	function newsai_ai_tag_pool_for_match( $limit = 100 ) {
		$limit = max( 10, min( 150, (int) $limit ) );
		$pool  = function_exists( 'newsai_tag_pool_editor_size_get' ) ? newsai_tag_pool_editor_size_get() : 300;
		$terms = get_terms(
			[
				'taxonomy'   => 'post_tag',
				'hide_empty' => false,
				'number'     => min( $limit, $pool ),
				'orderby'    => 'count',
				'order'      => 'DESC',
				'fields'     => 'all',
			]
		);
		if ( is_wp_error( $terms ) || empty( $terms ) ) {
			return [];
		}
		$out = [];
		foreach ( $terms as $t ) {
			if ( empty( $t->name ) ) {
				continue;
			}
			$out[] = [
				'id'   => (int) $t->term_id,
				'name' => (string) $t->name,
			];
		}
		return $out;
	}
}

if ( ! function_exists( 'newsai_ai_task_tag_match' ) ) {
	/**
	 * @param array{post_id:int,title:string,content:string} $ctx Editor payload.
	 * @return array{ok:bool,data:array,tags:array,error:string,cached:bool,remaining:int}
	 */
	function newsai_ai_task_tag_match( $ctx ) {
		$ctx             = newsai_ai_enrich_context( $ctx );
		$analysis_result = newsai_ai_get_article_analysis( $ctx );
		$analysis        = $analysis_result['ok'] ? $analysis_result['analysis'] : null;
		$pool = newsai_ai_tag_pool_for_match( 100 );
		if ( count( $pool ) < 5 ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'tag_pool_too_small',
			];
		}
		$names = array_map(
			static function ( $row ) {
				return (string) $row['name'];
			},
			$pool
		);
		$language  = newsai_ai_lang_label();
		$lang_rule = newsai_ai_language_directive();
		$system    = newsai_ai_seo_editor_preamble() . ' '
			. 'Pick the THREE most semantically relevant tags for the article from ALLOWED TAGS only (exact spelling). '
			. 'Also suggest up to THREE new tag labels NOT in the list if they are named entities, ongoing events, organizations, or strong search topics clearly supported by the article. '
			. 'Prefer: named entities, geopolitical topics, competitions, organizations, recurring search topics. '
			. 'Never invent facts. ' . $lang_rule . ' '
			. newsai_ai_json_output_rules( '{"tags":[{"name":"Exact tag from list","reason":"max 80 chars"}],"suggested_new_tags":[{"name":"new label","reason":"max 80 chars"}]}' );
		$extra = "ALLOWED TAGS (existing on site — use exact spelling for tags[]):\n" . implode( "\n", $names );
		$user  = newsai_ai_build_task_user_prompt( $ctx, $analysis, $extra );
		$seed  = 'tag-match-v3|' . md5( (string) $ctx['content_hash'] . '|' . implode( ',', $names ) );
		$res   = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}
		$data  = is_array( $res['data'] ) ? $res['data'] : [];
		$rows  = isset( $data['tags'] ) && is_array( $data['tags'] ) ? $data['tags'] : [];
		$by_name = [];
		foreach ( $pool as $row ) {
			$by_name[ mb_strtolower( $row['name'], 'UTF-8' ) ] = $row;
		}
		$clean = [];
		foreach ( $rows as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$name = isset( $row['name'] ) ? sanitize_text_field( (string) $row['name'] ) : '';
			if ( $name === '' ) {
				continue;
			}
			$key = mb_strtolower( $name, 'UTF-8' );
			if ( ! isset( $by_name[ $key ] ) ) {
				continue;
			}
			$clean[] = [
				'id'     => (int) $by_name[ $key ]['id'],
				'name'   => (string) $by_name[ $key ]['name'],
				'reason' => isset( $row['reason'] ) ? sanitize_text_field( (string) $row['reason'] ) : '',
			];
			if ( count( $clean ) >= 3 ) {
				break;
			}
		}
		$suggested = [];
		$new_rows  = isset( $data['suggested_new_tags'] ) && is_array( $data['suggested_new_tags'] ) ? $data['suggested_new_tags'] : [];
		$seen_new = [];
		foreach ( $names as $n ) {
			$seen_new[ mb_strtolower( (string) $n, 'UTF-8' ) ] = true;
		}
		foreach ( $new_rows as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$name = isset( $row['name'] ) ? sanitize_text_field( (string) $row['name'] ) : '';
			if ( $name === '' ) {
				continue;
			}
			$key = mb_strtolower( $name, 'UTF-8' );
			if ( isset( $seen_new[ $key ] ) || isset( $by_name[ $key ] ) ) {
				continue;
			}
			$suggested[] = [
				'name'   => $name,
				'reason' => isset( $row['reason'] ) ? sanitize_text_field( (string) $row['reason'] ) : '',
			];
			if ( count( $suggested ) >= 3 ) {
				break;
			}
		}
		if ( count( $clean ) < 1 && count( $suggested ) < 1 ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'no_valid_tags',
			];
		}
		$out_data = [
			'tags'                => $clean,
			'suggested_new_tags'  => $suggested,
		];
		if ( $analysis ) {
			$out_data['analysis'] = $analysis;
		}
		return [
			'ok'        => true,
			'data'      => $out_data,
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => newsai_ai_merge_remaining( (int) $res['remaining'], $analysis_result ),
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_tag_match' ) ) {
	function newsai_ai_ajax_tag_match() {
		newsai_ai_check_request();
		$ctx = newsai_ai_read_post_context();
		if ( ! $ctx || $ctx['content'] === '' ) {
			wp_send_json_error( [ 'message' => 'empty_content' ], 400 );
		}
		$res = newsai_ai_task_tag_match( $ctx );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_tag_match', 'newsai_ai_ajax_tag_match' );

// ============================================================
// Task — 1-click Content Brief (Pro BYOK, trends + Google News)
// ============================================================

if ( ! function_exists( 'newsai_ai_radar_headlines_for_brief' ) ) {
	/**
	 * Pull cached trend titles and Google News headlines for the target geo.
	 *
	 * @return array{trends:list<string>,news:list<array{title:string,url:string}>}
	 */
	function newsai_ai_radar_headlines_for_brief( $geo, $beat = '' ) {
		$geo  = strtoupper( sanitize_text_field( (string) $geo ) );
		$beat = function_exists( 'newsai_google_news_normalize_beat' )
			? newsai_google_news_normalize_beat( $beat )
			: sanitize_text_field( (string) $beat );

		$trends = function_exists( 'newsai_ai_fetch_trends_titles' )
			? newsai_ai_fetch_trends_titles( $geo )
			: [];

		$news_rows = [];
		if ( function_exists( 'newsai_google_news_get_items' ) ) {
			$pack = newsai_google_news_get_items( $geo, $beat, 12 );
			if ( ! is_wp_error( $pack ) && ! empty( $pack['items'] ) && is_array( $pack['items'] ) ) {
				foreach ( $pack['items'] as $row ) {
					if ( ! is_array( $row ) ) {
						continue;
					}
					$title = isset( $row['title'] ) ? sanitize_text_field( (string) $row['title'] ) : '';
					$url   = isset( $row['permalink'] ) ? esc_url_raw( (string) $row['permalink'] ) : '';
					if ( $title === '' || $url === '' ) {
						continue;
					}
					$news_rows[] = [
						'title' => $title,
						'url'   => $url,
					];
					if ( count( $news_rows ) >= 12 ) {
						break;
					}
				}
			}
		}

		return [
			'trends' => array_values( array_filter( array_map( 'sanitize_text_field', $trends ) ) ),
			'news'   => $news_rows,
		];
	}
}

if ( ! function_exists( 'newsai_ai_task_content_brief' ) ) {
	/**
	 * Structured editorial brief from a topic + live radar signals.
	 *
	 * @param string $topic Story angle seed (trend term, headline idea, etc.).
	 * @param string $geo   Target geo code.
	 * @param string $beat  Optional news beat for Google News feed.
	 */
	function newsai_ai_task_content_brief( $topic, $geo, $beat = '' ) {
		$topic = sanitize_text_field( (string) $topic );
		if ( $topic === '' ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'empty_topic',
			];
		}

		$geo      = strtoupper( sanitize_text_field( (string) $geo ) );
		$radar    = newsai_ai_radar_headlines_for_brief( $geo, $beat );
		$trends   = array_slice( $radar['trends'], 0, 15 );
		$news     = array_slice( $radar['news'], 0, 12 );
		$language = newsai_ai_lang_label();
		$lang_rule  = newsai_ai_language_directive();
		$loc        = newsai_ai_geo_to_locale();

		$news_lines = [];
		foreach ( $news as $row ) {
			$news_lines[] = '- ' . $row['title'] . ' | ' . $row['url'];
		}

		$system = newsai_ai_seo_editor_preamble() . ' '
			. 'Prepare a one-click content brief for journalists. '
			. 'Use ONLY the topic seed and the supplied trend/news signals — do not invent breaking facts. '
			. 'Output language: ' . $language . '. ' . $lang_rule . ' '
			. newsai_ai_json_output_rules(
				'{"angle":"one sentence story angle","target_keywords":["kw1"],"outline":["section idea"],"competing_urls":[{"title":"headline","url":"https://example.com","why":"one line"}]}'
			)
			. ' Rules: 3–6 target_keywords; 4–7 outline items; 3–6 competing_urls chosen ONLY from the Google News list (exact URLs). '
			. 'Angle must be specific to the topic and geo audience.';

		$user = "Topic seed: {$topic}\n"
			. "Target geo: {$geo}\n"
			. "Audience language: {$loc['native']} ({$loc['label']})\n\n"
			. "Google Trends (region):\n- "
			. ( $trends !== [] ? implode( "\n- ", $trends ) : '(none cached)' )
			. "\n\nGoogle News headlines (pick competitors from here only):\n"
			. ( $news_lines !== [] ? implode( "\n", $news_lines ) : '(none cached)' );

		$seed = 'content-brief-v2|' . $geo . '|' . $loc['code'] . '|' . md5( $topic . '|' . implode( '|', $trends ) . '|' . wp_json_encode( $news ) );
		$res  = newsai_ai_call( $system, $user, $seed );
		if ( empty( $res['ok'] ) ) {
			return $res;
		}

		$data = is_array( $res['data'] ) ? $res['data'] : [];
		$angle = isset( $data['angle'] ) ? sanitize_text_field( (string) $data['angle'] ) : '';
		if ( $angle === '' ) {
			return [
				'ok'    => false,
				'data'  => [],
				'error' => 'invalid_brief',
			];
		}

		$keywords = [];
		if ( isset( $data['target_keywords'] ) && is_array( $data['target_keywords'] ) ) {
			foreach ( $data['target_keywords'] as $kw ) {
				$kw = sanitize_text_field( (string) $kw );
				if ( $kw !== '' ) {
					$keywords[] = $kw;
				}
				if ( count( $keywords ) >= 8 ) {
					break;
				}
			}
		}

		$outline = [];
		if ( isset( $data['outline'] ) && is_array( $data['outline'] ) ) {
			foreach ( $data['outline'] as $line ) {
				$line = sanitize_text_field( (string) $line );
				if ( $line !== '' ) {
					$outline[] = $line;
				}
				if ( count( $outline ) >= 10 ) {
					break;
				}
			}
		}

		$allowed_urls = [];
		foreach ( $news as $row ) {
			$allowed_urls[ $row['url'] ] = $row['title'];
		}
		$competing = [];
		if ( isset( $data['competing_urls'] ) && is_array( $data['competing_urls'] ) ) {
			foreach ( $data['competing_urls'] as $row ) {
				if ( ! is_array( $row ) ) {
					continue;
				}
				$url = isset( $row['url'] ) ? esc_url_raw( (string) $row['url'] ) : '';
				if ( $url === '' || ! isset( $allowed_urls[ $url ] ) ) {
					continue;
				}
				$title = isset( $row['title'] ) ? sanitize_text_field( (string) $row['title'] ) : $allowed_urls[ $url ];
				$why   = isset( $row['why'] ) ? sanitize_text_field( (string) $row['why'] ) : '';
				$competing[] = [
					'title' => $title,
					'url'   => $url,
					'why'   => $why,
				];
				if ( count( $competing ) >= 8 ) {
					break;
				}
			}
		}

		return [
			'ok'        => true,
			'data'      => [
				'angle'            => $angle,
				'target_keywords'  => $keywords,
				'outline'          => $outline,
				'competing_urls'   => $competing,
				'geo'              => $geo,
				'topic'            => $topic,
				'trend_count'      => count( $trends ),
				'news_count'       => count( $news ),
			],
			'error'     => '',
			'cached'    => ! empty( $res['cached'] ),
			'remaining' => isset( $res['remaining'] ) ? (int) $res['remaining'] : 0,
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_content_brief' ) ) {
	function newsai_ai_ajax_content_brief() {
		newsai_ai_check_request();
		$topic = isset( $_POST['topic'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['topic'] ) ) : '';
		if ( $topic === '' ) {
			wp_send_json_error( [ 'message' => 'empty_topic' ], 400 );
		}
		$geo = isset( $_POST['geo'] ) ? strtoupper( sanitize_text_field( wp_unslash( (string) $_POST['geo'] ) ) ) : '';
		if ( $geo === '' && function_exists( 'newsai_get_target_geo' ) ) {
			$geo = strtoupper( (string) newsai_get_target_geo() );
		}
		$beat = isset( $_POST['beat'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['beat'] ) ) : '';
		$res  = newsai_ai_task_content_brief( $topic, $geo, $beat );
		if ( empty( $res['ok'] ) ) {
			wp_send_json_error( [ 'message' => (string) $res['error'] ], 400 );
		}
		wp_send_json_success( $res );
	}
}
add_action( 'wp_ajax_newsai_ai_content_brief', 'newsai_ai_ajax_content_brief' );

if ( ! function_exists( 'newsai_ai_ajax_create_task_from_brief' ) ) {
	/**
	 * Create an editorial task from a generated content brief (Brief v2).
	 */
	function newsai_ai_ajax_create_task_from_brief() {
		newsai_ai_check_request();

		$raw = isset( $_POST['brief'] ) ? (string) wp_unslash( $_POST['brief'] ) : '';
		$brief = json_decode( $raw, true );
		if ( ! is_array( $brief ) || empty( $brief['angle'] ) ) {
			wp_send_json_error( [ 'message' => 'invalid_brief' ], 400 );
		}

		$author_id = isset( $_POST['author_id'] ) ? (int) $_POST['author_id'] : 0;
		if ( $author_id > 0 && ! get_userdata( $author_id ) ) {
			wp_send_json_error( [ 'message' => 'invalid_author' ], 400 );
		}
		if ( $author_id > 0 && ! user_can( $author_id, 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => 'invalid_author' ], 400 );
		}
		if ( $author_id > 0 && ! current_user_can( 'edit_others_posts' ) && $author_id !== (int) get_current_user_id() ) {
			wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
		}

		$due_date = isset( $_POST['due_date'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['due_date'] ) ) : '';
		$due_ts   = 0;
		if ( $due_date !== '' && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $due_date ) ) {
			$due_ts = strtotime( $due_date . ' 12:00:00' );
		}
		if ( $due_ts < 1 ) {
			$due_ts = current_time( 'timestamp' ) + ( 7 * DAY_IN_SECONDS );
		}

		$topic  = isset( $brief['topic'] ) ? sanitize_text_field( (string) $brief['topic'] ) : '';
		$angle  = sanitize_text_field( (string) $brief['angle'] );
		$title  = strlen( $angle ) <= 200 ? $angle : ( $topic !== '' ? $topic : $angle );
		$title  = wp_trim_words( $title, 12, '…' );

		$keywords = [];
		if ( ! empty( $brief['target_keywords'] ) && is_array( $brief['target_keywords'] ) ) {
			foreach ( $brief['target_keywords'] as $kw ) {
				$kw = sanitize_text_field( (string) $kw );
				if ( $kw !== '' ) {
					$keywords[] = $kw;
				}
			}
		}

		$ref_link = '';
		if ( ! empty( $brief['competing_urls'] ) && is_array( $brief['competing_urls'] ) ) {
			$first = reset( $brief['competing_urls'] );
			if ( is_array( $first ) && ! empty( $first['url'] ) ) {
				$ref_link = esc_url_raw( (string) $first['url'] );
			}
		}

		if ( ! function_exists( 'newsai_create_editorial_task' ) ) {
			wp_send_json_error( [ 'message' => 'tasks_unavailable' ], 500 );
		}

		$task_id = newsai_create_editorial_task(
			[
				'title'          => $title,
				'notes'          => function_exists( 'newsai_format_brief_as_task_notes' )
					? newsai_format_brief_as_task_notes( $brief )
					: sanitize_textarea_field( $angle ),
				'target_keyword' => ! empty( $keywords ) ? $keywords[0] : '',
				'ref_link'       => $ref_link,
				'author_id'      => $author_id,
				'lead_days'      => 7,
				'due_ts'         => $due_ts,
			]
		);

		if ( ! $task_id ) {
			wp_send_json_error( [ 'message' => 'insert_failed' ], 500 );
		}

		$settings_url = admin_url( 'admin.php?page=newsai-settings&tab=alerts' );
		wp_send_json_success(
			[
				'task_id'      => (int) $task_id,
				'settings_url' => $settings_url,
				'message'      => __( 'Editorial task created.', 'newsroom-ai-assistant' ),
			]
		);
	}
}
add_action( 'wp_ajax_newsai_ai_create_task_from_brief', 'newsai_ai_ajax_create_task_from_brief' );

if ( ! function_exists( 'newsai_ai_policy_page_specs' ) ) {
	/**
	 * @return array<string, array{title:string,slug:string,option:string,prompt:string}>
	 */
	function newsai_ai_policy_page_specs() {
		return [
			'publishing_principles' => [
				'title'  => __( 'Publishing Principles', 'newsroom-ai-assistant' ),
				'slug'   => 'publishing-principles',
				'option' => 'nros_org_publishing_principles_url',
				'prompt' => 'Editorial standards, independence, sourcing, and corrections overview for a news publisher.',
			],
			'corrections_policy' => [
				'title'  => __( 'Corrections Policy', 'newsroom-ai-assistant' ),
				'slug'   => 'corrections-policy',
				'option' => 'nros_org_corrections_policy_url',
				'prompt' => 'How readers can report errors, how corrections are published, and update timestamps.',
			],
			'ownership_funding' => [
				'title'  => __( 'Ownership & Funding', 'newsroom-ai-assistant' ),
				'slug'   => 'ownership-funding',
				'option' => 'nros_org_ownership_funding_url',
				'prompt' => 'Who owns the publication, funding sources, and any relevant disclosures.',
			],
			'actionable_feedback' => [
				'title'  => __( 'Feedback Policy', 'newsroom-ai-assistant' ),
				'slug'   => 'feedback-policy',
				'option' => 'nros_org_actionable_feedback_url',
				'prompt' => 'How the public can submit feedback, complaints, and contact the newsroom.',
			],
		];
	}
}

if ( ! function_exists( 'newsai_ai_ajax_generate_policy_page' ) ) {
	function newsai_ai_ajax_generate_policy_page() {
		check_ajax_referer( 'newsai_ajax_nonce', 'nonce' );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
		}
		if ( ! function_exists( 'newsai_ai_is_active' ) || ! newsai_ai_is_active() ) {
			wp_send_json_error( [ 'message' => __( 'Configure your AI API key first.', 'newsroom-ai-assistant' ) ], 400 );
		}

		$policy = isset( $_POST['policy'] ) ? sanitize_key( wp_unslash( (string) $_POST['policy'] ) ) : '';
		$specs  = newsai_ai_policy_page_specs();
		if ( ! isset( $specs[ $policy ] ) ) {
			wp_send_json_error( [ 'message' => 'invalid_policy' ], 400 );
		}
		$spec = $specs[ $policy ];

		$site_name = (string) get_option( 'nros_site_name', get_bloginfo( 'name' ) );
		$site_desc = (string) get_option( 'newsai_site_desc', get_bloginfo( 'description' ) );
		$locale    = function_exists( 'newsai_ai_geo_to_locale' ) ? newsai_ai_geo_to_locale() : [ 'native' => 'English', 'code' => 'en' ];

		$system = newsai_ai_seo_editor_preamble() . ' '
			. 'Write concise policy pages for news publishers. '
			. newsai_ai_json_output_rules( '{"title":"Page title","content_html":"<h2>Section</h2><p>Paragraph</p>"}' )
			. ' Use h2/h3 and paragraphs in content_html, no script. Use placeholders like [Owner Name] only when data is unknown.';
		$user   = wp_json_encode(
			[
				'site_name'        => $site_name,
				'site_description' => $site_desc,
				'policy_type'      => $policy,
				'brief'            => $spec['prompt'],
				'language'         => $locale['native'] ?? 'English',
			]
		);

		$result = newsai_ai_call( $system, (string) $user, 'policy_page-v2_' . $policy . '_' . md5( $site_name . $locale['code'] ) );
		if ( empty( $result['ok'] ) || ! is_array( $result['data'] ) ) {
			wp_send_json_error( [ 'message' => (string) ( $result['error'] ?? __( 'AI request failed.', 'newsroom-ai-assistant' ) ) ], 500 );
		}

		$data    = $result['data'];
		$title   = ! empty( $data['title'] ) ? sanitize_text_field( (string) $data['title'] ) : $spec['title'];
		$content = ! empty( $data['content_html'] ) ? wp_kses_post( (string) $data['content_html'] ) : '';
		if ( $content === '' ) {
			wp_send_json_error( [ 'message' => __( 'AI returned empty content.', 'newsroom-ai-assistant' ) ], 500 );
		}

		$page_id = wp_insert_post(
			[
				'post_title'   => $title,
				'post_name'    => sanitize_title( $spec['slug'] ),
				'post_content' => $content,
				'post_status'  => 'draft',
				'post_type'    => 'page',
				'post_author'  => get_current_user_id(),
			],
			true
		);
		if ( is_wp_error( $page_id ) || ! $page_id ) {
			wp_send_json_error( [ 'message' => __( 'Could not create the page.', 'newsroom-ai-assistant' ) ], 500 );
		}

		$url = get_permalink( (int) $page_id );
		if ( ! $url ) {
			$url = '';
		}
		update_option( $spec['option'], esc_url_raw( $url ) );
		if ( function_exists( 'nros_schema_maybe_bump_global_version' ) ) {
			update_option( 'nros_schema_global_v', (string) time() );
		}

		wp_send_json_success(
			[
				'page_id'  => (int) $page_id,
				'url'      => $url,
				'edit_url' => get_edit_post_link( (int) $page_id, 'raw' ),
				'message'  => __( 'Draft policy page created. Review and publish when ready.', 'newsroom-ai-assistant' ),
			]
		);
	}
}
add_action( 'wp_ajax_newsai_ai_generate_policy_page', 'newsai_ai_ajax_generate_policy_page' );

if ( ! function_exists( 'newsai_render_content_brief_bar' ) ) {
	/**
	 * AI content brief bar for desk/chief workflow (Trend Radar → brief → task).
	 *
	 * @param array<string, mixed> $args Optional. context: dashboard.
	 */
	function newsai_render_content_brief_bar( $args = [] ) {
		$args = wp_parse_args(
			is_array( $args ) ? $args : [],
			[
				'context' => 'dashboard',
			]
		);

		if ( ! function_exists( 'newsai_is_pro' ) || ! newsai_is_pro() ) {
			return;
		}
		if ( ! function_exists( 'newsai_ai_is_active' ) || ! newsai_ai_is_active() ) {
			return;
		}
		if ( ! current_user_can( 'edit_others_posts' ) ) {
			return;
		}

		$due_default = wp_date( 'Y-m-d', current_time( 'timestamp' ) + ( 7 * DAY_IN_SECONDS ) );
		?>
		<div id="newsai-brief-bar" class="nros-brief-bar nros-brief-bar--dashboard">
			<label for="newsai-brief-topic" class="nros-brief-bar__label"><?php echo esc_html__( 'Content brief (1-click)', 'newsroom-ai-assistant' ); ?></label>
			<p class="nros-brief-bar__hint description"><?php echo esc_html__( 'Pick a trend or headline, generate an AI brief, then assign a desk task.', 'newsroom-ai-assistant' ); ?></p>
			<input type="text" id="newsai-brief-topic" class="nros-brief-bar__input regular-text" placeholder="<?php echo esc_attr__( 'Trend topic or headline idea…', 'newsroom-ai-assistant' ); ?>">
			<button type="button" id="newsai-brief-generate" class="button button-primary"><?php echo esc_html__( 'Generate brief', 'newsroom-ai-assistant' ); ?></button>
			<div id="newsai-brief-output" class="nros-brief-output" aria-live="polite"></div>
			<div id="newsai-brief-task-tools" class="nros-brief-task-tools" style="display:none;">
				<p class="description nros-brief-task-tools__lead"><?php echo esc_html__( 'Turn this brief into a desk task:', 'newsroom-ai-assistant' ); ?></p>
				<div class="nros-brief-task-tools__row">
					<div>
						<label for="newsai-brief-due-date" class="screen-reader-text"><?php echo esc_html__( 'Due date', 'newsroom-ai-assistant' ); ?></label>
						<input type="date" id="newsai-brief-due-date" class="nros-brief-bar__input" value="<?php echo esc_attr( $due_default ); ?>" aria-label="<?php echo esc_attr__( 'Due date', 'newsroom-ai-assistant' ); ?>">
					</div>
					<div>
						<label for="newsai-brief-assignee" class="screen-reader-text"><?php echo esc_html__( 'Assign to', 'newsroom-ai-assistant' ); ?></label>
						<?php
						wp_dropdown_users(
							[
								'show_option_all' => __( 'Pool (unassigned)', 'newsroom-ai-assistant' ),
								'name'            => 'newsai_brief_assignee',
								'id'              => 'newsai-brief-assignee',
								'selected'        => 0,
								'capability'      => 'edit_posts',
								'number'          => 100,
							]
						);
						?>
					</div>
					<button type="button" id="newsai-brief-create-task" class="button button-primary"><?php echo esc_html__( 'Create task', 'newsroom-ai-assistant' ); ?></button>
				</div>
				<div id="newsai-brief-task-status" class="nros-brief-task-status" aria-live="polite"></div>
			</div>
		</div>
		<?php
	}
}

if ( ! function_exists( 'newsai_should_enqueue_content_brief' ) ) {
	/**
	 * Whether the dashboard content-brief script should load.
	 */
	function newsai_should_enqueue_content_brief() {
		if ( ! function_exists( 'newsai_is_pro' ) || ! newsai_is_pro() ) {
			return false;
		}
		if ( ! function_exists( 'newsai_ai_is_active' ) || ! newsai_ai_is_active() ) {
			return false;
		}
		return current_user_can( 'edit_others_posts' );
	}
}
