<?php

namespace WPSN\Modules\WooCommerce;

use WPSN\Core\AbstractModule;
use WPSN\Domain\SeoDocumentContext;
use WPSN\Domain\SeoMeta;

defined('ABSPATH') || exit;

/**
 * WooCommerce SEO integration.
 *
 * This module is designed to be safe on sites without WooCommerce: it will no-op
 * when WooCommerce is not active.
 */
final class WooCommerceModule extends AbstractModule
{
    private const QV_MERCHANT_FEED = 'wpsn_merchant_feed';

    /**
     * Cache for settings-driven canonical query args stripping.
     *
     * @var array<int,string>|null
     */
    private ?array $canonicalExtraStripKeys = null;

    public function id(): string
    {
        return 'woocommerce';
    }

    public function name(): string
    {
        return __('WooCommerce', 'wp-seo-next');
    }

    public function description(): string
    {
        return __('WooCommerce SEO: Product schema, product OpenGraph tags, and structured data compatibility.', 'wp-seo-next');
    }

    public function defaultEnabled(): bool
    {
        // Safe default: enabled by default, but will no-op if WooCommerce isn’t active.
        return true;
    }

    public function register(): void
    {
        // If WooCommerce is not active, do nothing.
        if (!class_exists('WooCommerce') || !function_exists('WC')) {
            return;
        }

        // Merchant feed endpoint.
        add_action('init', [$this, 'registerMerchantFeedRewriteRules']);
        add_filter('query_vars', [$this, 'addMerchantFeedQueryVars']);
        add_action('template_redirect', [$this, 'handleMerchantFeedRequest'], 0);
        add_action('save_post_product', [$this, 'bustMerchantFeedCache'], 100, 3);

        // Disable WooCommerce native structured data to avoid duplicates (optional).
        add_action('wp', [$this, 'maybeDisableWooStructuredData'], 1);

        // Social integration.
        add_filter('wpsn/social/og_type', [$this, 'filterOgType'], 10, 3);
        add_action('wpsn/social/after', [$this, 'renderProductOpenGraph'], 10, 2);

        // Template variables (optional, for advanced templates).
        add_filter('wpsn/template_variables', [$this, 'addTemplateVariables'], 10, 2);

        // Canonical/robots helpers for WooCommerce filter URLs (layered nav, sorting, price filters, etc.).
        add_filter('wpsn/meta/canonical', [$this, 'filterCanonical'], 20, 2);
        add_filter('wpsn/meta/robots', [$this, 'filterRobots'], 20, 2);
    }

    /**
     * Strip layered navigation / variation query args from canonical URLs.
     */
    public function filterCanonical(string $canonical, SeoDocumentContext $ctx): string
    {
        $enabled = (bool) $this->settings->get('woocommerce.canonical.strip_filter_args', true);
        if (!$enabled || $canonical === '') {
            return $canonical;
        }

        if (!$this->isWooContext($ctx)) {
            return $canonical;
        }

        $parts = wp_parse_url($canonical);
        if (!is_array($parts)) {
            return $canonical;
        }

        $query = [];
        if (isset($parts['query']) && is_string($parts['query']) && $parts['query'] !== '') {
            parse_str($parts['query'], $query);
        }

        if (!is_array($query) || empty($query)) {
            return $canonical;
        }

        $clean = $this->stripWooQueryArgs($query);
        if ($clean === $query) {
            return $canonical;
        }

        // Rebuild.
        $scheme = isset($parts['scheme']) ? (string) $parts['scheme'] : '';
        $host = isset($parts['host']) ? (string) $parts['host'] : '';
        $port = isset($parts['port']) ? ':' . (string) ((int) $parts['port']) : '';
        $path = isset($parts['path']) ? (string) $parts['path'] : '';
        $fragment = isset($parts['fragment']) ? (string) $parts['fragment'] : '';

        $base = $canonical;
        if ($scheme !== '' && $host !== '') {
            $base = $scheme . '://' . $host . $port . $path;
        }

        $qs = '';
        if (!empty($clean)) {
            $qs = http_build_query($clean, '', '&', PHP_QUERY_RFC3986);
        }
        if ($qs !== '') {
            $base .= '?' . $qs;
        }
        if ($fragment !== '') {
            $base .= '#' . $fragment;
        }

        $base = esc_url_raw($base);
        return $base !== '' ? $base : $canonical;
    }

    /**
     * Optionally noindex filtered shop/category pages.
     *
     * @param array<string,mixed> $robots
     * @return array<string,mixed>
     */
    public function filterRobots(array $robots, SeoDocumentContext $ctx): array
    {
        $enabled = (bool) $this->settings->get('woocommerce.canonical.noindex_filtered_archives', false);
        if (!$enabled) {
            return $robots;
        }

        // Only apply on WooCommerce archive-like contexts.
        if ($ctx->type === SeoDocumentContext::TYPE_SINGULAR) {
            return $robots;
        }
        if (!$this->isWooArchiveContext($ctx)) {
            return $robots;
        }

        if (!$this->hasWooFilterArgs()) {
            return $robots;
        }

        $robots['noindex'] = true;
        unset($robots['index']);

        return $robots;
    }

    private function isWooContext(SeoDocumentContext $ctx): bool
    {
        // Product pages.
        if ($ctx->type === SeoDocumentContext::TYPE_SINGULAR && (string) ($ctx->objectSubType ?? '') === 'product') {
            return true;
        }

        // Product taxonomies (categories/tags/attributes).
        if ($ctx->type === SeoDocumentContext::TYPE_TERM) {
            $tax = (string) ($ctx->objectSubType ?? '');
            if (in_array($tax, ['product_cat', 'product_tag'], true)) {
                return true;
            }
            if ($tax !== '' && strpos($tax, 'pa_') === 0) {
                return true;
            }
        }

        // Shop / product archives.
        if (function_exists('is_shop') && is_shop()) {
            return true;
        }
        if (function_exists('is_product_taxonomy') && is_product_taxonomy()) {
            return true;
        }
        if (function_exists('is_post_type_archive') && is_post_type_archive('product')) {
            return true;
        }

        return false;
    }

    private function isWooArchiveContext(SeoDocumentContext $ctx): bool
    {
        if ($ctx->type === SeoDocumentContext::TYPE_TERM) {
            $tax = (string) ($ctx->objectSubType ?? '');
            if (in_array($tax, ['product_cat', 'product_tag'], true)) {
                return true;
            }
            if ($tax !== '' && strpos($tax, 'pa_') === 0) {
                return true;
            }
        }

        if ($ctx->type === SeoDocumentContext::TYPE_ARCHIVE) {
            if (function_exists('is_shop') && is_shop()) {
                return true;
            }
            if (function_exists('is_post_type_archive') && is_post_type_archive('product')) {
                return true;
            }
        }

        if (function_exists('is_product_taxonomy') && is_product_taxonomy()) {
            return true;
        }

        return false;
    }

    private function hasWooFilterArgs(): bool
    {
        if (empty($_GET) || !is_array($_GET)) {
            return false;
        }

        foreach (array_keys($_GET) as $k) {
            if (!is_string($k)) {
                continue;
            }
            $key = sanitize_key($k);
            if ($key === '') {
                continue;
            }
            if ($this->isWooStripKey($key)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array<string,mixed> $query
     * @return array<string,mixed>
     */
    private function stripWooQueryArgs(array $query): array
    {
        $out = $query;
        foreach (array_keys($out) as $k) {
            $key = is_string($k) ? sanitize_key($k) : '';
            if ($key === '') {
                continue;
            }
            if ($this->isWooStripKey($key)) {
                unset($out[$k]);
            }
        }
        return $out;
    }

    private function isWooStripKey(string $key): bool
    {
        // Common WooCommerce layered nav params:
        // - filter_color / query_type_color
        // - min_price / max_price
        // - orderby / rating_filter
        // Variations often add attribute_pa_size etc.
        if (strpos($key, 'filter_') === 0) {
            return true;
        }
        if (strpos($key, 'query_type_') === 0) {
            return true;
        }
        if (strpos($key, 'attribute_') === 0) {
            return true;
        }

        if (in_array($key, ['min_price', 'max_price', 'orderby', 'rating_filter', 'product-page', 'on_sale', 'instock', 'stock_status'], true)) {
            return true;
        }

        // Cart/variation params (should never affect canonical).
        if (in_array($key, ['add-to-cart', 'quantity', 'variation_id', 'product_id'], true)) {
            return true;
        }

        // Extra keys from Settings (comma-separated).
        if (in_array($key, $this->getCanonicalExtraStripKeys(), true)) {
            return true;
        }

        return false;
    }

    /**
     * @return array<int,string>
     */
    private function getCanonicalExtraStripKeys(): array
    {
        if (is_array($this->canonicalExtraStripKeys)) {
            return $this->canonicalExtraStripKeys;
        }

        $extra = (string) $this->settings->get('woocommerce.canonical.extra_strip_args', '');
        $extra = trim($extra);
        if ($extra === '') {
            $this->canonicalExtraStripKeys = [];
            return $this->canonicalExtraStripKeys;
        }

        $parts = preg_split('/\s*,\s*/', $extra);
        if (!is_array($parts) || empty($parts)) {
            $this->canonicalExtraStripKeys = [];
            return $this->canonicalExtraStripKeys;
        }

        $keys = [];
        foreach ($parts as $p) {
            if (!is_string($p)) {
                continue;
            }
            $p = sanitize_key($p);
            if ($p !== '') {
                $keys[] = $p;
            }
        }

        $keys = array_values(array_unique($keys));
        $this->canonicalExtraStripKeys = $keys;

        return $this->canonicalExtraStripKeys;
    }

    public function registerMerchantFeedRewriteRules(): void
    {
        // /merchant-feed.xml
        add_rewrite_rule('^merchant-feed\\.xml$', 'index.php?' . self::QV_MERCHANT_FEED . '=1', 'top');
    }

    /**
     * @param array<int,string> $vars
     * @return array<int,string>
     */
    public function addMerchantFeedQueryVars(array $vars): array
    {
        $vars[] = self::QV_MERCHANT_FEED;
        return $vars;
    }

    /**
     * Merchant feed endpoint handler.
     */
    public function handleMerchantFeedRequest(): void
    {
        $on = (string) get_query_var(self::QV_MERCHANT_FEED);
        if ($on === '') {
            return;
        }

        // Prevent WP canonical redirects for virtual XML endpoints.
        remove_action('template_redirect', 'redirect_canonical');

        $enabled = (bool) $this->settings->get('woocommerce.merchant_feed.enabled', false);
        if (!$enabled) {
            $this->send404();
            exit;
        }

        $xml = $this->buildMerchantFeedXml();
        $this->sendXml($xml);
        exit;
    }

    /**
     * Bust feed cache token (future-proof; used as part of cache keys).
     *
     * @param int $postId
     * @param \WP_Post $post
     * @param bool $update
     */
    public function bustMerchantFeedCache(int $postId, \WP_Post $post, bool $update): void
    {
        if (wp_is_post_autosave($postId) || wp_is_post_revision($postId)) {
            return;
        }

        // Lightweight cache bust token (doesn't generate or store feed).
        update_option('wpsn_merchant_feed_cache_bust', (string) time(), false);
    }

    private function merchantFeedCacheBustToken(): string
    {
        $v = get_option('wpsn_merchant_feed_cache_bust', '1');
        $v = is_string($v) ? $v : '1';
        return $v !== '' ? $v : '1';
    }

    private function buildMerchantFeedXml(): string
    {
        $siteName = get_bloginfo('name');
        $siteName = is_string($siteName) ? trim($siteName) : '';
        if ($siteName === '') {
            $siteName = home_url('/');
        }

        $siteUrl = home_url('/');
        $siteUrl = is_string($siteUrl) ? $siteUrl : '';

        $desc = get_bloginfo('description');
        $desc = is_string($desc) ? trim($desc) : '';

        $currency = function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : '';
        $currency = trim($currency);

        $condition = (string) $this->settings->get('woocommerce.merchant_feed.condition', 'new');
        $condition = strtolower(trim($condition));
        if (!in_array($condition, ['new', 'used', 'refurbished'], true)) {
            $condition = 'new';
        }

        $includeVariations = (bool) $this->settings->get('woocommerce.merchant_feed.include_variations', false);
        $inStockOnly = (bool) $this->settings->get('woocommerce.merchant_feed.in_stock_only', false);
        $excludeHidden = (bool) $this->settings->get('woocommerce.merchant_feed.exclude_hidden', true);
        $perPage = (int) $this->settings->get('woocommerce.merchant_feed.posts_per_page', 200);
        if ($perPage < 10) {
            $perPage = 10;
        }
        if ($perPage > 2000) {
            $perPage = 2000;
        }

        $maxProducts = (int) $this->settings->get('woocommerce.merchant_feed.max_products', 0);
        if ($maxProducts < 0) {
            $maxProducts = 0;
        }

        // Cache for a short time (safe default). Avoid caching huge feeds on sites without object cache.
        $cacheTtl = (int) $this->settings->get('woocommerce.merchant_feed.cache_ttl_sec', 0);
        if ($cacheTtl < 0) {
            $cacheTtl = 0;
        }
        if ($cacheTtl > 6 * HOUR_IN_SECONDS) {
            $cacheTtl = 6 * HOUR_IN_SECONDS;
        }

        $cacheKey = '';
        if ($cacheTtl > 0) {
            $cacheKey = 'wpsn_merchant_feed_' . md5(serialize([
                'v' => $this->merchantFeedCacheBustToken(),
                'vars' => [$includeVariations, $inStockOnly, $excludeHidden, $perPage, $maxProducts, $condition, $currency],
            ]));
            $cached = get_transient($cacheKey);
            if (is_string($cached) && $cached !== '') {
                return $cached;
            }
        }

        $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        $xml .= '<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">' . "\n";
        $xml .= "  <channel>\n";
        $xml .= '    <title>' . esc_html($siteName) . "</title>\n";
        $xml .= '    <link>' . esc_html(esc_url($siteUrl)) . "</link>\n";
        if ($desc !== '') {
            $xml .= '    <description>' . esc_html($desc) . "</description>\n";
        }
        $xml .= '    <lastBuildDate>' . esc_html(gmdate('D, d M Y H:i:s +0000')) . "</lastBuildDate>\n";

        $added = 0;
        $page = 1;

        do {
            $q = new \WP_Query([
                'post_type'              => 'product',
                'post_status'            => 'publish',
                'fields'                 => 'ids',
                'posts_per_page'         => $perPage,
                'paged'                  => $page,
                'orderby'                => 'ID',
                'order'                  => 'ASC',
                'no_found_rows'          => true,
                'update_post_meta_cache' => false,
                'update_post_term_cache' => false,
                'suppress_filters'       => true,
            ]);

            $ids = is_array($q->posts) ? $q->posts : [];
            if (empty($ids)) {
                break;
            }

            foreach ($ids as $id) {
                $pid = (int) $id;
                if ($pid < 1) {
                    continue;
                }

                // Respect noindex override.
                $r = (string) get_post_meta($pid, \WPSN\SEO\MetaKeys::ROBOTS_INDEX, true);
                if ($r === 'noindex') {
                    continue;
                }

                $product = function_exists('wc_get_product') ? wc_get_product($pid) : null;
                if (!$product || !is_object($product)) {
                    continue;
                }

                if ($excludeHidden && method_exists($product, 'get_catalog_visibility')) {
                    $vis = (string) $product->get_catalog_visibility();
                    if ($vis === 'hidden') {
                        continue;
                    }
                }

                if ($inStockOnly && method_exists($product, 'is_in_stock') && !$product->is_in_stock()) {
                    continue;
                }

                // Variables.
                if ($includeVariations && method_exists($product, 'is_type') && $product->is_type('variable') && method_exists($product, 'get_children')) {
                    $children = $product->get_children();
                    if (is_array($children) && !empty($children)) {
                        foreach ($children as $vid) {
                            $vid = (int) $vid;
                            if ($vid < 1) {
                                continue;
                            }
                            $variation = wc_get_product($vid);
                            if (!$variation || !is_object($variation)) {
                                continue;
                            }
                            if (method_exists($variation, 'exists') && !$variation->exists()) {
                                continue;
                            }
                            if (method_exists($variation, 'get_status') && (string) $variation->get_status() !== 'publish') {
                                continue;
                            }
                            if ($inStockOnly && method_exists($variation, 'is_in_stock') && !$variation->is_in_stock()) {
                                continue;
                            }
                            $row = $this->buildMerchantItemXml($variation, $currency, $condition, $pid);
                            if ($row !== '') {
                                $xml .= $row;
                                $added++;
                                if ($maxProducts > 0 && $added >= $maxProducts) {
                                    break 3;
                                }
                            }
                        }
                        continue;
                    }
                }

                $row = $this->buildMerchantItemXml($product, $currency, $condition, 0);
                if ($row !== '') {
                    $xml .= $row;
                    $added++;
                    if ($maxProducts > 0 && $added >= $maxProducts) {
                        break 2;
                    }
                }
            }

            $page++;
        } while (true);

        $xml .= "  </channel>\n";
        $xml .= "</rss>\n";

        if ($cacheTtl > 0 && $cacheKey !== '') {
            set_transient($cacheKey, $xml, $cacheTtl);
        }

        return $xml;
    }

    /**
     * @param \WC_Product $product
     */
    private function buildMerchantItemXml($product, string $currency, string $condition, int $itemGroupId): string
    {
        if (!is_object($product)) {
            return '';
        }

        $id = method_exists($product, 'get_id') ? (int) $product->get_id() : 0;
        if ($id < 1) {
            return '';
        }

        $link = method_exists($product, 'get_permalink') ? (string) $product->get_permalink() : '';
        $link = $link !== '' ? esc_url_raw($link) : '';
        if ($link === '') {
            return '';
        }

        $title = method_exists($product, 'get_name') ? (string) $product->get_name() : '';
        $title = trim(wp_strip_all_tags($title));
        if ($title === '') {
            return '';
        }

        $desc = '';
        if (method_exists($product, 'get_description')) {
            $desc = (string) $product->get_description();
        }
        if ($desc === '' && method_exists($product, 'get_short_description')) {
            $desc = (string) $product->get_short_description();
        }
        $desc = wp_strip_all_tags($desc);
        $desc = trim(preg_replace('/\s+/u', ' ', $desc) ?? '');
        if ($desc === '') {
            $desc = $title;
        }
        // Keep it safely under Merchant Center limits.
        if (function_exists('mb_substr') && mb_strlen($desc) > 5000) {
            $desc = (string) mb_substr($desc, 0, 5000);
        } elseif (strlen($desc) > 5000) {
            $desc = substr($desc, 0, 5000);
        }

        $image = '';
        if (method_exists($product, 'get_image_id')) {
            $imgId = (int) $product->get_image_id();
            if ($imgId > 0) {
                $u = wp_get_attachment_url($imgId);
                if (is_string($u) && $u !== '') {
                    $image = esc_url_raw($u);
                }
            }
        }
        if ($image === '' && method_exists($product, 'get_gallery_image_ids')) {
            $g = $product->get_gallery_image_ids();
            if (is_array($g) && !empty($g)) {
                $u = wp_get_attachment_url((int) $g[0]);
                if (is_string($u) && $u !== '') {
                    $image = esc_url_raw($u);
                }
            }
        }
        if ($image === '') {
            // Merchant Center usually requires an image; we skip items without one.
            return '';
        }

        $availability = '';
        if (method_exists($product, 'is_in_stock')) {
            $availability = $product->is_in_stock() ? 'in stock' : 'out of stock';
        }

        // Prices.
        $price = '';
        $salePrice = '';
        if (method_exists($product, 'get_price')) {
            $p = $product->get_price();
            $price = is_string($p) ? trim($p) : (is_numeric($p) ? (string) $p : '');
        }
        if (method_exists($product, 'get_sale_price')) {
            $sp = $product->get_sale_price();
            $salePrice = is_string($sp) ? trim($sp) : (is_numeric($sp) ? (string) $sp : '');
        }

        if ($price === '' || !is_numeric($price)) {
            // Skip items without a numeric price.
            return '';
        }

        // ID preference: SKU if available.
        $sku = method_exists($product, 'get_sku') ? trim((string) $product->get_sku()) : '';
        $itemId = $sku !== '' ? $sku : (string) $id;

        $brand = $this->resolveProductBrand($id);
        if ($brand === '') {
            $brand = (string) get_bloginfo('name');
            $brand = is_string($brand) ? trim($brand) : '';
        }

        $productType = $this->resolveProductTypePath($id);

        $xml = "    <item>\n";
        $xml .= '      <g:id>' . esc_html($itemId) . "</g:id>\n";
        if ($itemGroupId > 0) {
            $xml .= '      <g:item_group_id>' . esc_html((string) $itemGroupId) . "</g:item_group_id>\n";
        }
        $xml .= '      <title>' . esc_html($title) . "</title>\n";
        $xml .= '      <description>' . esc_html($desc) . "</description>\n";
        $xml .= '      <link>' . esc_html(esc_url($link)) . "</link>\n";
        $xml .= '      <g:image_link>' . esc_html(esc_url($image)) . "</g:image_link>\n";

        if ($brand !== '') {
            $xml .= '      <g:brand>' . esc_html($brand) . "</g:brand>\n";
        }

        if ($productType !== '') {
            $xml .= '      <g:product_type>' . esc_html($productType) . "</g:product_type>\n";
        }

        if ($availability !== '') {
            $xml .= '      <g:availability>' . esc_html($availability) . "</g:availability>\n";
        }

        $xml .= '      <g:condition>' . esc_html($condition) . "</g:condition>\n";

        if ($currency !== '') {
            $xml .= '      <g:price>' . esc_html($price . ' ' . $currency) . "</g:price>\n";
            if ($salePrice !== '' && is_numeric($salePrice) && (float) $salePrice > 0 && (float) $salePrice < (float) $price) {
                $xml .= '      <g:sale_price>' . esc_html($salePrice . ' ' . $currency) . "</g:sale_price>\n";
            }
        } else {
            $xml .= '      <g:price>' . esc_html($price) . "</g:price>\n";
        }

        $xml .= "    </item>\n";
        return $xml;
    }

    private function resolveProductTypePath(int $productId): string
    {
        if (!taxonomy_exists('product_cat')) {
            return '';
        }
        $terms = get_the_terms($productId, 'product_cat');
        if (!is_array($terms) || empty($terms)) {
            return '';
        }

        // Pick deepest term and build path.
        $best = null;
        $bestDepth = -1;
        foreach ($terms as $t) {
            if (!$t instanceof \WP_Term) {
                continue;
            }
            $anc = get_ancestors($t->term_id, 'product_cat');
            $depth = is_array($anc) ? count($anc) : 0;
            if ($depth > $bestDepth) {
                $best = $t;
                $bestDepth = $depth;
            }
        }

        if (!$best instanceof \WP_Term) {
            return '';
        }

        $path = [];
        $anc = get_ancestors($best->term_id, 'product_cat');
        if (is_array($anc) && !empty($anc)) {
            $anc = array_reverse($anc);
            foreach ($anc as $aid) {
                $term = get_term((int) $aid, 'product_cat');
                if ($term instanceof \WP_Term) {
                    $path[] = (string) $term->name;
                }
            }
        }
        $path[] = (string) $best->name;

        $path = array_filter(array_map('trim', $path), static fn($s) => $s !== '');
        return implode(' > ', $path);
    }

    private function sendXml(string $xml): void
    {
        nocache_headers();
        header('Content-Type: application/xml; charset=UTF-8');
        echo $xml;
    }

    private function send404(): void
    {
        status_header(404);
        nocache_headers();
        header('Content-Type: text/plain; charset=UTF-8');
        echo 'Not found';
    }

    public function maybeDisableWooStructuredData(): void
    {
        $disable = (bool) $this->settings->get('woocommerce.schema.disable_wc_structured_data', true);
        if (!$disable) {
            return;
        }

        if (!function_exists('WC')) {
            return;
        }

        $wc = WC();
        if (!$wc || !is_object($wc)) {
            return;
        }

        // WooCommerce stores its structured data service on WC()->structured_data.
        if (isset($wc->structured_data) && is_object($wc->structured_data)) {
            // Common hooks used by WooCommerce (version dependent). Remove both head/footer attempts.
            remove_action('wp_footer', [$wc->structured_data, 'output_structured_data'], 10);
            remove_action('wp_head', [$wc->structured_data, 'output_structured_data'], 10);
        }
    }

    /**
     * Filter OpenGraph og:type.
     */
    public function filterOgType(string $ogType, SeoDocumentContext $ctx, SeoMeta $meta): string
    {
        $enabled = (bool) $this->settings->get('woocommerce.social.set_og_type_product', true);
        if (!$enabled) {
            return $ogType;
        }

        if ($this->isProductContext($ctx)) {
            return 'product';
        }

        return $ogType;
    }

    /**
     * Output OpenGraph product meta tags (price/currency/availability) for product pages.
     */
    public function renderProductOpenGraph(SeoDocumentContext $ctx, SeoMeta $meta): void
    {
        if (is_admin() || is_feed()) {
            return;
        }

        $enabled = (bool) $this->settings->get('woocommerce.social.enabled', true);
        if (!$enabled) {
            return;
        }

        if (!$this->isProductContext($ctx)) {
            return;
        }

        if (!function_exists('wc_get_product')) {
            return;
        }

        $product = wc_get_product((int) $ctx->objectId);
        if (!$product || !is_object($product)) {
            return;
        }

        $includePrice = (bool) $this->settings->get('woocommerce.social.include_price', true);
        $includeAvailability = (bool) $this->settings->get('woocommerce.social.include_availability', true);

        $currency = function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : '';

        $price = '';
        if ($includePrice) {
            // For variable products, expose the lowest variation price.
            if (method_exists($product, 'is_type') && $product->is_type('variable') && method_exists($product, 'get_variation_price')) {
                $p = $product->get_variation_price('min', true);
                $price = is_numeric($p) ? (string) $p : (string) $p;
            } elseif (method_exists($product, 'get_price')) {
                $p = $product->get_price();
                $price = is_string($p) ? trim($p) : (is_numeric($p) ? (string) $p : '');
            }
        }

        $availability = '';
        if ($includeAvailability && method_exists($product, 'is_in_stock')) {
            $availability = $product->is_in_stock() ? 'in stock' : 'out of stock';
        }

        // Output meta tags.
        echo "\n";
        if ($includePrice && $price !== '') {
            echo '<meta property="product:price:amount" content="' . esc_attr($price) . '" />' . "\n";
            if ($currency !== '') {
                echo '<meta property="product:price:currency" content="' . esc_attr($currency) . '" />' . "\n";
            }
        }
        if ($includeAvailability && $availability !== '') {
            echo '<meta property="product:availability" content="' . esc_attr($availability) . '" />' . "\n";
        }
    }

    /**
     * Add WooCommerce template variables.
     *
     * Available vars:
     *  - %product_price%
     *  - %product_currency%
     *  - %product_sku%
     *  - %product_brand%
     *  - %product_category%
     *
     * @param array<string,string> $vars
     * @return array<string,string>
     */
    public function addTemplateVariables(array $vars, SeoDocumentContext $ctx): array
    {
        if (!$this->isProductContext($ctx)) {
            return $vars;
        }

        if (!function_exists('wc_get_product')) {
            return $vars;
        }

        $product = wc_get_product((int) $ctx->objectId);
        if (!$product || !is_object($product)) {
            return $vars;
        }

        $currency = function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : '';
        $vars['product_currency'] = $currency;

        $price = '';
        if (method_exists($product, 'is_type') && $product->is_type('variable') && method_exists($product, 'get_variation_price')) {
            $p = $product->get_variation_price('min', true);
            $price = is_numeric($p) ? (string) $p : (string) $p;
        } elseif (method_exists($product, 'get_price')) {
            $p = $product->get_price();
            $price = is_string($p) ? trim($p) : (is_numeric($p) ? (string) $p : '');
        }
        $vars['product_price'] = $price;

        $sku = method_exists($product, 'get_sku') ? (string) $product->get_sku() : '';
        $vars['product_sku'] = trim($sku);

        // Category (deepest product_cat).
        $vars['product_category'] = $this->resolveDeepestProductCategoryName((int) $ctx->objectId);

        // Brand (best-effort): product_brand taxonomy or pa_brand attribute taxonomy.
        $vars['product_brand'] = $this->resolveProductBrand((int) $ctx->objectId);

        return $vars;
    }

    private function isProductContext(SeoDocumentContext $ctx): bool
    {
        return $ctx->isSingular() && ((string) $ctx->objectSubType === 'product');
    }

    private function resolveDeepestProductCategoryName(int $productId): string
    {
        if (!taxonomy_exists('product_cat')) {
            return '';
        }

        $terms = get_the_terms($productId, 'product_cat');
        if (!is_array($terms) || empty($terms)) {
            return '';
        }

        $best = null;
        $bestDepth = -1;
        foreach ($terms as $t) {
            if (!$t instanceof \WP_Term) {
                continue;
            }
            $anc = get_ancestors($t->term_id, 'product_cat');
            $depth = is_array($anc) ? count($anc) : 0;
            if ($depth > $bestDepth) {
                $best = $t;
                $bestDepth = $depth;
            }
        }

        return $best instanceof \WP_Term ? (string) $best->name : '';
    }

    private function resolveProductBrand(int $productId): string
    {
        $candidates = [];
        if (taxonomy_exists('product_brand')) {
            $candidates[] = 'product_brand';
        }
        if (taxonomy_exists('pa_brand')) {
            $candidates[] = 'pa_brand';
        }

        foreach ($candidates as $tax) {
            $terms = get_the_terms($productId, $tax);
            if (!is_array($terms) || empty($terms)) {
                continue;
            }
            foreach ($terms as $t) {
                if ($t instanceof \WP_Term && $t->name !== '') {
                    return (string) $t->name;
                }
            }
        }

        return '';
    }
}
