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

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

/**
 * Shared helpers for SEO content strategies.
 */
abstract class SmartyPress_AI_SEO_Strategy_Base implements SmartyPress_AI_SEO_Strategy_Interface {

	/**
	 * Find a compact Gutenberg block by client ID.
	 *
	 * @param array  $blocks    Blocks.
	 * @param string $client_id Client ID.
	 * @return array|null
	 */
	protected function find_block( $blocks, $client_id ) {
		foreach ( (array) $blocks as $block ) {
			if ( isset( $block['clientId'] ) && $block['clientId'] === $client_id ) {
				return $block;
			}

			if ( ! empty( $block['innerBlocks'] ) ) {
				$inner = $this->find_block( $block['innerBlocks'], $client_id );
				if ( $inner ) {
					return $inner;
				}
			}
		}

		return null;
	}

	/**
	 * Get all compact text blocks.
	 *
	 * @param array $context Cached SEO context.
	 * @return array
	 */
	protected function get_blocks( $context ) {
		return isset( $context['post']['blocks'] ) && is_array( $context['post']['blocks'] ) ? $context['post']['blocks'] : array();
	}

	/**
	 * Get the current primary keyword from analysis/context.
	 *
	 * @param array $context  Cached SEO context.
	 * @param array $analysis Full-context SEO analysis.
	 * @return string
	 */
	protected function get_primary_keyword( $context, $analysis ) {
		if ( ! empty( $analysis['primary_keyword'] ) ) {
			return (string) $analysis['primary_keyword'];
		}

		if ( ! empty( $context['primaryKeyword'] ) ) {
			return (string) $context['primaryKeyword'];
		}

		return isset( $context['seo']['focus_keywords'][0] ) ? (string) $context['seo']['focus_keywords'][0] : '';
	}

	/**
	 * Normalize text for exact-ish Unicode-safe comparisons.
	 *
	 * @param string $value Value.
	 * @return string
	 */
	protected function normalize_text( $value ) {
		$value = trim( preg_replace( '/\s+/u', ' ', wp_strip_all_tags( (string) $value ) ) );
		$value = function_exists( 'mb_strtolower' ) ? mb_strtolower( $value ) : strtolower( $value );

		return str_replace( 'ё', 'е', $value );
	}

	/**
	 * Check whether generated text meaningfully differs from the target block.
	 *
	 * @param array  $context   Cached SEO context.
	 * @param string $client_id Client ID.
	 * @param string $html      Generated HTML.
	 * @return bool
	 */
	protected function has_meaningful_change( $context, $client_id, $html ) {
		$block = $this->find_block( $this->get_blocks( $context ), $client_id );
		if ( ! $block ) {
			return true;
		}

		$before = $this->normalize_text( $block['text'] ?? '' );
		$after  = $this->normalize_text( $html );

		return '' !== $after && $before !== $after;
	}

	/**
	 * Detect obvious repeated adjacent words produced by the AI.
	 *
	 * @param string $text Text or HTML.
	 * @return bool
	 */
	protected function has_adjacent_word_duplication( $text ) {
		$text = $this->normalize_text( $text );

		return (bool) preg_match( '/\b([\p{L}\p{N}]{3,})(?:[,\s]+)\1\b/u', $text );
	}

	/**
	 * Check whether a text includes the primary keyword.
	 *
	 * @param string $text    Text.
	 * @param string $keyword Keyword.
	 * @return bool
	 */
	protected function includes_keyword( $text, $keyword ) {
		$text    = $this->normalize_text( $text );
		$keyword = $this->normalize_text( $keyword );

		return '' !== $keyword && false !== strpos( $text, $keyword );
	}

	/**
	 * Return the most specific validation failure reason available.
	 *
	 * @param array  $warnings Warnings collected during validation.
	 * @param string $fallback Fallback message.
	 * @return string
	 */
	protected function get_failure_reason( $warnings, $fallback ) {
		$warnings = array_values( array_filter( array_map( 'sanitize_text_field', (array) $warnings ) ) );

		return ! empty( $warnings ) ? end( $warnings ) : $fallback;
	}

	/**
	 * Get focused AI prompt rules for the current strategy.
	 *
	 * @return array
	 */
	public function get_strategy_rules() {
		return array(
			'Only fix the requested testId.',
			'Make the smallest possible edit that can pass the test.',
			'Do not rewrite unrelated sentences, headings, facts, links, or HTML structure.',
			'Do not create cosmetic edits that only change whitespace or punctuation.',
			'Never produce repeated punctuation or duplicated words such as "keyword,keyword ,".',
			'For inflected languages, keep the exact primary_keyword unchanged but rewrite the surrounding sentence so the phrase reads naturally.',
			'If you cannot safely fix the requested test, return skipped_tests instead of a fake operation.',
		);
	}

	/**
	 * Get a default content operation response schema.
	 *
	 * @return array
	 */
	public function get_response_schema() {
		return array(
			'type'                 => 'object',
			'additionalProperties' => false,
			'required'             => array( 'content_operations', 'fixed_tests', 'skipped_tests', 'warnings' ),
			'properties'           => array(
				'content_operations' => array(
					'type'  => 'array',
					'items' => array(
						'type'                 => 'object',
						'additionalProperties' => false,
						'required'             => array( 'type', 'reason', 'testId', 'label', 'blockClientId', 'html', 'plainSummary' ),
						'properties'           => array(
							'type'          => array( 'type' => 'string' ),
							'reason'        => array( 'type' => 'string' ),
							'testId'        => array( 'type' => 'string' ),
							'label'         => array( 'type' => 'string' ),
							'blockClientId' => array( 'type' => 'string' ),
							'html'          => array( 'type' => 'string' ),
							'plainSummary'  => array( 'type' => 'string' ),
						),
					),
				),
				'fixed_tests'        => array(
					'type'  => 'array',
					'items' => array( 'type' => 'string' ),
				),
				'skipped_tests'      => array(
					'type'  => 'array',
					'items' => array( 'type' => 'object' ),
				),
				'warnings'           => array(
					'type'  => 'array',
					'items' => array( 'type' => 'string' ),
				),
			),
		);
	}

	/**
	 * Normalize common operation fields.
	 *
	 * @param array $operation Operation.
	 * @return array
	 */
	protected function normalize_operation( $operation ) {
		$operation = is_array( $operation ) ? $operation : array();

		return array(
			'type'          => sanitize_key( $operation['type'] ?? 'replace' ),
			'reason'        => sanitize_key( $operation['reason'] ?? $this->get_reason() ),
			'testId'        => preg_replace( '/[^A-Za-z0-9_-]/', '', (string) ( $operation['testId'] ?? $this->get_test_id() ) ),
			'label'         => sanitize_text_field( $operation['label'] ?? $this->get_label() ),
			'blockClientId' => sanitize_text_field( $operation['blockClientId'] ?? '' ),
			'html'          => wp_kses_post( $operation['html'] ?? '' ),
			'plainSummary'  => sanitize_text_field( $operation['plainSummary'] ?? '' ),
		);
	}
}
