<?php
/**
 * Readability SEO strategy.
 *
 * @package SmartyPress_AI_Engine
 * @since 1.0.0
 */

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

/**
 * Applies focused editorial readability edits to paragraph blocks.
 */
class SmartyPress_AI_SEO_Strategy_Readability extends SmartyPress_AI_SEO_Strategy_Base {

	/**
	 * Test ID.
	 *
	 * @var string
	 */
	private $test_id;

	/**
	 * Reason.
	 *
	 * @var string
	 */
	private $reason;

	/**
	 * Label.
	 *
	 * @var string
	 */
	private $label;

	/**
	 * Constructor.
	 *
	 * @param string $test_id Test ID.
	 * @param string $reason  Reason.
	 * @param string $label   Label.
	 */
	public function __construct( $test_id, $reason, $label ) {
		$this->test_id = (string) $test_id;
		$this->reason  = (string) $reason;
		$this->label   = (string) $label;
	}

	public function get_test_id() {
		return $this->test_id;
	}

	public function get_reason() {
		return $this->reason;
	}

	public function get_label() {
		return $this->label;
	}

	public function get_context_scope() {
		return 'expanded';
	}

	public function get_strategy_rules() {
		return array_merge(
			parent::get_strategy_rules(),
			array(
				'Rewrite several paragraph blocks when needed to improve the article-level Yoast readability score.',
				'Preserve facts, meaning, links, names, numbers, and language.',
				'For sentenceLengthInText, split or shorten long sentences without removing important facts.',
				'For transitionWords, add natural transition words or connecting phrases to multiple sentences across multiple paragraphs.',
				'Return complete replacement paragraphs, not fragments.',
				'Prefer 3-6 high-impact replacements over one tiny edit when the problem is article-wide.',
			)
		);
	}

	public function is_applicable( $context, $analysis ) {
		return ! empty( $this->select_context( $context, $analysis )['candidate_blocks'] );
	}

	public function select_context( $context, $analysis ) {
		$candidates = array();
		foreach ( $this->get_blocks( $context ) as $block ) {
			if ( 'core/paragraph' !== ( $block['name'] ?? '' ) || empty( $block['text'] ) ) {
				continue;
			}

			$text      = sanitize_textarea_field( $block['text'] );
			$metrics   = $this->measure_text( $text );
			$candidates[] = array(
				'clientId' => sanitize_text_field( $block['clientId'] ?? '' ),
				'name'     => 'core/paragraph',
				'text'     => $text,
				'metrics'  => $metrics,
			);
		}

		usort( $candidates, array( $this, 'sort_candidates' ) );
		$candidates = array_slice( $candidates, 0, 8 );

		$article_metrics = $this->measure_blocks( $this->get_blocks( $context ) );

		if ( 'transitionWords' === $this->get_test_id() ) {
			$target = 'Raise transition sentence coverage to at least 30% across the whole article. Add natural transitions to several currently weak paragraphs.';
		} else {
			$target = 'Reduce sentences over 15 words to 25% or less across the whole article. Split several long highlighted sentences.';
		}

		return array(
			'testId'          => $this->get_test_id(),
			'reason'          => $this->get_reason(),
			'label'           => $this->get_label(),
			'context_scope'   => $this->get_context_scope(),
			'primary_keyword' => $this->get_primary_keyword( $context, $analysis ),
			'article_summary' => $analysis['content_summary'] ?? '',
			'article_metrics' => $article_metrics,
			'target'          => $target,
			'candidate_blocks' => $candidates,
		);
	}

	public function validate_response( $response, $context, $analysis ) {
		$operations = array();
		$warnings   = array_values( array_filter( array_map( 'sanitize_text_field', (array) ( $response['warnings'] ?? array() ) ) ) );
		$before     = $this->measure_blocks( $this->get_blocks( $context ) );
		$after_map  = array();

		foreach ( (array) ( $response['content_operations'] ?? array() ) as $operation ) {
			$operation = $this->normalize_operation( $operation );
			if ( 'replace' !== $operation['type'] || '' === $operation['blockClientId'] || '' === $operation['html'] ) {
				continue;
			}

			if ( ! $this->has_meaningful_change( $context, $operation['blockClientId'], $operation['html'] ) || $this->has_adjacent_word_duplication( $operation['html'] ) ) {
				$warnings[] = __( 'The readability fix was rejected because it did not make a meaningful clean edit.', 'smartypress-ai-engine' );
				continue;
			}

			$operations[] = $operation;
			$after_map[ $operation['blockClientId'] ] = $operation['html'];
			if ( count( $operations ) >= 6 ) {
				break;
			}
		}

		$after    = $this->measure_blocks( $this->get_blocks( $context ), $after_map );
		$improved = $this->metrics_improved( $before, $after );
		if ( ! $improved ) {
			$warnings[] = __( 'The readability fix was rejected because the article-level readability metric did not improve.', 'smartypress-ai-engine' );
			$operations = array();
		}

		return array(
			'status'             => ! empty( $operations ) ? 'success' : 'validation_failure',
			'testId'             => $this->get_test_id(),
			'reason'             => $this->get_reason(),
			'label'              => $this->get_label(),
			'content_operations' => $operations,
			'fixed_tests'        => ! empty( $operations ) ? array( $this->get_test_id() ) : array(),
			'skipped_tests'      => empty( $operations ) ? array( array( 'id' => $this->get_test_id(), 'reason' => $this->get_failure_reason( $warnings, __( 'AI could not safely improve this readability check.', 'smartypress-ai-engine' ) ) ) ) : array(),
			'warnings'           => array_values( array_unique( $warnings ) ),
		);
	}

	/**
	 * Sort candidates by relevance to the active readability test.
	 *
	 * @param array $left  First candidate.
	 * @param array $right Second candidate.
	 * @return int
	 */
	private function sort_candidates( $left, $right ) {
		$left_metrics  = $left['metrics'] ?? array();
		$right_metrics = $right['metrics'] ?? array();

		if ( 'sentenceLengthInText' === $this->get_test_id() ) {
			return ( $right_metrics['longSentences'] ?? 0 ) <=> ( $left_metrics['longSentences'] ?? 0 );
		}

		$left_missing  = max( 0, ( $left_metrics['sentences'] ?? 0 ) - ( $left_metrics['transitionSentences'] ?? 0 ) );
		$right_missing = max( 0, ( $right_metrics['sentences'] ?? 0 ) - ( $right_metrics['transitionSentences'] ?? 0 ) );

		return $right_missing <=> $left_missing;
	}

	/**
	 * Check whether the article-level metric improved enough to trust the operation.
	 *
	 * @param array $before Metrics before.
	 * @param array $after  Metrics after.
	 * @return bool
	 */
	private function metrics_improved( $before, $after ) {
		if ( 'sentenceLengthInText' === $this->get_test_id() ) {
			return ( $after['longSentenceRatio'] ?? 100 ) <= 25 || ( $after['longSentenceRatio'] ?? 100 ) < ( $before['longSentenceRatio'] ?? 100 );
		}

		return ( $after['transitionRatio'] ?? 0 ) >= 30 || ( $after['transitionRatio'] ?? 0 ) > ( $before['transitionRatio'] ?? 0 );
	}

	/**
	 * Measure all paragraph blocks, optionally replacing some block texts.
	 *
	 * @param array $blocks       Blocks.
	 * @param array $replacement_by_block Replacement HTML by block ID.
	 * @return array
	 */
	private function measure_blocks( $blocks, $replacement_by_block = array() ) {
		$total = array(
			'sentences' => 0,
			'longSentences' => 0,
			'transitionSentences' => 0,
			'longSentenceRatio' => 0,
			'transitionRatio' => 0,
		);

		foreach ( (array) $blocks as $block ) {
			if ( 'core/paragraph' !== ( $block['name'] ?? '' ) ) {
				continue;
			}

			$client_id = (string) ( $block['clientId'] ?? '' );
			$text      = $replacement_by_block[ $client_id ] ?? ( $block['text'] ?? '' );
			$metrics   = $this->measure_text( $text );

			$total['sentences'] += $metrics['sentences'];
			$total['longSentences'] += $metrics['longSentences'];
			$total['transitionSentences'] += $metrics['transitionSentences'];
		}

		if ( $total['sentences'] > 0 ) {
			$total['longSentenceRatio'] = round( ( $total['longSentences'] / $total['sentences'] ) * 100, 1 );
			$total['transitionRatio']   = round( ( $total['transitionSentences'] / $total['sentences'] ) * 100, 1 );
		}

		return $total;
	}

	/**
	 * Measure sentence length and transition coverage in text.
	 *
	 * @param string $text Text.
	 * @return array
	 */
	private function measure_text( $text ) {
		$text      = trim( preg_replace( '/\s+/u', ' ', wp_strip_all_tags( (string) $text ) ) );
		$sentences = preg_split( '/(?<=[.!?])\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY );
		$metrics   = array(
			'sentences' => 0,
			'longSentences' => 0,
			'transitionSentences' => 0,
		);

		foreach ( (array) $sentences as $sentence ) {
			$sentence = trim( $sentence );
			if ( '' === $sentence ) {
				continue;
			}

			$metrics['sentences']++;
			if ( $this->count_words( $sentence ) > 15 ) {
				$metrics['longSentences']++;
			}
			if ( $this->sentence_has_transition( $sentence ) ) {
				$metrics['transitionSentences']++;
			}
		}

		return $metrics;
	}

	/**
	 * Count words in a Unicode sentence.
	 *
	 * @param string $sentence Sentence.
	 * @return int
	 */
	private function count_words( $sentence ) {
		preg_match_all( '/[\p{L}\p{N}]+/u', (string) $sentence, $matches );

		return count( $matches[0] ?? array() );
	}

	/**
	 * Approximate transition phrase coverage for Russian Yoast checks.
	 *
	 * @param string $sentence Sentence.
	 * @return bool
	 */
	private function sentence_has_transition( $sentence ) {
		$sentence = $this->normalize_text( $sentence );
		$markers  = array(
			'во первых',
			'во вторых',
			'прежде всего',
			'кроме того',
			'более того',
			'однако',
			'поэтому',
			'при этом',
			'таким образом',
			'например',
			'далее',
			'затем',
			'в результате',
			'следовательно',
			'наконец',
		);

		foreach ( $markers as $marker ) {
			if ( 0 === strpos( $sentence, $marker ) || false !== strpos( $sentence, ' ' . $marker . ' ' ) ) {
				return true;
			}
		}

		return false;
	}
}
