<?php
/**
 * Frontend SEO Score Display
 *
 * Injects a circular SEO score badge into post content on the frontend.
 * Supports configurable position (top, bottom, both, custom via shortcode)
 * and per-post-type / per-post visibility controls.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Frontend;

use SEOvaultSEO\Core\Options;
use SEOvaultSEO\Core\PostMeta;
use SEOvaultSEO\Utils\SeoAssessment;

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

class FrontendScore {

	/**
	 * Singleton instance.
	 *
	 * @var FrontendScore|null
	 */
	private static $instance = null;

	/**
	 * Whether the frontend CSS has been printed.
	 *
	 * @var bool
	 */
	private $css_added = false;

	/**
	 * Per-request SEO score cache keyed by post ID.
	 *
	 * @var array<int, int>
	 */
	private $score_cache = array();

	/**
	 * Get singleton.
	 *
	 * @return FrontendScore
	 */
	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {}

	/**
	 * Initialize hooks.
	 */
	public static function init() {
		if ( ! \SEOvaultSEO\Compatibility\Detector::should_output_seo() ) {
			return;
		}

		$instance = self::get_instance();
		add_action( 'wp_enqueue_scripts', array( $instance, 'enqueue_assets' ) );
		add_filter( 'the_content', array( $instance, 'insert_score' ) );
		add_shortcode( 'seovault_seo_score', array( $instance, 'shortcode' ) );
	}

	/**
	 * Enqueue frontend-score CSS early when the badge will render.
	 *
	 * @return void
	 */
	public function enqueue_assets() {
		if ( ! \SEOvaultSEO\Utils\FrontendSeoEnqueue::should_enqueue_frontend_seo_ui() ) {
			return;
		}

		if ( ! $this->is_enabled_for_current_post() ) {
			return;
		}

		$position = Options::get_frontend_score_position();
		if ( 'custom' === $position ) {
			// Shortcode may still render; keep CSS available when enabled for this post.
		}

		$this->add_css();
	}

	/**
	 * Insert score badge before/after content based on settings.
	 *
	 * @param string $content Original post content.
	 * @return string
	 */
	public function insert_score( $content ) {
		if ( ! $this->is_enabled_for_current_post() ) {
			return $content;
		}

		$position = Options::get_frontend_score_position();

		if ( 'custom' === $position ) {
			return $content;
		}

		if ( 'top' === $position || 'both' === $position ) {
			$content = $this->render_badge( array( 'class' => 'before-content' ) ) . $content;
		}

		if ( 'bottom' === $position || 'both' === $position ) {
			$content = $content . $this->render_badge( array( 'class' => 'after-content' ) );
		}

		return $content;
	}

	/**
	 * Shortcode callback.
	 *
	 * @param array $atts Shortcode attributes.
	 * @return string
	 */
	public function shortcode( $atts ) {
		if ( ! $this->is_enabled_for_current_post() ) {
			return '';
		}

		$atts = shortcode_atts(
			array(
				'class' => 'as-shortcode',
			),
			$atts,
			'seovault_seo_score'
		);

		return $this->render_badge( $atts );
	}

	/**
	 * Check if frontend score is enabled for the current post context.
	 *
	 * @return bool
	 */
	public function is_enabled_for_current_post() {
		if ( ! \SEOvaultSEO\Utils\FrontendSeoEnqueue::should_enqueue_frontend_seo_ui() ) {
			return false;
		}

		if ( is_front_page() || ! is_singular() || ! did_action( 'loop_start' ) ) {
			return false;
		}

		if ( ! Options::is_frontend_score_enabled() ) {
			return false;
		}

		$post_type = get_post_type();
		$allowed   = Options::get_frontend_score_post_types();

		if ( ! in_array( $post_type, $allowed, true ) ) {
			return false;
		}

		$post_id = get_the_ID();
		if ( ! $post_id ) {
			return false;
		}

		if ( PostMeta::is_frontend_score_hidden( $post_id ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Render the score badge HTML.
	 *
	 * @param array $args {
	 *     Optional. Arguments for the badge.
	 *     @type string $class   Additional CSS class.
	 *     @type int    $post_id Post ID (0 = current post).
	 * }
	 * @return string
	 */
	public function render_badge( $args = array() ) {
		$args = wp_parse_args(
			$args,
			array(
				'class'   => '',
				'post_id' => 0,
			)
		);

		$post_id = (int) $args['post_id'];
		if ( 0 === $post_id ) {
			$post_id = get_the_ID();
		}

		if ( ! $post_id ) {
			return '';
		}

		$score  = $this->calculate_score( $post_id );
		$rating = $this->get_rating( $score );

		if ( 0 === $score ) {
			return '';
		}

		$this->add_css();

		$is_shortcode = ( false !== strpos( $args['class'], 'as-shortcode' ) );

		$buffer_level = ob_get_level();
		ob_start();
		try {
			if ( ! $is_shortcode ) :
				?>
			<div class="seovault-seo-score-wrap <?php echo esc_attr( $args['class'] ); ?>">
			<?php endif; ?>
			<div class="seovault-seo-score rating-<?php echo esc_attr( $rating ); ?> <?php echo esc_attr( $args['class'] ); ?>">
				<span class="seovault-seo-score-number">
					<?php echo esc_html( absint( $score ) ); ?>
					<span class="seovault-seo-score-outof">/ 100</span>
				</span>
				<span class="seovault-seo-score-label"><?php esc_html_e( 'SEO Score', 'seovault-ai' ); ?></span>
			</div>
			<?php if ( ! $is_shortcode ) : ?>
			</div>
				<?php
		endif;
			return ob_get_clean();
		} finally {
			while ( ob_get_level() > $buffer_level ) {
				ob_end_clean();
			}
		}
	}

	/**
	 * Calculate a numeric SEO score (0-100) for a post.
	 *
	 * Converts SeoAssessment check statuses into a weighted percentage:
	 * - good:   100% of weight
	 * - okay:    50% of weight
	 * - bad:      0% of weight
	 * - unknown:  0% of weight
	 *
	 * @param int $post_id Post ID.
	 * @return int Score 0-100.
	 */
	public function calculate_score( $post_id ) {
		$post_id = (int) $post_id;
		if ( isset( $this->score_cache[ $post_id ] ) ) {
			return $this->score_cache[ $post_id ];
		}

		$post = get_post( $post_id );
		if ( ! $post ) {
			$this->score_cache[ $post_id ] = 0;
			return 0;
		}

		$result = SeoAssessment::assess_post( $post_id );
		$checks = $result['checks'];

		if ( empty( $checks ) ) {
			$this->score_cache[ $post_id ] = 0;
			return 0;
		}

		$total_weight = 0;
		$earned       = 0;

		foreach ( $checks as $check ) {
			$weight        = $this->get_check_weight( $check['id'] );
			$total_weight += $weight;

			switch ( $check['status'] ) {
				case SeoAssessment::STATUS_GOOD:
					$earned += $weight;
					break;
				case SeoAssessment::STATUS_OKAY:
					$earned += (int) ( $weight / 2 );
					break;
				case SeoAssessment::STATUS_BAD:
				case SeoAssessment::STATUS_UNKNOWN:
				default:
					break;
			}
		}

		if ( $total_weight <= 0 ) {
			$this->score_cache[ $post_id ] = 0;
			return 0;
		}

		$score                         = (int) round( ( $earned / $total_weight ) * 100 );
		$this->score_cache[ $post_id ] = $score;
		return $score;
	}

	/**
	 * Calculate a numeric SEO score (0-100) from a checks array.
	 *
	 * Used by the live assessment AJAX handler where we don't have
	 * a full SeoAssessment::assess_post() call but already have checks.
	 *
	 * @param array $checks Array of check results with 'id' and 'status'.
	 * @return int Score 0-100.
	 */
	public function calculate_score_from_checks( $checks ) {
		if ( empty( $checks ) || ! is_array( $checks ) ) {
			return 0;
		}

		$total_weight = 0;
		$earned       = 0;

		foreach ( $checks as $check ) {
			$weight        = $this->get_check_weight( $check['id'] );
			$total_weight += $weight;

			switch ( $check['status'] ) {
				case SeoAssessment::STATUS_GOOD:
					$earned += $weight;
					break;
				case SeoAssessment::STATUS_OKAY:
					$earned += (int) ( $weight / 2 );
					break;
				case SeoAssessment::STATUS_BAD:
				case SeoAssessment::STATUS_UNKNOWN:
				default:
					break;
			}
		}

		if ( $total_weight <= 0 ) {
			return 0;
		}

		return (int) round( ( $earned / $total_weight ) * 100 );
	}

	/**
	 * Get the weight for a given check ID.
	 *
	 * Weights are tuned so that critical SEO factors (title, description,
	 * keyword, indexing) have more impact than secondary factors.
	 *
	 * @param string $check_id Check ID from SeoAssessment.
	 * @return int
	 */
	private function get_check_weight( $check_id ) {
		$weights = array(
			'title'       => 20,
			'description' => 15,
			'keyword'     => 15,
			'indexing'    => 20,
			'following'   => 5,
			'canonical'   => 5,
			'branding'    => 5,
			'redirect'    => 10,
		);

		return isset( $weights[ $check_id ] ) ? $weights[ $check_id ] : 5;
	}

	/**
	 * Convert numeric score to a textual rating.
	 *
	 * @param int $score SEO score (0-100).
	 * @return string 'unknown', 'bad', 'good', or 'great'.
	 */
	public function get_rating( $score ) {
		if ( 0 === $score ) {
			return 'unknown';
		}
		if ( $score <= 50 ) {
			return 'bad';
		}
		if ( $score <= 80 ) {
			return 'good';
		}
		return 'great';
	}

	/**
	 * Output inline CSS once.
	 */
	private function add_css() {
		if ( $this->css_added ) {
			return;
		}

		$css_path = SEOVAULT_SEO_DIR . 'assets/css/frontend-score.css';
		if ( file_exists( $css_path ) ) {
			wp_enqueue_style(
				'seovaultseo-frontend-score',
				SEOVAULT_SEO_URL . 'assets/css/frontend-score.css',
				array(),
				SEOVAULT_SEO_VERSION
			);
		}

		$this->css_added = true;
	}
}
