<?php
/**
 * Odysseo SEO — AI generation (AJAX) + shared prompt/JSON helpers.
 *
 * Runs ONLY when an editor clicks "Generate with AI". Sends the post title +
 * content to the configured provider, expects a strict JSON object back, parses
 * it defensively, and returns the five fields for the editor to review and save.
 *
 * @package    Odysseo
 * @author     Chandrabhan Shekhawat
 * @company    Gigai Kripa Services
 * @link       https://gigaikripaservices.com/
 * @copyright  Copyright (c) 2026, Gigai Kripa Services
 */

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

/**
 * The exact keys we ask the model for (and validate on the way back).
 *
 * @return string[]
 */
function odysseo_seo_required_keys() {
	return array( 'seo_title', 'meta_description', 'focus_keywords', 'og_title', 'og_description' );
}

/**
 * System instruction shared by every provider.
 *
 * @return string
 */
function odysseo_seo_system_prompt() {
	return 'You are an expert SEO copywriter. You always respond with a single valid, minified JSON object and absolutely no text, markdown, or code fences around it.';
}

/**
 * Build the user prompt from a post's title + content.
 *
 * @param string $title   Post title.
 * @param string $content Plain-text content.
 * @return string
 */
function odysseo_seo_build_prompt( $title, $content ) {
	$keys = implode( ', ', odysseo_seo_required_keys() );
	return "Write SEO metadata for the web page below. Respond with ONLY a JSON object that has exactly these keys: {$keys}.\n"
		. "- seo_title: compelling, <= 60 characters.\n"
		. "- meta_description: persuasive summary, <= 155 characters.\n"
		. "- focus_keywords: 3-6 relevant keywords, comma-separated.\n"
		. "- og_title: social share title, <= 60 characters.\n"
		. "- og_description: social share description, <= 200 characters.\n"
		. "Do not invent facts, prices, or ratings. Base everything on the provided text.\n\n"
		. "PAGE TITLE: {$title}\n\nPAGE CONTENT:\n{$content}";
}

/**
 * Defensively pull a JSON object out of a model's text response.
 *
 * Strips code fences, isolates the outermost {...}, decodes, and validates that
 * it's an object. Returns the assoc array or a WP_Error.
 *
 * @param string $text Raw model output.
 * @return array|WP_Error
 */
function odysseo_seo_extract_json( $text ) {
	$text = trim( (string) $text );
	if ( '' === $text ) {
		return new WP_Error( 'empty', __( 'The AI returned an empty response.', 'odysseo-core' ) );
	}

	// Remove ```json ... ``` / ``` ... ``` fences if present.
	$text = preg_replace( '/^```(?:json)?\s*/i', '', $text );
	$text = preg_replace( '/\s*```$/', '', $text );

	// Isolate the outermost JSON object.
	$start = strpos( $text, '{' );
	$end   = strrpos( $text, '}' );
	if ( false !== $start && false !== $end && $end > $start ) {
		$text = substr( $text, $start, $end - $start + 1 );
	}

	$data = json_decode( $text, true );
	if ( ! is_array( $data ) ) {
		return new WP_Error( 'parse', __( 'The AI response was not valid JSON.', 'odysseo-core' ) );
	}
	return $data;
}

/**
 * Provider factory.
 *
 * @param string $name Provider slug.
 * @return Odysseo_SEO_Provider_Interface
 */
function odysseo_seo_make_provider( $name ) {
	if ( 'gemini' === $name ) {
		return new Odysseo_SEO_Provider_Gemini();
	}
	return new Odysseo_SEO_Provider_OpenAI();
}

/**
 * The AJAX endpoint.
 */
class Odysseo_SEO_AI {

	/**
	 * Hook up (logged-in only — no nopriv).
	 */
	public function __construct() {
		add_action( 'wp_ajax_odysseo_seo_generate', array( $this, 'handle' ) );
	}

	/**
	 * Generate metadata for a post and return it as JSON.
	 */
	public function handle() {
		if ( ! check_ajax_referer( 'odysseo_seo_generate', 'nonce', false ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed. Reload the page and try again.', 'odysseo-core' ) ), 403 );
		}

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
		if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( array( 'message' => __( 'You are not allowed to edit this item.', 'odysseo-core' ) ), 403 );
		}
		if ( ! odysseo_seo_has_api_key() ) {
			wp_send_json_error( array( 'message' => __( 'No API key is configured.', 'odysseo-core' ) ) );
		}

		$post = get_post( $post_id );
		if ( ! $post ) {
			wp_send_json_error( array( 'message' => __( 'Item not found.', 'odysseo-core' ) ) );
		}

		$title   = (string) get_the_title( $post );
		$content = wp_strip_all_tags( strip_shortcodes( (string) $post->post_content ) );
		$content = trim( preg_replace( '/\s+/', ' ', $content ) );
		if ( strlen( $content ) > 6000 ) {
			$content = substr( $content, 0, 6000 );
		}
		if ( '' === trim( $title . $content ) ) {
			wp_send_json_error( array( 'message' => __( 'Add a title or some content first, then generate.', 'odysseo-core' ) ) );
		}

		$settings = odysseo_seo_get_settings();
		$provider = odysseo_seo_make_provider( $settings['provider'] );
		$model    = ( 'gemini' === $settings['provider'] ) ? $settings['model_gemini'] : $settings['model_openai'];

		$result = $provider->generate( $title, $content, $model, odysseo_seo_get_api_key() );
		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		}

		$out = array();
		foreach ( odysseo_seo_required_keys() as $k ) {
			$v        = isset( $result[ $k ] ) ? (string) $result[ $k ] : '';
			$out[ $k ] = in_array( $k, array( 'meta_description', 'og_description' ), true )
				? sanitize_textarea_field( $v )
				: sanitize_text_field( $v );
		}
		wp_send_json_success( $out );
	}
}
