<?php
defined( 'ABSPATH' ) || exit;

/**
 * Extracts valid Schema.org JSON-LD blocks from generated article content.
 */
final class Costudio_Publisher_Schema {
	const META_KEY = '_costudio_schema_json_ld';
	private const JSON_FLAGS = JSON_UNESCAPED_SLASHES
		| JSON_UNESCAPED_UNICODE
		| JSON_PRETTY_PRINT
		| JSON_HEX_TAG
		| JSON_HEX_AMP
		| JSON_HEX_APOS
		| JSON_HEX_QUOT;

	private function __construct() {
	}

	public static function extract_json_ld( string $content ): string {
		$payloads = self::extract_payloads( $content );
		if ( empty( $payloads ) ) {
			return '';
		}

		$schemas = array();
		foreach ( $payloads as $payload ) {
			$schemas[] = '<script type="application/ld+json">' . $payload . '</script>';
		}

		if ( function_exists( 'costudio_log_debug' ) ) {
			costudio_log_debug( 'publisher.schema_extracted', array( 'count' => count( $schemas ) ) );
		}

		return "\n\n<!-- wp:html -->\n" . implode( "\n", $schemas ) . "\n<!-- /wp:html -->";
	}

	/**
	 * @return array<int,string> normalized JSON payloads.
	 */
	public static function extract_payloads( string $content ): array {
		$payloads = array();
		$seen     = array();

		$add_payload = static function ( string $payload ) use ( &$payloads, &$seen ): void {
			$payload = self::normalize_payload( $payload );
			if ( '' === $payload ) {
				return;
			}
			$hash = md5( $payload );
			if ( isset( $seen[ $hash ] ) ) {
				return;
			}
			$seen[ $hash ] = true;
			$payloads[]    = $payload;
		};

		if ( preg_match_all( '/<script\s+type=["\']application\/ld\+json["\']\s*>(.*?)<\/script>/is', $content, $matches ) ) {
			foreach ( $matches[1] as $payload ) {
				$add_payload( $payload );
			}
		}

		if ( preg_match_all( '/```json\s*\n(.*?)\n```/is', $content, $matches ) ) {
			foreach ( $matches[1] as $payload ) {
				$add_payload( $payload );
			}
		}

		return $payloads;
	}

	public static function strip_json_ld( string $content ): string {
		if ( '' === $content ) {
			return '';
		}

		$content = preg_replace(
			'/<!--\s*wp:html\s*-->\s*<script\s+type=["\']application\/ld\+json["\']\s*>.*?<\/script>\s*<!--\s*\/wp:html\s*-->/is',
			'',
			$content
		) ?? $content;

		$content = preg_replace(
			'/<script\s+type=["\']application\/ld\+json["\']\s*>.*?<\/script>/is',
			'',
			$content
		) ?? $content;

		$content = preg_replace_callback(
			'/```json\s*\n(.*?)\n```/is',
			static function ( array $matches ): string {
				return self::is_schema_org_json( trim( (string) $matches[1] ) ) ? '' : $matches[0];
			},
			$content
		) ?? $content;

		// If a script tag was stripped by KSES, the remaining raw JSON-LD usually
		// starts at @context and sits at the end of the article. Remove that tail
		// before Markdown/Gutenberg conversion can turn it into visible paragraphs.
		$content = preg_replace(
			'/(?:^|\R)\s*(?:\{|\[)\s*\{?\s*"@context"\s*:\s*"https?:\/\/schema\.org"[\s\S]*\z/i',
			'',
			$content
		) ?? $content;

		return trim( preg_replace( "/\n{3,}/", "\n\n", $content ) ?? $content );
	}

	public static function register_meta(): void {
		if ( ! function_exists( 'register_post_meta' ) ) {
			return;
		}

		register_post_meta(
			'',
			self::META_KEY,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'sanitize_callback' => array( __CLASS__, 'sanitize_meta_value' ),
				'auth_callback'     => static function (): bool {
					return function_exists( 'current_user_can' ) ? current_user_can( 'edit_posts' ) : false;
				},
			)
		);
	}

	public static function sanitize_meta_value( $value ): string {
		$payloads = json_decode( (string) $value, true );
		if ( ! is_array( $payloads ) ) {
			return '';
		}

		$valid = array();
		foreach ( $payloads as $payload ) {
			$payload = self::normalize_payload( (string) $payload );
			if ( '' !== $payload ) {
				$valid[] = $payload;
			}
		}

		return empty( $valid ) ? '' : ( wp_json_encode( $valid, self::JSON_FLAGS ) ?: '' );
	}

	public static function store_for_post( int $post_id, string $schema_markup ): void {
		if ( $post_id <= 0 || ! function_exists( 'update_post_meta' ) ) {
			return;
		}

		$payloads = self::extract_payloads( $schema_markup );
		if ( empty( $payloads ) ) {
			if ( function_exists( 'delete_post_meta' ) ) {
				delete_post_meta( $post_id, self::META_KEY );
			}
			return;
		}

		update_post_meta(
			$post_id,
			self::META_KEY,
			wp_json_encode( $payloads, self::JSON_FLAGS )
		);

		if ( function_exists( 'costudio_log_debug' ) ) {
			costudio_log_debug(
				'publisher.schema_stored_meta',
				array(
					'post_id' => $post_id,
					'count'   => count( $payloads ),
				)
			);
		}
	}

	public static function content_has_schema( string $content ): bool {
		if ( '' === $content || false === stripos( $content, 'application/ld+json' ) ) {
			return false;
		}

		return ! empty( self::extract_payloads( $content ) );
	}

	public static function render_head(): void {
		if (
			! function_exists( 'is_singular' )
			|| ! is_singular()
			|| ! function_exists( 'get_queried_object_id' )
			|| ! function_exists( 'get_post_meta' )
		) {
			return;
		}

		$post_id = (int) get_queried_object_id();
		if ( $post_id <= 0 ) {
			return;
		}

		if ( function_exists( 'get_post_field' ) ) {
			$content = (string) get_post_field( 'post_content', $post_id );
			if ( self::content_has_schema( $content ) ) {
				if ( function_exists( 'costudio_log_debug' ) ) {
					costudio_log_debug( 'publisher.schema_head_skipped_content_present', array( 'post_id' => $post_id ) );
				}
				return;
			}
		}

		$stored = (string) get_post_meta( $post_id, self::META_KEY, true );
		if ( '' === $stored ) {
			return;
		}

		$payloads = json_decode( $stored, true );
		if ( ! is_array( $payloads ) || empty( $payloads ) ) {
			return;
		}

		if ( function_exists( 'costudio_log_debug' ) ) {
			costudio_log_debug(
				'publisher.schema_head_fallback_rendered',
				array(
					'post_id' => $post_id,
					'count'   => count( $payloads ),
				)
			);
		}

		echo "\n<!-- Contentosapp Studio JSON-LD -->\n";
		foreach ( $payloads as $payload ) {
			$payload = self::normalize_payload( (string) $payload );
			if ( '' === $payload ) {
				continue;
			}
			// Core helper prints the inline JSON-LD safely (no EscapeOutput suppression);
			// payload is validated and re-encoded with JSON_HEX_* flags by normalize_payload().
			wp_print_inline_script_tag( $payload, array( 'type' => 'application/ld+json' ) );
		}
	}

	public static function is_schema_org_json( string $payload ): bool {
		if ( '' === $payload ) {
			return false;
		}
		$decoded = json_decode( $payload, true );
		if ( ! is_array( $decoded ) ) {
			return false;
		}
		$context = $decoded['@context'] ?? '';
		if ( is_array( $context ) ) {
			$context = $context['@vocab'] ?? '';
		}
		return is_string( $context ) && false !== stripos( $context, 'schema.org' );
	}

	private static function normalize_payload( string $payload ): string {
		$payload = trim( $payload );
		if ( ! self::is_schema_org_json( $payload ) ) {
			return '';
		}

		$decoded = json_decode( $payload, true );
		if ( ! is_array( $decoded ) ) {
			return '';
		}

		return wp_json_encode( $decoded, self::JSON_FLAGS ) ?: '';
	}
}
