<?php

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

final class IComm_SM_Recommendations {

	private const TRANSIENT_DASH = 'icomm_sm_reco_dash_v1';
	private const TRANSIENT_KW = 'icomm_sm_reco_kw_v1_';
	private const TRANSIENT_OPP = 'icomm_sm_opp_board_v2';
	private const TRANSIENT_TREND = 'icomm_sm_seo_trend_v4';

	public static function clear_cache(): void {
		delete_transient(self::TRANSIENT_DASH);
		delete_transient(self::TRANSIENT_OPP);
		delete_transient(self::TRANSIENT_OPP . '_cov');
		delete_transient(self::TRANSIENT_TREND);
	}

	/**
	 * Monthly SEO trend + action recommendations for the dashboard chart.
	 *
	 * @return array{
	 * months: array<int, array<string,mixed>>,
	 * direction: string,
	 * direction_label: string,
	 * summary: string,
	 * range_note: string,
	 * deltas: array{clicks_pct:float,impressions_pct:float,ctr_pct:float,position_delta:float},
	 * yoy: array{available:bool,clicks_pct:float,impressions_pct:float,label:string},
	 * long_term: array{available:bool,clicks_pct:float,impressions_pct:float,label:string},
	 * recommendations: array<int, array{title:string,details:string,priority:string}>
	 * }
	 */
	public static function seo_monthly_trend(): array {
		$empty = [
			'months' => [],
			'direction' => 'unknown',
			'direction_label' => __('אין מספיק נתונים', 'icomm-metrix'),
			'summary' => __('סנכרנו נתוני חיפוש כדי לראות מגמה חודשית.', 'icomm-metrix'),
			'range_note' => '',
			'deltas' => [
				'clicks_pct' => 0.0,
				'impressions_pct' => 0.0,
				'ctr_pct' => 0.0,
				'position_delta' => 0.0,
			],
			'yoy' => [
				'available' => false,
				'clicks_pct' => 0.0,
				'impressions_pct' => 0.0,
				'label' => '',
			],
			'long_term' => [
				'available' => false,
				'clicks_pct' => 0.0,
				'impressions_pct' => 0.0,
				'label' => '',
			],
			'recommendations' => [],
		];
		if (!IComm_SM_Features::can_use('seo_trend')) {
			return $empty;
		}

		$cached = get_transient(self::TRANSIENT_TREND);
		if (is_array($cached) && isset($cached['months'], $cached['direction'], $cached['recommendations'], $cached['yoy'])) {
			return $cached;
		}

		$hist = IComm_SM_Features::trend_history_days();
		$months_n = (int) min(16, max(3, (int) ceil($hist / 28)));
		$months = IComm_SM_DB::monthly_seo_series($months_n);
		if ($months === []) {
			return $empty;
		}

		$with_data = array_values(array_filter($months, static function ($m) {
			return ((int) ($m['clicks'] ?? 0) + (int) ($m['impressions'] ?? 0)) > 0;
		}));
		$range_note = sprintf(
			/* translators: 1: months with data, 2: max months Google typically keeps */
			__('מוצגים עד %1$d חודשים מתוך היסטוריית GSC (מקסימום כ-%2$d חודשים אחורה). אין נתונים לשנים ישנות יותר מזה.', 'icomm-metrix'),
			count($with_data),
			16
		);

		if (count($with_data) < 2) {
			$out = $empty;
			$out['months'] = $months;
			$out['range_note'] = $range_note;
			$out['summary'] = __('נדרשים לפחות שני חודשים עם נתונים להשוואה. סנכרנו שוב אחרי עדכון השרת כדי למשוך היסטוריה ארוכה.', 'icomm-metrix');
			set_transient(self::TRANSIENT_TREND, $out, HOUR_IN_SECONDS);
			return $out;
		}

		$prev = $with_data[count($with_data) - 2];
		$cur = $with_data[count($with_data) - 1];
		// Very early in the month: compare last two completed months instead of tiny partial totals.
		if (!empty($cur['is_partial']) && (int) ($cur['days_with_data'] ?? 0) < 5 && count($with_data) >= 3) {
			$cur = $with_data[count($with_data) - 2];
			$prev = $with_data[count($with_data) - 3];
		}

		$cur_days = max(1, (int) ($cur['days_with_data'] ?? 0));
		$prev_days = max(1, (int) ($prev['days_with_data'] ?? 0));
		$use_pace = !empty($cur['is_partial']) && $cur_days >= 5;

		if ($use_pace) {
			// Partial month: compare daily pace, not raw totals vs a full previous month.
			$clicks_pct = self::pct_change(
				(float) ((int) ($prev['clicks'] ?? 0) / $prev_days),
				(float) ((int) ($cur['clicks'] ?? 0) / $cur_days)
			);
			$impr_pct = self::pct_change(
				(float) ((int) ($prev['impressions'] ?? 0) / $prev_days),
				(float) ((int) ($cur['impressions'] ?? 0) / $cur_days)
			);
		} else {
			$clicks_pct = self::pct_change((int) ($prev['clicks'] ?? 0), (int) ($cur['clicks'] ?? 0));
			$impr_pct = self::pct_change((int) ($prev['impressions'] ?? 0), (int) ($cur['impressions'] ?? 0));
		}
		$ctr_prev = (float) ($prev['ctr'] ?? 0);
		$ctr_cur = (float) ($cur['ctr'] ?? 0);
		$ctr_pct = self::pct_change($ctr_prev * 1000, $ctr_cur * 1000);
		$pos_prev = (float) ($prev['position'] ?? 0);
		$pos_cur = (float) ($cur['position'] ?? 0);
		// Positive position_delta = improved rankings (lower number).
		$position_delta = ($pos_prev > 0 && $pos_cur > 0) ? round($pos_prev - $pos_cur, 1) : 0.0;

		$yoy = self::compute_yoy($with_data, $cur);
		$long_term = self::compute_long_term($with_data);

		$score = 0.0;
		$score += max(-2, min(2, $clicks_pct / 15));
		$score += max(-1.5, min(1.5, $impr_pct / 20));
		$score += max(-1, min(1, $ctr_pct / 20));
		$score += max(-1.5, min(1.5, $position_delta / 2));
		if (!empty($long_term['available'])) {
			$score += max(-1.5, min(1.5, ((float) $long_term['clicks_pct']) / 25));
		}
		if (!empty($yoy['available'])) {
			$score += max(-1, min(1, ((float) $yoy['clicks_pct']) / 30));
		}

		if ($score >= 0.45) {
			$direction = 'up';
			$direction_label = __('מגמה חיובית', 'icomm-metrix');
		} elseif ($score <= -0.45) {
			$direction = 'down';
			$direction_label = __('מגמה שלילית', 'icomm-metrix');
		} else {
			$direction = 'stable';
			$direction_label = __('מגמה יציבה', 'icomm-metrix');
		}

		if ($use_pace) {
			$cal_day = 0;
			$cur_ym_now = gmdate('Y-m');
			if ((string) ($cur['ym'] ?? '') === $cur_ym_now) {
				$cal_day = (int) current_time('j');
			}
			if ($cal_day > 0 && $cal_day !== $cur_days) {
				$summary = sprintf(
					/* translators: 1: prev month, 2: cur month, 3: GSC days, 4: calendar day, 5: clicks pace %, 6: impressions pace %, 7: position delta */
					__('חודש חלקי (%1$s → %2$s): יש נתוני GSC ל־%3$d ימים (היום בלוח: %4$d לחודש; גוגל לרוב מדווח באיחור של 1-3 ימים). קצב יומי קליקים %5$s%%, קצב חשיפות %6$s%%, שיפור מיקום %7$s.', 'icomm-metrix'),
					(string) ($prev['label'] ?? ''),
					(string) ($cur['label'] ?? ''),
					$cur_days,
					$cal_day,
					self::signed_pct($clicks_pct),
					self::signed_pct($impr_pct),
					($position_delta > 0 ? '+' : '') . (string) $position_delta
				);
			} else {
				$summary = sprintf(
					/* translators: 1: prev month, 2: cur month, 3: GSC days, 4: clicks pace %, 5: impressions pace %, 6: position delta */
					__('חודש חלקי (%1$s → %2$s): יש נתוני GSC ל־%3$d ימים בחודש. קצב יומי קליקים %4$s%%, קצב חשיפות %5$s%%, שיפור מיקום %6$s. ההשוואה לפי קצב יומי כי החודש עוד לא הסתיים.', 'icomm-metrix'),
					(string) ($prev['label'] ?? ''),
					(string) ($cur['label'] ?? ''),
					$cur_days,
					self::signed_pct($clicks_pct),
					self::signed_pct($impr_pct),
					($position_delta > 0 ? '+' : '') . (string) $position_delta
				);
			}
		} else {
			$summary = sprintf(
				/* translators: 1: prev month label, 2: cur month label, 3: clicks %, 4: impressions %, 5: position delta */
				__('חודש מול חודש (%1$s → %2$s): קליקים %3$s%%, חשיפות %4$s%%, שיפור מיקום %5$s.', 'icomm-metrix'),
				(string) ($prev['label'] ?? ''),
				(string) ($cur['label'] ?? ''),
				self::signed_pct($clicks_pct),
				self::signed_pct($impr_pct),
				($position_delta > 0 ? '+' : '') . (string) $position_delta
			);
		}

		$recs = self::build_trend_recommendations($direction, $clicks_pct, $impr_pct, $ctr_pct, $position_delta, $cur, $use_pace);
		if (!empty($yoy['available']) && (float) $yoy['clicks_pct'] <= -15) {
			array_unshift($recs, [
				'title' => __('ירידה מול אותה תקופה אשתקד', 'icomm-metrix'),
				'details' => __('השוואת Year-over-Year מראה ירידה בקליקים. בדקו עונתיות, אובדן דירוגים, או שינוי בביקוש למילות המפתח העיקריות.', 'icomm-metrix'),
				'priority' => 'high',
			]);
		} elseif (!empty($yoy['available']) && (float) $yoy['clicks_pct'] >= 15) {
			array_unshift($recs, [
				'title' => __('עלייה מול אותה תקופה אשתקד', 'icomm-metrix'),
				'details' => __('Year-over-Year חיובי. שמרו על העמודים שצמחו והרחיבו תוכן בנושאים שעובדים.', 'icomm-metrix'),
				'priority' => 'medium',
			]);
		}
		$recs = array_slice($recs, 0, 5);

		$out = [
			'months' => $months,
			'direction' => $direction,
			'direction_label' => $direction_label,
			'summary' => $summary,
			'range_note' => $range_note,
			'deltas' => [
				'clicks_pct' => round($clicks_pct, 1),
				'impressions_pct' => round($impr_pct, 1),
				'ctr_pct' => round($ctr_pct, 1),
				'position_delta' => $position_delta,
			],
			'yoy' => $yoy,
			'long_term' => $long_term,
			'recommendations' => $recs,
		];
		set_transient(self::TRANSIENT_TREND, $out, HOUR_IN_SECONDS);
		return $out;
	}

	/**
	 * @param array<int, array<string,mixed>> $with_data
	 * @param array<string,mixed> $cur
	 * @return array{available:bool,clicks_pct:float,impressions_pct:float,label:string}
	 */
	private static function compute_yoy(array $with_data, array $cur): array {
		$out = [
			'available' => false,
			'clicks_pct' => 0.0,
			'impressions_pct' => 0.0,
			'label' => '',
		];
		$cur_month = (int) ($cur['month'] ?? 0);
		$cur_year = (int) ($cur['year'] ?? 0);
		if ($cur_month < 1 || $cur_year < 1) {
			return $out;
		}
		$prev_year = $cur_year - 1;
		$match = null;
		foreach ($with_data as $m) {
			if ((int) ($m['month'] ?? 0) === $cur_month && (int) ($m['year'] ?? 0) === $prev_year) {
				$match = $m;
				break;
			}
		}
		if ($match === null) {
			return $out;
		}
		$out['available'] = true;
		$out['clicks_pct'] = self::pct_change((int) ($match['clicks'] ?? 0), (int) ($cur['clicks'] ?? 0));
		$out['impressions_pct'] = self::pct_change((int) ($match['impressions'] ?? 0), (int) ($cur['impressions'] ?? 0));
		$out['label'] = sprintf(
			/* translators: 1: previous year month label, 2: current month label */
			__('שנה מול שנה: %1$s → %2$s', 'icomm-metrix'),
			(string) ($match['label'] ?? ''),
			(string) ($cur['label'] ?? '')
		);
		return $out;
	}

	/**
	 * Last ~3 full months vs previous ~3 full months.
	 *
	 * @param array<int, array<string,mixed>> $with_data
	 * @return array{available:bool,clicks_pct:float,impressions_pct:float,label:string}
	 */
	private static function compute_long_term(array $with_data): array {
		$out = [
			'available' => false,
			'clicks_pct' => 0.0,
			'impressions_pct' => 0.0,
			'label' => '',
		];
		$full = array_values(array_filter($with_data, static function ($m) {
			return empty($m['is_partial']);
		}));
		if (count($full) < 6) {
			return $out;
		}
		$recent = array_slice($full, -3);
		$earlier = array_slice($full, -6, 3);
		$sum = static function (array $rows, string $key): int {
			$t = 0;
			foreach ($rows as $r) {
				$t += (int) ($r[$key] ?? 0);
			}
			return $t;
		};
		$out['available'] = true;
		$out['clicks_pct'] = self::pct_change($sum($earlier, 'clicks'), $sum($recent, 'clicks'));
		$out['impressions_pct'] = self::pct_change($sum($earlier, 'impressions'), $sum($recent, 'impressions'));
		$out['label'] = sprintf(
			/* translators: 1: earlier range, 2: recent range */
			__('מגמה ארוכה: %1$s מול %2$s (3 חודשים)', 'icomm-metrix'),
			(string) ($earlier[0]['label'] ?? '') . '-' . (string) ($earlier[2]['label'] ?? ''),
			(string) ($recent[0]['label'] ?? '') . '-' . (string) ($recent[2]['label'] ?? '')
		);
		return $out;
	}

	/**
	 * @param array<string,mixed> $cur
	 * @return array<int, array{title:string,details:string,priority:string}>
	 */
	private static function build_trend_recommendations(
		string $direction,
		float $clicks_pct,
		float $impr_pct,
		float $ctr_pct,
		float $position_delta,
		array $cur,
		bool $pace_based = false
	): array {
		$recs = [];

		if ($impr_pct <= -12 && $clicks_pct <= -8) {
			$recs[] = [
				'title' => __('ירידה בחשיפה האורגנית', 'icomm-metrix'),
				'details' => __('בדקו אילו עמודים איבדו מיקומים, ודאו שאין בעיות אינדוקס, וחזקו תוכן בעמודים עם ירידה חדה בחשיפות.', 'icomm-metrix'),
				'priority' => 'high',
			];
		} elseif ($impr_pct >= 10 && $clicks_pct <= -5) {
			$recs[] = [
				'title' => __('יותר חשיפות, פחות קליקים', 'icomm-metrix'),
				'details' => __('שפרו כותרות Title ותיאורי Meta בעמודים עם CTR נמוך. הוסיפו שאלה ברורה בכותרת והתאמה לכוונת החיפוש.', 'icomm-metrix'),
				'priority' => 'high',
			];
		}

		if ($position_delta <= -1.5) {
			$recs[] = [
				'title' => __('החמרה במיקום הממוצע', 'icomm-metrix'),
				'details' => __('מיקום ממוצע עלה (גרוע יותר). עדכנו תוכן ישן, חזקו קישורים פנימיים, והתמקדו בשאילתות קרובות לטופ 10.', 'icomm-metrix'),
				'priority' => 'high',
			];
		} elseif ($position_delta >= 1.0) {
			$recs[] = [
				'title' => __('שיפור בדירוגים', 'icomm-metrix'),
				'details' => __('המיקום הממוצע השתפר. המשיכו לעדכן עמודים שעולים, והרחיבו FAQ/תוכן משלים באותם נושאים.', 'icomm-metrix'),
				'priority' => 'medium',
			];
		}

		if ($ctr_pct <= -15 && $impr_pct > -5) {
			$recs[] = [
				'title' => __('ירידה ב-CTR', 'icomm-metrix'),
				'details' => __('החשיפות יציבות יחסית אבל שיעור הקליקים ירד. בדקו תחרות בכותרות בתוצאות החיפוש ורעננו snippets.', 'icomm-metrix'),
				'priority' => 'medium',
			];
		}

		if ($direction === 'up') {
			$recs[] = [
				'title' => __('שמרו על המומנטום', 'icomm-metrix'),
				'details' => __('פרסמו תוכן חדש בנושאים שעולים, סמנו הזדמנויות SEO כבוצעו ועקבו אחרי השיפור, והמשיכו סנכרון שבועי.', 'icomm-metrix'),
				'priority' => 'low',
			];
		} elseif ($direction === 'down') {
			$recs[] = [
				'title' => __('תוכנית התאוששות ל-30 יום', 'icomm-metrix'),
				'details' => __('1) סנכרון + בדיקת אינדוקס. 2) שיפור 3-5 עמודים עם ירידה. 3) בניית עמוד חדש לנושא עם ביקוש בלי כיסוי. 4) מעקב שבוע אחרי שבוע.', 'icomm-metrix'),
				'priority' => 'high',
			];
		} else {
			$recs[] = [
				'title' => __('יציבות: חפשו ניצחונות קלים', 'icomm-metrix'),
				'details' => __('התמקדו בשאילתות במיקומים 8-20 וב-CTR נמוך עם חשיפות גבוהות. אלה נותנים שיפור מהיר בלי תחרות על מקום 1.', 'icomm-metrix'),
				'priority' => 'medium',
			];
		}

		if (!empty($cur['is_partial'])) {
			$days = max(1, (int) ($cur['days_with_data'] ?? 0));
			$cal_day = 0;
			if ((string) ($cur['ym'] ?? '') === gmdate('Y-m')) {
				$cal_day = (int) current_time('j');
			}
			$days_note = ($cal_day > 0 && $cal_day !== $days)
				? sprintf(
					/* translators: 1: GSC days with data, 2: calendar day of month */
					__('יש נתוני GSC ל־%1$d ימים בחודש (היום בלוח: %2$d; גוגל לרוב מדווח באיחור של 1-3 ימים).', 'icomm-metrix'),
					$days,
					$cal_day
				)
				: sprintf(
					/* translators: %d: GSC days with data */
					__('יש נתוני GSC ל־%d ימים בחודש הנוכחי.', 'icomm-metrix'),
					$days
				);
			if ($pace_based && $direction === 'up') {
				$recs[] = [
					'title' => __('חודש חלקי בקצב חיובי', 'icomm-metrix'),
					'details' => $days_note . ' ' . __(
						'הקצב היומי גבוה מהחודש הקודם, לכן הסכומים הגולמיים עדיין יכולים להיות נמוכים יותר כי החודש לא הסתיים. המגמה לפי קצב, לא לפי סכום מול חודש מלא.',
						'icomm-metrix'
					),
					'priority' => 'low',
				];
			} elseif ($pace_based && $direction === 'down') {
				$recs[] = [
					'title' => __('חודש חלקי בקצב נמוך', 'icomm-metrix'),
					'details' => $days_note . ' ' . __(
						'הקצב היומי נמוך מהחודש הקודם. חכו לסיום החודש לפני מסקנה סופית, אבל כדאי לבדוק ירידות מוקדם.',
						'icomm-metrix'
					),
					'priority' => 'medium',
				];
			} else {
				$recs[] = [
					'title' => __('החודש הנוכחי חלקי', 'icomm-metrix'),
					'details' => $days_note . ' ' . __(
						'ההשוואה מבוססת על קצב יומי כשיש מספיק ימים עם נתונים, כדי לא להשוות סכום חלקי לחודש מלא.',
						'icomm-metrix'
					),
					'priority' => 'low',
				];
			}
		}

		// Keep top 4 by priority.
		$order = ['high' => 0, 'medium' => 1, 'low' => 2];
		usort($recs, static function ($a, $b) use ($order) {
			$pa = $order[(string) ($a['priority'] ?? 'low')] ?? 9;
			$pb = $order[(string) ($b['priority'] ?? 'low')] ?? 9;
			return $pa <=> $pb;
		});
		return array_slice($recs, 0, 4);
	}

	private static function pct_change(float $prev, float $cur): float {
		if ($prev <= 0 && $cur <= 0) {
			return 0.0;
		}
		if ($prev <= 0) {
			return 100.0;
		}
		return round((($cur - $prev) / $prev) * 100, 1);
	}

	private static function signed_pct(float $pct): string {
		$s = (string) round($pct, 1);
		return ($pct > 0 ? '+' : '') . $s;
	}

	/**
	 * SEO Opportunity Finder board (Phase B): GSC query↔page mapping + improve / adapt / build.
	 *
	 * @return array{
	 * summary: array{improve:int,adapt:int,build:int,total:int},
	 * improve: array<int, array<string,mixed>>,
	 * adapt: array<int, array<string,mixed>>,
	 * build: array<int, array<string,mixed>>,
	 * coverage: array<string,mixed>
	 * }
	 */
	public static function opportunity_board(): array {
		$empty = [
			'summary' => ['improve' => 0, 'adapt' => 0, 'build' => 0, 'total' => 0],
			'improve' => [],
			'adapt' => [],
			'build' => [],
			'coverage' => [
				'published_no_gsc' => [],
				'gsc_no_wp' => [],
				'matched' => 0,
				'published_total' => 0,
				'gsc_total' => 0,
				'published_no_gsc_total' => 0,
				'gsc_no_wp_total' => 0,
			],
		];
		if (!IComm_SM_Features::can_use('opportunities') && !IComm_SM_Features::can_use('recommendations')) {
			return $empty;
		}

		$cached = get_transient(self::TRANSIENT_OPP);
		if (is_array($cached) && isset($cached['summary'], $cached['improve'], $cached['adapt'], $cached['build'], $cached['coverage'])) {
			return $cached;
		}

		$days = min(28, IComm_SM_Features::history_days());
		/** @var array<string, array<string,mixed>> $by_query */
		$by_query = [];

		foreach (IComm_SM_DB::query_opportunities(20, $days, 150, 0.025) as $row) {
			self::merge_signal_row($by_query, $row, 'low_ctr');
		}
		foreach (IComm_SM_DB::query_near_top(20, $days, 80, 8.0, 20.0) as $row) {
			self::merge_signal_row($by_query, $row, 'near_top');
		}
		foreach (IComm_SM_DB::query_rising(15, 8, 4) as $row) {
			$norm = [
				'query' => (string) ($row['query'] ?? ''),
				'impressions' => (int) ($row['impressions_cur'] ?? $row['impressions'] ?? 0),
				'clicks' => (int) ($row['clicks_cur'] ?? $row['clicks'] ?? 0),
				'position' => (float) ($row['position'] ?? 0),
				'ctr' => 0.0,
			];
			$impr = (int) $norm['impressions'];
			$clk = (int) $norm['clicks'];
			$norm['ctr'] = $impr > 0 ? ($clk / $impr) : 0.0;
			$norm['clicks_prev'] = (int) ($row['clicks_prev'] ?? 0);
			$norm['clicks_cur'] = (int) ($row['clicks_cur'] ?? 0);
			self::merge_signal_row($by_query, $norm, 'rising');
		}

		$board = $empty;
		foreach ($by_query as $q => $row) {
			$item = self::classify_opportunity($q, $row, $days);
			if ($item === null) {
				continue;
			}
			$action = (string) ($item['action'] ?? '');
			if (!isset($board[$action]) || !is_array($board[$action])) {
				continue;
			}
			$board[$action][] = $item;
		}

		foreach (['improve', 'adapt', 'build'] as $bucket) {
			usort($board[$bucket], static function ($a, $b) {
				return ((int) ($b['impressions'] ?? 0)) <=> ((int) ($a['impressions'] ?? 0));
			});
			$board[$bucket] = array_slice($board[$bucket], 0, 12);
		}

		// Hide queries already marked as fixed (tracked separately for before/after).
		$done_keys = self::done_query_keys();
		if ($done_keys !== []) {
			foreach (['improve', 'adapt', 'build'] as $bucket) {
				$board[$bucket] = array_values(array_filter(
					(array) $board[$bucket],
					static function ($item) use ($done_keys) {
						if (!is_array($item)) {
							return false;
						}
						$key = self::normalize_query_key((string) ($item['query'] ?? ''));
						return $key === '' || !isset($done_keys[$key]);
					}
				));
			}
		}

		foreach (['improve', 'adapt', 'build'] as $bucket) {
			$board['summary'][$bucket] = count($board[$bucket]);
		}
		$board['summary']['total'] = (int) $board['summary']['improve']
			+ (int) $board['summary']['adapt']
			+ (int) $board['summary']['build'];
		$board['coverage'] = IComm_SM_DB::coverage_gaps($days);

		set_transient(self::TRANSIENT_OPP, $board, HOUR_IN_SECONDS);
		return $board;
	}

	private const MAX_OPP_DONE = 40;

	public static function normalize_query_key(string $query): string {
		$query = trim($query);
		if ($query === '') {
			return '';
		}
		if (function_exists('mb_strtolower')) {
			return (string) mb_strtolower($query, 'UTF-8');
		}
		return strtolower($query);
	}

	/**
	 * @return array<string, true>
	 */
	public static function done_query_keys(): array {
		$s = IComm_SM_Settings::get();
		$out = [];
		foreach ((array) ($s['opp_done'] ?? []) as $row) {
			if (!is_array($row)) {
				continue;
			}
			$key = self::normalize_query_key((string) ($row['query'] ?? ''));
			if ($key !== '') {
				$out[$key] = true;
			}
		}
		return $out;
	}

	/**
	 * Snapshot an opportunity as "fixed" for before/after tracking.
	 *
	 * @param array<string,mixed> $item Opportunity card fields (query, metrics, action…).
	 */
	public static function mark_opportunity_done(array $item): bool {
		$query = trim((string) ($item['query'] ?? ''));
		$key = self::normalize_query_key($query);
		if ($key === '') {
			return false;
		}

		$before = [
			'impressions' => (int) ($item['impressions'] ?? 0),
			'clicks' => (int) ($item['clicks'] ?? 0),
			'ctr' => (float) ($item['ctr'] ?? 0),
			'position' => (float) ($item['position'] ?? 0),
		];
		// Prefer live DB snapshot when available.
		$live = IComm_SM_DB::query_metrics_for($query, min(28, IComm_SM_Features::history_days()));
		if (is_array($live)) {
			$before = [
				'impressions' => (int) ($live['impressions'] ?? 0),
				'clicks' => (int) ($live['clicks'] ?? 0),
				'ctr' => (float) ($live['ctr'] ?? 0),
				'position' => (float) ($live['position'] ?? 0),
			];
		}

		$entry = [
			'query' => $query,
			'action' => sanitize_key((string) ($item['action'] ?? '')),
			'action_label' => sanitize_text_field((string) ($item['action_label'] ?? '')),
			'marked_at' => (int) current_time('timestamp'),
			'before' => $before,
			'post_id' => max(0, (int) ($item['post_id'] ?? 0)),
			'edit_url' => esc_url_raw((string) ($item['edit_url'] ?? '')),
			'view_url' => esc_url_raw((string) ($item['view_url'] ?? '')),
		];

		$s = IComm_SM_Settings::get();
		$done = [];
		foreach ((array) ($s['opp_done'] ?? []) as $row) {
			if (!is_array($row)) {
				continue;
			}
			$rk = self::normalize_query_key((string) ($row['query'] ?? ''));
			if ($rk === '' || $rk === $key) {
				continue;
			}
			$done[] = $row;
		}
		array_unshift($done, $entry);
		if (count($done) > self::MAX_OPP_DONE) {
			$done = array_slice($done, 0, self::MAX_OPP_DONE);
		}
		$s['opp_done'] = $done;
		IComm_SM_Settings::save($s);
		self::clear_cache();
		return true;
	}

	public static function unmark_opportunity_done(string $query): bool {
		$key = self::normalize_query_key($query);
		if ($key === '') {
			return false;
		}
		$s = IComm_SM_Settings::get();
		$done = [];
		$removed = false;
		foreach ((array) ($s['opp_done'] ?? []) as $row) {
			if (!is_array($row)) {
				continue;
			}
			$rk = self::normalize_query_key((string) ($row['query'] ?? ''));
			if ($rk === $key) {
				$removed = true;
				continue;
			}
			$done[] = $row;
		}
		if (!$removed) {
			return false;
		}
		$s['opp_done'] = $done;
		IComm_SM_Settings::save($s);
		self::clear_cache();
		return true;
	}

	/**
	 * Tracked fixes with current GSC metrics and simple before/after deltas.
	 *
	 * @return array<int, array<string,mixed>>
	 */
	public static function tracked_opportunity_progress(): array {
		$s = IComm_SM_Settings::get();
		$rows = (array) ($s['opp_done'] ?? []);
		if ($rows === []) {
			return [];
		}
		$days = min(28, IComm_SM_Features::history_days());
		$now_ts = (int) current_time('timestamp');
		$out = [];

		foreach ($rows as $row) {
			if (!is_array($row)) {
				continue;
			}
			$query = trim((string) ($row['query'] ?? ''));
			if ($query === '') {
				continue;
			}
			$before = is_array($row['before'] ?? null) ? (array) $row['before'] : [];
			$b_impr = (int) ($before['impressions'] ?? 0);
			$b_clk = (int) ($before['clicks'] ?? 0);
			$b_ctr = (float) ($before['ctr'] ?? 0);
			$b_pos = (float) ($before['position'] ?? 0);

			$cur = IComm_SM_DB::query_metrics_for($query, $days);
			$c_impr = is_array($cur) ? (int) ($cur['impressions'] ?? 0) : null;
			$c_clk = is_array($cur) ? (int) ($cur['clicks'] ?? 0) : null;
			$c_ctr = is_array($cur) ? (float) ($cur['ctr'] ?? 0) : null;
			$c_pos = is_array($cur) ? (float) ($cur['position'] ?? 0) : null;

			$marked_at = (int) ($row['marked_at'] ?? 0);
			$age_days = $marked_at > 0 ? (int) floor(max(0, $now_ts - $marked_at) / DAY_IN_SECONDS) : 0;

			$status = 'waiting';
			$status_label = __('ממתינים לנתוני GSC (בדקו שוב בעוד שבוע־שבועיים)', 'icomm-metrix');
			if (!is_array($cur)) {
				$status = 'no_data';
				$status_label = __('אין עדיין נתונים לשאילתה אחרי הסימון', 'icomm-metrix');
			} elseif ($age_days < 7) {
				$status = 'waiting';
				$status_label = sprintf(
					__('סומן לפני %d ימים, מוקדם מדי למסקנה; סנכרנו שוב בעוד שבוע־שבועיים', 'icomm-metrix'),
					$age_days
				);
			} else {
				$ctr_pp = (($c_ctr ?? 0) - $b_ctr) * 100;
				$pos_delta = ($c_pos ?? 0) - $b_pos; // negative = better
				$improved = ($ctr_pp >= 0.3) || ($b_pos > 0 && $pos_delta <= -0.8);
				$worse = ($ctr_pp <= -0.3) || ($b_pos > 0 && $pos_delta >= 0.8);
				if ($improved && !$worse) {
					$status = 'improved';
					$status_label = __('נראה שיפור מול הצילום בסימון', 'icomm-metrix');
				} elseif ($worse && !$improved) {
					$status = 'worse';
					$status_label = __('עדיין חלש / נחלש מול הצילום בסימון', 'icomm-metrix');
				} else {
					$status = 'stable';
					$status_label = __('אין שינוי משמעותי עדיין', 'icomm-metrix');
				}
			}

			$out[] = [
				'query' => $query,
				'action' => (string) ($row['action'] ?? ''),
				'action_label' => (string) ($row['action_label'] ?? ''),
				'marked_at' => $marked_at,
				'age_days' => $age_days,
				'before' => [
					'impressions' => $b_impr,
					'clicks' => $b_clk,
					'ctr' => $b_ctr,
					'position' => $b_pos,
				],
				'current' => is_array($cur) ? [
					'impressions' => $c_impr,
					'clicks' => $c_clk,
					'ctr' => $c_ctr,
					'position' => $c_pos,
				] : null,
				'delta' => is_array($cur) ? [
					'impressions' => $c_impr - $b_impr,
					'clicks' => $c_clk - $b_clk,
					'ctr_pp' => round((($c_ctr ?? 0) - $b_ctr) * 100, 2),
					'position' => round(($c_pos ?? 0) - $b_pos, 1),
				] : null,
				'status' => $status,
				'status_label' => $status_label,
				'post_id' => (int) ($row['post_id'] ?? 0),
				'edit_url' => (string) ($row['edit_url'] ?? ''),
				'view_url' => (string) ($row['view_url'] ?? ''),
			];
		}

		return $out;
	}

	/**
	 * @param array<string, array<string,mixed>> $by_query
	 * @param array<string,mixed> $row
	 */
	private static function merge_signal_row(array &$by_query, array $row, string $signal): void {
		$q = trim((string) ($row['query'] ?? ''));
		if ($q === '' || mb_strlen($q) < 3) {
			return;
		}
		if (!isset($by_query[$q])) {
			$by_query[$q] = [
				'query' => $q,
				'impressions' => 0,
				'clicks' => 0,
				'ctr' => 0.0,
				'position' => 0.0,
				'signals' => [],
				'clicks_prev' => 0,
				'clicks_cur' => 0,
			];
		}
		$by_query[$q]['impressions'] = max((int) $by_query[$q]['impressions'], (int) ($row['impressions'] ?? 0));
		$by_query[$q]['clicks'] = max((int) $by_query[$q]['clicks'], (int) ($row['clicks'] ?? 0));
		$pos = (float) ($row['position'] ?? 0);
		if ($pos > 0 && ((float) $by_query[$q]['position'] <= 0 || $pos < (float) $by_query[$q]['position'])) {
			$by_query[$q]['position'] = $pos;
		}
		$ctr = (float) ($row['ctr'] ?? 0);
		if ($ctr > 0) {
			$by_query[$q]['ctr'] = $ctr;
		} elseif ((int) $by_query[$q]['impressions'] > 0) {
			$by_query[$q]['ctr'] = ((int) $by_query[$q]['clicks']) / ((int) $by_query[$q]['impressions']);
		}
		if (isset($row['clicks_prev'])) {
			$by_query[$q]['clicks_prev'] = (int) $row['clicks_prev'];
		}
		if (isset($row['clicks_cur'])) {
			$by_query[$q]['clicks_cur'] = (int) $row['clicks_cur'];
		}
		$sigs = (array) $by_query[$q]['signals'];
		$sigs[] = $signal;
		$by_query[$q]['signals'] = array_values(array_unique($sigs));
	}

	/**
	 * Build one opportunity card for an arbitrary query (used by niche research).
	 *
	 * @param array<string,mixed> $row Optional metrics/signals.
	 * @return array<string,mixed>|null
	 */
	public static function build_opportunity_card(string $query, array $row = [], int $days = 28): ?array {
		if (!isset($row['signals']) || !is_array($row['signals']) || $row['signals'] === []) {
			$row['signals'] = ['niche'];
		}
		return self::classify_opportunity($query, $row, $days);
	}

	/**
	 * @param array<string,mixed> $row
	 * @return array<string,mixed>|null
	 */
	private static function classify_opportunity(string $query, array $row, int $days = 28): ?array {
		$query = trim($query);
		if ($query === '') {
			return null;
		}

		$page = self::suggest_page_for_query($query, $days);
		$match = (string) ($page['match_strength'] ?? 'none');
		$signals = array_values(array_filter(array_map('strval', (array) ($row['signals'] ?? []))));
		$primary = $signals[0] ?? 'demand';
		$impr = (int) ($row['impressions'] ?? 0);
		$ctr = (float) ($row['ctr'] ?? 0);
		$pos = (float) ($row['position'] ?? 0);

		$metrics = sprintf(
			__('חשיפות %1$d · CTR %2$s%% · מיקום %3$.1f', 'icomm-metrix'),
			$impr,
			(string) round($ctr * 100, 1),
			$pos
		);
		if (in_array('rising', $signals, true)) {
			$metrics .= ' · ' . sprintf(
				__('קליקים %1$d → %2$d', 'icomm-metrix'),
				(int) ($row['clicks_prev'] ?? 0),
				(int) ($row['clicks_cur'] ?? max((int) ($row['clicks'] ?? 0), 0))
			);
		}

		$base = [
			'query' => $query,
			'impressions' => $impr,
			'clicks' => (int) ($row['clicks'] ?? 0),
			'ctr' => $ctr,
			'position' => $pos,
			'signals' => $signals,
			'primary_signal' => $primary,
			'metrics' => $metrics,
			'match_source' => (string) ($page['match_source'] ?? 'none'),
			'gsc_page_url' => (string) ($page['gsc_page_url'] ?? ''),
		];

		$plan_mode = 'low_ctr';
		if ($primary === 'rising') {
			$plan_mode = 'rising';
		} elseif ($primary === 'near_top') {
			$plan_mode = 'near_top';
		} elseif ($primary === 'niche_idea' || $primary === 'niche') {
			$plan_mode = 'niche';
		}

		if ($match === 'none' || (empty($page['edit_url']) && empty($page['gsc_page_url']))) {
			$build_guidance = $plan_mode === 'niche'
				? __(
					'מילת חיפוש רלוונטית לתחום, ובאתר לא נמצא עמוד שעונה עליה. מומלץ לבנות עמוד ייעודי.',
					'icomm-metrix'
				)
				: __(
					'יש ביקוש בחיפוש, ובאתר לא נמצא עמוד שעונה על השאילתה. מומלץ לבנות עמוד ייעודי בתחום העיסוק שלכם.',
					'icomm-metrix'
				);
			return array_merge($base, [
				'action' => 'build',
				'action_label' => __('בנו עמוד חדש', 'icomm-metrix'),
				'guidance' => $build_guidance,
				'content_plan' => self::content_plan_text($query, $plan_mode),
			]);
		}

		if ($match === 'gsc_orphan' && !empty($page['gsc_page_url'])) {
			return array_merge($base, [
				'action' => 'adapt',
				'action_label' => __('התאימו / תקנו נחיתה', 'icomm-metrix'),
				'view_url' => (string) $page['gsc_page_url'],
				'guidance' => sprintf(
					__(
						'גוגל מציג את השאילתה על כתובת שלא ממופה לפוסט/עמוד באתר: %1$s. בדקו הפניות, עמוד ישן, או בנו עמוד חדש שיחליף את הנחיתה.',
						'icomm-metrix'
					),
					(string) $page['gsc_page_url']
				),
				'content_plan' => self::content_plan_text($query, $plan_mode),
			]);
		}

		$with_page = array_merge($base, [
			'post_id' => (int) ($page['post_id'] ?? 0),
			'post_title' => (string) ($page['post_title'] ?? ''),
			'edit_url' => (string) ($page['edit_url'] ?? ''),
			'view_url' => !empty($page['post_id'])
				? (string) get_permalink((int) $page['post_id'])
				: (string) ($page['gsc_page_url'] ?? ''),
		]);

		if ($match === 'weak') {
			return array_merge($with_page, [
				'action' => 'adapt',
				'action_label' => __('התאימו את ההצעה', 'icomm-metrix'),
				'guidance' => sprintf(
					__(
						'נמצא עמוד קרוב (“%1$s”), אבל הוא לא מדבר במדויק את שפת הביקוש “%2$s”. התאימו כותרת, פתיח והצעת ערך לכוונת החיפוש.',
						'icomm-metrix'
					),
					(string) ($page['post_title'] ?? ''),
					$query
				),
			]);
		}

		$from_gsc = ((string) ($page['match_source'] ?? '')) === 'gsc';
		$guidance = $from_gsc
			? sprintf(
				__('גוגל מנחית את השאילתה על “%1$s”. %2$s', 'icomm-metrix'),
				(string) ($page['post_title'] ?? ''),
				self::improve_guidance($query, $primary, $ctr, $pos)
			)
			: self::improve_guidance($query, $primary, $ctr, $pos);

		return array_merge($with_page, [
			'action' => 'improve',
			'action_label' => __('שפרו עמוד קיים', 'icomm-metrix'),
			'guidance' => $guidance,
		]);
	}

	private static function improve_guidance(string $query, string $signal, float $ctr, float $pos): string {
		if ($signal === 'low_ctr' || ($ctr > 0 && $ctr <= 0.025)) {
			return sprintf(
				__('יש ביקוש ל־“%s” והעמוד כבר קיים, אבל ה־CTR נמוך. שפרו Title/Meta, פסקת פתיחה חדה, וכותרת שמושכת הקלקה.', 'icomm-metrix'),
				$query
			);
		}
		if ($signal === 'near_top' || ($pos >= 8 && $pos <= 20)) {
			return sprintf(
				__('“%s” קרוב לטופ. העמיקו את העמוד: תשובה ברורה למעלה, FAQ, דוגמאות, וקישורים פנימיים, כדי לדחוף למיקומים 1-5.', 'icomm-metrix'),
				$query
			);
		}
		return sprintf(
			__('הביקוש ל־“%s” בעלייה. עדכנו את העמוד עכשיו (תוכן טרי, FAQ, CTA) כדי לתפוס את הצמיחה.', 'icomm-metrix'),
			$query
		);
	}

	/**
	 * @return array<int, array{type:string,title:string,details:string,query?:string,post_id?:int,post_title?:string,edit_url?:string,content_plan?:string}>
	 */
	public static function dashboard_items(): array {
		if (!IComm_SM_Features::can_use('recommendations')) {
			return [];
		}
		$cached = get_transient(self::TRANSIENT_DASH);
		if (is_array($cached)) {
			return $cached;
		}

		$items = [];

		$low_ctr = IComm_SM_DB::query_opportunities(6, min(30, IComm_SM_Features::history_days()), 200, 0.02);
		foreach ($low_ctr as $row) {
			$q = trim((string) ($row['query'] ?? ''));
			if ($q === '') {
				continue;
			}
			$page = self::suggest_page_for_query($q);
			$plan = empty($page['edit_url']) ? ['content_plan' => self::content_plan_text($q, 'low_ctr')] : [];
			$items[] = array_merge([
				'type' => 'low_ctr',
				'title' => __('CTR נמוך עם הרבה חשיפות', 'icomm-metrix'),
				'details' => sprintf(
					__('שאילתה: "%1$s" · חשיפות %2$d · CTR %3$s%% · מיקום %4$.1f', 'icomm-metrix'),
					$q,
					(int) ($row['impressions'] ?? 0),
					(string) round(((float) ($row['ctr'] ?? 0)) * 100, 1),
					(float) ($row['position'] ?? 0)
				),
				'query' => $q,
			], $page, $plan);
		}

		$near = IComm_SM_DB::query_near_top(6, min(30, IComm_SM_Features::history_days()), 120, 8.0, 20.0);
		foreach ($near as $row) {
			$q = trim((string) ($row['query'] ?? ''));
			if ($q === '') {
				continue;
			}
			$page = self::suggest_page_for_query($q);
			$plan = empty($page['edit_url']) ? ['content_plan' => self::content_plan_text($q, 'near_top')] : [];
			$items[] = array_merge([
				'type' => 'near_top',
				'title' => __('קרוב לטופ 5 (מיקום 8-20)', 'icomm-metrix'),
				'details' => sprintf(
					__('שאילתה: "%1$s" · מיקום %2$.1f · חשיפות %3$d · CTR %4$s%%', 'icomm-metrix'),
					$q,
					(float) ($row['position'] ?? 0),
					(int) ($row['impressions'] ?? 0),
					(string) round(((float) ($row['ctr'] ?? 0)) * 100, 1)
				),
				'query' => $q,
			], $page, $plan);
		}

		$rising = IComm_SM_DB::query_rising(6, 10, 5);
		foreach ($rising as $row) {
			$q = trim((string) ($row['query'] ?? ''));
			if ($q === '') {
				continue;
			}
			$page = self::suggest_page_for_query($q);
			$plan = empty($page['edit_url']) ? ['content_plan' => self::content_plan_text($q, 'rising')] : [];
			$items[] = array_merge([
				'type' => 'rising',
				'title' => __('שאילתה בעלייה (7 ימים)', 'icomm-metrix'),
				'details' => sprintf(
					__('שאילתה: "%1$s" · קליקים %2$d → %3$d · חשיפות %4$d · מיקום %5$.1f', 'icomm-metrix'),
					$q,
					(int) ($row['clicks_prev'] ?? 0),
					(int) ($row['clicks_cur'] ?? 0),
					(int) ($row['impressions_cur'] ?? 0),
					(float) ($row['position'] ?? 0)
				),
				'query' => $q,
			], $page, $plan);
		}

		// keep concise
		$items = array_slice($items, 0, 12);
		set_transient(self::TRANSIENT_DASH, $items, HOUR_IN_SECONDS);
		return $items;
	}

	/**
	 * Recommendations focused on a chosen keyword watchlist (exact match).
	 *
	 * @param string[] $keywords
	 * @return array<int, array{type:string,title:string,details:string,query?:string,post_id?:int,post_title?:string,edit_url?:string,content_plan?:string}>
	 */
	public static function items_for_keywords(array $keywords): array {
		if (!IComm_SM_Features::can_use('recommendations') || !IComm_SM_Features::can_use('keyword_watch')) {
			return [];
		}

		$keywords = array_values(array_unique(array_filter(array_map('strval', array_map('trim', $keywords)))));
		if ($keywords === []) {
			return [];
		}

		$key = self::TRANSIENT_KW . md5(implode("\n", $keywords));
		$cached = get_transient($key);
		if (is_array($cached)) {
			return $cached;
		}

		$items = [];
		$days = min(30, IComm_SM_Features::history_days());
		$sum = IComm_SM_DB::keywords_summary($keywords, $days, 0);
		$rising = IComm_SM_DB::keywords_rising($keywords, 10, 5);

		// Heuristics (same spirit as the general dashboard):
		// low_ctr: many impressions but low CTR
		// near_top: position 8-20 with decent impressions
		foreach ($keywords as $q) {
			$row = (array) ($sum[$q] ?? ['clicks' => 0, 'impressions' => 0, 'ctr' => 0.0, 'position' => 0.0]);
			$impr = (int) ($row['impressions'] ?? 0);
			$ctr = (float) ($row['ctr'] ?? 0);
			$pos = (float) ($row['position'] ?? 0);

			if ($impr >= 200 && $ctr > 0 && $ctr <= 0.02) {
				$items[] = self::make_item(
					'low_ctr',
					__('CTR נמוך עם הרבה חשיפות', 'icomm-metrix'),
					sprintf(
						__('שאילתה: "%1$s" · חשיפות %2$d · CTR %3$s%% · מיקום %4$.1f', 'icomm-metrix'),
						$q,
						$impr,
						(string) round($ctr * 100, 1),
						$pos
					),
					$q,
					'low_ctr'
				);
			}

			if ($impr >= 120 && $pos >= 8.0 && $pos <= 20.0) {
				$items[] = self::make_item(
					'near_top',
					__('קרוב לטופ 5 (מיקום 8-20)', 'icomm-metrix'),
					sprintf(
						__('שאילתה: "%1$s" · מיקום %2$.1f · חשיפות %3$d · CTR %4$s%%', 'icomm-metrix'),
						$q,
						$pos,
						$impr,
						(string) round($ctr * 100, 1)
					),
					$q,
					'near_top'
				);
			}
		}

		foreach ($rising as $q => $row) {
			$items[] = self::make_item(
				'rising',
				__('שאילתה בעלייה (7 ימים)', 'icomm-metrix'),
				sprintf(
					__('שאילתה: "%1$s" · קליקים %2$d → %3$d · חשיפות %4$d · מיקום %5$.1f', 'icomm-metrix'),
					$q,
					(int) ($row['clicks_prev'] ?? 0),
					(int) ($row['clicks_cur'] ?? 0),
					(int) ($row['impressions_cur'] ?? 0),
					(float) ($row['position'] ?? 0)
				),
				$q,
				'rising'
			);
		}

		$items = array_values(array_filter($items));
		$items = array_slice($items, 0, 12);
		set_transient($key, $items, HOUR_IN_SECONDS);
		return $items;
	}

	/**
	 * @return array{type:string,title:string,details:string,query?:string,post_id?:int,post_title?:string,edit_url?:string,content_plan?:string}
	 */
	private static function make_item(string $type, string $title, string $details, string $query, string $plan_mode): array {
		$query = trim($query);
		$page = self::suggest_page_for_query($query);
		$plan = empty($page['edit_url']) ? ['content_plan' => self::content_plan_text($query, $plan_mode)] : [];
		return array_merge([
			'type' => $type,
			'title' => $title,
			'details' => $details,
			'query' => $query,
		], $page, $plan);
	}

	/**
	 * Prefer Google query↔page mapping; fall back to WP title search.
	 *
	 * @return array{post_id?:int,post_title?:string,edit_url?:string,match_strength?:string,match_source?:string,gsc_page_url?:string}
	 */
	private static function suggest_page_for_query(string $query, int $days = 28): array {
		$query = trim($query);
		if ($query === '' || mb_strlen($query) < 3) {
			return ['match_strength' => 'none', 'match_source' => 'none'];
		}

		$gsc = IComm_SM_DB::top_page_for_query($query, $days);
		if (is_array($gsc) && !empty($gsc['page_url'])) {
			$gsc_url = (string) $gsc['page_url'];
			$pid = IComm_SM_DB::url_to_local_post_id($gsc_url);
			if ($pid > 0) {
				return [
					'post_id' => $pid,
					'post_title' => (string) get_the_title($pid),
					'edit_url' => admin_url('post.php?action=edit&post=' . $pid),
					'match_strength' => 'strong',
					'match_source' => 'gsc',
					'gsc_page_url' => $gsc_url,
				];
			}
			return [
				'match_strength' => 'gsc_orphan',
				'match_source' => 'gsc',
				'gsc_page_url' => $gsc_url,
			];
		}

		$args = [
			'post_type' => ['page', 'post'],
			'post_status' => 'publish',
			'posts_per_page' => 3,
			's' => $query,
			'ignore_sticky_posts' => true,
			'no_found_rows' => true,
		];
		$q = new \WP_Query($args);
		if (!$q->have_posts()) {
			return ['match_strength' => 'none', 'match_source' => 'none'];
		}

		$best = null;
		$best_score = -1.0;
		foreach ($q->posts as $post) {
			if (!$post || empty($post->ID)) {
				continue;
			}
			$pid = (int) $post->ID;
			$title = (string) get_the_title($pid);
			$score = self::title_query_score($query, $title);
			if ($score > $best_score) {
				$best_score = $score;
				$best = [
					'post_id' => $pid,
					'post_title' => $title,
					'edit_url' => admin_url('post.php?action=edit&post=' . $pid),
					'match_strength' => $score >= 0.45 ? 'strong' : 'weak',
					'match_source' => 'wp_search',
				];
			}
		}

		return is_array($best) ? $best : ['match_strength' => 'none', 'match_source' => 'none'];
	}

	/** @return float 0..1 overlap of meaningful tokens / substring hit */
	private static function title_query_score(string $query, string $title): float {
		$q = mb_strtolower(trim($query));
		$t = mb_strtolower(trim($title));
		if ($q === '' || $t === '') {
			return 0.0;
		}
		if (mb_strpos($t, $q) !== false || mb_strpos($q, $t) !== false) {
			return 1.0;
		}
		$tokens = preg_split('/[\s\-\|,\/]+/u', $q) ?: [];
		$tokens = array_values(array_filter(array_map('trim', $tokens), static function ($w) {
			return mb_strlen((string) $w) >= 2;
		}));
		if ($tokens === []) {
			return 0.0;
		}
		$hits = 0;
		foreach ($tokens as $w) {
			if (mb_strpos($t, (string) $w) !== false) {
				$hits++;
			}
		}
		return $hits / count($tokens);
	}

	private static function content_plan_text(string $query, string $mode): string {
		$query = trim($query);
		if ($query === '') {
			return '';
		}
		$site = wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES);
		$tagline = trim((string) get_bloginfo('description'));

		$intent = 'info';
		$lower = mb_strtolower($query);
		foreach (['מחיר', 'עלות', 'קנייה', 'רכישה', 'הזמנה', 'חנות', 'מבצע'] as $w) {
			if (mb_strpos($lower, $w) !== false) {
				$intent = 'buy';
				break;
			}
		}
		foreach (['איך', 'מה זה', 'מדריך', 'למה', 'מתי', 'טיפים'] as $w) {
			if (mb_strpos($lower, $w) !== false) {
				$intent = 'how';
				break;
			}
		}

		$headline = $query;
		$meta_title = $query . ' | ' . $site;
		$meta_desc = ($tagline !== '' ? ($tagline . ', ') : '') . $query . ', ' . __('מדריך קצר, דוגמאות, שאלות נפוצות ומה עושים בפועל.', 'icomm-metrix');

		if ($mode === 'niche') {
			$why = __('הסיבה: מילת חיפוש רלוונטית לתחום שחקרתם. עמוד ייעודי יכול לתפוס ביקוש עוד לפני שיש חשיפות ב־GSC של האתר.', 'icomm-metrix');
		} elseif ($mode === 'low_ctr') {
			$why = __('הסיבה: הרבה חשיפות אבל מעט הקלקות. צריך לשפר Title/Meta ותשובה ברורה בתחילת העמוד.', 'icomm-metrix');
		} elseif ($mode === 'near_top') {
			$why = __('הסיבה: אתם במיקום 8-20. עמוד ממוקד עם תשובה טובה יכול לדחוף לטופ 5.', 'icomm-metrix');
		} else {
			$why = __('הסיבה: השאילתה בעלייה. עכשיו הזמן להעמיק תוכן ולהוסיף FAQ כדי לגדול מהר.', 'icomm-metrix');
		}

		$cta = $intent === 'buy'
			? __('CTA מומלץ: “קבלו הצעת מחיר / דברו איתנו” + טופס/טלפון.', 'icomm-metrix')
			: __('CTA מומלץ: “דברו איתנו ליישום / קבלו בדיקה” או “הורידו צ׳ק-ליסט”.', 'icomm-metrix');

		$h2 = [
			__('מה זה ולמי זה מתאים', 'icomm-metrix'),
			__('איך זה עובד, בקצרה', 'icomm-metrix'),
			__('מה מקבלים (יתרונות)', 'icomm-metrix'),
			__('טעויות נפוצות ואיך להימנע', 'icomm-metrix'),
			__('שאלות נפוצות (FAQ)', 'icomm-metrix'),
		];
		if ($intent === 'buy') {
			array_splice($h2, 3, 0, [__('מחיר / עלויות ומה משפיע', 'icomm-metrix')]);
		}

		$faq = [
			sprintf(__('כמה זמן לוקח להגיע לתוצאה בנושא “%s”?', 'icomm-metrix'), $query),
			sprintf(__('מה צריך להכין/לדעת לפני שמתחילים עם “%s”?', 'icomm-metrix'), $query),
			sprintf(__('איך בוחרים ספק/פתרון נכון עבור “%s”?', 'icomm-metrix'), $query),
			sprintf(__('מה ההבדל בין פתרונות שונים סביב “%s”?', 'icomm-metrix'), $query),
		];

		$lines = [];
		$lines[] = 'H1: ' . $headline;
		$lines[] = $why;
		$lines[] = '';
		$lines[] = 'Meta title: ' . $meta_title;
		$lines[] = 'Meta description: ' . $meta_desc;
		$lines[] = '';
		$lines[] = __('מבנה מומלץ:', 'icomm-metrix');
		foreach ($h2 as $sec) {
			$lines[] = 'H2: ' . $sec;
		}
		$lines[] = '';
		$lines[] = __('FAQ מוצע:', 'icomm-metrix');
		foreach ($faq as $qf) {
			$lines[] = '- ' . $qf;
		}
		$lines[] = '';
		$lines[] = $cta;
		$lines[] = __('טיפ: פסקת פתיחה עם תשובה ברורה ב-2-3 משפטים + דוגמאות/מקרים אמיתיים.', 'icomm-metrix');

		return implode("\n", $lines);
	}
}

