<?php
/**
 * Formatting Utilities
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Utils;

use SEOvaultSEO\ReplaceVariables\Manager;
use SEOvaultSEO\Utils\RequestCache;

/**
 * Provides formatting functions for SEO data
 */
class Formatter {

	/**
	 * Format title with template variables (%variable% syntax)
	 *
	 * @param string $template Title template
	 * @param array  $args     Context args (post data, etc.)
	 * @return string
	 */
	public static function format_title( $template, $args = array() ) {
		if ( empty( $template ) ) {
			return '';
		}

		$template = self::migrate_old_syntax( $template );

		return Manager::do_replace( $template, $args );
	}

	/**
	 * Format description with template variables (%variable% syntax)
	 *
	 * @param string $template Description template
	 * @param array  $args     Context args (post data, etc.)
	 * @return string
	 */
	public static function format_description( $template, $args = array() ) {
		if ( empty( $template ) ) {
			return '';
		}

		$template = self::migrate_old_syntax( $template );

		return Manager::do_replace( $template, $args );
	}

	/**
	 * Convert old {variable} syntax to %variable% syntax for backward compatibility.
	 *
	 * @param string $template Template string.
	 * @return string
	 */
	private static function migrate_old_syntax( $template ) {
		$map = array(
			'{site_name}'        => '%sitename%',
			'{site_description}' => '%sitedesc%',
			'{post_title}'       => '%title%',
			'{category}'         => '%category%',
			'{tag}'              => '%tag%',
			'{excerpt}'          => '%excerpt%',
		);

		return str_replace( array_keys( $map ), array_values( $map ), $template );
	}

	/**
	 * Truncate text to specified length
	 *
	 * @param string $text Input text
	 * @param int    $length Maximum length
	 * @param string $suffix Suffix to add if truncated
	 * @return string
	 */
	public static function truncate( $text, $length = 160, $suffix = '...' ) {
		if ( mb_strlen( $text ) <= $length ) {
			return $text;
		}

		return mb_substr( $text, 0, $length - mb_strlen( $suffix ) ) . $suffix;
	}

	/**
	 * Strip tags and normalize whitespace
	 *
	 * @param string $text Input text
	 * @return string
	 */
	public static function clean_text( $text ) {
		$text = wp_strip_all_tags( $text );
		$text = preg_replace( '/\s+/', ' ', $text );
		return trim( $text );
	}

	/**
	 * Prepare post content for SEO text analysis.
	 *
	 * Renders block and shortcode content, then appends formatted ACF field text
	 * when ACF is active. This keeps auto descriptions and keyword suggestions
	 * useful for pages whose visible content is stored outside post_content.
	 *
	 * @param string $content Post content.
	 * @param int    $post_id Post ID.
	 * @return string
	 */
	public static function prepare_content_for_analysis( $content, $post_id = 0 ) {
		$prepared = (string) $content;

		if ( '' !== $prepared && function_exists( 'has_blocks' ) && has_blocks( $prepared ) && function_exists( 'do_blocks' ) ) {
			$prepared = do_blocks( $prepared );
		}

		if ( '' !== $prepared && function_exists( 'do_shortcode' ) ) {
			$prepared = do_shortcode( $prepared );
		}

		$acf_text = self::get_acf_fields_text( $post_id );
		if ( '' !== $acf_text ) {
			$prepared = trim( $prepared . "\n\n" . $acf_text );
		}

		return $prepared;
	}

	/**
	 * Convert mixed values into human-readable text for SEO templates.
	 *
	 * @param mixed $value Value to convert.
	 * @return string
	 */
	public static function value_to_text( $value ) {
		if ( null === $value || false === $value ) {
			return '';
		}

		if ( true === $value ) {
			return '1';
		}

		if ( is_scalar( $value ) ) {
			return trim( wp_strip_all_tags( (string) $value ) );
		}

		if ( $value instanceof \WP_Post ) {
			return $value->post_title;
		}

		if ( $value instanceof \WP_Term ) {
			return $value->name;
		}

		if ( $value instanceof \WP_User ) {
			return $value->display_name;
		}

		if ( is_object( $value ) ) {
			if ( isset( $value->post_title ) ) {
				return self::value_to_text( $value->post_title );
			}
			if ( isset( $value->name ) ) {
				return self::value_to_text( $value->name );
			}
			if ( isset( $value->display_name ) ) {
				return self::value_to_text( $value->display_name );
			}

			$value = get_object_vars( $value );
		}

		if ( is_array( $value ) ) {
			foreach ( array( 'alt', 'label', 'title', 'caption', 'name' ) as $preferred_key ) {
				if ( ! empty( $value[ $preferred_key ] ) ) {
					$text = self::value_to_text( $value[ $preferred_key ] );
					if ( '' !== $text ) {
						return $text;
					}
				}
			}

			$skip_keys = array(
				'ID',
				'id',
				'url',
				'link',
				'filename',
				'filesize',
				'mime_type',
				'subtype',
				'type',
				'width',
				'height',
				'sizes',
			);

			$parts = array();
			foreach ( $value as $key => $item ) {
				if ( is_string( $key ) && in_array( $key, $skip_keys, true ) ) {
					continue;
				}

				$text = self::value_to_text( $item );
				if ( '' !== $text ) {
					$parts[] = $text;
				}
			}

			$parts = array_values( array_unique( $parts ) );
			return implode( ', ', $parts );
		}

		return '';
	}

	/**
	 * Get formatted text from ACF fields attached to a post.
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	private static function get_acf_fields_text( $post_id ) {
		if ( empty( $post_id ) || ! function_exists( 'get_field_objects' ) ) {
			return '';
		}

		$fields = get_field_objects( $post_id );
		if ( empty( $fields ) || ! is_array( $fields ) ) {
			return '';
		}

		$parts = array();
		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) || ! array_key_exists( 'value', $field ) ) {
				continue;
			}

			$text = self::value_to_text( $field['value'] );
			if ( '' !== $text ) {
				$parts[] = $text;
			}
		}

		$parts = array_values( array_unique( $parts ) );
		return implode( "\n", $parts );
	}

	/**
	 * Get effective SEO title for a post
	 *
	 * Falls back to post title if custom SEO title is not set.
	 * Adds "Protected:" prefix for password-protected posts and
	 * "Page X of Y" suffix for paginated posts when the title is
	 * auto-generated (not manually entered).
	 *
	 * @param int $post_id Post ID
	 * @return string
	 */
	public static function get_effective_title( $post_id ) {
		return RequestCache::remember(
			'effective_title:' . $post_id,
			function () use ( $post_id ) {
				$custom_title = \SEOvaultSEO\Core\PostMeta::get_seo_title( $post_id );

				if ( ! empty( $custom_title ) ) {
					return $custom_title;
				}

				// In manual mode, do not auto-generate — return empty.
				if ( ! \SEOvaultSEO\Compatibility\Detector::is_auto_generation() ) {
					return '';
				}

				$post = get_post( $post_id );

				if ( $post && \SEOvaultSEO\Core\Options::is_templates_enabled() ) {
					$template = \SEOvaultSEO\Core\Options::get_title_template();
					if ( ! empty( $template ) ) {
						$title = self::format_title( $template, (array) $post );
						return self::add_context_additions( $title, $post );
					}
				}

				$title = $post ? $post->post_title : '';
				return self::add_context_additions( $title, $post );
			}
		);
	}

	/**
	 * Add protected prefix and pagination suffix to an auto-generated title.
	 *
	 * Does not modify manually entered SEO titles (those are returned early
	 * by get_effective_title).
	 *
	 * @param string   $title The auto-generated title.
	 * @param \WP_Post $post  The post object.
	 * @return string
	 */
	public static function add_context_additions( $title, $post ) {
		if ( ! $post ) {
			return $title;
		}

		// Add "Protected:" prefix for password-protected posts.
		if ( ! empty( $post->post_password ) ) {
			$prefix = __( 'Protected:', 'seovault-ai' );
			if ( false === stripos( $title, $prefix ) ) {
				$title = $prefix . ' ' . $title;
			}
		}

		// Add "Page X of Y" suffix for paginated posts.
		if ( self::is_multipage() ) {
			$page     = (int) get_query_var( 'page' );
			$numpages = (int) substr_count( $post->post_content, '<!--nextpage-->' ) + 1;
			if ( $page > 1 && $numpages > 1 ) {
				$suffix = sprintf(
					/* translators: 1: Current page number, 2: Total page count. */
					__( 'Page %1$d of %2$d', 'seovault-ai' ),
					$page,
					$numpages
				);
				if ( false === stripos( $title, $suffix ) ) {
					$title .= ' - ' . $suffix;
				}
			}
		}

		return $title;
	}

	/**
	 * Check if the current query is a multipage singular view.
	 *
	 * @return bool
	 */
	public static function is_multipage() {
		if ( ! is_singular() || is_404() || is_admin() ) {
			return false;
		}

		$post = get_queried_object();
		if ( ! $post ) {
			return false;
		}

		$content = $post->post_content;
		if ( false === strpos( $content, '<!--nextpage-->' ) ) {
			return false;
		}

		$page = (int) get_query_var( 'page' );
		return $page > 1;
	}

	/**
	 * Get effective social title for a post (OG / Twitter).
	 *
	 * When social_title_rem_additions is enabled, returns the plain title
	 * without branding/additions (no site name suffix, no pagination, no
	 * protected prefix). Falls back to get_effective_title otherwise.
	 *
	 * @param int $post_id Post ID
	 * @return string
	 */
	public static function get_effective_social_title( $post_id ) {
		if ( ! \SEOvaultSEO\Core\Options::is_social_title_rem_additions() ) {
			return self::get_effective_og_title( $post_id );
		}

		$custom_og_title = \SEOvaultSEO\Core\PostMeta::get_og_title( $post_id );
		if ( ! empty( $custom_og_title ) ) {
			return $custom_og_title;
		}

		$custom_seo_title = \SEOvaultSEO\Core\PostMeta::get_seo_title( $post_id );
		if ( ! empty( $custom_seo_title ) ) {
			return $custom_seo_title;
		}

		// In manual mode, do not auto-generate — return empty.
		if ( ! \SEOvaultSEO\Compatibility\Detector::is_auto_generation() ) {
			return '';
		}

		$post = get_post( $post_id );
		return $post ? $post->post_title : '';
	}

	/**
	 * Get effective SEO description for a post
	 * Falls back to excerpt or auto-generated description if custom description is not set
	 *
	 * @param int $post_id Post ID
	 * @return string
	 */
	public static function get_effective_description( $post_id ) {
		return RequestCache::remember(
			'effective_description:' . $post_id,
			function () use ( $post_id ) {
				$custom_description = \SEOvaultSEO\Core\PostMeta::get_seo_description( $post_id );

				if ( ! empty( $custom_description ) ) {
					return $custom_description;
				}

				// In manual mode, do not auto-generate — return empty.
				if ( ! \SEOvaultSEO\Compatibility\Detector::is_auto_generation() ) {
					return '';
				}

				$post = get_post( $post_id );

				if ( $post && \SEOvaultSEO\Core\Options::is_templates_enabled() ) {
					$template = \SEOvaultSEO\Core\Options::get_description_template();
					if ( ! empty( $template ) ) {
						return self::format_description( $template, (array) $post );
					}
				}

				if ( $post && ! empty( $post->post_excerpt ) ) {
					return self::truncate( self::clean_text( $post->post_excerpt ), 160 );
				}

				if ( $post ) {
					$content = self::prepare_content_for_analysis( $post->post_content, $post_id );
					return self::truncate( self::clean_text( $content ), 160 );
				}

				return '';
			}
		);
	}

	/**
	 * Get effective canonical URL for a post
	 * Falls back to permalink if custom canonical is not set
	 *
	 * @param int $post_id Post ID
	 * @return string
	 */
	public static function get_effective_canonical( $post_id ) {
		$custom_canonical = \SEOvaultSEO\Core\PostMeta::get_canonical_url( $post_id );

		if ( ! empty( $custom_canonical ) ) {
			return $custom_canonical;
		}

		// In manual mode, do not auto-generate — return empty.
		if ( ! \SEOvaultSEO\Compatibility\Detector::is_auto_generation() ) {
			return '';
		}

		return get_permalink( $post_id );
	}

	/**
	 * Get effective OG image for a post
	 *
	 * Full fallback chain:
	 * 1. Custom OG image (PostMeta)
	 * 2. Featured image (post thumbnail)
	 * 3. First content image (from post content)
	 * 4. Default social image (Options)
	 * 5. Site custom logo (theme mod)
	 * 6. Site icon (site icon option)
	 * 7. Empty string
	 *
	 * @param int $post_id Post ID (0 for non-singular contexts like blog homepage)
	 * @return string
	 */
	public static function get_effective_og_image( $post_id ) {
		return RequestCache::remember(
			'og_image:' . $post_id,
			function () use ( $post_id ) {
				$custom_image = \SEOvaultSEO\Core\PostMeta::get_og_image( $post_id );

				if ( ! empty( $custom_image ) ) {
					return $custom_image;
				}

				// In manual mode, do not auto-generate — return empty.
				if ( ! \SEOvaultSEO\Compatibility\Detector::is_auto_generation() ) {
					return '';
				}

				$featured_image = get_post_thumbnail_id( $post_id );
				if ( $featured_image ) {
					$image_url = wp_get_attachment_image_url( $featured_image, 'full' );
					if ( $image_url ) {
						return $image_url;
					}
				}

				$content_image = self::get_first_content_image_url( $post_id );
				if ( ! empty( $content_image ) ) {
					return $content_image;
				}

				$default_social = \SEOvaultSEO\Core\Options::get_default_social_image();
				if ( ! empty( $default_social ) ) {
					return $default_social;
				}

				$site_logo = self::get_site_logo_url();
				if ( ! empty( $site_logo ) ) {
					return $site_logo;
				}

				$site_icon = self::get_site_icon_url();
				if ( ! empty( $site_icon ) ) {
					return $site_icon;
				}

				return '';
			}
		);
	}

	/**
	 * Extract the first valid image URL from post content.
	 *
	 * Skips data URIs and non-image URLs. Normalizes relative URLs
	 * to absolute using home_url().
	 *
	 * @param int $post_id Post ID
	 * @return string Image URL or empty string
	 */
	public static function get_first_content_image_url( $post_id ) {
		$post = get_post( $post_id );

		if ( ! $post || empty( $post->post_content ) ) {
			return '';
		}

		$content = $post->post_content;

		if ( strlen( $content ) <= 10 || false === stripos( $content, '<img ' ) ) {
			return '';
		}

		if ( ! preg_match_all( '/<img\b[^>]+?\bsrc=(["\'])?([^"\'>\s]+)\1?[^>]*?>/mi', $content, $matches, PREG_SET_ORDER ) ) {
			return '';
		}

		$valid_extensions = array( 'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp' );

		foreach ( $matches as $match ) {
			$url = isset( $match[2] ) ? trim( $match[2] ) : '';

			if ( empty( $url ) ) {
				continue;
			}

			if ( 0 === stripos( $url, 'data:' ) ) {
				continue;
			}

			if ( 0 === strpos( $url, '//' ) ) {
				$url = set_url_scheme( $url );
			} elseif ( 0 === strpos( $url, '/' ) ) {
				$url = home_url( $url );
			}

			$path = wp_parse_url( $url, PHP_URL_PATH );

			if ( ! $path ) {
				continue;
			}

			$extension = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) );

			if ( in_array( $extension, $valid_extensions, true ) ) {
				return $url;
			}
		}

		return '';
	}

	/**
	 * Get the site custom logo URL.
	 *
	 * Retrieves the full-size URL of the theme's custom logo.
	 *
	 * @return string Logo URL or empty string if no custom logo is set.
	 */
	public static function get_site_logo_url() {
		$logo_id = get_theme_mod( 'custom_logo', 0 );

		if ( ! $logo_id ) {
			return '';
		}

		$url = wp_get_attachment_image_url( $logo_id, 'full' );

		return $url ? $url : '';
	}

	/**
	 * Get the site icon URL.
	 *
	 * Retrieves the URL of the site icon (favicon) at the largest available size.
	 *
	 * @return string Site icon URL or empty string if no site icon is set.
	 */
	public static function get_site_icon_url() {
		$url = get_site_icon_url( 512 );

		return $url ? $url : '';
	}

	/**
	 * Get effective OG title for a post
	 * Falls back to SEO title, then post title
	 *
	 * @param int $post_id Post ID
	 * @return string
	 */
	public static function get_effective_og_title( $post_id ) {
		$custom_og_title = \SEOvaultSEO\Core\PostMeta::get_og_title( $post_id );

		if ( ! empty( $custom_og_title ) ) {
			return $custom_og_title;
		}

		// In manual mode, do not auto-generate — return empty.
		if ( ! \SEOvaultSEO\Compatibility\Detector::is_auto_generation() ) {
			return '';
		}

		return self::get_effective_title( $post_id );
	}

	/**
	 * Get effective OG description for a post
	 * Falls back to SEO description, then effective description
	 *
	 * @param int $post_id Post ID
	 * @return string
	 */
	public static function get_effective_og_description( $post_id ) {
		$custom_og_description = \SEOvaultSEO\Core\PostMeta::get_og_description( $post_id );

		if ( ! empty( $custom_og_description ) ) {
			return $custom_og_description;
		}

		// In manual mode, do not auto-generate — return empty.
		if ( ! \SEOvaultSEO\Compatibility\Detector::is_auto_generation() ) {
			return '';
		}

		return self::get_effective_description( $post_id );
	}

	/**
	 * Generate a fallback description for the blog/posts page.
	 *
	 * Produces a "Latest posts from {site name}" style description
	 * when no custom description is available.
	 *
	 * @return string Generated description or empty string if site name is empty.
	 */
	public static function generate_blog_description() {
		$site_name = get_bloginfo( 'name' );

		if ( empty( $site_name ) ) {
			return '';
		}

		return sprintf( 'Latest posts from %s', $site_name );
	}
}
