<?php
if (!defined('ABSPATH')) exit;

/**
 * Google Search & Bing rich-result oriented validation and enrichment hints.
 *
 * @see https://developers.google.com/search/docs/appearance/structured-data
 */
class AISB_Rich_Results {

    /** Required / recommended fields per primary @type (Google rich results). */
    public static function field_rules() {
        return [
            'Article' => [
                'required'    => ['headline', 'image', 'datePublished', 'author'],
                'recommended' => ['dateModified', 'publisher', 'description', 'mainEntityOfPage'],
            ],
            'BlogPosting' => [
                'required'    => ['headline', 'image', 'datePublished', 'author'],
                'recommended' => ['dateModified', 'publisher', 'description'],
            ],
            'WebPage' => [
                'required'    => ['name'],
                'recommended' => ['description', 'url', 'primaryImageOfPage', 'dateModified'],
            ],
            'Product' => [
                'required'    => ['name', 'image', 'offers'],
                'recommended' => ['description', 'sku', 'brand', 'aggregateRating', 'review'],
            ],
            'FAQPage' => [
                'required'    => ['mainEntity'],
                'recommended' => [],
            ],
            'LocalBusiness' => [
                'required'    => ['name', 'address'],
                'recommended' => ['image', 'telephone', 'openingHoursSpecification', 'geo', 'url'],
            ],
            'Organization' => [
                'required'    => ['name'],
                'recommended' => ['url', 'logo', 'sameAs', 'contactPoint'],
            ],
            'Event' => [
                'required'    => ['name', 'startDate', 'location'],
                'recommended' => ['description', 'image', 'endDate', 'offers', 'performer'],
            ],
            'HowTo' => [
                'required'    => ['name', 'step'],
                'recommended' => ['image', 'totalTime', 'supply', 'tool'],
            ],
            'Recipe' => [
                'required'    => ['name', 'image', 'recipeIngredient'],
                'recommended' => ['recipeInstructions', 'prepTime', 'cookTime', 'nutrition'],
            ],
            'Course' => [
                'required'    => ['name', 'description', 'provider'],
                'recommended' => ['offers'],
            ],
            'JobPosting' => [
                'required'    => ['title', 'description', 'datePosted', 'hiringOrganization', 'jobLocation'],
                'recommended' => ['employmentType', 'baseSalary', 'validThrough'],
            ],
            'BreadcrumbList' => [
                'required'    => ['itemListElement'],
                'recommended' => [],
            ],
            'WebSite' => [
                'required'    => ['name', 'url'],
                'recommended' => ['potentialAction'],
            ],
        ];
    }

    public static function primary_type_public($schema) {
        return self::primary_type($schema);
    }

    public static function validate($schema) {
        $errors = [];
        $warnings = [];
        $type = self::primary_type($schema);

        if (!$type) {
            $errors[] = __('Missing @type', 'pewit-ai-schema-builder');
            return compact('errors', 'warnings', 'type');
        }

        if (!self::is_allowed_type($type)) {
            $warnings[] = sprintf(
                /* translators: %s: schema.org type */
                /* translators: %s are replaced with dynamic values */ __('Type %s is valid JSON-LD but may not support Google rich results.', 'pewit-ai-schema-builder'),
                $type
            );
        }

        $rules = self::field_rules();
        $rule = $rules[$type] ?? null;
        if ($rule) {
            foreach ($rule['required'] as $field) {
                if (!self::has_field($schema, $field)) {
                    $errors[] = sprintf(
                        /* translators: 1: field name, 2: schema type */
                        /* translators: %1$s, %2$s are replaced with dynamic values */ __('Missing required field "%1$s" for %2$s (Google/Bing).', 'pewit-ai-schema-builder'),
                        $field,
                        $type
                    );
                }
            }
            foreach ($rule['recommended'] as $field) {
                if (!self::has_field($schema, $field)) {
                    $warnings[] = sprintf(
                        /* translators: %1$s, %2$s are replaced with dynamic values */ __('Recommended: %1$s for %2$s rich results.', 'pewit-ai-schema-builder'),
                        $field,
                        $type
                    );
                }
            }
        }

        self::validate_type_specific($schema, $type, $errors, $warnings);

        return [
            'errors'   => $errors,
            'warnings' => $warnings,
            'type'     => $type,
            'google'   => empty($errors),
            'bing'     => empty($errors),
        ];
    }

    private static function primary_type($schema) {
        if (!empty($schema['@type'])) {
            return is_array($schema['@type']) ? ($schema['@type'][0] ?? '') : $schema['@type'];
        }
        if (!empty($schema['@graph']) && is_array($schema['@graph'])) {
            foreach ($schema['@graph'] as $node) {
                if (!empty($node['@type'])) {
                    return is_array($node['@type']) ? ($node['@type'][0] ?? '') : $node['@type'];
                }
            }
        }
        return '';
    }

    private static function is_allowed_type($type) {
        return isset(self::field_rules()[$type]);
    }

    private static function has_field($schema, $field) {
        if (isset($schema[$field]) && $schema[$field] !== '' && $schema[$field] !== []) {
            return true;
        }
        if ($field === 'author' && !empty($schema['author'])) {
            return true;
        }
        if ($field === 'offers' && !empty($schema['offers'])) {
            return true;
        }
        if ($field === 'mainEntity' && !empty($schema['mainEntity'])) {
            return true;
        }
        if ($field === 'step' && !empty($schema['step'])) {
            return true;
        }
        if ($field === 'recipeIngredient' && !empty($schema['recipeIngredient'])) {
            return true;
        }
        if ($field === 'itemListElement' && !empty($schema['itemListElement'])) {
            return true;
        }
        return false;
    }

    private static function validate_type_specific($schema, $type, &$errors, &$warnings) {
        if ($type === 'FAQPage' && !empty($schema['mainEntity'])) {
            foreach ((array) $schema['mainEntity'] as $i => $q) {
                if (empty($q['@type']) || $q['@type'] !== 'Question') {
                    $warnings[] = sprintf(/* translators: %d are replaced with dynamic values */ __('FAQ item %d should be Question type.', 'pewit-ai-schema-builder'), $i + 1);
                }
                if (empty($q['acceptedAnswer']['text'])) {
                    $errors[] = sprintf(/* translators: %d are replaced with dynamic values */ __('FAQ item %d missing acceptedAnswer.text.', 'pewit-ai-schema-builder'), $i + 1);
                }
            }
        }
        if ($type === 'Product' && !empty($schema['offers'])) {
            $offers = $schema['offers'];
            if (isset($offers['@type']) && $offers['@type'] === 'Offer') {
                if (empty($offers['price']) && empty($offers['priceSpecification'])) {
                    $warnings[] = __('Product Offer: add price for merchant listings.', 'pewit-ai-schema-builder');
                }
            }
        }
        if (in_array($type, ['Article', 'BlogPosting'], true)) {
            if (!empty($schema['image']) && is_string($schema['image'])) {
                // OK
            } elseif (empty($schema['image'])) {
                $errors[] = __('Article image must be a URL or ImageObject (min 1200px wide recommended).', 'pewit-ai-schema-builder');
            }
        }
    }

    /** Enrich schema with WP post data for Google/Bing compliance. */
    public static function enrich_from_post($schema, $post_id) {
        $post = get_post($post_id);
        if (!$post) {
            return $schema;
        }
        $type = self::primary_type($schema);
        $url = get_permalink($post_id);
        $thumb = get_the_post_thumbnail_url($post_id, 'full');

        if (in_array($type, ['Article', 'BlogPosting', 'WebPage', 'AboutPage', 'ContactPage'], true)) {
            if (empty($schema['headline']) && empty($schema['name'])) {
                $schema['headline'] = get_the_title($post_id);
            }
            if (empty($schema['description'])) {
                $schema['description'] = wp_trim_words(wp_strip_all_tags($post->post_excerpt ?: $post->post_content), 40);
            }
            if (empty($schema['datePublished'])) {
                $schema['datePublished'] = get_the_date('c', $post_id);
            }
            if (empty($schema['dateModified'])) {
                $schema['dateModified'] = get_the_modified_date('c', $post_id);
            }
            if (empty($schema['author'])) {
                $schema['author'] = [
                    '@type' => 'Person',
                    'name'  => get_the_author_meta('display_name', $post->post_author),
                ];
            }
            if (empty($schema['image']) && $thumb) {
                $schema['image'] = $thumb;
            }
            if (empty($schema['mainEntityOfPage'])) {
                $schema['mainEntityOfPage'] = ['@type' => 'WebPage', '@id' => $url];
            }
            if (empty($schema['publisher'])) {
                $schema['publisher'] = self::site_organization();
            }
        }

        if ($type === 'Product' && class_exists('WooCommerce')) {
            $product = wc_get_product($post_id);
            if ($product) {
                if (empty($schema['name'])) {
                    $schema['name'] = $product->get_name();
                }
                if ($product->is_type('variable')) {
                    $schema = self::enrich_variable_product($schema, $product, $url);
                } elseif (empty($schema['offers'])) {
                    $schema['offers'] = [
                        '@type'         => 'Offer',
                        'price'         => $product->get_price(),
                        'priceCurrency' => get_woocommerce_currency(),
                        'availability'  => $product->is_in_stock()
                            ? 'https://schema.org/InStock'
                            : 'https://schema.org/OutOfStock',
                        'url'           => $url,
                    ];
                }
                if (empty($schema['sku'])) {
                    $schema['sku'] = $product->get_sku();
                }
            }
        }

        if (empty($schema['@context'])) {
            $schema['@context'] = 'https://schema.org';
        }

        $schema = AISB_Field_Mapper::apply_to_schema($schema, $post_id, $type);

        return apply_filters('aisb_enriched_schema', $schema, $post_id);
    }

    private static function enrich_variable_product($schema, $product, $url) {
        if (!method_exists($product, 'get_available_variations')) {
            return $schema;
        }
        $offers = [];
        $currency = get_woocommerce_currency();
        foreach ($product->get_available_variations() as $variation) {
            $var = wc_get_product($variation['variation_id'] ?? 0);
            if (!$var) {
                continue;
            }
            $offers[] = [
                '@type'         => 'Offer',
                'sku'           => $var->get_sku() ?: ($variation['sku'] ?? ''),
                'price'         => $var->get_price(),
                'priceCurrency' => $currency,
                'availability'  => $var->is_in_stock()
                    ? 'https://schema.org/InStock'
                    : 'https://schema.org/OutOfStock',
                'url'           => $url,
                'name'          => $var->get_name(),
            ];
        }
        if (count($offers) === 1) {
            $schema['offers'] = $offers[0];
        } elseif (count($offers) > 1) {
            $schema['offers'] = $offers;
            if (empty($schema['sku'])) {
                $schema['sku'] = $product->get_sku();
            }
        } elseif (empty($schema['offers'])) {
            $schema['offers'] = [
                '@type'         => 'AggregateOffer',
                'lowPrice'      => $product->get_variation_price('min'),
                'highPrice'     => $product->get_variation_price('max'),
                'priceCurrency' => $currency,
                'offerCount'    => count($product->get_children()),
                'url'           => $url,
            ];
        }
        return $schema;
    }

    public static function site_organization() {
        $name = get_bloginfo('name');
        $logo = '';
        $custom_logo_id = get_theme_mod('custom_logo');
        if ($custom_logo_id) {
            $logo = wp_get_attachment_image_url($custom_logo_id, 'full');
        }
        $org = [
            '@type' => 'Organization',
            'name'  => $name,
            'url'   => home_url('/'),
        ];
        if ($logo) {
            $org['logo'] = ['@type' => 'ImageObject', 'url' => $logo];
        }
        return $org;
    }

    public static function build_global_graph() {
        $s = aisb_get_settings();
        if (empty($s['global_schema_enabled'])) {
            return null;
        }
        $site_id = home_url('/#website');
        $org_id = home_url('/#organization');
        $org = self::site_organization();
        $org['@id'] = $org_id;

        $graph = [
            '@context' => 'https://schema.org',
            '@graph'    => [
                [
                    '@type' => 'WebSite',
                    '@id'   => $site_id,
                    'url'   => home_url('/'),
                    'name'  => get_bloginfo('name'),
                    'publisher' => ['@id' => $org_id],
                    'potentialAction' => [
                        '@type'       => 'SearchAction',
                        'target'      => [
                            '@type'       => 'EntryPoint',
                            'urlTemplate' => home_url('/?s={search_term_string}'),
                        ],
                        'query-input' => 'required name=search_term_string',
                    ],
                ],
                $org,
            ],
        ];
        return apply_filters('aisb_global_schema_graph', $graph);
    }

    public static function build_breadcrumbs($post_id) {
        $s = aisb_get_settings();
        if (empty($s['breadcrumb_enabled'])) {
            return null;
        }
        $items = [];
        $pos = 1;
        $items[] = [
            '@type'    => 'ListItem',
            'position' => $pos++,
            'name'     => __('Home', 'pewit-ai-schema-builder'),
            'item'     => home_url('/'),
        ];
        $ancestors = array_reverse(get_post_ancestors($post_id));
        foreach ($ancestors as $aid) {
            $items[] = [
                '@type'    => 'ListItem',
                'position' => $pos++,
                'name'     => get_the_title($aid),
                'item'     => get_permalink($aid),
            ];
        }
        $items[] = [
            '@type'    => 'ListItem',
            'position' => $pos,
            'name'     => get_the_title($post_id),
            'item'     => get_permalink($post_id),
        ];
        return [
            '@context'        => 'https://schema.org',
            '@type'           => 'BreadcrumbList',
            'itemListElement' => $items,
        ];
    }

    public static function test_urls() {
        return [
            'google' => 'https://search.google.com/test/rich-results',
            'bing'   => 'https://www.bing.com/webmasters/markup-validator',
        ];
    }
}
