<?php
/**
 * Schema.org JSON-LD: scan, validation, recommendations, generate, publish.
 *
 * @package LLMagnet_AI_SEO_Optimizer
 */

namespace LLMagnet_AI_SEO_Optimizer;

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

/**
 * JSON-LD Schema services and REST API.
 */
class Schema_Jsonld {

	public const OPTION_SETTINGS     = 'llmagnet_schema_settings';
	public const OPTION_PUBLISHED    = 'llmagnet_schema_published_ld';
	public const OPTION_LAST_SCAN    = 'llmagnet_schema_last_scan';
	public const OPTION_SCAN_HISTORY = 'llmagnet_schema_scan_history';

	/**
	 * Bootstrap hooks.
	 *
	 * @return void
	 */
	public function init(): void {
		add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
		add_action( 'wp_head', [ $this, 'output_published_jsonld' ], 99 );
	}

	/**
	 * Output published JSON-LD on the front (non-destructive: only our block).
	 *
	 * @return void
	 */
	public function output_published_jsonld(): void {
		if ( is_admin() ) {
			return;
		}
		$settings = $this->get_settings();
		if ( empty( $settings['enabled'] ) ) {
			return;
		}
		$published = get_option( self::OPTION_PUBLISHED, '' );
		if ( ! is_string( $published ) || '' === $published ) {
			return;
		}
		$data = json_decode( $published, true );
		if ( ! is_array( $data ) || empty( $data ) ) {
			return;
		}
		$json = wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT );
		if ( ! is_string( $json ) ) {
			return;
		}
		echo "\n<!-- LLMagnet Schema JSON-LD -->\n<script type=\"application/ld+json\">\n";
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON-LD is machine-readable; sanitized via json_encode from structured array.
		echo $json;
		echo "\n</script>\n";
	}

	/**
	 * @return array<string, mixed>
	 */
	private function get_settings(): array {
		$defaults = [
			'enabled' => true,
		];
		$stored = get_option( self::OPTION_SETTINGS, [] );
		return is_array( $stored ) ? array_merge( $defaults, $stored ) : $defaults;
	}

	/**
	 * Plan: Pro, Plus, Enterprise, or trial — can generate/publish non-commerce schema.
	 */
	public static function can_use_schema_tools(): bool {
		if ( function_exists( 'lltg_fs' ) ) {
			$fs = lltg_fs();
			return (bool) ( $fs->can_use_premium_code() || $fs->is_trial() );
		}
		return current_user_can( 'manage_options' );
	}

	/**
	 * Plus / Enterprise / trial — commerce-related schema pack.
	 */
	public static function can_use_commerce_schema(): bool {
		if ( function_exists( 'lltg_fs' ) ) {
			$fs = lltg_fs();
			if ( $fs->is_trial() ) {
				return true;
			}
			return $fs->is_plan( 'plus' ) || $fs->is_plan( 'enterprise' );
		}
		return current_user_can( 'manage_options' );
	}

	/**
	 * Register REST routes.
	 *
	 * @return void
	 */
	public function register_rest_routes(): void {
		register_rest_route(
			'llm-analytics/v1',
			'/schema/scan',
			[
				'methods'             => 'GET',
				'callback'            => [ $this, 'rest_scan' ],
				'permission_callback' => function () {
					return current_user_can( 'manage_options' );
				},
				'args'                => [
					'refresh' => [
						'type'    => 'boolean',
						'default' => false,
					],
				],
			]
		);

		register_rest_route(
			'llm-analytics/v1',
			'/schema/recommendations',
			[
				'methods'             => 'GET',
				'callback'            => [ $this, 'rest_recommendations' ],
				'permission_callback' => function () {
					return current_user_can( 'manage_options' );
				},
			]
		);

		register_rest_route(
			'llm-analytics/v1',
			'/schema/preview',
			[
				'methods'             => 'GET',
				'callback'            => [ $this, 'rest_preview' ],
				'permission_callback' => function () {
					return current_user_can( 'manage_options' );
				},
			]
		);

		register_rest_route(
			'llm-analytics/v1',
			'/schema/history',
			[
				'methods'             => 'GET',
				'callback'            => [ $this, 'rest_history' ],
				'permission_callback' => function () {
					return current_user_can( 'manage_options' );
				},
			]
		);

		register_rest_route(
			'llm-analytics/v1',
			'/schema/generate',
			[
				'methods'             => 'POST',
				'callback'            => [ $this, 'rest_generate' ],
				'permission_callback' => function () {
					return current_user_can( 'manage_options' );
				},
			]
		);

		register_rest_route(
			'llm-analytics/v1',
			'/schema/publish',
			[
				'methods'             => 'POST',
				'callback'            => [ $this, 'rest_publish' ],
				'permission_callback' => function () {
					return current_user_can( 'manage_options' );
				},
			]
		);
	}

	/**
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response
	 */
	public function rest_scan( \WP_REST_Request $request ): \WP_REST_Response {
		$raw     = $request->get_param( 'refresh' );
		$refresh = filter_var( $raw, FILTER_VALIDATE_BOOLEAN ) || '1' === (string) $raw || 'true' === (string) $raw;
		if ( ! $refresh ) {
			$cached = get_option( self::OPTION_LAST_SCAN, null );
			if ( is_array( $cached ) && ! empty( $cached['pages'] ) ) {
				return new \WP_REST_Response( $cached, 200 );
			}
		}
		$result = $this->run_full_scan();
		update_option( self::OPTION_LAST_SCAN, $result, false );
		$this->push_history( $result );
		return new \WP_REST_Response( $result, 200 );
	}

	/**
	 * @return \WP_REST_Response
	 */
	public function rest_recommendations(): \WP_REST_Response {
		$scan = get_option( self::OPTION_LAST_SCAN, null );
		if ( ! is_array( $scan ) || empty( $scan['pages'] ) ) {
			$scan = $this->run_full_scan();
			update_option( self::OPTION_LAST_SCAN, $scan, false );
			$this->push_history( $scan );
		}
		return new \WP_REST_Response(
			[
				'recommendations' => isset( $scan['recommendations'] ) ? $scan['recommendations'] : [],
				'score'           => isset( $scan['overall_score'] ) ? $scan['overall_score'] : null,
				'scannedAt'       => isset( $scan['scanned_at'] ) ? $scan['scanned_at'] : null,
			],
			200
		);
	}

	/**
	 * @return \WP_REST_Response
	 */
	public function rest_preview(): \WP_REST_Response {
		$published = get_option( self::OPTION_PUBLISHED, '' );
		$decoded   = is_string( $published ) && '' !== $published ? json_decode( $published, true ) : null;
		$settings  = $this->get_settings();
		return new \WP_REST_Response(
			[
				'published' => is_array( $decoded ) ? $decoded : null,
				'enabled'   => ! empty( $settings['enabled'] ),
				'canFix'    => self::can_use_schema_tools(),
				'canStore'  => self::can_use_commerce_schema(),
			],
			200
		);
	}

	/**
	 * @return \WP_REST_Response
	 */
	public function rest_history(): \WP_REST_Response {
		$hist = get_option( self::OPTION_SCAN_HISTORY, [] );
		if ( ! is_array( $hist ) ) {
			$hist = [];
		}
		return new \WP_REST_Response( [ 'items' => array_slice( $hist, 0, 10 ) ], 200 );
	}

	/**
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function rest_generate( \WP_REST_Request $request ) {
		if ( ! self::can_use_schema_tools() ) {
			return new \WP_Error(
				'schema_plan',
				__( 'Schema generation is available on Pro, Plus, and Enterprise.', 'llmagnet-llm-txt-generator' ),
				[ 'status' => 403 ]
			);
		}
		$body = $request->get_json_params();
		if ( ! is_array( $body ) ) {
			$body = [];
		}
		$graph = $this->build_graph_from_wizard( $body );
		if ( is_wp_error( $graph ) ) {
			return $graph;
		}
		$json = wp_json_encode( $graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT );
		return new \WP_REST_Response(
			[
				'graph'     => $graph,
				'json'      => is_string( $json ) ? $json : '',
				'canStore'  => self::can_use_commerce_schema(),
				'published' => false,
			],
			200
		);
	}

	/**
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function rest_publish( \WP_REST_Request $request ) {
		if ( ! self::can_use_schema_tools() ) {
			return new \WP_Error(
				'schema_plan',
				__( 'Publishing schema is available on Pro, Plus, and Enterprise.', 'llmagnet-llm-txt-generator' ),
				[ 'status' => 403 ]
			);
		}
		$body = $request->get_json_params();
		if ( ! is_array( $body ) ) {
			$body = [];
		}
		$graph = $this->build_graph_from_wizard( $body );
		if ( is_wp_error( $graph ) ) {
			return $graph;
		}
		$encoded = wp_json_encode( $graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		update_option( self::OPTION_PUBLISHED, is_string( $encoded ) ? $encoded : '', false );
		$settings = $this->get_settings();
		$settings['enabled'] = true;
		update_option( self::OPTION_SETTINGS, $settings, false );
		return new \WP_REST_Response(
			[
				'success'   => true,
				'graph'     => $graph,
				'enabled'   => $settings['enabled'],
				'message'   => __( 'Schema saved and will appear in your site HTML.', 'llmagnet-llm-txt-generator' ),
			],
			200
		);
	}

	/**
	 * Run scan across key URLs.
	 *
	 * @return array<string, mixed>
	 */
	private function run_full_scan(): array {
		$urls   = $this->collect_sample_urls();
		$pages  = [];
		$types_seen = [];

		foreach ( $urls as $item ) {
			$row = $this->scan_url( $item['url'], $item['label'] );
			$pages[] = $row;
			foreach ( $row['types_found'] as $t ) {
				$types_seen[ $t ] = true;
			}
		}

		$recommendations = $this->compile_recommendations( $pages, $types_seen );
		$score           = $this->compute_overall_score( $pages );

		return [
			'scanned_at'       => current_time( 'mysql', true ),
			'overall_score'    => $score,
			'pages'            => $pages,
			'recommendations'  => $recommendations,
			'home_url'         => home_url( '/' ),
			'wooCommerceActive' => class_exists( 'WooCommerce' ),
		];
	}

	/**
	 * @return array<int, array{url:string,label:string}>
	 */
	private function collect_sample_urls(): array {
		$out   = [];
		$out[] = [ 'url' => home_url( '/' ), 'label' => __( 'Homepage', 'llmagnet-llm-txt-generator' ) ];

		foreach ( [ 'about', 'contact' ] as $slug ) {
			$page = get_page_by_path( $slug );
			if ( $page && 'publish' === $page->post_status ) {
				$out[] = [ 'url' => get_permalink( $page ), 'label' => sprintf( /* translators: %s page slug */ __( 'Page: %s', 'llmagnet-llm-txt-generator' ), $slug ) ];
			}
		}

		$posts = get_posts(
			[
				'post_type'      => 'post',
				'post_status'    => 'publish',
				'posts_per_page' => 3,
				'orderby'        => 'date',
				'order'          => 'DESC',
				'no_found_rows'  => true,
			]
		);
		foreach ( $posts as $p ) {
			$out[] = [ 'url' => get_permalink( $p ), 'label' => __( 'Recent post', 'llmagnet-llm-txt-generator' ) ];
		}

		if ( class_exists( 'WooCommerce' ) ) {
			$products = get_posts(
				[
					'post_type'      => 'product',
					'post_status'    => 'publish',
					'posts_per_page' => 3,
					'orderby'        => 'date',
					'order'          => 'DESC',
					'no_found_rows'  => true,
				]
			);
			foreach ( $products as $pr ) {
				$out[] = [ 'url' => get_permalink( $pr ), 'label' => __( 'Product', 'llmagnet-llm-txt-generator' ) ];
			}
		}

		return $out;
	}

	/**
	 * @param string $url   URL.
	 * @param string $label Label.
	 * @return array<string, mixed>
	 */
	private function scan_url( string $url, string $label ): array {
		$response = wp_remote_get(
			$url,
			[
				'timeout'    => 20,
				'user-agent' => 'LLMagnetSchemaScanner/1.0; ' . home_url( '/' ),
				'sslverify'  => true,
			]
		);

		if ( is_wp_error( $response ) ) {
			return [
				'label'        => $label,
				'url'          => $url,
				'ok'           => false,
				'error'        => $response->get_error_message(),
				'blocks'       => [],
				'types_found'  => [],
				'issues'       => [
					[
						'severity' => 'error',
						'code'     => 'fetch_failed',
						'message'  => __( 'Could not load this page to read structured data.', 'llmagnet-llm-txt-generator' ),
					],
				],
				'block_count'  => 0,
			];
		}

		$code = wp_remote_retrieve_response_code( $response );
		$body = wp_remote_retrieve_body( $response );
		if ( 200 !== (int) $code || ! is_string( $body ) ) {
			return [
				'label'       => $label,
				'url'         => $url,
				'ok'          => false,
				'error'       => sprintf( /* translators: %d HTTP status */ __( 'Unexpected response (%d)', 'llmagnet-llm-txt-generator' ), (int) $code ),
				'blocks'      => [],
				'types_found' => [],
				'issues'      => [],
				'block_count' => 0,
			];
		}

		$blocks = $this->extract_jsonld_blocks( $body );
		$all_issues = [];
		$types      = [];

		foreach ( $blocks as $idx => $block ) {
			$parsed = $this->parse_jsonld_block( $block['raw'] );
			$blocks[ $idx ]['valid_json'] = $parsed['ok'];
			$blocks[ $idx ]['entities']  = $parsed['entities'];
			if ( ! $parsed['ok'] ) {
				$all_issues[] = [
					'severity' => 'error',
					'code'     => 'invalid_json',
					'message'  => __( 'A JSON-LD block contains invalid JSON.', 'llmagnet-llm-txt-generator' ),
				];
				continue;
			}
			foreach ( $parsed['entities'] as $entity ) {
				$type = isset( $entity['@type'] ) ? $entity['@type'] : null;
				if ( is_string( $type ) ) {
					$types[ $type ] = true;
				} elseif ( is_array( $type ) ) {
					foreach ( $type as $t ) {
						if ( is_string( $t ) ) {
							$types[ $t ] = true;
						}
					}
				}
				$all_issues = array_merge( $all_issues, $this->validate_entity( $entity ) );
			}
		}

		if ( count( $blocks ) === 0 ) {
			$all_issues[] = [
				'severity' => 'warning',
				'code'     => 'no_jsonld',
				'message'  => __( 'No JSON-LD was found on this page. AI and search engines rely on structured data to understand your content.', 'llmagnet-llm-txt-generator' ),
			];
		}

		return [
			'label'        => $label,
			'url'          => $url,
			'ok'           => true,
			'blocks'       => $blocks,
			'types_found'  => array_keys( $types ),
			'issues'       => $all_issues,
			'block_count'  => count( $blocks ),
			'duplicate_entity_warning' => count( $blocks ) > 1 ? __( 'Multiple JSON-LD blocks detected. If you use another SEO plugin, avoid duplicating the same business or product entities.', 'llmagnet-llm-txt-generator' ) : null,
		];
	}

	/**
	 * @param string $html HTML.
	 * @return array<int, array{raw:string, snippet:string}>
	 */
	private function extract_jsonld_blocks( string $html ): array {
		$out = [];
		if ( ! preg_match_all( '/<script[^>]*type\s*=\s*["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/is', $html, $matches ) ) {
			return $out;
		}
		foreach ( $matches[1] as $raw ) {
			$raw = html_entity_decode( trim( $raw ), ENT_QUOTES | ENT_HTML5, 'UTF-8' );
			if ( '' === $raw ) {
				continue;
			}
			$snippet = mb_substr( preg_replace( '/\s+/', ' ', $raw ), 0, 200 );
			$out[]   = [
				'raw'     => $raw,
				'snippet' => $snippet . ( mb_strlen( $raw ) > 200 ? '…' : '' ),
			];
		}
		return $out;
	}

	/**
	 * @param string $raw Raw JSON.
	 * @return array{ok:bool, entities:array<int, array<string, mixed>>}
	 */
	private function parse_jsonld_block( string $raw ): array {
		$data = json_decode( $raw, true );
		if ( json_last_error() !== JSON_ERROR_NONE ) {
			return [ 'ok' => false, 'entities' => [] ];
		}
		$entities = [];
		if ( is_array( $data ) && isset( $data['@graph'] ) && is_array( $data['@graph'] ) ) {
			foreach ( $data['@graph'] as $node ) {
				if ( is_array( $node ) ) {
					$entities[] = $node;
				}
			}
		} elseif ( is_array( $data ) ) {
			if ( isset( $data['@type'] ) ) {
				$entities[] = $data;
			} elseif ( isset( $data[0] ) && is_array( $data[0] ) ) {
				foreach ( $data as $node ) {
					if ( is_array( $node ) && isset( $node['@type'] ) ) {
						$entities[] = $node;
					}
				}
			}
		}
		return [ 'ok' => true, 'entities' => $entities ];
	}

	/**
	 * Basic field completeness checks.
	 *
	 * @param array<string, mixed> $entity Entity.
	 * @return array<int, array{severity:string,code:string,message:string}>
	 */
	private function validate_entity( array $entity ): array {
		$issues = [];
		$type   = isset( $entity['@type'] ) ? $entity['@type'] : '';
		if ( is_array( $type ) ) {
			$type = implode( ', ', $type );
		}

		$check_required = function ( array $fields, string $friendly ) use ( &$issues, $entity ) {
			foreach ( $fields as $f ) {
				if ( ! isset( $entity[ $f ] ) || '' === $entity[ $f ] ) {
					$issues[] = [
						'severity' => 'info',
						'code'     => 'missing_' . $f,
						'message'  => sprintf(
							/* translators: 1: schema type, 2: field name */
							__( 'Consider adding "%2$s" to your %1$s schema so machines can read key facts reliably.', 'llmagnet-llm-txt-generator' ),
							$friendly,
							$f
						),
					];
				}
			}
		};

		if ( is_string( $type ) && false !== strpos( strtolower( $type ), 'organization' ) ) {
			$check_required( [ 'name', 'url' ], __( 'Organization', 'llmagnet-llm-txt-generator' ) );
		}
		if ( is_string( $type ) && false !== strpos( strtolower( $type ), 'localbusiness' ) ) {
			$check_required( [ 'name', 'address', 'telephone' ], __( 'LocalBusiness', 'llmagnet-llm-txt-generator' ) );
		}
		if ( is_string( $type ) && false !== strpos( strtolower( $type ), 'product' ) ) {
			$check_required( [ 'name', 'image', 'offers' ], __( 'Product', 'llmagnet-llm-txt-generator' ) );
		}
		if ( is_string( $type ) && false !== strpos( strtolower( $type ), 'article' ) ) {
			$check_required( [ 'headline', 'author', 'datePublished' ], __( 'Article', 'llmagnet-llm-txt-generator' ) );
		}
		if ( is_string( $type ) && false !== strpos( strtolower( $type ), 'faqpage' ) ) {
			if ( empty( $entity['mainEntity'] ) || ! is_array( $entity['mainEntity'] ) ) {
				$issues[] = [
					'severity' => 'warning',
					'code'     => 'faq_questions',
					'message'  => __( 'FAQ schema should list question/answer pairs so assistants can reuse them.', 'llmagnet-llm-txt-generator' ),
				];
			}
		}

		return $issues;
	}

	/**
	 * @param array<int, mixed> $pages Pages.
	 * @param array<string, bool> $types_seen Types.
	 * @return array<int, array{severity:string,title:string,detail:string,cta?:string}>
	 */
	private function compile_recommendations( array $pages, array $types_seen ): array {
		$rec = [];

		$has_org = ! empty( $types_seen['Organization'] ) || ! empty( $types_seen['LocalBusiness'] );
		if ( ! $has_org ) {
			$rec[] = [
				'severity' => 'high',
				'title'    => __( 'Add business identity to your site', 'llmagnet-llm-txt-generator' ),
				'detail'   => __( 'Organization or LocalBusiness schema helps AI and search engines understand who you are and how to cite you.', 'llmagnet-llm-txt-generator' ),
				'cta'      => __( 'Use Fix & Publish', 'llmagnet-llm-txt-generator' ),
			];
		}

		$has_product_schema = ! empty( $types_seen['Product'] );
		if ( class_exists( 'WooCommerce' ) && ! $has_product_schema ) {
			$rec[] = [
				'severity' => 'medium',
				'title'    => __( 'Product pages may be missing Product schema', 'llmagnet-llm-txt-generator' ),
				'detail'   => __( 'For stores, Product and Offer schema improves how prices and availability appear. This pack is included on Plus and Enterprise.', 'llmagnet-llm-txt-generator' ),
				'cta'      => __( 'Learn about store schema', 'llmagnet-llm-txt-generator' ),
			];
		}

		$has_web = ! empty( $types_seen['WebSite'] ) || ! empty( $types_seen['WebPage'] );
		if ( ! $has_web ) {
			$rec[] = [
				'severity' => 'low',
				'title'    => __( 'Clarify site-wide context', 'llmagnet-llm-txt-generator' ),
				'detail'   => __( 'WebSite or WebPage JSON-LD connects your brand, search box, and key pages into one machine-readable graph.', 'llmagnet-llm-txt-generator' ),
			];
		}

		$bad_json = false;
		foreach ( $pages as $p ) {
			foreach ( $p['issues'] as $issue ) {
				if ( 'invalid_json' === ( $issue['code'] ?? '' ) ) {
					$bad_json = true;
				}
			}
		}
		if ( $bad_json ) {
			$rec[] = [
				'severity' => 'high',
				'title'    => __( 'Fix invalid JSON-LD', 'llmagnet-llm-txt-generator' ),
				'detail'   => __( 'Broken JSON-LD blocks are ignored by crawlers. Use the preview in Fix & Publish to validate before going live.', 'llmagnet-llm-txt-generator' ),
			];
		}

		return $rec;
	}

	/**
	 * @param array<int, mixed> $pages Pages.
	 * @return int 0-100
	 */
	private function compute_overall_score( array $pages ): int {
		if ( empty( $pages ) ) {
			return 0;
		}
		$sum = 0;
		foreach ( $pages as $p ) {
			$block_score = isset( $p['block_count'] ) && $p['block_count'] > 0 ? 50 : 0;
			$issue_pen   = 0;
			foreach ( $p['issues'] as $issue ) {
				if ( 'error' === ( $issue['severity'] ?? '' ) ) {
					$issue_pen += 25;
				} elseif ( 'warning' === ( $issue['severity'] ?? '' ) ) {
					$issue_pen += 10;
				} else {
					$issue_pen += 3;
				}
			}
			$page_score = max( 0, min( 100, $block_score + 50 - min( 50, $issue_pen ) ) );
			$sum       += $page_score;
		}
		return (int) round( $sum / count( $pages ) );
	}

	/**
	 * @param array<string, mixed> $scan Scan.
	 * @return void
	 */
	private function push_history( array $scan ): void {
		$hist = get_option( self::OPTION_SCAN_HISTORY, [] );
		if ( ! is_array( $hist ) ) {
			$hist = [];
		}
		array_unshift(
			$hist,
			[
				'at'    => isset( $scan['scanned_at'] ) ? $scan['scanned_at'] : current_time( 'mysql', true ),
				'score' => isset( $scan['overall_score'] ) ? $scan['overall_score'] : null,
			]
		);
		$hist = array_slice( $hist, 0, 10 );
		update_option( self::OPTION_SCAN_HISTORY, $hist, false );
	}

	/**
	 * Build @graph from wizard payload.
	 *
	 * @param array<string, mixed> $body Body.
	 * @return array<int, array<string, mixed>>|\WP_Error
	 */
	private function build_graph_from_wizard( array $body ) {
		$site_type   = isset( $body['siteType'] ) ? sanitize_text_field( (string) $body['siteType'] ) : 'business';
		$org_name    = isset( $body['organizationName'] ) ? sanitize_text_field( (string) $body['organizationName'] ) : get_bloginfo( 'name' );
		$org_url     = isset( $body['organizationUrl'] ) ? esc_url_raw( (string) $body['organizationUrl'] ) : home_url( '/' );
		$description = isset( $body['description'] ) ? sanitize_textarea_field( (string) $body['description'] ) : get_bloginfo( 'description' );
		$logo        = isset( $body['logoUrl'] ) ? esc_url_raw( (string) $body['logoUrl'] ) : '';
		$same_as     = isset( $body['sameAs'] ) && is_array( $body['sameAs'] ) ? $body['sameAs'] : [];
		$same_clean  = [];
		foreach ( $same_as as $u ) {
			$u = esc_url_raw( (string) $u );
			if ( $u ) {
				$same_clean[] = $u;
			}
		}

		$include_product = ! empty( $body['includeProductExample'] );
		if ( $include_product && ! self::can_use_commerce_schema() ) {
			return new \WP_Error(
				'schema_commerce',
				__( 'Store/Product schema templates are available on Plus and Enterprise.', 'llmagnet-llm-txt-generator' ),
				[ 'status' => 403 ]
			);
		}

		$graph = [];

		$org_id = trailingslashit( $org_url ) . '#organization';
		$org    = [
			'@type'        => 'Organization',
			'@id'          => $org_id,
			'name'         => $org_name,
			'url'          => $org_url,
			'description'  => $description,
		];
		if ( $logo ) {
			$org['logo'] = $logo;
		}
		if ( ! empty( $same_clean ) ) {
			$org['sameAs'] = $same_clean;
		}
		if ( 'local_business' === $site_type ) {
			$org['@type'] = 'LocalBusiness';
			if ( ! empty( $body['telephone'] ) ) {
				$org['telephone'] = sanitize_text_field( (string) $body['telephone'] );
			}
			if ( ! empty( $body['streetAddress'] ) ) {
				$org['address'] = [
					'@type'           => 'PostalAddress',
					'streetAddress'   => sanitize_text_field( (string) $body['streetAddress'] ),
					'addressLocality' => isset( $body['addressLocality'] ) ? sanitize_text_field( (string) $body['addressLocality'] ) : '',
					'addressRegion'   => isset( $body['addressRegion'] ) ? sanitize_text_field( (string) $body['addressRegion'] ) : '',
					'postalCode'      => isset( $body['postalCode'] ) ? sanitize_text_field( (string) $body['postalCode'] ) : '',
					'addressCountry'  => isset( $body['addressCountry'] ) ? sanitize_text_field( (string) $body['addressCountry'] ) : '',
				];
			}
		}
		$graph[] = $org;

		$web_id      = home_url( '/' ) . '#website';
		$website     = [
			'@type'     => 'WebSite',
			'@id'       => $web_id,
			'url'       => home_url( '/' ),
			'name'      => get_bloginfo( 'name' ),
			'publisher' => [ '@id' => $org_id ],
		];
		$graph[]     = $website;

		if ( 'blog' === $site_type ) {
			$graph[] = [
				'@type' => 'WebPage',
				'@id'   => home_url( '/' ) . '#webpage',
				'url'   => home_url( '/' ),
				'name'  => get_bloginfo( 'name' ),
				'isPartOf' => [ '@id' => $web_id ],
			];
		}

		if ( $include_product && class_exists( 'WooCommerce' ) ) {
			$sample_product = get_posts(
				[
					'post_type'      => 'product',
					'post_status'    => 'publish',
					'posts_per_page' => 1,
					'orderby'        => 'date',
					'order'          => 'DESC',
					'no_found_rows'  => true,
				]
			);
			if ( ! empty( $sample_product ) ) {
				$pid   = $sample_product[0]->ID;
				$purl  = get_permalink( $pid );
				$image = get_the_post_thumbnail_url( $pid, 'full' );
				$product = wc_get_product( $pid );
				$price   = $product ? $product->get_price() : '';

				$graph[] = [
					'@type'      => 'Product',
					'@id'        => $purl . '#product',
					'name'       => get_the_title( $pid ),
					'description'=> wp_strip_all_tags( get_post_field( 'post_excerpt', $pid ) ? get_post_field( 'post_excerpt', $pid ) : get_post_field( 'post_content', $pid ) ),
					'image'      => $image ? [ $image ] : [],
					'brand'      => [ '@type' => 'Brand', 'name' => $org_name ],
					'offers'     => [
						'@type'           => 'Offer',
						'priceCurrency'   => function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : 'USD',
						'price'           => $price,
						'url'             => $purl,
						'availability'    => 'https://schema.org/InStock',
					],
				];
			} else {
				$graph[] = [
					'@type'      => 'Product',
					'@id'        => $org_url . '#demo-product',
					'name'       => __( 'Example product', 'llmagnet-llm-txt-generator' ),
					'description'=> __( 'Publish a WooCommerce product to replace this placeholder in your graph.', 'llmagnet-llm-txt-generator' ),
					'offers'     => [
						'@type'         => 'Offer',
						'priceCurrency' => 'USD',
						'price'         => '0',
						'availability'  => 'https://schema.org/InStock',
					],
				];
			}
		}

		return [
			'@context' => 'https://schema.org',
			'@graph'   => $graph,
		];
	}
}
