<?php
/**
 * SEO Length Guidelines
 *
 * Provides character and approximate pixel-width limits for SEO titles
 * and descriptions across search, Open Graph, and Twitter contexts.
 * Supports locale-aware calibration for scripts that use wider characters.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Utils;

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

class Guidelines {

	/**
	 * Google search title limits (character-based approximation).
	 */
	const TITLE_MIN       = 30;
	const TITLE_IDEAL_MIN = 50;
	const TITLE_IDEAL_MAX = 60;
	const TITLE_MAX       = 70;

	/**
	 * Google search description limits.
	 */
	const DESC_MIN       = 70;
	const DESC_IDEAL_MIN = 120;
	const DESC_IDEAL_MAX = 155;
	const DESC_MAX       = 200;

	/**
	 * Open Graph title limits (Facebook / LinkedIn).
	 */
	const OG_TITLE_MIN       = 15;
	const OG_TITLE_IDEAL_MIN = 25;
	const OG_TITLE_IDEAL_MAX = 88;
	const OG_TITLE_MAX       = 100;

	/**
	 * Open Graph description limits.
	 */
	const OG_DESC_MIN       = 45;
	const OG_DESC_IDEAL_MIN = 80;
	const OG_DESC_IDEAL_MAX = 200;
	const OG_DESC_MAX       = 300;

	/**
	 * Twitter card title limits.
	 */
	const TWITTER_TITLE_MIN       = 15;
	const TWITTER_TITLE_IDEAL_MIN = 25;
	const TWITTER_TITLE_IDEAL_MAX = 69;
	const TWITTER_TITLE_MAX       = 70;

	/**
	 * Twitter card description limits.
	 */
	const TWITTER_DESC_MIN       = 45;
	const TWITTER_DESC_IDEAL_MIN = 80;
	const TWITTER_DESC_IDEAL_MAX = 200;
	const TWITTER_DESC_MAX       = 200;

	/**
	 * Approximate average pixel width per character.
	 * Used for rough SERP truncation estimation.
	 */
	const AVG_PIXEL_PER_CHAR = 8.0;
	const TITLE_PIXEL_LIMIT  = 580;
	const DESC_PIXEL_LIMIT   = 990;

	/**
	 * Locale character-width adjustment ratios.
	 * Scripts with wider characters get a lower effective character limit.
	 *
	 * @var array<string,float>
	 */
	private static $char_adjustments = array(
		'as'    => 0.925, // Assamese
		'de_AT' => 0.988, // Austrian German
		'de_CH' => 0.988, // Swiss German
		'de_DE' => 0.988, // German
		'gu'    => 0.925, // Gujarati
		'ml_IN' => 0.625, // Malayalam
		'ja'    => 0.438, // Japanese
		'ko_KR' => 0.513, // Korean
		'ta_IN' => 0.750, // Tamil
		'zh_TW' => 0.438, // Taiwanese Mandarin
		'zh_HK' => 0.438, // Hong Kong Chinese
		'zh_CN' => 0.438, // Mandarin (Simplified)
	);

	/**
	 * Locale pixel-width adjustment ratios for RTL scripts.
	 *
	 * @var array<string,float>
	 */
	private static $pixel_adjustments = array(
		'ar'    => 0.835, // Arabic
		'ary'   => 0.835, // Moroccan Arabic
		'azb'   => 0.835, // South Azerbaijani
		'fa_IR' => 0.835, // Iran Farsi
		'haz'   => 0.835, // Hazaragi
		'ckb'   => 0.835, // Central Kurdish
	);

	/**
	 * Cached locale adjustments.
	 *
	 * @var array<string,array>
	 */
	private static $cache = array();

	/**
	 * Get the character and pixel adjustment ratios for a locale.
	 *
	 * @param string|null $locale Locale code. Defaults to current WordPress locale.
	 * @return array{char_adjust:float,pixel_adjust:float}
	 */
	public static function get_locale_adjustments( $locale = null ) {
		$locale = substr( $locale ?? \get_locale(), 0, 5 );

		if ( isset( self::$cache[ $locale ] ) ) {
			return self::$cache[ $locale ];
		}

		$c_adjust = self::$char_adjustments[ $locale ] ?? 1.0;
		$p_adjust = self::$pixel_adjustments[ $locale ] ?? 1.0;

		$result = array(
			'char_adjust'  => $c_adjust,
			'pixel_adjust' => $p_adjust,
		);

		self::$cache[ $locale ] = $result;

		return $result;
	}

	/**
	 * Get title guidelines for the current or specified locale.
	 *
	 * @param string|null $locale Locale code.
	 * @return array
	 */
	public static function get_title_limits( $locale = null ) {
		$adj = self::get_locale_adjustments( $locale );

		return array(
			'min'         => (int) round( self::TITLE_MIN * $adj['char_adjust'] ),
			'ideal_min'   => (int) round( self::TITLE_IDEAL_MIN * $adj['char_adjust'] ),
			'ideal_max'   => (int) round( self::TITLE_IDEAL_MAX * $adj['char_adjust'] ),
			'max'         => (int) round( self::TITLE_MAX * $adj['char_adjust'] ),
			'pixel_limit' => (int) round( self::TITLE_PIXEL_LIMIT * $adj['pixel_adjust'] ),
		);
	}

	/**
	 * Get description guidelines for the current or specified locale.
	 *
	 * @param string|null $locale Locale code.
	 * @return array
	 */
	public static function get_description_limits( $locale = null ) {
		$adj = self::get_locale_adjustments( $locale );

		return array(
			'min'         => (int) round( self::DESC_MIN * $adj['char_adjust'] ),
			'ideal_min'   => (int) round( self::DESC_IDEAL_MIN * $adj['char_adjust'] ),
			'ideal_max'   => (int) round( self::DESC_IDEAL_MAX * $adj['char_adjust'] ),
			'max'         => (int) round( self::DESC_MAX * $adj['char_adjust'] ),
			'pixel_limit' => (int) round( self::DESC_PIXEL_LIMIT * $adj['pixel_adjust'] ),
		);
	}

	/**
	 * Get Open Graph title guidelines.
	 *
	 * @param string|null $locale Locale code.
	 * @return array
	 */
	public static function get_og_title_limits( $locale = null ) {
		return array(
			'min'       => self::OG_TITLE_MIN,
			'ideal_min' => self::OG_TITLE_IDEAL_MIN,
			'ideal_max' => self::OG_TITLE_IDEAL_MAX,
			'max'       => self::OG_TITLE_MAX,
		);
	}

	/**
	 * Get Open Graph description guidelines.
	 *
	 * @param string|null $locale Locale code.
	 * @return array
	 */
	public static function get_og_description_limits( $locale = null ) {
		return array(
			'min'       => self::OG_DESC_MIN,
			'ideal_min' => self::OG_DESC_IDEAL_MIN,
			'ideal_max' => self::OG_DESC_IDEAL_MAX,
			'max'       => self::OG_DESC_MAX,
		);
	}

	/**
	 * Get Twitter title guidelines.
	 *
	 * @param string|null $locale Locale code.
	 * @return array
	 */
	public static function get_twitter_title_limits( $locale = null ) {
		return array(
			'min'       => self::TWITTER_TITLE_MIN,
			'ideal_min' => self::TWITTER_TITLE_IDEAL_MIN,
			'ideal_max' => self::TWITTER_TITLE_IDEAL_MAX,
			'max'       => self::TWITTER_TITLE_MAX,
		);
	}

	/**
	 * Get Twitter description guidelines.
	 *
	 * @param string|null $locale Locale code.
	 * @return array
	 */
	public static function get_twitter_description_limits( $locale = null ) {
		return array(
			'min'       => self::TWITTER_DESC_MIN,
			'ideal_min' => self::TWITTER_DESC_IDEAL_MIN,
			'ideal_max' => self::TWITTER_DESC_IDEAL_MAX,
			'max'       => self::TWITTER_DESC_MAX,
		);
	}

	/**
	 * Get all guidelines in a structured array for all contexts.
	 *
	 * @param string|null $locale Locale code.
	 * @return array{
	 *   title: array{search:array, opengraph:array, twitter:array},
	 *   description: array{search:array, opengraph:array, twitter:array}
	 * }
	 */
	public static function get_all_guidelines( $locale = null ) {
		return array(
			'title'       => array(
				'search'    => self::get_title_limits( $locale ),
				'opengraph' => self::get_og_title_limits( $locale ),
				'twitter'   => self::get_twitter_title_limits( $locale ),
			),
			'description' => array(
				'search'    => self::get_description_limits( $locale ),
				'opengraph' => self::get_og_description_limits( $locale ),
				'twitter'   => self::get_twitter_description_limits( $locale ),
			),
		);
	}

	/**
	 * Estimate pixel width of a string.
	 *
	 * @param string $text
	 * @return float
	 */
	public static function estimate_pixel_width( $text ) {
		$len   = mb_strlen( $text );
		$upper = preg_match_all( '/[A-Z]/', $text );
		$lower = preg_match_all( '/[a-z]/', $text );
		$other = $len - $upper - $lower;

		// Uppercase chars are wider; narrow chars (i, l, etc.) are narrower.
		// This is a rough approximation — not pixel-perfect but good enough
		// for flagging titles that are likely to be truncated.
		$width = ( $upper * 10.0 ) + ( $lower * 7.0 ) + ( $other * 8.5 );

		return $width;
	}

	/**
	 * Check if a title is likely to be truncated in Google SERPs.
	 *
	 * @param string $title
	 * @return bool
	 */
	public static function title_likely_truncated( $title ) {
		return self::estimate_pixel_width( $title ) > self::TITLE_PIXEL_LIMIT;
	}

	/**
	 * Check if a description is likely to be truncated in Google SERPs.
	 *
	 * @param string $description
	 * @return bool
	 */
	public static function description_likely_truncated( $description ) {
		return self::estimate_pixel_width( $description ) > self::DESC_PIXEL_LIMIT;
	}

	/**
	 * Get a human-readable label for a character count vs. limits.
	 *
	 * @param int   $length Current length.
	 * @param array $limits Limits array (min, ideal_min, ideal_max, max).
	 * @return string  One of: 'empty', 'too-short', 'short', 'good', 'long', 'too-long'.
	 */
	public static function length_label( $length, $limits ) {
		if ( 0 === $length ) {
			return 'empty';
		}
		if ( $length < $limits['min'] ) {
			return 'too-short';
		}
		if ( $length < $limits['ideal_min'] ) {
			return 'short';
		}
		if ( $length <= $limits['ideal_max'] ) {
			return 'good';
		}
		if ( $length <= $limits['max'] ) {
			return 'long';
		}
		return 'too-long';
	}
}
