<?php
/**
 * Base service helpers for SEO optimization.
 *
 * @package SmartyPress_AI_Engine
 * @since 1.0.0
 */

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

/**
 * Provides shared sanitizing and string helpers for SEO services.
 */
abstract class SmartyPress_AI_SEO_Controller_Service_Base {

	/**
	 * Sanitize nested data.
	 *
	 * @param mixed $value      Input value.
	 * @param bool  $allow_html Whether HTML is allowed.
	 * @return mixed
	 */
	protected function sanitize_recursive( $value, $allow_html = false ) {
		if ( is_array( $value ) ) {
			$sanitized = array();
			foreach ( $value as $key => $item ) {
				$clean_key = is_string( $key ) ? preg_replace( '/[^A-Za-z0-9_-]/', '', $key ) : $key;
				$sanitized[ $clean_key ] = $this->sanitize_recursive( $item, $allow_html );
			}
			return $sanitized;
		}

		if ( is_bool( $value ) || is_numeric( $value ) ) {
			return $value;
		}

		$value = (string) $value;

		return $allow_html ? wp_kses_post( $value ) : sanitize_text_field( $value );
	}

	/**
	 * Normalize text for Unicode-safe matching.
	 *
	 * @param string $value Value.
	 * @return string
	 */
	protected function normalize_match_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 );
	}

	/**
	 * Count characters with multibyte support when available.
	 *
	 * @param string $value Input value.
	 * @return int
	 */
	protected function string_length( $value ) {
		return function_exists( 'mb_strlen' ) ? mb_strlen( $value ) : strlen( $value );
	}

	/**
	 * Truncate text to a character limit without cutting the last word.
	 *
	 * @param string $value     Input value.
	 * @param int    $max_chars Maximum characters.
	 * @return string
	 */
	protected function truncate_text( $value, $max_chars ) {
		$value = trim( preg_replace( '/\s+/', ' ', (string) $value ) );

		if ( $this->string_length( $value ) <= $max_chars ) {
			return $value;
		}

		$truncated = function_exists( 'mb_substr' ) ? mb_substr( $value, 0, $max_chars ) : substr( $value, 0, $max_chars );
		$truncated = preg_replace( '/\s+\S*$/u', '', $truncated );

		return trim( $truncated );
	}

	/**
	 * Check whether a focus keyword is concise enough for SEO plugin fields.
	 *
	 * @param string $keyword Keyword.
	 * @return bool
	 */
	protected function is_concise_keyword( $keyword ) {
		$words = preg_split( '/\s+/u', trim( wp_strip_all_tags( (string) $keyword ) ) );
		$words = array_values( array_filter( $words ) );

		return count( $words ) >= 2 && count( $words ) <= 3;
	}

	/**
	 * Keep a unique secondary keyword list without repeating the primary.
	 *
	 * @param string $primary  Primary keyword.
	 * @param array  $keywords Candidate keywords.
	 * @param int    $limit    Maximum count.
	 * @return array
	 */
	protected function normalize_keyword_list( $primary, $keywords, $limit ) {
		$result       = array();
		$primary_key  = $this->normalize_match_text( $primary );
		$seen         = '' !== $primary_key ? array( $primary_key => true ) : array();
		$keyword_pool = array_values( array_filter( array_map( 'sanitize_text_field', (array) $keywords ) ) );

		foreach ( $keyword_pool as $keyword ) {
			if ( ! $this->is_concise_keyword( $keyword ) ) {
				continue;
			}

			$key = $this->normalize_match_text( $keyword );
			if ( '' === $key || isset( $seen[ $key ] ) ) {
				continue;
			}

			$seen[ $key ] = true;
			$result[]     = $keyword;
			if ( count( $result ) >= $limit ) {
				break;
			}
		}

		return $result;
	}
}
