<?php
/** @noinspection PhpLackOfCohesionInspection */
declare(strict_types=1);

namespace RankingCoach\Inc\Modules\ModuleLibrary\Technical\MetaTags;

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

use BeyondSEO\Domain\Integrations\WordPress\Seo\Entities\WebPages\Content\Elements\MetaTags\WPWebPageMetaTag;
use Exception;
use InvalidArgumentException;
use RankingCoach\Inc\Core\Base\BaseConstants;
use RankingCoach\Inc\Core\Base\Traits\RcLoggerTrait;
use RankingCoach\Inc\Core\Helpers\CoreHelper;
use RankingCoach\Inc\Core\Helpers\SocialMediaHelper;
use RankingCoach\Inc\Core\Helpers\Traits\RcApiTrait;
use RankingCoach\Inc\Core\Helpers\WordpressHelpers;
use RankingCoach\Inc\Core\Settings\SettingsManager;
use RankingCoach\Inc\Interfaces\MetaHeadBuilderInterface;
use RankingCoach\Inc\Modules\ModuleBase\BaseModule;
use RankingCoach\Inc\Modules\ModuleManager;
use ReflectionException;

/**
 * Class MetaTags
 */
class MetaTags extends BaseModule implements MetaHeadBuilderInterface
{

    use RcApiTrait;
    use RcLoggerTrait;

    public const MODULE_NAME = 'metaTags';

    public const META_SEO_TITLE = BaseConstants::META_KEY_SEO_TITLE;
    public const META_SEO_DESCRIPTION = BaseConstants::META_KEY_SEO_DESCRIPTION;
    public const META_SOCIAL_TITLE = 'rankingcoach_social_title';
    public const META_SOCIAL_DESCRIPTION = 'rankingcoach_social_description';

    public const META_SEO_SECONDARY_KEYWORDS = 'rankingcoach_secondary_keywords';
    public const META_TITLE_SEPARATOR = 'rankingcoach_title_separator';
    public const META_NOINDEX_FOR_PAGE = 'rankingcoach_noindex_for_page';
    public const META_NOARCHIVE_FOR_PAGE = 'rankingcoach_noarchive_for_page';
    public const META_NOSNIPPET_FOR_PAGE = 'rankingcoach_nosnippet_for_page';
    public const META_NOIMAGEINDEX_FOR_PAGE = 'rankingcoach_noimageindex_for_page';
    public const META_EXCLUDE_SITEMAP_FOR_PAGE = 'rankingcoach_exclude_sitemap_for_page';
    public const META_CANONICAL_URL = 'rankingcoach_canonical_url';
    public const META_DISABLE_AUTO_LINKS = 'rankingcoach_disable_auto_links';
    public const META_VIEWPORT_FOR_PAGE = 'rankingcoach_enable_viewport_for_page';
    public const AUTOGENERATED_TAGS = [
        MetaTags::META_SEO_TITLE,
        MetaTags::META_SEO_DESCRIPTION
    ];
    public const META_SEO_BUSINESS_LOCATION = 'rankingcoach_business_location';
    public const META_VIEWPORT = 'rankingcoach_viewport';

    public const MISSING_FIELDS_PAYLOAD = 'Missing fields payload.';
    public const INVALID_FIELDS_FORMAT = 'Invalid fields format.';
    public const INVALID_FIELDS_MESSAGE = 'Payload must include valid fields.';
    public const INVALID_FIELDS_CODE = 'INVALID_FIELDS';
    public const INVALID_FIELDS_DETAILS = 'The request to create SEO metaTags failed. The API requires specific fields in the request, and some of the fields you provided were not recognized. This means the data you sent was not in the correct format for the API to understand.';

    /** @var int|null The content (page/post) ID. */
    protected ?int $contentId = null;

    /** @var array The payload data. */
    protected array $payload;

    /** @var string $titleKey key used for the title field value. */
    protected string $titleKey;

    /** @var string $descriptionKey key used for the description field value. */
    protected string $descriptionKey;

    /** @var string $keywordsKey key used for the keywords field value. */
    protected string $keywordsKey;

    /** @var array $viewportCache Cache for viewport meta values */
    private static array $viewportCache = [];

    /**
     * MetaTags constructor.
     * @param ModuleManager $moduleManager
     * @throws ReflectionException
     */
    public function __construct(ModuleManager $moduleManager)
    {
        $initialization = [
            'active' => true,
            'title' => 'Meta Tags Manager',
            'description' => 'Manages and optimizes meta tags (title, description, keywords, viewport, Open Graph, etc.) for individual pages and posts to improve SEO, responsive design, and social media sharing. Provides default values and allows for customization.',
            'version' => '1.2.0',
            'name' => 'metaTags',
            'priority' => 5,
            'dependencies' => [],
            'settings' => [['key' => 'default_description', 'type' => 'string', 'default' => 'Welcome to our site!', 'description' => 'The default meta description used for pages and posts that do not have a custom description set.  This description should be concise and enticing.'], ['key' => 'default_keywords', 'type' => 'string', 'default' => 'WordPress, SEO, blog', 'description' => 'Default meta keywords. While less impactful than descriptions, these can still provide context to search engines.'], ['key' => 'og_image_source', 'type' => 'string', 'default' => 'featured_image', 'description' => 'Source for the Open Graph image: \'featured_image\', \'custom_url\', or \'none\'.'], ['key' => 'default_viewport', 'type' => 'string', 'default' => 'width=device-width, initial-scale=1.0', 'description' => 'Default viewport meta tag content for responsive design. Controls how the page is displayed on mobile devices.'], ['key' => 'enable_viewport', 'type' => 'boolean', 'default' => true, 'description' => 'Enable automatic viewport meta tag generation for responsive design optimization.']],
            'explain' => 'If a user creates a new blog post and does not enter a meta description, this module automatically uses the configured default description (\'Welcome to our site!\').  If the user specifies keywords, those are used; otherwise, the default keywords are used. The module also generates Open Graph tags for social media sharing, using the featured image as the OG image based on the configured settings.  This ensures that every page has optimized meta information for both search engines and social platforms.',
        ];
        parent::__construct($moduleManager, $initialization);

        $this->titleKey = strtolower(WPWebPageMetaTag::TAG_TYPE_TITLE);
        $this->descriptionKey = strtolower(WPWebPageMetaTag::TAG_TYPE_DESCRIPTION);
        $this->keywordsKey = strtolower(WPWebPageMetaTag::TAG_TYPE_KEYWORDS);
    }

    /**
     * Registers the hooks for the module.
     * @return void
     */
    public function initializeModule(): void
    {
        if (!$this->module_active) {
            return;
        }

        // Define capabilities specific to the module
        $this->defineCapabilities();

        parent::initializeModule();
    }

    /**
     * Retrieves the name of the module.
     * @return string The name of the module.
     */
    public static function getModuleNameStatic(): string
    {
        return self::MODULE_NAME;
    }

    /**
     * Create necessary SQL tables if they don't already exist.
     * @param string $table_name
     * @param string $charset_collate
     * @return string
     * @noinspection SqlNoDataSourceInspection
     */
    protected function getTableSchema(string $table_name, string $charset_collate): string
    {
        if(!$this->isActive()) {
            return '';
        }
        return "CREATE TABLE $table_name (
            id int(11) NOT NULL AUTO_INCREMENT,
            post_id bigint(20) NOT NULL,
            type ENUM('title', 'social_title', 'description', 'social_description', 'keywords', 'viewport') NOT NULL,
            content text NOT NULL,
            template text NOT NULL,
            auto_generated tinyint(1) NOT NULL,
            variables text NOT NULL,
            unique_key varchar(255) NOT NULL,
            PRIMARY KEY  (id),
        	UNIQUE KEY unique_post_type (post_id, type),
        	KEY idx_post_id (post_id),
        	KEY idx_unique_key (unique_key),
        	KEY idx_auto_generated (auto_generated),
        	KEY idx_post_type (post_id, type)
        ) $charset_collate;";
    }

    /**
     * Save the SEO metadata for a given content ID.
     * 
     * @param int $contentID The post/page ID to save metadata for
     * @param array $payload The metadata payload containing SEO and social data
     * @return bool True on success, false on failure
     * @throws InvalidArgumentException When contentID is invalid or payload is malformed
     */
    public function saveMetaData(int $contentID, array $payload = []): bool
    {
        // Validate input parameters
        if ($contentID <= 0) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
            throw new InvalidArgumentException(__('Content ID must be a positive integer', 'beyondseo'));
        }

        // Verify post exists
        if (!get_post($contentID)) {
            /* translators: %d: post ID */
            throw new InvalidArgumentException(sprintf(__('Post with ID %d does not exist', 'beyondseo'), $contentID)); // phpcs:ignore  WordPress.Security.EscapeOutput.ExceptionNotEscaped
        }

        // Use provided payload or fallback to instance payload
        $metaPayload = !empty($payload) ? $payload : $this->payload;
        
        if (empty($metaPayload)) {
            $this->log('Empty payload provided for saveMetaData: ' . json_encode(['contentID' => $contentID]), 'DEBUG');
            return false;
        }

        try {
            // Begin transaction-like operation tracking
            $operations = [];
            
            // Process SEO metadata
            $this->processSeoMetadata($contentID, $metaPayload, $operations);
            
            // Process social metadata
            $this->processSocialMetadata($contentID, $metaPayload, $operations);
            
            // Process keywords with primary/secondary separation
            $this->processKeywordsMetadata($contentID, $metaPayload, $operations);
            
            // Process viewport metadata
            $this->processViewportMetadata($contentID, $metaPayload, $operations);
            
            // Log successful operations for debugging
            if (!empty($operations)) {
                $this->log('Meta tags saved successfully: ' . json_encode([
                    'contentID' => $contentID,
                    'operations' => $operations,
                    'payload_keys' => array_keys($metaPayload)
                ]), 'DEBUG');
            }
            
            return true;
            
        } catch (Exception $e) {
            $this->log('Failed to save meta data: ' . json_encode([
                'contentID' => $contentID,
                'error' => $e->getMessage(),
                'payload' => $metaPayload
            ]), 'ERROR');
            return false;
        }
    }

    /**
     * Process SEO metadata (title, description, keywords)
     * 
     * @param int $contentID
     * @param array $payload
     * @param array &$operations
     * @return void
     */
    private function processSeoMetadata(int $contentID, array $payload, array &$operations): void
    {
        // Process SEO title
        if (isset($payload[$this->titleKey])) {
            $title = $this->sanitizeMetaValue($payload[$this->titleKey]);
            if (!empty($title)) {
                update_post_meta($contentID, self::META_SEO_TITLE, $title);
                $operations[] = 'seo_title_updated';
            } else {
                delete_post_meta($contentID, self::META_SEO_TITLE);
                $operations[] = 'seo_title_deleted';
            }
        }

        // Process SEO description
        if (isset($payload[$this->descriptionKey])) {
            $description = $this->sanitizeMetaValue($payload[$this->descriptionKey]);
            if (!empty($description)) {
                update_post_meta($contentID, self::META_SEO_DESCRIPTION, $description);
                $operations[] = 'seo_description_updated';
            } else {
                delete_post_meta($contentID, self::META_SEO_DESCRIPTION);
                $operations[] = 'seo_description_deleted';
            }
        }
    }

    /**
     * Process social media metadata
     * 
     * @param int $contentID
     * @param array $payload
     * @param array &$operations
     * @return void
     */
    private function processSocialMetadata(int $contentID, array $payload, array &$operations): void
    {
        $socialTitleKey = strtolower(WPWebPageMetaTag::TAG_TYPE_SOCIAL_TITLE);
        $socialDescriptionKey = strtolower(WPWebPageMetaTag::TAG_TYPE_SOCIAL_DESCRIPTION);

        // Process social title
        if (isset($payload[$socialTitleKey])) {
            $socialTitle = $this->sanitizeMetaValue($payload[$socialTitleKey]);
            if (!empty($socialTitle)) {
                update_post_meta($contentID, self::META_SOCIAL_TITLE, $socialTitle);
                $operations[] = 'social_title_updated';
            } else {
                delete_post_meta($contentID, self::META_SOCIAL_TITLE);
                $operations[] = 'social_title_deleted';
            }
        }

        // Process social description
        if (isset($payload[$socialDescriptionKey])) {
            $socialDescription = $this->sanitizeMetaValue($payload[$socialDescriptionKey]);
            if (!empty($socialDescription)) {
                update_post_meta($contentID, self::META_SOCIAL_DESCRIPTION, $socialDescription);
                $operations[] = 'social_description_updated';
            } else {
                delete_post_meta($contentID, self::META_SOCIAL_DESCRIPTION);
                $operations[] = 'social_description_deleted';
            }
        }
    }

    /**
     * Process keywords metadata with primary/secondary separation
     * 
     * @param int $contentID
     * @param array $payload
     * @param array &$operations
     * @return void
     */
    private function processKeywordsMetadata(int $contentID, array $payload, array &$operations): void
    {
        if (!isset($payload[$this->keywordsKey])) {
            return;
        }

        $rawKeywords = $payload[$this->keywordsKey];
        
        // Normalize keywords to string format
        $keywordsString = $this->normalizeKeywords($rawKeywords);
        
        if (empty($keywordsString)) {
            // Clean up all keyword-related meta if empty
            $this->cleanupKeywordsMeta($contentID);
            $operations[] = 'keywords_cleaned';
            return;
        }

        // Store the original keywords
        update_post_meta($contentID, BaseConstants::META_KEY_SEO_KEYWORDS, $keywordsString);
        $operations[] = 'keywords_updated';

        // Parse and separate primary/secondary keywords
        $keywordsArray = $this->parseKeywordsArray($keywordsString);
        
        if (!empty($keywordsArray)) {
            $primaryKeyword = array_shift($keywordsArray);
            
            // Update primary keyword
            update_post_meta($contentID, BaseConstants::META_KEY_PRIMARY_KEYWORD, $primaryKeyword);
            $operations[] = 'primary_keyword_updated';
            
            // Update secondary keywords
            if (!empty($keywordsArray)) {
                $secondaryKeywords = implode(',', $keywordsArray);
                update_post_meta($contentID, self::META_SEO_SECONDARY_KEYWORDS, $secondaryKeywords);
                $operations[] = 'secondary_keywords_updated';
            } else {
                delete_post_meta($contentID, self::META_SEO_SECONDARY_KEYWORDS);
                $operations[] = 'secondary_keywords_deleted';
            }
        } else {
            $this->cleanupKeywordsMeta($contentID);
            $operations[] = 'keywords_cleaned_invalid';
        }
    }

    /**
     * Sanitize meta value for safe storage
     * 
     * @param mixed $value
     * @return string
     */
    private function sanitizeMetaValue($value): string
    {
        if (is_array($value)) {
            $value = implode(' ', $value);
        }
        
        return trim(sanitize_text_field((string) $value));
    }

    /**
     * Normalize keywords from various input formats to string
     * 
     * @param mixed $keywords
     * @return string
     */
    private function normalizeKeywords($keywords): string
    {
        if (is_array($keywords)) {
            $filtered = array_filter(array_map('trim', $keywords));
            return implode(',', $filtered);
        }
        
        return trim((string) $keywords);
    }

    /**
     * Parse keywords string into clean array
     * 
     * @param string $keywordsString
     * @return array
     */
    private function parseKeywordsArray(string $keywordsString): array
    {
        return array_filter(
            array_map('trim', explode(',', $keywordsString)),
            fn($keyword) => !empty($keyword)
        );
    }

    /**
     * Clean up all keyword-related meta fields
     * 
     * @param int $contentID
     * @return void
     */
    private function cleanupKeywordsMeta(int $contentID): void
    {
        delete_post_meta($contentID, BaseConstants::META_KEY_SEO_KEYWORDS);
        delete_post_meta($contentID, BaseConstants::META_KEY_PRIMARY_KEYWORD);
        delete_post_meta($contentID, self::META_SEO_SECONDARY_KEYWORDS);
    }

    /**
     * Process viewport metadata
     * 
     * @param int $contentID
     * @param array $payload
     * @param array &$operations
     * @return void
     */
    private function processViewportMetadata(int $contentID, array $payload, array &$operations): void
    {
        $viewportKey = 'viewport';
        
        if (isset($payload[$viewportKey])) {
            $viewport = $this->sanitizeViewportValue($payload[$viewportKey]);
            if (!empty($viewport)) {
                update_post_meta($contentID, self::META_VIEWPORT, $viewport);
                $operations[] = 'viewport_updated';
            } else {
                delete_post_meta($contentID, self::META_VIEWPORT);
                $operations[] = 'viewport_deleted';
            }
            
            // Invalidate cache for this content
            $cacheKey = 'viewport_' . $contentID;
            unset(self::$viewportCache[$cacheKey]);
        }
    }

    /**
     * Sanitize viewport value for safe storage
     * 
     * @param mixed $value
     * @return string
     */
    private function sanitizeViewportValue($value): string
    {
        $viewport = trim(sanitize_text_field((string) $value));
        
        // Handle preset viewport configurations
        $presets = $this->getViewportPresets();
        if (isset($presets[$viewport])) {
            return $presets[$viewport];
        }
        
        // Validate viewport format - enhanced validation for viewport attributes
        if (empty($viewport)) {
            return '';
        }
        
        // Allow common viewport attributes with enhanced validation
        $allowedAttributes = [
            'width' => '/^width=(device-width|\d+)$/i',
            'height' => '/^height=(device-height|\d+)$/i',
            'initial-scale' => '/^initial-scale=([0-9]*\.?[0-9]+)$/i',
            'minimum-scale' => '/^minimum-scale=([0-9]*\.?[0-9]+)$/i',
            'maximum-scale' => '/^maximum-scale=([0-9]*\.?[0-9]+)$/i',
            'user-scalable' => '/^user-scalable=(yes|no|0|1)$/i',
            'viewport-fit' => '/^viewport-fit=(auto|contain|cover)$/i',
            'shrink-to-fit' => '/^shrink-to-fit=(yes|no)$/i'
        ];
        
        $parts = array_map('trim', explode(',', $viewport));
        $validParts = [];
        
        foreach ($parts as $part) {
            foreach ($allowedAttributes as $attr => $pattern) {
                if (preg_match($pattern, $part)) {
                    $validParts[] = $part;
                    break;
                }
            }
        }
        
        return implode(', ', $validParts);
    }

    /**
     * Get predefined viewport presets
     * 
     * @return array
     */
    private function getViewportPresets(): array
    {
        return [
            'responsive' => 'width=device-width, initial-scale=1.0',
            'mobile-fixed' => 'width=device-width, initial-scale=1.0, user-scalable=no',
            'desktop-only' => 'width=1024',
            'tablet-optimized' => 'width=device-width, initial-scale=1.0, maximum-scale=2.0',
            'accessibility' => 'width=device-width, initial-scale=1.0, user-scalable=yes, maximum-scale=5.0'
        ];
    }

    /**
     * Retrieves the meta title for a given content ID.
     * @param $contentID
     * @return string|bool
     */
    public function getMetaTitle($contentID): bool|string
    {
        return get_post_meta($contentID, self::META_SEO_TITLE, true);
    }

    /**
     * Retrieves the meta description for a given content ID.
     * @param $contentID
     * @return string|bool
     */
    public function getMetaDescription($contentID): bool|string
    {
        $description = get_post_meta($contentID, self::META_SEO_DESCRIPTION, true);

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

        // Fallback to default description
        return get_bloginfo('description');
    }

    /**
     * Retrieves the meta keywords for a given content ID.
     * @param $contentID
     * @return string|bool
     */
    public function getMetaKeywords($contentID): bool|string
    {
        return get_post_meta($contentID, BaseConstants::META_KEY_SEO_KEYWORDS, true);
    }

    /**
     * Retrieves the social title for a given content ID.
     * @param $contentID
     * @return string|bool
     */
    public function getSocialTitle($contentID): bool|string
    {
        return get_post_meta($contentID, self::META_SOCIAL_TITLE, true);
    }

    /**
     * Retrieves the social description for a given content ID.
     * @param $contentID
     * @return string|bool
     */
    public function getSocialDescription($contentID): bool|string
    {
        return get_post_meta($contentID, self::META_SOCIAL_DESCRIPTION, true);
    }

    /**
     * Retrieves the viewport meta content for a given content ID.
     * @param $contentID
     * @return string|bool
     */
    public function getViewport($contentID): bool|string
    {
        // Use cache to avoid repeated database queries
        $cacheKey = 'viewport_' . $contentID;
        
        if (!isset(self::$viewportCache[$cacheKey])) {
            self::$viewportCache[$cacheKey] = get_post_meta($contentID, self::META_VIEWPORT, true);
        }
        
        return self::$viewportCache[$cacheKey];
    }

    /**
     * Generates the meta tags for the page.
     * @return string
     */
    public function generateMetaTags(): string
    {
        if (!$this->contentId) {
            $this->contentId = get_queried_object_id();
        }

        $supportedLanguages = SettingsManager::instance()->supported_languages;
        $locale = get_locale();
        $currentLanguage = substr($locale, 0, 2);

        // Get SEO description with fallback logic using the centralized helper
        $customSeoDescription = get_post_meta($this->contentId, self::META_SEO_DESCRIPTION, true);
        $description = esc_attr(WordpressHelpers::getMetaDescription($this->contentId, $customSeoDescription ?: null, 160));
        $keywords = esc_attr($this->getMetaKeywords($this->contentId));
        $socialTitle = esc_attr($this->getSocialTitle($this->contentId));
        $socialDescription = esc_attr($this->getSocialDescription($this->contentId));

        // Get post author ID
        $authorId = get_post_field('post_author', $this->contentId);

        // Get author display name
        $authorName = esc_attr(get_the_author_meta('display_name', $authorId));

        // Generate basic meta tags
        $metaTags = '<meta name="description" data-postid="' . $this->contentId . '" content="' . $description . '" />' . "\r\n\t";

        if (!empty($keywords)) {
            $metaTags .= '<meta name="keywords" data-postid="' . $this->contentId . '" content="' . $keywords . '" />' . "\r\n\t";
        }
        $metaTags .= '<meta name="author" content="' . $authorName . '" />' . "\r\n\t";

        // Robots meta tag with support for various directives
        $metaTags .= $this->generateMetaRobotsTag();

        // Viewport
        $metaTags .= $this->generateViewportMetaTag(1);

        // Canonical URL
        $canonicalUrl = get_post_meta($this->contentId, self::META_CANONICAL_URL, true);
        if ($canonicalUrl) {
            $metaTags .= '<link rel="canonical" href="' . esc_url($canonicalUrl) . '" />' . "\r\n\t";
        } else {
            $metaTags .= '<link rel="canonical" href="' . esc_url(get_permalink($this->contentId)) . '" />' . "\r\n\t";
        }

        // Pagination meta tags (rel="prev" and rel="next")
        $metaTags .= $this->generatePaginationMetaTags();

        // Generate social media meta tags
        $metaTags .= $this->generateSocialMetaTags($socialTitle, $socialDescription);

        // Locale and language tags
        $metaTags .= "\r\n\t" . '<meta property="og:locale" content="' . esc_attr($locale) . '" />';

        // Alternate language tags with proper translated URLs (WPML/Polylang support)
        $metaTags .= $this->generateHreflangTags($supportedLanguages, $currentLanguage);

        // Format detection: telephone=no, email=no, address=no
        $disableAutoLinks = get_post_meta($this->contentId, MetaTags::META_DISABLE_AUTO_LINKS, true);
        if ($disableAutoLinks) {
            $metaTags .= "\r\n\t" . '<meta name="format-detection" content="telephone=no, email=no, address=no" />';
        }

        // Generator tag (optional)
        // $metaTags .= "\r\n\t" . '<meta name="generator" content="rankingCoach" />';

        // Referrer policy
        $metaTags .= "\r\n\t" . '<meta name="referrer" content="strict-origin-when-cross-origin" />';

        // Favicon
        $faviconUrl = CoreHelper::getFaviconUrl();
        if ($faviconUrl) {
            $metaTags .= "\r\n\t" . '<link rel="icon" href="' . esc_url($faviconUrl) . '" type="image/x-icon" />';
        }

        // Color scheme and theme color
        $colorScheme = get_theme_support('color-scheme');
        if (!$colorScheme) {
            $colorScheme = ['light', 'dark'];
        }
        $metaTags .= "\r\n\t" . '<meta name="color-scheme" content="' . esc_attr(implode(' ', $colorScheme)) . '" />';
        $themeColor = get_theme_mod('theme_color', '#ffffff');
        if ($themeColor) {
            $metaTags .= "\r\n\t" . '<meta name="theme-color" content="' . esc_attr($themeColor) . '" />';
        }

        // X-UA-Compatible for IE
        $metaTags .= "\r\n\t" . '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';

        // Site verification meta tags
        $metaTags .= $this->generateVerificationMetaTags();

        return $metaTags;
    }

    /**
     * Generate site verification meta tags for search engines
     *
     * Outputs meta tags for Google Search Console and Bing Webmaster Tools
     * verification based on codes stored in WebmasterTools module settings.
     *
     * @return string HTML meta tags for site verification
     */
    private function generateVerificationMetaTags(): string
    {
        $output = '';
        
        $settings = SettingsManager::instance();
        
        // Google Search Console verification
        $googleCode = $settings->get_option('google_verification_code', '');
        if (!empty($googleCode)) {
            $output .= "\r\n\t" . '<meta name="google-site-verification" content="' . esc_attr($googleCode) . '" />';
        }
        
        // Bing Webmaster Tools verification
        $bingCode = $settings->get_option('bing_verification_code', '');
        if (!empty($bingCode)) {
            $output .= "\r\n\t" . '<meta name="msvalidate.01" content="' . esc_attr($bingCode) . '" />';
        }
        
        return $output;
    }

    /**
     * Generate the meta robots tag with support for various directives
     *
     * Checks for noindex, noarchive, nosnippet, and noimageindex directives
     * from post meta and constructs the appropriate robots meta tag.
     *
     * @return string HTML meta robots tag
     */
    private function generateMetaRobotsTag(): string
    {
        $noIndex = get_post_meta($this->contentId, self::META_NOINDEX_FOR_PAGE, true);
        
        // If noindex is set, output noindex, nofollow and skip other directives
        if ($noIndex) {
            return '<meta name="robots" content="noindex, nofollow" />' . "\r\n\t";
        }
        
        // Build robots content with index, follow as base
        $robotsDirectives = ['index', 'follow'];
        
        // Check for noarchive directive
        $noArchive = get_post_meta($this->contentId, self::META_NOARCHIVE_FOR_PAGE, true);
        if ($noArchive) {
            $robotsDirectives[] = 'noarchive';
        }
        
        // Check for nosnippet directive
        $noSnippet = get_post_meta($this->contentId, self::META_NOSNIPPET_FOR_PAGE, true);
        if ($noSnippet) {
            $robotsDirectives[] = 'nosnippet';
        }
        
        // Check for noimageindex directive
        $noImageIndex = get_post_meta($this->contentId, self::META_NOIMAGEINDEX_FOR_PAGE, true);
        if ($noImageIndex) {
            $robotsDirectives[] = 'noimageindex';
        }
        
        // Add default max directives
        $robotsDirectives[] = 'max-snippet:-1';
        $robotsDirectives[] = 'max-image-preview:large';
        $robotsDirectives[] = 'max-video-preview:-1';
        
        $robotsContent = implode(', ', $robotsDirectives);
        
        return '<meta name="robots" content="' . esc_attr($robotsContent) . '" />' . "\r\n\t";
    }

    /**
     * Generates social media meta tags (Open Graph and Twitter Cards).
     * @param string $socialTitle
     * @param string $socialDescription
     * @return string
     */
    private function generateSocialMetaTags(string $socialTitle, string $socialDescription): string
    {
        $socialTags = '';
        
        // Get post data
        $post = get_post($this->contentId);
        $postTitle = $socialTitle ?: get_the_title($this->contentId);
        
        // Get description with fallback logic for social meta tags
        // First try the provided social description, then fall back to the helper with full fallback logic
        $postDescription = !empty($socialDescription)
            ? $socialDescription
            : WordpressHelpers::getMetaDescription($this->contentId, null, 160);
        $postUrl = get_permalink($this->contentId);
        $siteName = get_bloginfo('name');
        $postType = is_single() ? 'article' : 'website';
        
        // Get social image
        $socialImageUrl = SocialMediaHelper::getSelectedSocialImageUrl($this->contentId);
        if (!$socialImageUrl) {
            $socialImageUrl = get_the_post_thumbnail_url($this->contentId, 'large');
        }
        
        // Open Graph tags
        $socialTags .= '<meta property="og:title" content="' . esc_attr($postTitle) . '" />' . "\r\n\t";
        $socialTags .= '<meta property="og:description" content="' . esc_attr($postDescription) . '" />' . "\r\n\t";
        $socialTags .= '<meta property="og:type" content="' . esc_attr($postType) . '" />' . "\r\n\t";
        $socialTags .= '<meta property="og:url" content="' . esc_url($postUrl) . '" />' . "\r\n\t";
        $socialTags .= '<meta property="og:site_name" content="' . esc_attr($siteName) . '" />' . "\r\n\t";
        
        if ($socialImageUrl) {
            $socialTags .= '<meta property="og:image" content="' . esc_url($socialImageUrl) . '" />' . "\r\n\t";
            $socialTags .= '<meta property="og:image:alt" content="' . esc_attr($postTitle) . '" />' . "\r\n\t";
            
            // Add og:image:width and og:image:height
            $imageDimensions = WordpressHelpers::getImageDimensions($socialImageUrl);
            if (!empty($imageDimensions['width']) && !empty($imageDimensions['height'])) {
                $socialTags .= '<meta property="og:image:width" content="' . esc_attr((string) $imageDimensions['width']) . '" />' . "\r\n\t";
                $socialTags .= '<meta property="og:image:height" content="' . esc_attr((string) $imageDimensions['height']) . '" />' . "\r\n\t";
            }
            
            // Add og:image:type for MIME type
            $mimeType = wp_check_filetype($socialImageUrl)['type'] ?? '';
            if (!empty($mimeType)) {
                $socialTags .= '<meta property="og:image:type" content="' . esc_attr($mimeType) . '" />' . "\r\n\t";
            }
        }
        
        // Article specific tags
        if ($postType === 'article' && $post) {
            $socialTags .= '<meta property="article:published_time" content="' . esc_attr(get_the_date('c', $this->contentId)) . '" />' . "\r\n\t";
            $socialTags .= '<meta property="article:modified_time" content="' . esc_attr(get_the_modified_date('c', $this->contentId)) . '" />' . "\r\n\t";
            
            $authorName = get_the_author_meta('display_name', $post->post_author);
            if ($authorName) {
                $socialTags .= '<meta property="article:author" content="' . esc_attr($authorName) . '" />' . "\r\n\t";
            }
            
            // Add categories and tags
            $categories = get_the_category($this->contentId);
            foreach ($categories as $category) {
                $socialTags .= '<meta property="article:section" content="' . esc_attr($category->name) . '" />' . "\r\n\t";
            }
            
            $tags = get_the_tags($this->contentId);
            if ($tags) {
                foreach ($tags as $tag) {
                    $socialTags .= '<meta property="article:tag" content="' . esc_attr($tag->name) . '" />' . "\r\n\t";
                }
            }
        }
        
        // Twitter Card tags
        $socialTags .= '<meta name="twitter:card" content="summary_large_image" />' . "\r\n\t";
        $socialTags .= '<meta name="twitter:title" content="' . esc_attr($postTitle) . '" />' . "\r\n\t";
        $socialTags .= '<meta name="twitter:description" content="' . esc_attr($postDescription) . '" />' . "\r\n\t";
        
        if ($socialImageUrl) {
            $socialTags .= '<meta name="twitter:image" content="' . esc_url($socialImageUrl) . '" />' . "\r\n\t";
            $socialTags .= '<meta name="twitter:image:alt" content="' . esc_attr($postTitle) . '" />' . "\r\n\t";
        }
        
        // Twitter site handle (if configured)
        $twitterHandle = get_option(BaseConstants::OPTION_METATAGS_TWITTER_HANDLE);
        if ($twitterHandle) {
            $socialTags .= '<meta name="twitter:site" content="' . esc_attr($twitterHandle) . '" />' . "\r\n\t";
        }
        
        return rtrim($socialTags, "\r\n\t");
    }

    /**
     * Generates the viewport meta tag
     *
     * @param int $scaleFactor
     * @return string
     */
    private function generateViewportMetaTag(int $scaleFactor = 1): string
    {
        // Hook 1: Allow complete disabling via filter (highest priority override)
        $forceDisable = apply_filters('rankingcoach_viewport_force_disable', false, $this->contentId);
        if ($forceDisable === true) {
            return '';
        }

        // Check per-page viewport setting first
        // Per-page ON should ALWAYS output viewport, regardless of global setting AND theme viewport
        $viewportForPage = false;
        if ($this->contentId) {
            $viewportForPage = (bool) get_post_meta($this->contentId, self::META_VIEWPORT_FOR_PAGE, true);
        }

        // Per-page setting takes precedence: if explicitly enabled, skip global check AND theme check
        if ($viewportForPage) {
            // Per-page is ON - proceed to output viewport regardless of global setting and theme
            // Skip both the global setting check and theme viewport check entirely
            // This is intentional: user explicitly wants viewport for this page
        } else {
            // No per-page override, use global setting
            $setting = SettingsManager::instance();
            $enableViewport = (bool)$setting->get_option('enable_viewport', true) === true;
            
            // Hook 2: Allow per-post override of global setting via filter
            $enableViewport = apply_filters('rankingcoach_viewport_enabled', $enableViewport, $this->contentId);
            
            // If viewport is not enabled globally (and no per-page override), don't output
            if (!$enableViewport) {
                return '';
            }
            
            // Check if theme already provides viewport (avoid duplication)
            // Hook 3: Allow overriding theme viewport check via filter
            // Default: false (don't override - respect theme's viewport)
            // When filtered to true: skip theme check and output plugin's viewport
            //
            // IMPORTANT: When overriding theme viewport, the theme may still output its own
            // viewport meta tag. To prevent duplicate viewport tags, users should either:
            // 1. Remove theme's viewport support: remove_theme_support('viewport-meta')
            // 2. Or use the theme's hooks to disable its viewport output
            // The plugin's removeDefaultViewportTags() will attempt to filter out non-BeyondSEO
            // viewport tags, but this works best when combined with disabling theme viewport.
            $overrideThemeViewport = apply_filters('rankingcoach_viewport_override_for_theme', false, $this->contentId);
            
            if ($this->hasThemeViewportSupport() && !$overrideThemeViewport) {
                // Theme has viewport support and we're not overriding - don't output ours
                return '';
            }
        }

        // Remove default WordPress/theme viewport meta tags while preserving ours
        $this->removeDefaultViewportTags();

        // Get custom viewport for this specific content
        $customViewport = '';
        if ($this->contentId) {
            $customViewport = $this->getViewport($this->contentId);
        }

        // Hook 4: Validate scale factor is within safe range
        $scaleFactor = apply_filters('rankingcoach_viewport_scale_factor', $scaleFactor, $this->contentId);
        $scaleFactor = max(0.1, min(5.0, (float)$scaleFactor)); // Clamp between 0.1 and 5.0
        
        // Use custom viewport if available, otherwise use default with validated scale
        $viewportContent = !empty($customViewport)
            ? $customViewport
            : 'width=device-width, initial-scale=' . $scaleFactor;

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

        // Hook 5: Final filter to modify viewport content before output
        $viewportContent = apply_filters('rankingcoach_viewport_content', $viewportContent, $this->contentId);

        // Add RankingCoach identifier to preserve this tag during filtering
        return '<meta name="viewport" content="' . esc_attr($viewportContent) . '" data-source="beyondseo" />' . "\r\n\t";
    }

    /**
     * Remove default WordPress/theme viewport meta tags while preserving RankingCoach ones
     * 
     * @return void
     */
    private function removeDefaultViewportTags(): void
    {
        static $hooked = false;
        
        if ($hooked) {
            return;
        }
        
        // Use a more targeted approach with wp_head content filtering
        add_filter('rankingcoach_head_meta_manager_content', [$this, 'filterViewportTags'], 10, 1);
        
        $hooked = true;
    }

    /**
     * Filter viewport tags to remove defaults while preserving RankingCoach ones
     *
     * @param string $content
     * @return string
     */
    public function filterViewportTags(string $content): string
    {
        // Pattern to match viewport meta tags
        $pattern = '/<meta\s+name=["\']viewport["\']\s+content=["\'][^"\']*["\']\s*\/?>\s*/i';
        
        return preg_replace_callback($pattern, function($matches) {
            $tag = $matches[0];
            
            // Preserve our viewport tags by checking for rankingCoach identifiers
            if ($this->isRankingCoachViewportTag($tag)) {
                return $tag;
            }
            
            // Remove default viewport tags
            return '';
        }, $content);
    }

    /**
     * Check if a viewport tag belongs to RankingCoach
     * 
     * @param string $tag
     * @return bool
     */
    private function isRankingCoachViewportTag(string $tag): bool
    {
        // Primary identifier: check for our data-source attribute
        if (str_contains($tag, 'data-source="beyondseo"')) {
            return true;
        }
        
        return false;
    }

    /**
     * Check if current theme already provides viewport meta tag
     *
     * @return bool
     */
    private function hasThemeViewportSupport(): bool
    {
        // Check for common theme viewport support indicators
        $themeSupport = current_theme_supports('html5', 'style') ||
                       current_theme_supports('responsive-embeds') ||
                       get_theme_support('viewport-meta');

        return apply_filters('rankingcoach_theme_has_viewport', $themeSupport);
    }

    /**
     * Generates hreflang tags with proper translated URLs.
     * Supports WPML, Polylang, and falls back to basic language tags.
     *
     * @param array|object|null $supportedLanguages Array or object of supported languages from settings.
     * @param string $currentLanguage Current language code.
     * @return string The generated hreflang meta tags.
     */
    private function generateHreflangTags(array|object|null $supportedLanguages, string $currentLanguage): string
    {
        // Convert stdClass to array if needed (SettingsManager may return stdClass)
        if ($supportedLanguages instanceof \stdClass) {
            $supportedLanguages = (array) $supportedLanguages;
        }
        
        $hreflangTags = '';
        $defaultLanguage = WordpressHelpers::getDefaultLanguage();
        $defaultUrl = null;

        // Try to get actual translated URLs from WPML/Polylang
        $translatedUrls = WordpressHelpers::getTranslatedUrls($this->contentId);

        if (!empty($translatedUrls) && count($translatedUrls) > 1) {
            // We have translations from WPML or Polylang
            foreach ($translatedUrls as $langCode => $url) {
                $hreflangTags .= "\r\n\t" . '<link rel="alternate" hreflang="' . esc_attr($langCode) . '" href="' . esc_url($url) . '" />';
                
                // Track the default language URL for x-default
                if ($langCode === $defaultLanguage) {
                    $defaultUrl = $url;
                }
            }

            // Add x-default pointing to the default language URL
            if ($defaultUrl) {
                $hreflangTags .= "\r\n\t" . '<link rel="alternate" hreflang="x-default" href="' . esc_url($defaultUrl) . '" />';
            } else {
                // Fallback to first URL if default language not found
                $firstUrl = reset($translatedUrls);
                $hreflangTags .= "\r\n\t" . '<link rel="alternate" hreflang="x-default" href="' . esc_url($firstUrl) . '" />';
            }
        } elseif ($supportedLanguages && is_array($supportedLanguages) && count($supportedLanguages) > 1) {
            // Fallback to supported languages from settings (without actual translations)
            // Note: This outputs same URL for all languages when no translation plugin is active
            $currentUrl = get_permalink($this->contentId);
            
            foreach ($supportedLanguages as $language => $langName) {
                $hreflangTags .= "\r\n\t" . '<link rel="alternate" hreflang="' . esc_attr($language) . '" href="' . esc_url($currentUrl) . '" />';
            }
            $hreflangTags .= "\r\n\t" . '<link rel="alternate" hreflang="x-default" href="' . esc_url($currentUrl) . '" />';
        } else {
            // Single language site - output current language and x-default
            $currentUrl = get_permalink($this->contentId);
            $hreflangTags .= "\r\n\t" . '<link rel="alternate" hreflang="' . esc_attr($currentLanguage) . '" href="' . esc_url($currentUrl) . '" />';
            $hreflangTags .= "\r\n\t" . '<link rel="alternate" hreflang="x-default" href="' . esc_url($currentUrl) . '" />';
        }

        return $hreflangTags;
    }

    /**
     * Generates pagination meta tags (rel="prev" and rel="next").
     * Supports archive pages, search results, and paginated single posts.
     *
     * @return string The generated pagination meta tags.
     */
    private function generatePaginationMetaTags(): string
    {
        $paginationTags = '';
        
        $paginationLinks = WordpressHelpers::getPaginationLinks();
        
        if (!empty($paginationLinks['prev'])) {
            $paginationTags .= '<link rel="prev" href="' . esc_url($paginationLinks['prev']) . '" />' . "\r\n\t";
        }
        
        if (!empty($paginationLinks['next'])) {
            $paginationTags .= '<link rel="next" href="' . esc_url($paginationLinks['next']) . '" />' . "\r\n\t";
        }
        
        return $paginationTags;
    }

    /**
     * Retrieves the priority of the meta tags.
     * @return int
     */
    public function getMetaTagsPriority(): int {
        return 5;
    }
}
