<?php
/**
 * Odysseo SEO — per-page JSON-LD structured data.
 *
 * Built from REAL field data only. When a required value is missing the property
 * is omitted rather than faked (no invented ratings or prices).
 *
 * @package    Odysseo
 * @author     Chandrabhan Shekhawat
 * @company    Gigai Kripa Services
 * @link       https://gigaikripaservices.com/
 * @copyright  Copyright (c) 2026, Gigai Kripa Services
 */

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

/**
 * Emits schema.org JSON-LD appropriate to the current view.
 */
class Odysseo_SEO_Schema {

	/**
	 * Hook up (defers to a real SEO plugin if one is active).
	 */
	public function __construct() {
		if ( odysseo_seo_other_plugin_active() ) {
			return;
		}
		add_action( 'wp_head', array( $this, 'output' ), 5 );
		add_action( 'init', array( $this, 'retire_legacy' ), 99 );
	}

	/**
	 * Detaches the theme's inline extra-schema hook so this class is the sole source.
	 */
	public function retire_legacy() {
		if ( function_exists( 'odysseo_output_extra_seo_schema' ) ) {
			remove_action( 'wp_head', 'odysseo_output_extra_seo_schema', 30 );
		}
	}

	/* ---------------------------------------------------------------------- *
	 *  Helpers.
	 * ---------------------------------------------------------------------- */

	/**
	 * Print one JSON-LD node (skips empties).
	 *
	 * @param array $data Node.
	 */
	private function print_node( $data ) {
		$data = array_filter( $data, function ( $v ) { return '' !== $v && array() !== $v && null !== $v; } );
		if ( empty( $data ) ) {
			return;
		}
		echo '<script type="application/ld+json">' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . '</script>' . "\n";
	}

	/**
	 * Featured image for a post, else the theme default logo.
	 *
	 * @param int $id Post ID.
	 * @return string
	 */
	private function image( $id ) {
		if ( has_post_thumbnail( $id ) ) {
			$url = get_the_post_thumbnail_url( $id, 'full' );
			if ( $url ) {
				return $url;
			}
		}
		return get_template_directory_uri() . '/assets/images/logo.png';
	}

	/**
	 * A clean description for a post (excerpt -> trimmed content).
	 *
	 * @param WP_Post $post Post.
	 * @return string
	 */
	private function description( $post ) {
		if ( '' !== trim( (string) $post->post_excerpt ) ) {
			return wp_strip_all_tags( $post->post_excerpt );
		}
		return wp_trim_words( wp_strip_all_tags( strip_shortcodes( (string) $post->post_content ) ), 40, '…' );
	}

	/**
	 * Currency code (theme option; defaults USD). Omitted-safe for plugin use.
	 *
	 * @return string
	 */
	private function currency() {
		$c = get_theme_mod( 'currency_code', 'USD' );
		return $c ? $c : 'USD';
	}

	/**
	 * Numeric price from a free-text price field, or '' if none.
	 *
	 * @param string $raw Raw price.
	 * @return string
	 */
	private function price( $raw ) {
		$num = preg_replace( '/[^0-9.]/', '', (string) $raw );
		return '' !== $num ? $num : '';
	}

	/* ---------------------------------------------------------------------- *
	 *  Output orchestrator.
	 * ---------------------------------------------------------------------- */

	/**
	 * Build + print all nodes for the current request.
	 */
	public function output() {
		$on = function_exists( 'odysseo_seo_schema_on' ) ? 'odysseo_seo_schema_on' : '__return_true';

		if ( is_front_page() ) {
			if ( call_user_func( $on, 'org' ) )     { $this->print_node( $this->organization() ); }
			if ( call_user_func( $on, 'website' ) ) { $this->print_node( $this->website() ); }
			if ( call_user_func( $on, 'service' ) ) { $svc = $this->service(); if ( $svc ) { $this->print_node( $svc ); } }
		} elseif ( is_page( 'contact' ) ) {
			if ( call_user_func( $on, 'org' ) ) { $this->print_node( $this->organization() ); }
		}

		// FAQPage — emitted on the About page (self-filters by template).
		if ( call_user_func( $on, 'faq' ) && is_page() ) {
			$faq = $this->faqpage();
			if ( $faq ) { $this->print_node( $faq ); }
		}

		if ( is_singular( 'post' ) ) {
			if ( call_user_func( $on, 'article' ) ) { $this->print_node( $this->article() ); }
		} elseif ( is_singular( 'destination' ) ) {
			if ( call_user_func( $on, 'destination' ) ) { $this->print_node( $this->destination() ); }
		} elseif ( is_singular( 'tour_package' ) ) {
			if ( call_user_func( $on, 'package' ) ) { $this->print_node( $this->tour_package() ); }
		} elseif ( is_singular( 'fleet' ) ) {
			if ( call_user_func( $on, 'vehicle' ) ) { $this->print_node( $this->fleet() ); }
		}

		if ( ! is_front_page() && call_user_func( $on, 'breadcrumb' ) ) {
			$bc = $this->breadcrumbs();
			if ( $bc ) {
				$this->print_node( $bc );
			}
		}
	}

	/**
	 * Organization display name: schema legal name, else site title.
	 */
	private function org_name() {
		$s = function_exists( 'odysseo_seo_get_settings' ) ? odysseo_seo_get_settings() : array();
		$legal = isset( $s['schema_legal_name'] ) ? $s['schema_legal_name'] : '';
		return $legal ? $legal : get_bloginfo( 'name' );
	}

	/**
	 * Organization logo URL: schema logo, else theme logo.
	 */
	private function org_logo() {
		$s = function_exists( 'odysseo_seo_get_settings' ) ? odysseo_seo_get_settings() : array();
		$logo = isset( $s['schema_logo'] ) ? $s['schema_logo'] : '';
		return $logo ? $logo : get_template_directory_uri() . '/assets/images/logo.png';
	}

	/**
	 * FAQPage built from the About page FAQ rows. Null unless on the About template.
	 *
	 * @return array|null
	 */
	private function faqpage() {
		$pid = get_queried_object_id();
		if ( ! $pid || 'page-templates/template-about.php' !== get_page_template_slug( $pid ) ) {
			return null;
		}
		if ( ! function_exists( 'odysseo_pf_rows' ) ) {
			return null;
		}
		$defaults = function_exists( 'odysseo_default_rows' ) ? odysseo_default_rows( 'faqs' ) : array();
		$rows     = odysseo_pf_rows( 'faqs', $defaults, $pid );
		$items    = array();
		foreach ( $rows as $r ) {
			$q = isset( $r[0] ) ? wp_strip_all_tags( $r[0] ) : '';
			$a = isset( $r[1] ) ? wp_strip_all_tags( $r[1] ) : '';
			if ( '' === $q || '' === $a ) {
				continue;
			}
			$items[] = array(
				'@type'          => 'Question',
				'name'           => $q,
				'acceptedAnswer' => array( '@type' => 'Answer', 'text' => $a ),
			);
		}
		if ( ! $items ) {
			return null;
		}
		return array(
			'@context'   => 'https://schema.org',
			'@type'      => 'FAQPage',
			'mainEntity' => $items,
		);
	}

	/**
	 * Service node with an OfferCatalog of published tour packages (home page).
	 *
	 * @return array
	 */
	private function service() {
		$node = array(
			'@context'    => 'https://schema.org',
			'@type'       => 'Service',
			'serviceType' => __( 'Tour operator & car rental', 'odysseo-core' ),
			'provider'    => array(
				'@type' => 'TravelAgency',
				'@id'   => home_url( '/#business' ),
				'name'  => $this->org_name(),
			),
			'areaServed'  => get_theme_mod( 'contact_country', '' ),
		);
		$pkgs = get_posts( array( 'post_type' => 'tour_package', 'posts_per_page' => 10, 'post_status' => 'publish' ) );
		if ( $pkgs ) {
			$items = array();
			foreach ( $pkgs as $p ) {
				$items[] = array(
					'@type'       => 'Offer',
					'itemOffered' => array( '@type' => 'Service', 'name' => get_the_title( $p ) ),
				);
			}
			$node['hasOfferCatalog'] = array(
				'@type'           => 'OfferCatalog',
				'name'            => __( 'Tour Packages', 'odysseo-core' ),
				'itemListElement' => $items,
			);
		}
		return $node;
	}

	/* ---------------------------------------------------------------------- *
	 *  Node builders (real data only).
	 * ---------------------------------------------------------------------- */

	/**
	 * TravelAgency / LocalBusiness for home + contact.
	 *
	 * @return array
	 */
	private function organization() {
		$settings = function_exists( 'odysseo_seo_get_settings' ) ? odysseo_seo_get_settings() : array();
		$node = array(
			'@context' => 'https://schema.org',
			'@type'    => 'TravelAgency',
			'@id'      => home_url( '/#business' ),
			'name'     => $this->org_name(),
			'url'      => home_url( '/' ),
			'logo'     => $this->org_logo(),
		);

		$desc = get_bloginfo( 'description' );
		if ( $desc ) {
			$node['description'] = $desc;
		}

		if ( ! empty( $settings['schema_founding'] ) ) {
			$node['foundingDate'] = preg_replace( '/[^0-9\-]/', '', (string) $settings['schema_founding'] );
		}
		if ( ! empty( $settings['schema_price_range'] ) ) {
			$node['priceRange'] = (string) $settings['schema_price_range'];
		}

		$phone = get_theme_mod( 'contact_phone', '' );
		if ( $phone ) {
			$node['telephone'] = $phone;
		}
		$email = get_theme_mod( 'contact_email', '' );
		if ( $email ) {
			$node['email'] = $email;
		}

		$address = array_filter(
			array(
				'@type'           => 'PostalAddress',
				'streetAddress'   => get_theme_mod( 'contact_address', '' ),
				'addressLocality' => get_theme_mod( 'contact_city', '' ),
				'addressRegion'   => get_theme_mod( 'contact_region', '' ),
				'addressCountry'  => get_theme_mod( 'contact_country_code', '' ),
			)
		);
		if ( count( $address ) > 1 ) {
			$node['address'] = $address;
		}

		$lat = get_theme_mod( 'contact_geo_lat', '' );
		$lng = get_theme_mod( 'contact_geo_lng', '' );
		if ( '' !== $lat && '' !== $lng ) {
			$node['geo'] = array( '@type' => 'GeoCoordinates', 'latitude' => $lat, 'longitude' => $lng );
		}

		$same = array_values(
			array_filter(
				array(
					get_theme_mod( 'social_facebook', '' ),
					get_theme_mod( 'social_instagram', '' ),
					get_theme_mod( 'social_twitter', '' ),
					get_theme_mod( 'social_youtube', '' ),
					get_theme_mod( 'social_linkedin', '' ),
				),
				function ( $v ) { return $v && '#' !== $v; }
			)
		);
		if ( $same ) {
			$node['sameAs'] = $same;
		}

		$rating = $this->testimonial_rating();
		if ( $rating && ( ! function_exists( 'odysseo_seo_schema_on' ) || odysseo_seo_schema_on( 'rating' ) ) ) {
			$node['aggregateRating'] = $rating;
		}

		return $node;
	}

	/**
	 * AggregateRating from real testimonial post meta, or null if none exist.
	 *
	 * @return array|null
	 */
	private function testimonial_rating() {
		$ids = get_posts(
			array(
				'post_type'      => 'testimonial',
				'posts_per_page' => -1,
				'post_status'    => 'publish',
				'fields'         => 'ids',
			)
		);
		if ( empty( $ids ) ) {
			return null;
		}
		$sum   = 0;
		$count = 0;
		foreach ( $ids as $id ) {
			$r = (int) get_post_meta( $id, '_testimonial_rating', true );
			if ( $r < 1 || $r > 5 ) {
				$r = 5;
			}
			$sum += $r;
			$count++;
		}
		if ( ! $count ) {
			return null;
		}
		return array(
			'@type'       => 'AggregateRating',
			'ratingValue' => round( $sum / $count, 1 ),
			'reviewCount' => $count,
			'bestRating'  => 5,
			'worstRating' => 1,
		);
	}

	/**
	 * WebSite + SearchAction (front page).
	 *
	 * @return array
	 */
	private function website() {
		return array(
			'@context'        => 'https://schema.org',
			'@type'           => 'WebSite',
			'name'            => get_bloginfo( 'name' ),
			'url'             => home_url( '/' ),
			'potentialAction' => array(
				'@type'       => 'SearchAction',
				'target'      => home_url( '/?s={search_term_string}' ),
				'query-input' => 'required name=search_term_string',
			),
		);
	}

	/**
	 * Article for single posts.
	 *
	 * @return array
	 */
	private function article() {
		$post = get_queried_object();
		return array(
			'@context'         => 'https://schema.org',
			'@type'            => 'Article',
			'headline'         => get_the_title(),
			'description'      => $this->description( $post ),
			'image'            => $this->image( $post->ID ),
			'datePublished'    => get_the_date( 'c' ),
			'dateModified'     => get_the_modified_date( 'c' ),
			'author'           => array( '@type' => 'Person', 'name' => get_the_author_meta( 'display_name' ) ),
			'publisher'        => array(
				'@type' => 'Organization',
				'name'  => get_bloginfo( 'name' ),
				'logo'  => array( '@type' => 'ImageObject', 'url' => get_template_directory_uri() . '/assets/images/logo.png' ),
			),
			'mainEntityOfPage' => get_permalink(),
		);
	}

	/**
	 * TouristDestination for single destinations.
	 *
	 * @return array
	 */
	private function destination() {
		$post  = get_queried_object();
		$id    = $post->ID;
		$node  = array(
			'@context'    => 'https://schema.org',
			'@type'       => 'TouristDestination',
			'name'        => get_the_title(),
			'description' => $this->description( $post ),
			'image'       => $this->image( $id ),
			'url'         => get_permalink(),
		);

		$states = get_the_terms( $id, 'dest_state' );
		$region = ( $states && ! is_wp_error( $states ) ) ? $states[0]->name : '';
		$addr   = array_filter(
			array(
				'@type'           => 'PostalAddress',
				'addressLocality' => get_the_title(),
				'addressRegion'   => $region,
				'addressCountry'  => get_theme_mod( 'contact_country_code', '' ),
			)
		);
		if ( count( $addr ) > 1 ) {
			$node['address'] = $addr;
		}

		$raw = get_post_meta( $id, '_dest_attractions', true );
		if ( $raw ) {
			$items = array_filter( array_map( 'trim', preg_split( '/[\r\n]+/', $raw ) ) );
			$list  = array();
			foreach ( array_slice( $items, 0, 10 ) as $a ) {
				$list[] = array( '@type' => 'TouristAttraction', 'name' => wp_strip_all_tags( $a ) );
			}
			if ( $list ) {
				$node['includesAttraction'] = $list;
			}
		}
		return $node;
	}

	/**
	 * Tour package: Product+Offer when a real price exists, else TouristTrip.
	 *
	 * @return array
	 */
	private function tour_package() {
		$post  = get_queried_object();
		$id    = $post->ID;
		$image = get_post_meta( $id, '_pkg_image', true );
		$image = $image ? $image : $this->image( $id );
		$price = $this->price( get_post_meta( $id, '_pkg_price', true ) );

		if ( '' !== $price ) {
			return array(
				'@context'    => 'https://schema.org',
				'@type'       => 'Product',
				'name'        => get_the_title(),
				'description' => $this->description( $post ),
				'image'       => $image,
				'url'         => get_permalink(),
				'offers'      => array(
					'@type'         => 'Offer',
					'price'         => $price,
					'priceCurrency' => $this->currency(),
					'availability'  => 'https://schema.org/InStock',
					'url'           => get_permalink(),
				),
			);
		}

		return array(
			'@context'    => 'https://schema.org',
			'@type'       => 'TouristTrip',
			'name'        => get_the_title(),
			'description' => $this->description( $post ),
			'image'       => $image,
			'url'         => get_permalink(),
		);
	}

	/**
	 * Fleet vehicle: Vehicle (+ Offer when a real price exists).
	 *
	 * @return array
	 */
	private function fleet() {
		$post  = get_queried_object();
		$id    = $post->ID;
		$node  = array(
			'@context'    => 'https://schema.org',
			'@type'       => 'Vehicle',
			'name'        => get_the_title(),
			'description' => $this->description( $post ),
			'image'       => $this->image( $id ),
			'url'         => get_permalink(),
		);
		$type = get_post_meta( $id, '_fleet_type', true );
		if ( $type ) {
			$node['vehicleConfiguration'] = $type;
		}
		$seats = (int) get_post_meta( $id, '_fleet_seats', true );
		if ( $seats ) {
			$node['seatingCapacity'] = $seats;
		}
		$price = $this->price( get_post_meta( $id, '_fleet_price', true ) );
		if ( '' !== $price ) {
			$node['offers'] = array(
				'@type'         => 'Offer',
				'price'         => $price,
				'priceCurrency' => $this->currency(),
				'availability'  => 'https://schema.org/InStock',
				'url'           => get_permalink(),
			);
		}
		return $node;
	}

	/**
	 * BreadcrumbList for non-front views.
	 *
	 * @return array|null
	 */
	private function breadcrumbs() {
		$crumbs = array( array( 'name' => __( 'Home', 'odysseo-core' ), 'item' => home_url( '/' ) ) );

		if ( is_singular() ) {
			$pt = get_post_type();
			if ( in_array( $pt, array( 'destination', 'tour_package', 'fleet' ), true ) ) {
				$archive = get_post_type_archive_link( $pt );
				$obj     = get_post_type_object( $pt );
				if ( $archive && $obj ) {
					$crumbs[] = array( 'name' => $obj->labels->name, 'item' => $archive );
				}
			}
			$crumbs[] = array( 'name' => get_the_title(), 'item' => get_permalink() );
		} elseif ( is_post_type_archive() || is_archive() ) {
			$crumbs[] = array( 'name' => wp_strip_all_tags( get_the_archive_title() ), 'item' => home_url( add_query_arg( array() ) ) );
		} else {
			return null;
		}

		$items = array();
		foreach ( $crumbs as $i => $c ) {
			$items[] = array(
				'@type'    => 'ListItem',
				'position' => $i + 1,
				'name'     => $c['name'],
				'item'     => $c['item'],
			);
		}
		return array(
			'@context'        => 'https://schema.org',
			'@type'           => 'BreadcrumbList',
			'itemListElement' => $items,
		);
	}
}
