<?php
/**
 * Schema.org JSON-LD generator.
 *
 * Automatically outputs structured data in the <head> for
 * improved search engine and AI engine visibility.
 *
 * @package MonitorCat\Modules\Seo\Generator
 */

declare(strict_types=1);

namespace MonitorCat\Modules\Seo\Generator;

defined( 'ABSPATH' ) || exit;

use MonitorCat\Settings;

/**
 * Generates and outputs JSON-LD structured data via wp_head.
 */
final class SchemaGenerator {

	/**
	 * Plugin settings.
	 *
	 * @var Settings
	 */
	private Settings $settings;

	/**
	 * Constructor.
	 *
	 * @param Settings $settings Plugin settings.
	 */
	public function __construct( Settings $settings ) {
		$this->settings = $settings;
	}

	/**
	 * Register the wp_head hook.
	 */
	public function boot(): void {
		if ( ! $this->settings->get( 'seo.auto_schema', true ) ) {
			return;
		}

		// Skip if a major SEO plugin handles schema.
		if ( defined( 'WPSEO_VERSION' ) || defined( 'AIOSEO_VERSION' ) || defined( 'RANK_MATH_VERSION' ) ) {
			return;
		}

		add_action( 'wp_head', array( $this, 'render' ), 1 );
	}

	/**
	 * Output JSON-LD structured data blocks.
	 */
	public function render(): void {
		$schemas = array();

		$schemas[] = $this->get_website_schema();

		$schemas[] = $this->get_organization_schema();

		if ( is_singular() ) {
			$post = get_queried_object();
			if ( $post && isset( $post->ID ) ) {
				$schemas[] = $this->get_breadcrumb_schema( $post );

				if ( 'post' === ( $post->post_type ?? '' ) ) {
					$schemas[] = $this->get_article_schema( $post );
				} else {
					$schemas[] = $this->get_webpage_schema( $post );
				}

				$faq = $this->get_faq_schema( $post );
				if ( $faq ) {
					$schemas[] = $faq;
				}
			}
		}

		foreach ( $schemas as $schema ) {
			$json = wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT );
			if ( $json ) {
				// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON-LD is pre-encoded by wp_json_encode().
				echo '<script type="application/ld+json">' . "\n" . $json . "\n" . '</script>' . "\n";
			}
		}
	}

	/**
	 * Generate WebSite schema with SearchAction.
	 *
	 * @return array<string, mixed>
	 */
	public function get_website_schema(): array {
		$name = $this->settings->get( 'seo.organization_name', '' );
		if ( empty( $name ) ) {
			$name = get_bloginfo( 'name' );
		}

		return array(
			'@context'        => 'https://schema.org',
			'@type'           => 'WebSite',
			'name'            => $name,
			'url'             => home_url( '/' ),
			'potentialAction' => array(
				'@type'       => 'SearchAction',
				'target'      => array(
					'@type'       => 'EntryPoint',
					'urlTemplate' => home_url( '/?s={search_term_string}' ),
				),
				'query-input' => 'required name=search_term_string',
			),
		);
	}

	/**
	 * Generate Organization schema from settings.
	 *
	 * @return array<string, mixed>
	 */
	public function get_organization_schema(): array {
		$name = $this->settings->get( 'seo.organization_name', '' );
		$name = is_string( $name ) ? $name : '';

		if ( empty( $name ) ) {
			$name = get_bloginfo( 'name' );
		}

		$schema = array(
			'@context' => 'https://schema.org',
			'@type'    => 'Organization',
			'name'     => $name,
			'url'      => home_url( '/' ),
		);

		$logo = $this->settings->get( 'seo.organization_logo', '' );
		$logo = is_string( $logo ) ? $logo : '';
		if ( ! empty( $logo ) ) {
			$schema['logo'] = array(
				'@type' => 'ImageObject',
				'url'   => $logo,
			);
		}

		$socials = $this->settings->get( 'seo.social_profiles', array() );
		$socials = is_array( $socials ) ? $socials : array();
		if ( ! empty( $socials ) ) {
			$schema['sameAs'] = array_values( $socials );
		}

		return $schema;
	}

	/**
	 * Generate BreadcrumbList schema from page hierarchy.
	 *
	 * @param object $post The current post object.
	 * @return array<string, mixed>
	 */
	public function get_breadcrumb_schema( object $post ): array {
		$items = array();

		// Home.
		$items[] = array(
			'@type'    => 'ListItem',
			'position' => 1,
			'name'     => get_bloginfo( 'name' ),
			'item'     => home_url( '/' ),
		);

		// Parent pages.
		$ancestors = get_post_ancestors( $post->ID );
		$ancestors = array_reverse( $ancestors );
		$position  = 2;

		foreach ( $ancestors as $ancestor_id ) {
			$items[] = array(
				'@type'    => 'ListItem',
				'position' => $position,
				'name'     => get_the_title( $ancestor_id ),
				'item'     => get_permalink( $ancestor_id ),
			);
			++$position;
		}

		// Current page.
		$items[] = array(
			'@type'    => 'ListItem',
			'position' => $position,
			'name'     => get_the_title( $post->ID ),
			'item'     => get_permalink( $post->ID ),
		);

		return array(
			'@context'        => 'https://schema.org',
			'@type'           => 'BreadcrumbList',
			'itemListElement' => $items,
		);
	}

	/**
	 * Generate Article / BlogPosting schema for posts.
	 *
	 * @param object $post The current post object.
	 * @return array<string, mixed>
	 */
	public function get_article_schema( object $post ): array {
		$schema = array(
			'@context'         => 'https://schema.org',
			'@type'            => 'Article',
			'headline'         => get_the_title( $post->ID ),
			'url'              => get_permalink( $post->ID ),
			'datePublished'    => $post->post_date ?? '',
			'dateModified'     => $post->post_modified ?? '',
			'mainEntityOfPage' => array(
				'@type' => 'WebPage',
				'@id'   => get_permalink( $post->ID ),
			),
		);

		// Author.
		$author_id = $post->post_author ?? 0;
		if ( $author_id ) {
			$author = get_userdata( (int) $author_id );
			if ( $author ) {
				$schema['author'] = array(
					'@type' => 'Person',
					'name'  => $author->display_name ?? '',
					'url'   => get_author_posts_url( (int) $author_id ),
				);
			}
		}

		// Featured image.
		$thumb_id = get_post_thumbnail_id( $post->ID );
		if ( $thumb_id ) {
			$image_src = wp_get_attachment_image_src( $thumb_id, 'large' );
			if ( $image_src ) {
				$schema['image'] = array(
					'@type'  => 'ImageObject',
					'url'    => $image_src[0],
					'width'  => $image_src[1],
					'height' => $image_src[2],
				);
			}
		}

		// Publisher (organization).
		$org_name = $this->settings->get( 'seo.organization_name', '' );
		$org_name = is_string( $org_name ) ? $org_name : '';
		if ( empty( $org_name ) ) {
			$org_name = get_bloginfo( 'name' );
		}

		$schema['publisher'] = array(
			'@type' => 'Organization',
			'name'  => $org_name,
		);

		$logo = $this->settings->get( 'seo.organization_logo', '' );
		$logo = is_string( $logo ) ? $logo : '';
		if ( ! empty( $logo ) ) {
			$schema['publisher']['logo'] = array(
				'@type' => 'ImageObject',
				'url'   => $logo,
			);
		}

		// Description from excerpt.
		$excerpt = get_the_excerpt( $post->ID );
		if ( ! empty( $excerpt ) ) {
			$schema['description'] = wp_trim_words( wp_strip_all_tags( $excerpt ), 30, '...' );
		}

		return $schema;
	}

	/**
	 * Generate WebPage schema for pages.
	 *
	 * @param object $post The current post object.
	 * @return array<string, mixed>
	 */
	public function get_webpage_schema( object $post ): array {
		$schema = array(
			'@context'      => 'https://schema.org',
			'@type'         => 'WebPage',
			'name'          => get_the_title( $post->ID ),
			'url'           => get_permalink( $post->ID ),
			'datePublished' => $post->post_date ?? '',
			'dateModified'  => $post->post_modified ?? '',
		);

		$excerpt = get_the_excerpt( $post->ID );
		if ( ! empty( $excerpt ) ) {
			$schema['description'] = wp_trim_words( wp_strip_all_tags( $excerpt ), 30, '...' );
		}

		return $schema;
	}

	/**
	 * Generate FAQPage schema by auto-detecting Q&A patterns.
	 *
	 * Scans for H2/H3 headings that end with "?" and treats the following
	 * paragraph as the answer.
	 *
	 * @param object $post The current post object.
	 * @return array<string, mixed>|null Null if no FAQ pairs found.
	 */
	public function get_faq_schema( object $post ): ?array {
		$content = get_post_field( 'post_content', $post->ID );
		$content = is_string( $content ) ? $content : '';

		// Find question headings followed by answer paragraphs.
		preg_match_all(
			'/<h[2-3][^>]*>(.*?\?)\s*<\/h[2-3]>\s*<p[^>]*>(.*?)<\/p>/is',
			$content,
			$matches,
			PREG_SET_ORDER
		);

		if ( empty( $matches ) ) {
			return null;
		}

		$faq_items = array();
		foreach ( $matches as $match ) {
			$question = wp_strip_all_tags( $match[1] );
			$answer   = wp_strip_all_tags( $match[2] );

			if ( ! empty( $question ) && ! empty( $answer ) ) {
				$faq_items[] = array(
					'@type'          => 'Question',
					'name'           => $question,
					'acceptedAnswer' => array(
						'@type' => 'Answer',
						'text'  => $answer,
					),
				);
			}
		}

		if ( empty( $faq_items ) ) {
			return null;
		}

		return array(
			'@context'   => 'https://schema.org',
			'@type'      => 'FAQPage',
			'mainEntity' => $faq_items,
		);
	}
}
