<?php
/**
 * Livada SEO — Module OpenGraph & Twitter Cards
 *
 * Outputs Open Graph and Twitter Card meta tags in <head>.
 * Hooked at priority 2 (after Meta module at priority 1).
 *
 * Tag resolution order:
 *   title/desc  : post meta (_livada_seo_title / _livada_seo_description)
 *                 → WP native title / excerpt / content snippet
 *   og:image    : post meta (_livada_seo_og_image)
 *                 → featured image
 *                 → first <img> src found in post content
 *                 → og_default_image setting
 *
 * @package Livada_SEO
 */

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

class Livada_SEO_OpenGraph {

	// ── Bootstrap ─────────────────────────────────────────────────────────────

	/**
	 * Register the wp_head hook.
	 * Called by Livada_SEO_Loader::load_class() via static init pattern.
	 */
	public static function init(): void {
		add_action( 'wp_head', [ __CLASS__, 'output_tags' ], 2 );
	}

	// ── Main output ───────────────────────────────────────────────────────────

	/**
	 * Build and echo all OG + Twitter Card tags.
	 * Bails on non-singular, non-front-page contexts where OG is not meaningful.
	 */
	public static function output_tags(): void {
		// Only output on front page, singular posts/pages, and archives.
		// Skip feeds, admin, REST requests, etc.
		if ( is_feed() || is_robots() || is_trackback() ) {
			return;
		}

		$title       = self::get_title();
		$description = self::get_description();
		$url         = self::get_url();
		$type        = is_front_page() ? 'website' : 'article';
		$locale      = 'fr_FR';
		$site_name   = self::get_site_name();

		echo "\n<!-- Livada SEO: Open Graph -->\n";

		// ── Core OG tags ──────────────────────────────────────────────────────

		self::tag( 'og:title',       $title );
		self::tag( 'og:description', $description );
		self::tag( 'og:url',         $url, true );
		self::tag( 'og:type',        $type );
		self::tag( 'og:locale',      $locale );

		if ( $site_name ) {
			self::tag( 'og:site_name', $site_name );
		}

		// ── OG image ──────────────────────────────────────────────────────────

		$image_data = self::get_og_image();

		if ( ! empty( $image_data['url'] ) ) {
			self::tag( 'og:image', $image_data['url'], true );

			if ( ! empty( $image_data['width'] ) ) {
				self::tag( 'og:image:width', (string) $image_data['width'] );
			}

			if ( ! empty( $image_data['height'] ) ) {
				self::tag( 'og:image:height', (string) $image_data['height'] );
			}
		}

		// ── Twitter Card tags ─────────────────────────────────────────────────

		echo "\n<!-- Livada SEO: Twitter Card -->\n";

		$card_type = Livada_SEO_Loader::get_setting( 'twitter_card_type', 'summary_large_image' );

		self::tag( 'twitter:card',        $card_type,   false, 'name' );
		self::tag( 'twitter:title',       $title,       false, 'name' );
		self::tag( 'twitter:description', $description, false, 'name' );

		if ( ! empty( $image_data['url'] ) ) {
			self::tag( 'twitter:image', $image_data['url'], true, 'name' );
		}

		echo "\n";
	}

	// ── Image resolution ──────────────────────────────────────────────────────

	/**
	 * Resolves the best available image for OG tags.
	 *
	 * Resolution order:
	 *  1. Post meta  _livada_seo_og_image  (URL stored directly, no dimensions)
	 *  2. Featured image attachment        (full size, dimensions available)
	 *  3. First <img src="…"> in content   (URL only)
	 *  4. og_default_image setting         (URL stored directly, no dimensions)
	 *
	 * Returns an array with keys:
	 *   url    string  Absolute URL to the image.
	 *   width  int|0   Width in pixels (0 when unknown).
	 *   height int|0   Height in pixels (0 when unknown).
	 *
	 * @return array{url:string,width:int,height:int}
	 */
	public static function get_og_image(): array {
		$empty = [ 'url' => '', 'width' => 0, 'height' => 0 ];

		// ── 1. Custom post meta override ──────────────────────────────────────

		if ( is_singular() ) {
			$post_id  = get_queried_object_id();
			$meta_url = get_post_meta( $post_id, '_livada_seo_og_image', true );

			if ( $meta_url && filter_var( $meta_url, FILTER_VALIDATE_URL ) ) {
				// Meta stores a plain URL; dimensions are not reliably available.
				return [ 'url' => $meta_url, 'width' => 0, 'height' => 0 ];
			}
		}

		// ── 2. Featured image ─────────────────────────────────────────────────

		if ( is_singular() ) {
			$post_id        = get_queried_object_id();
			$thumbnail_id   = get_post_thumbnail_id( $post_id );

			if ( $thumbnail_id ) {
				$src = wp_get_attachment_image_src( $thumbnail_id, 'full' );

				if ( $src ) {
					return [
						'url'    => $src[0],
						'width'  => (int) $src[1],
						'height' => (int) $src[2],
					];
				}
			}
		}

		// ── 3. First <img> in post content ────────────────────────────────────

		if ( is_singular() ) {
			$post    = get_queried_object();
			$content = isset( $post->post_content ) ? $post->post_content : '';

			if ( $content ) {
				if ( preg_match( '/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $content, $matches ) ) {
					$img_url = $matches[1];

					if ( filter_var( $img_url, FILTER_VALIDATE_URL ) ) {
						return [ 'url' => $img_url, 'width' => 0, 'height' => 0 ];
					}
				}
			}
		}

		// ── 4. Default image from settings ────────────────────────────────────

		$default_url = Livada_SEO_Loader::get_setting( 'og_default_image', '' );

		if ( $default_url && filter_var( $default_url, FILTER_VALIDATE_URL ) ) {
			// Attempt to resolve dimensions if the default image is a WP attachment.
			$attachment_id = attachment_url_to_postid( $default_url );

			if ( $attachment_id ) {
				$src = wp_get_attachment_image_src( $attachment_id, 'full' );

				if ( $src ) {
					return [
						'url'    => $src[0],
						'width'  => (int) $src[1],
						'height' => (int) $src[2],
					];
				}
			}

			return [ 'url' => $default_url, 'width' => 0, 'height' => 0 ];
		}

		// ── 5. Last resort: programmatic OG image (when the OG Image module is active) ──
		// Filter `livada_seo_og_image_url` is used by `class-og-image.php` to
		// inject a generated 1200×630 PNG URL when nothing else matched.

		$post_id_for_filter = is_singular() ? (int) get_queried_object_id() : 0;
		$generated_url      = (string) apply_filters( 'livada_seo_og_image_url', '', $post_id_for_filter );

		if ( '' !== $generated_url ) {
			return [ 'url' => $generated_url, 'width' => 1200, 'height' => 630 ];
		}

		return $empty;
	}

	// ── Title / description resolvers ─────────────────────────────────────────

	/**
	 * Resolves the OG title for the current page.
	 *
	 * Order: post meta _livada_seo_title → wp_get_document_title() fallback.
	 *
	 * @return string Plain (unescaped) title string.
	 */
	private static function get_title(): string {
		if ( is_singular() ) {
			$post_id    = get_queried_object_id();
			$meta_title = get_post_meta( $post_id, '_livada_seo_title', true );

			if ( $meta_title ) {
				return (string) $meta_title;
			}

			// On front page, fall through to wp_get_document_title()
			// which is already filtered by the Meta module with title_home.
			if ( ! is_front_page() ) {
				$post = get_queried_object();

				if ( $post && isset( $post->post_title ) ) {
					return get_the_title( $post->ID );
				}
			}
		}

		// Front page (uses title_home via Meta filter), archives, search, 404, etc.
		return wp_get_document_title();
	}

	/**
	 * Resolves the OG description for the current page.
	 *
	 * Order: post meta _livada_seo_description → manual excerpt → auto excerpt
	 * (first 160 characters of content, tags stripped) → site tagline.
	 *
	 * @return string Plain (unescaped) description string.
	 */
	private static function get_description(): string {
		if ( is_singular() ) {
			$post_id  = get_queried_object_id();
			$meta_desc = get_post_meta( $post_id, '_livada_seo_description', true );

			if ( $meta_desc ) {
				return (string) $meta_desc;
			}

			// Front page: use desc_home from settings before falling back to excerpt/content.
			if ( is_front_page() || is_home() ) {
				$desc_home = Livada_SEO_Loader::get_setting( 'desc_home', '' );

				if ( '' !== $desc_home ) {
					return wp_strip_all_tags( $desc_home );
				}

				$tagline = get_bloginfo( 'description' );

				if ( $tagline ) {
					return $tagline;
				}
			}

			$post = get_queried_object();

			if ( $post ) {
				// Use the manually written excerpt when available.
				if ( ! empty( $post->post_excerpt ) ) {
					return wp_strip_all_tags( $post->post_excerpt );
				}

				// Auto-generate a 160-character snippet from content.
				if ( ! empty( $post->post_content ) ) {
					$text = wp_strip_all_tags( strip_shortcodes( $post->post_content ) );
					$text = preg_replace( '/\s+/', ' ', $text );
					$text = trim( $text );

					if ( strlen( $text ) > 160 ) {
						$text = substr( $text, 0, 157 ) . '…';
					}

					if ( $text ) {
						return $text;
					}
				}
			}
		}

		if ( is_front_page() || is_home() ) {
			$tagline = get_bloginfo( 'description' );

			if ( $tagline ) {
				return $tagline;
			}
		}

		if ( is_category() || is_tag() || is_tax() ) {
			$term_desc = term_description();

			if ( $term_desc ) {
				return wp_strip_all_tags( $term_desc );
			}
		}

		// Final fallback: site tagline.
		return get_bloginfo( 'description' );
	}

	/**
	 * Returns the canonical URL for the current request.
	 *
	 * @return string Absolute URL.
	 */
	private static function get_url(): string {
		if ( is_singular() ) {
			return (string) get_permalink();
		}

		if ( is_front_page() || is_home() ) {
			return home_url( '/' );
		}

		if ( is_category() || is_tag() || is_tax() ) {
			$term = get_queried_object();

			if ( $term ) {
				$link = get_term_link( $term );

				if ( ! is_wp_error( $link ) ) {
					return $link;
				}
			}
		}

		if ( is_author() ) {
			$author = get_queried_object();

			if ( $author ) {
				return (string) get_author_posts_url( $author->ID );
			}
		}

		// Fallback: current request URI.
		global $wp;

		return home_url( $wp->request );
	}

	/**
	 * Returns og:site_name from settings, falling back to get_bloginfo().
	 *
	 * @return string Plain (unescaped) site name.
	 */
	private static function get_site_name(): string {
		$name = Livada_SEO_Loader::get_setting( 'site_name', '' );

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

		return (string) $name;
	}

	// ── Output helper ─────────────────────────────────────────────────────────

	/**
	 * Echoes a single <meta> tag with proper escaping.
	 *
	 * @param string $property  Property / name value (e.g. "og:title").
	 * @param string $content   Tag content value.
	 * @param bool   $is_url    When true, applies esc_url() instead of esc_attr().
	 * @param string $key_attr  Attribute name for the property key: "property" or "name".
	 */
	private static function tag(
		string $property,
		string $content,
		bool   $is_url    = false,
		string $key_attr  = 'property'
	): void {
		if ( '' === $content ) {
			return;
		}

		$escaped_content = $is_url
			? esc_url( $content )
			: esc_attr( $content );

		printf(
			'<meta %s="%s" content="%s" />' . "\n",
			esc_attr( $key_attr ),
			esc_attr( $property ),
			$escaped_content
		);
	}
}
