<?php

/**
 * Schema Generator
 * Generates JSON-LD schema markup from configurations
 * 
 * @package ProRank\SEO\Core\Schema
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\Schema;

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




class SchemaGenerator {

    /**
     * Schema 2025 Types handler
     *
     * @var Schema2025Types
     */
    private $schema_2025;

    /**
     * Constructor
     */
    public function __construct() {
        $this->schema_2025 = new Schema2025Types();
    }

    /**
     * Generate schema for a post
     */
    public function generate_for_post($post_id, $schema_configs = []) {
        $post = get_post($post_id);
        if (!$post) {
            return null;
        }
        
        // Find applicable schema configurations
        $applicable_schemas = $this->find_applicable_schemas($post, $schema_configs);
        
        if (empty($applicable_schemas)) {
            return null;
        }
        
        $schemas = [];
        foreach ($applicable_schemas as $config) {
            $schema = $this->generate_schema($post, $config);
            if ($schema) {
                $schemas[] = $schema;
            }
        }
        
        return $schemas;
    }
    
    /**
     * Generate preview of schema
     */
    public function generate_preview($post_id, $schema_data) {
        $post = get_post($post_id);
        if (!$post) {
            return null;
        }
        
        return $this->generate_schema($post, [
            'type' => $schema_data['type'] ?? 'Article',
            'mapping' => $schema_data['mapping'] ?? []
        ]);
    }
    
    /**
     * Generate schema from configuration
     */
    private function generate_schema($post, $config) {
        $type = $config['type'] ?? 'Article';
        $mapping = $config['mapping'] ?? [];

        if ($type === 'CustomMarkup') {
            $custom_map = $mapping['customMarkup'] ?? null;
            $custom_schema = $custom_map ? $this->get_mapped_value($post, $custom_map) : null;
            return is_array($custom_schema) ? $custom_schema : null;
        }

        // Check if this is a 2025 schema type - use dedicated generator
        if (in_array($type, ['DiscussionForumPosting', 'ProfilePage'])) {
            return $this->generate_2025_schema($post, $type, $config);
        }

        // Base schema structure
        $schema = [
            '@context' => 'https://schema.org',
            '@type' => $type
        ];

        // Apply mappings
        foreach ($mapping as $property => $map) {
            $value = $this->get_mapped_value($post, $map);
            if ($value !== null) {
                $schema[$property] = $value;
            }
        }

        // Auto-fill AggregateRating values from user ratings when available
        if ($type === 'AggregateRating') {
            $rating_summary = $this->get_post_rating_summary($post->ID);
            if ($rating_summary['count'] > 0) {
                if (!isset($schema['ratingValue'])) {
                    $schema['ratingValue'] = $rating_summary['average'];
                }
                if (!isset($schema['ratingCount'])) {
                    $schema['ratingCount'] = $rating_summary['count'];
                }
                if (!isset($schema['reviewCount'])) {
                    $schema['reviewCount'] = $rating_summary['count'];
                }
                if (!isset($schema['bestRating'])) {
                    $schema['bestRating'] = 5;
                }
                if (!isset($schema['worstRating'])) {
                    $schema['worstRating'] = 1;
                }
            }
        }
        
        // Add default values if not mapped
        if (!isset($schema['headline']) && isset($schema['name'])) {
            $schema['headline'] = $schema['name'];
        }
        
        if (!isset($schema['name']) && $post) {
            $schema['name'] = get_the_title($post);
        }
        
        if (!isset($schema['url']) && $post) {
            $schema['url'] = get_permalink($post);
        }
        
        if (!isset($schema['datePublished']) && $post) {
            $schema['datePublished'] = get_the_date('c', $post);
        }
        
        if (!isset($schema['dateModified']) && $post) {
            $schema['dateModified'] = get_the_modified_date('c', $post);
        }
        
        // Add organization/publisher data
        if (in_array($type, ['Article', 'BlogPosting', 'NewsArticle'])) {
            $schema['publisher'] = $this->get_organization_schema();
            
            if (!isset($schema['author'])) {
                $author_schema = $this->get_author_schema($post);
                if ($author_schema !== null) {
                    $schema['author'] = $author_schema;
                }
            }
        }
        
        // Add image if available
        if (!isset($schema['image']) && has_post_thumbnail($post)) {
            $schema['image'] = $this->get_image_schema($post);
        }
        
        return $schema;
    }
    
    /**
     * Get mapped value from post
     */
    private function get_mapped_value($post, $map) {
        if (!is_array($map)) {
            return null;
        }
        
        $source = $map['source'] ?? '';
        $type = $map['type'] ?? 'field';
        
        switch ($type) {
            case 'static':
                return $map['value'] ?? '';

            case 'json':
                $raw_json = $map['value'] ?? '';
                if ($raw_json === '' && !empty($map['customField'])) {
                    $raw_json = (string) $this->get_custom_field_value($post->ID, $map['customField']);
                }
                if ($raw_json === '' && $this->is_custom_field_source($source)) {
                    $field_name = $this->extract_custom_field($source);
                    $raw_json = (string) $this->get_custom_field_value($post->ID, $field_name);
                }
                return $this->decode_json_value((string) $raw_json);
                
            case 'custom':
                $field_name = $map['customField'] ?? $this->extract_custom_field($source);
                if (!$field_name) {
                    return null;
                }
                return $this->get_custom_field_value($post->ID, $field_name);

            case 'list':
                $raw_value = null;
                if (($map['source'] ?? '') === 'static') {
                    $raw_value = $map['value'] ?? '';
                } elseif (($map['source'] ?? '') === 'function') {
                    $raw_value = $this->call_dynamic_function($post, $map['function'] ?? '');
                } elseif (!empty($map['customField'])) {
                    $raw_value = $this->get_custom_field_value($post->ID, $map['customField']);
                } else {
                    $raw_value = $this->get_field_value($post, $source);
                }
                return $this->normalize_list_value($raw_value, $map['listSeparator'] ?? '');

            case 'repeater':
                return $this->build_repeater_items($post, $map);
                
            case 'function':
                return $this->call_dynamic_function($post, $map['function'] ?? '');
                
            case 'field':
            default:
                if ($this->is_custom_field_source($source)) {
                    $field_name = $this->extract_custom_field($source);
                    return $this->get_custom_field_value($post->ID, $field_name);
                }
                return $this->get_field_value($post, $source);
        }
    }
    
    /**
     * Get field value from post
     */
    private function get_field_value($post, $field) {
        switch ($field) {
            case 'post_title':
                return get_the_title($post);
                
            case 'post_content':
                return wp_strip_all_tags($post->post_content);
                
            case 'post_excerpt':
                return get_the_excerpt($post);
                
            case 'post_author':
                return $this->get_author_schema($post);
                
            case 'post_date':
                return get_the_date('c', $post);
                
            case 'post_modified':
                return get_the_modified_date('c', $post);
                
            case 'featured_image':
                return $this->get_image_schema($post);
                
            case 'post_url':
                return get_permalink($post);
                
            case 'post_categories':
                $categories = get_the_category($post->ID);
                return wp_list_pluck($categories, 'name');
                
            case 'post_tags':
                $tags = get_the_tags($post->ID);
                return $tags ? wp_list_pluck($tags, 'name') : [];
                
            case 'comment_count':
                return (int) $post->comment_count;
                
            case 'site_name':
                return get_bloginfo('name');
                
            case 'site_logo':
                $custom_logo_id = get_theme_mod('custom_logo');
                return $custom_logo_id ? wp_get_attachment_image_url($custom_logo_id, 'full') : '';
                
            case 'author_name':
                $author = get_userdata($post->post_author);
                return $author ? $author->display_name : '';
                
            case 'author_bio':
                $author = get_userdata($post->post_author);
                return $author ? $author->description : '';
                
            case 'author_avatar':
                return get_avatar_url($post->post_author);

            case 'user_rating_avg':
                return $this->get_post_rating_summary($post->ID)['average'];

            case 'user_rating_count':
                return $this->get_post_rating_summary($post->ID)['count'];
                
            default:
                return null;
        }
    }
    
    /**
     * Call dynamic function
     */
    private function call_dynamic_function($post, $function) {
        switch ($function) {
            case 'current_datetime':
                return current_time('c');
                
            case 'word_count':
                return str_word_count(wp_strip_all_tags($post->post_content));
                
            case 'reading_time':
                $word_count = str_word_count(wp_strip_all_tags($post->post_content));
                $reading_time = ceil($word_count / 200); // Average reading speed
                return "PT{$reading_time}M";
                
            case 'comment_count':
                return (int) $post->comment_count;
                
            case 'category_list':
                $categories = get_the_category($post->ID);
                return wp_list_pluck($categories, 'name');
                
            case 'tag_list':
                $tags = get_the_tags($post->ID);
                return $tags ? wp_list_pluck($tags, 'name') : [];
                
            case 'author_social':
                return $this->get_author_social_profiles($post->post_author);
                
            default:
                return null;
        }
    }
    
    /**
     * Get organization schema
     */
    private function get_organization_schema() {
        $org_data = get_option('prorank_seo_organization', []);
        
        $schema = [
            '@type' => 'Organization',
            'name' => $org_data['name'] ?? get_bloginfo('name'),
            'url' => $org_data['url'] ?? home_url()
        ];
        
        if (!empty($org_data['logo'])) {
            $schema['logo'] = [
                '@type' => 'ImageObject',
                'url' => $org_data['logo']
            ];
        } elseif ($custom_logo_id = get_theme_mod('custom_logo')) {
            $schema['logo'] = [
                '@type' => 'ImageObject',
                'url' => wp_get_attachment_image_url($custom_logo_id, 'full')
            ];
        }
        
        if (!empty($org_data['sameAs'])) {
            $schema['sameAs'] = $org_data['sameAs'];
        }
        
        return $schema;
    }
    
    /**
     * Get author schema
     */
    private function get_author_schema($post) {
        $author = get_userdata($post->post_author);
        if (!$author) {
            return null;
        }
        
        $schema = [
            '@type' => 'Person',
            'name' => $author->display_name
        ];
        
        if ($author->user_url) {
            $schema['url'] = $author->user_url;
        }
        
        if ($author->description) {
            $schema['description'] = $author->description;
        }
        
        $avatar_url = get_avatar_url($author->ID);
        if ($avatar_url) {
            $schema['image'] = $avatar_url;
        }
        
        return $schema;
    }
    
    /**
     * Get image schema
     */
    private function get_image_schema($post) {
        if (!has_post_thumbnail($post)) {
            return null;
        }
        
        $thumbnail_id = get_post_thumbnail_id($post);
        $image = wp_get_attachment_image_src($thumbnail_id, 'full');
        
        if (!$image) {
            return null;
        }
        
        return [
            '@type' => 'ImageObject',
            'url' => $image[0],
            'width' => $image[1],
            'height' => $image[2]
        ];
    }
    
    /**
     * Get author social profiles
     */
    private function get_author_social_profiles($author_id) {
        $profiles = [];

        $social_fields = [
            'facebook' => get_user_meta($author_id, 'facebook', true),
            'twitter' => get_user_meta($author_id, 'twitter', true),
            'linkedin' => get_user_meta($author_id, 'linkedin', true),
            'instagram' => get_user_meta($author_id, 'instagram', true),
            'youtube' => get_user_meta($author_id, 'youtube', true)
        ];

        foreach ($social_fields as $network => $url) {
            if ($url) {
                $profiles[] = $url;
            }
        }

        return $profiles;
    }

    /**
     * Generate 2025 schema types
     *
     * Dispatches to Schema2025Types for new schema types
     *
     * @param \WP_Post $post The post object.
     * @param string   $type Schema type.
     * @param array    $config Configuration options.
     * @return array Schema data.
     */
    private function generate_2025_schema($post, $type, $config) {
        switch ($type) {
            case 'DiscussionForumPosting':
                return $this->schema_2025->generate_discussion_forum_posting($post, $config);

            case 'ProfilePage':
                // For ProfilePage, we need a user ID
                $author_id = $post->post_author ?? get_current_user_id();
                return $this->schema_2025->generate_profile_page($author_id, $config);

            default:
                return [];
        }
    }

    /**
     * Find applicable schema configurations for a post
     */
    private function find_applicable_schemas($post, $schema_configs) {
        $applicable = [];
        
        foreach ($schema_configs as $config) {
            if ($this->is_schema_applicable($post, $config)) {
                $applicable[] = $config;
            }
        }
        
        // Sort by priority
        usort($applicable, function($a, $b) {
            return ($b['priority'] ?? 10) - ($a['priority'] ?? 10);
        });
        
        return $applicable;
    }
    
    /**
     * Check if schema configuration applies to post
     */
    private function is_schema_applicable($post, $config) {
        if ($config['status'] !== 'active') {
            return false;
        }
        
        $targeting = $config['targeting'] ?? [];
        $exclude = is_array($targeting['exclude'] ?? null) ? $targeting['exclude'] : [];
        
        // Auto mode - smart detection
        if (($targeting['mode'] ?? 'manual') === 'auto') {
            if (!$this->auto_detect_schema($post, $config['type'])) {
                return false;
            }
        }
        
        if (!$this->matches_targeting_rules($post, $targeting)) {
            return false;
        }

        if (!empty($exclude) && $this->matches_targeting_rules($post, $exclude, true)) {
            return false;
        }

        if (!empty($targeting['userRoles']) && !$this->matches_user_roles($targeting['userRoles'])) {
            return false;
        }

        return true;
    }
    
    /**
     * Auto-detect if schema applies to post
     */
    private function auto_detect_schema($post, $schema_type) {
        // Smart detection based on content and post type
        switch ($schema_type) {
            case 'Product':
                return $post->post_type === 'product' ||
                       stripos($post->post_content, 'price') !== false;

            case 'Recipe':
                return stripos($post->post_content, 'ingredients') !== false ||
                       stripos($post->post_content, 'instructions') !== false;

            case 'Event':
                return $post->post_type === 'event' ||
                       stripos($post->post_content, 'event') !== false;

            case 'FAQPage':
                return stripos($post->post_content, 'faq') !== false ||
                       stripos($post->post_title, 'faq') !== false;

            case 'HowTo':
                return stripos($post->post_title, 'how to') !== false ||
                       stripos($post->post_title, 'guide') !== false;

            case 'JobPosting':
                return $post->post_type === 'job' ||
                       $post->post_type === 'job_listing';

            case 'Article':
            case 'BlogPosting':
                return $post->post_type === 'post';

            // 2025 TYPES
            case 'DiscussionForumPosting':
                return $post->post_type === 'forum' ||
                       $post->post_type === 'topic' ||
                       stripos($post->post_content, 'discussion') !== false ||
                       $post->comment_count > 0;

            case 'ProfilePage':
                return $post->post_type === 'profile' ||
                       stripos($post->post_title, 'profile') !== false;

            default:
                return false;
        }
    }
    
    /**
     * Evaluate custom rule
     */
    private function evaluate_rule($post, $rule) {
        $field = $rule['field'] ?? '';
        $operator = $rule['operator'] ?? 'equals';
        $value = $rule['value'] ?? '';
        
        $field_value = '';
        
        switch ($field) {
            case 'post_title':
                $field_value = $post->post_title;
                break;
            case 'post_url':
                $field_value = get_permalink($post);
                break;
            case 'post_author':
                $author = get_userdata($post->post_author);
                $field_value = $author ? $author->display_name : '';
                break;
            case 'post_status':
                $field_value = $post->post_status;
                break;
            case 'post_type':
                $field_value = $post->post_type;
                break;
            case 'template':
                $field_value = get_page_template_slug($post);
                break;
            case 'custom_field':
                // Value should be in format: field_name=field_value
                $parts = explode('=', $value, 2);
                if (count($parts) === 2) {
                    $custom_field_name = $parts[0];
                    $value = $parts[1];
                    $field_value = get_post_meta($post->ID, $custom_field_name, true);
                }
                break;
        }
        
        switch ($operator) {
            case 'equals':
                return $field_value === $value;
            case 'not_equals':
                return $field_value !== $value;
            case 'contains':
                return stripos($field_value, $value) !== false;
            case 'starts_with':
                return stripos($field_value, $value) === 0;
            case 'ends_with':
                return substr($field_value, -strlen($value)) === $value;
            default:
                return false;
        }
    }

    /**
     * Check if post matches targeting rules (include/exclude).
     */
    private function matches_targeting_rules($post, array $targeting, bool $is_exclusion = false): bool {
        if (!empty($targeting['postTypes'])) {
            $matches = in_array($post->post_type, $targeting['postTypes'], true);
            if ($is_exclusion && $matches) {
                return true;
            }
            if (!$is_exclusion && !$matches) {
                return false;
            }
        }

        if (!empty($targeting['categories'])) {
            $post_categories = wp_get_post_categories($post->ID);
            $matches = !empty(array_intersect($post_categories, $targeting['categories']));
            if ($is_exclusion && $matches) {
                return true;
            }
            if (!$is_exclusion && !$matches) {
                return false;
            }
        }

        if (!empty($targeting['tags'])) {
            $post_tags = wp_get_post_tags($post->ID, ['fields' => 'ids']);
            $matches = !empty(array_intersect($post_tags, $targeting['tags']));
            if ($is_exclusion && $matches) {
                return true;
            }
            if (!$is_exclusion && !$matches) {
                return false;
            }
        }

        if (!empty($targeting['specific'])) {
            $specific_ids = array_map('intval', $targeting['specific']);
            $matches = in_array($post->ID, $specific_ids, true);
            if ($is_exclusion && $matches) {
                return true;
            }
            if (!$is_exclusion && !$matches) {
                return false;
            }
        }

        if (!empty($targeting['rules'])) {
            foreach ($targeting['rules'] as $rule) {
                $matches = $this->evaluate_rule($post, $rule);
                if ($is_exclusion && $matches) {
                    return true;
                }
                if (!$is_exclusion && !$matches) {
                    return false;
                }
            }
        }

        return !$is_exclusion;
    }

    /**
     * Check if current visitor matches user role targeting.
     */
    private function matches_user_roles(array $roles): bool {
        $roles = array_filter(array_map('sanitize_text_field', $roles));
        if (empty($roles)) {
            return true;
        }

        if (!is_user_logged_in()) {
            return in_array('guest', $roles, true);
        }

        $user = wp_get_current_user();
        if (!$user || empty($user->roles)) {
            return in_array('guest', $roles, true);
        }

        return !empty(array_intersect($roles, $user->roles));
    }

    /**
     * Build repeater items for schema list properties.
     */
    private function build_repeater_items($post, array $map): ?array {
        $field_name = $map['field'] ?? $this->extract_custom_field($map['source'] ?? '');
        if (!$field_name) {
            return null;
        }

        $rows = $this->get_custom_field_value($post->ID, $field_name);
        if (!is_array($rows) || empty($rows)) {
            return null;
        }

        $items = [];
        $sub_mapping = is_array($map['subMapping'] ?? null) ? $map['subMapping'] : [];
        $nested_types = is_array($map['nestedTypes'] ?? null) ? $map['nestedTypes'] : [];
        $item_type = $map['itemType'] ?? '';

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $item = [];
            if (!empty($item_type)) {
                $item['@type'] = $item_type;
            }

            foreach ($sub_mapping as $path => $field_key) {
                if ($field_key === '') {
                    continue;
                }

                $value = $row[$field_key] ?? null;
                if ($value === null || $value === '') {
                    continue;
                }

                $this->set_nested_value($item, $path, $value);
            }

            foreach ($nested_types as $path => $type) {
                if (!empty($type)) {
                    $this->set_nested_value($item, $path . '.@type', $type);
                }
            }

            if (!empty($item)) {
                $items[] = $item;
            }
        }

        return !empty($items) ? $items : null;
    }

    /**
     * Normalize list values for schema properties.
     */
    private function normalize_list_value($raw_value, string $separator = ''): ?array {
        if (is_array($raw_value)) {
            return array_values(array_filter(array_map('trim', $raw_value)));
        }

        if (!is_string($raw_value) || $raw_value === '') {
            return null;
        }

        $separator = $separator ?: (strpos($raw_value, "\n") !== false ? "\n" : ',');
        $parts = array_map('trim', explode($separator, $raw_value));
        $parts = array_filter($parts, fn($value) => $value !== '');

        return !empty($parts) ? array_values($parts) : null;
    }

    /**
     * Decode a JSON string into an array.
     */
    private function decode_json_value(string $value): ?array {
        if ($value === '') {
            return null;
        }

        $decoded = json_decode($value, true);
        return is_array($decoded) ? $decoded : null;
    }

    /**
     * Determine if a source points to a custom field.
     */
    private function is_custom_field_source(string $source): bool {
        return strpos($source, 'custom_field:') === 0 || strpos($source, 'acf:') === 0 || strpos($source, 'meta:') === 0;
    }

    /**
     * Extract custom field name from a source string.
     */
    private function extract_custom_field(string $source): string {
        $prefixes = ['custom_field:', 'acf:', 'meta:'];
        foreach ($prefixes as $prefix) {
            if (strpos($source, $prefix) === 0) {
                return substr($source, strlen($prefix));
            }
        }
        return $source;
    }

    /**
     * Get custom field value with ACF support.
     */
    private function get_custom_field_value(int $post_id, string $field_name) {
        if (function_exists('get_field')) {
            $acf_value = get_field($field_name, $post_id);
            if ($acf_value !== null) {
                return $acf_value;
            }
        }

        return get_post_meta($post_id, $field_name, true);
    }

    /**
     * Set a nested value in an array using dot notation.
     */
    private function set_nested_value(array &$array, string $path, $value): void {
        $segments = explode('.', $path);
        $current =& $array;
        foreach ($segments as $segment) {
            if ($segment === '') {
                continue;
            }
            if (!isset($current[$segment]) || !is_array($current[$segment])) {
                $current[$segment] = [];
            }
            $current =& $current[$segment];
        }
        $current = $value;
    }

    /**
     * Get post rating summary for schema mapping.
     */
    private function get_post_rating_summary(int $post_id): array {
        $average = (float) get_post_meta($post_id, '_prorank_user_rating_avg', true);
        $count = (int) get_post_meta($post_id, '_prorank_user_rating_count', true);

        return [
            'average' => $average,
            'count'   => $count,
        ];
    }
}
