<?php
/**
 * SCHEMAFY SEO Analyzer Class
 *
 * Analyzes post content and provides SEO scoring with recommendations
 *
 * @package CustomSchemaSchemafy
 * @since 2.0.0
 */

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

class SCHEMAFY_SEO_Analyzer {

    /**
     * Database instance
     * @var SCHEMAFY_Database
     */
    private $database;

    /**
     * SEO criteria with weights
     * @var array
     */
    private $criteria = array(
        'title_length' => array(
            'weight' => 15,
            'optimal_min' => 50,
            'optimal_max' => 60,
            'absolute_max' => 70
        ),
        'title_has_keyword' => array(
            'weight' => 20
        ),
        'description_length' => array(
            'weight' => 15,
            'optimal_min' => 120,
            'optimal_max' => 160,
            'absolute_max' => 200
        ),
        'description_has_keyword' => array(
            'weight' => 15
        ),
        'keyword_in_content' => array(
            'weight' => 10
        ),
        'has_schema' => array(
            'weight' => 10
        ),
        'has_og_tags' => array(
            'weight' => 8
        ),
        'has_twitter_tags' => array(
            'weight' => 7
        )
    );

    /**
     * Constructor
     *
     * @param SCHEMAFY_Database|null $database Database instance
     */
    public function __construct($database = null) {
        $this->database = $database;
    }

    /**
     * Analyze a post and return SEO score
     *
     * @param int $post_id Post ID
     * @param string $focus_keyword Focus keyword to analyze
     * @param array $override_data Optional override data (title, content, description)
     * @return array Analysis results with score and checks
     */
    public function analyze_post($post_id, $focus_keyword = '', $override_data = array()) {
        $post = get_post($post_id);

        if (!$post) {
            return array(
                'success' => false,
                'error' => 'Post not found',
                'overall_score' => 0,
                'checks' => array()
            );
        }

        // Get data (use overrides if provided, for live editing)
        $title = isset($override_data['title']) ? $override_data['title'] : $this->get_seo_title($post_id);
        $description = isset($override_data['description']) ? $override_data['description'] : $this->get_seo_description($post_id);
        $content = isset($override_data['content']) ? $override_data['content'] : $post->post_content;
        $post_url = get_permalink($post_id);

        // Normalize focus keyword
        $focus_keyword = strtolower(trim($focus_keyword));

        // Run all checks
        $checks = array();
        $total_score = 0;
        $total_weight = 0;

        // Title length check
        $checks[] = $this->check_title_length($title);

        // Title keyword check
        if (!empty($focus_keyword)) {
            $checks[] = $this->check_title_keyword($title, $focus_keyword);
        }

        // Description length check
        $checks[] = $this->check_description_length($description);

        // Description keyword check
        if (!empty($focus_keyword)) {
            $checks[] = $this->check_description_keyword($description, $focus_keyword);
        }

        // Content keyword check
        if (!empty($focus_keyword)) {
            $checks[] = $this->check_content_keyword($content, $focus_keyword);
        }

        // Schema check
        $checks[] = $this->check_has_schema($post_url);

        // OG tags check
        $checks[] = $this->check_og_tags($post_id);

        // Twitter tags check
        $checks[] = $this->check_twitter_tags($post_id);

        // Calculate overall score
        foreach ($checks as $check) {
            $total_weight += $check['weight'];
            if ($check['status'] === 'good') {
                $total_score += $check['weight'];
            } elseif ($check['status'] === 'warning') {
                $total_score += ($check['weight'] * 0.5);
            }
        }

        $overall_score = $total_weight > 0 ? round(($total_score / $total_weight) * 100) : 0;

        return array(
            'success' => true,
            'overall_score' => $overall_score,
            'score_label' => $this->get_score_label($overall_score),
            'focus_keyword' => $focus_keyword,
            'checks' => $checks,
            'summary' => $this->get_summary($checks)
        );
    }

    /**
     * Get SEO title for post
     *
     * @param int $post_id Post ID
     * @return string SEO title
     */
    private function get_seo_title($post_id) {
        list($title) = SCHEMAFY_Meta_Store::get($post_id);

        // Fallback to post title
        return '' !== $title ? $title : get_the_title($post_id);
    }

    /**
     * Get SEO description for post
     *
     * @param int $post_id Post ID
     * @return string SEO description
     */
    private function get_seo_description($post_id) {
        list(, $description) = SCHEMAFY_Meta_Store::get($post_id);

        if ('' !== $description) {
            return $description;
        }

        // Fallback to excerpt
        $post = get_post($post_id);
        return !empty($post->post_excerpt) ? $post->post_excerpt : '';
    }

    /**
     * Check title length
     *
     * @param string $title Title to check
     * @return array Check result
     */
    private function check_title_length($title) {
        $length = strlen($title);
        $criteria = $this->criteria['title_length'];

        if ($length >= $criteria['optimal_min'] && $length <= $criteria['optimal_max']) {
            return array(
                'id' => 'title_length',
                'title' => __('SEO Title Length', 'schemafy'),
                'status' => 'good',
                'message' => sprintf(
                    // translators: %1$d, %2$d, %3$d are values inserted at runtime.
                    __('Title length is %1$d characters - perfect! (optimal: %2$d-%3$d)', 'schemafy'),
                    $length, $criteria['optimal_min'], $criteria['optimal_max']
                ),
                'weight' => $criteria['weight']
            );
        } elseif ($length > 0 && $length < $criteria['optimal_min']) {
            return array(
                'id' => 'title_length',
                'title' => __('SEO Title Length', 'schemafy'),
                'status' => 'warning',
                'message' => sprintf(
                    // translators: %1$d, %2$d are values inserted at runtime.
                    __('Title is too short (%1$d chars). Add %2$d+ more characters.', 'schemafy'),
                    $length, $criteria['optimal_min'] - $length
                ),
                'weight' => $criteria['weight']
            );
        } elseif ($length > $criteria['optimal_max']) {
            return array(
                'id' => 'title_length',
                'title' => __('SEO Title Length', 'schemafy'),
                'status' => $length > $criteria['absolute_max'] ? 'error' : 'warning',
                'message' => sprintf(
                    // translators: %1$d, %2$d are values inserted at runtime.
                    __('Title is too long (%1$d chars). Remove %2$d characters.', 'schemafy'),
                    $length, $length - $criteria['optimal_max']
                ),
                'weight' => $criteria['weight']
            );
        } else {
            return array(
                'id' => 'title_length',
                'title' => __('SEO Title Length', 'schemafy'),
                'status' => 'error',
                'message' => __('No SEO title set. Add a compelling title.', 'schemafy'),
                'weight' => $criteria['weight']
            );
        }
    }

    /**
     * Check if title contains focus keyword
     *
     * @param string $title Title to check
     * @param string $keyword Focus keyword
     * @return array Check result
     */
    private function check_title_keyword($title, $keyword) {
        $title_lower = strtolower($title);
        $contains = strpos($title_lower, $keyword) !== false;

        return array(
            'id' => 'title_has_keyword',
            'title' => __('Keyword in Title', 'schemafy'),
            'status' => $contains ? 'good' : 'error',
            'message' => $contains
                ? __('Focus keyword found in title.', 'schemafy')
                // translators: %s is a value inserted at runtime.
                : sprintf(__('Add your focus keyword "%s" to the title.', 'schemafy'), $keyword),
            'weight' => $this->criteria['title_has_keyword']['weight']
        );
    }

    /**
     * Check description length
     *
     * @param string $description Description to check
     * @return array Check result
     */
    private function check_description_length($description) {
        $length = strlen($description);
        $criteria = $this->criteria['description_length'];

        if ($length >= $criteria['optimal_min'] && $length <= $criteria['optimal_max']) {
            return array(
                'id' => 'description_length',
                'title' => __('Meta Description Length', 'schemafy'),
                'status' => 'good',
                'message' => sprintf(
                    // translators: %d is a value inserted at runtime.
                    __('Description length is %d characters - perfect!', 'schemafy'),
                    $length
                ),
                'weight' => $criteria['weight']
            );
        } elseif ($length > 0 && $length < $criteria['optimal_min']) {
            return array(
                'id' => 'description_length',
                'title' => __('Meta Description Length', 'schemafy'),
                'status' => 'warning',
                'message' => sprintf(
                    // translators: %1$d, %2$d are values inserted at runtime.
                    __('Description is too short (%1$d chars). Add %2$d+ more.', 'schemafy'),
                    $length, $criteria['optimal_min'] - $length
                ),
                'weight' => $criteria['weight']
            );
        } elseif ($length > $criteria['optimal_max']) {
            return array(
                'id' => 'description_length',
                'title' => __('Meta Description Length', 'schemafy'),
                'status' => 'warning',
                'message' => sprintf(
                    // translators: %1$d, %2$d are values inserted at runtime.
                    __('Description may be truncated (%1$d chars). Keep under %2$d.', 'schemafy'),
                    $length, $criteria['optimal_max']
                ),
                'weight' => $criteria['weight']
            );
        } else {
            return array(
                'id' => 'description_length',
                'title' => __('Meta Description Length', 'schemafy'),
                'status' => 'error',
                'message' => __('No meta description set. Add a compelling description.', 'schemafy'),
                'weight' => $criteria['weight']
            );
        }
    }

    /**
     * Check if description contains focus keyword
     *
     * @param string $description Description to check
     * @param string $keyword Focus keyword
     * @return array Check result
     */
    private function check_description_keyword($description, $keyword) {
        $desc_lower = strtolower($description);
        $contains = strpos($desc_lower, $keyword) !== false;

        return array(
            'id' => 'description_has_keyword',
            'title' => __('Keyword in Description', 'schemafy'),
            'status' => $contains ? 'good' : 'warning',
            'message' => $contains
                ? __('Focus keyword found in meta description.', 'schemafy')
                : __('Consider adding your focus keyword to the description.', 'schemafy'),
            'weight' => $this->criteria['description_has_keyword']['weight']
        );
    }

    /**
     * Check if content contains focus keyword
     *
     * @param string $content Post content
     * @param string $keyword Focus keyword
     * @return array Check result
     */
    private function check_content_keyword($content, $keyword) {
        $content_text = wp_strip_all_tags($content);
        $content_lower = strtolower($content_text);
        $keyword_count = substr_count($content_lower, $keyword);
        $word_count = str_word_count($content_text);

        // Calculate keyword density
        $density = $word_count > 0 ? ($keyword_count / $word_count) * 100 : 0;

        // Check if keyword is in first paragraph
        $first_para = '';
        if (preg_match('/<p[^>]*>(.*?)<\/p>/is', $content, $matches)) {
            $first_para = strtolower(wp_strip_all_tags($matches[1]));
        } else {
            // No <p> tags, take first 300 chars
            $first_para = strtolower(substr($content_text, 0, 300));
        }
        $in_first_para = strpos($first_para, $keyword) !== false;

        if ($keyword_count === 0) {
            return array(
                'id' => 'keyword_in_content',
                'title' => __('Keyword in Content', 'schemafy'),
                'status' => 'error',
                'message' => __('Focus keyword not found in content. Add it naturally.', 'schemafy'),
                'weight' => $this->criteria['keyword_in_content']['weight']
            );
        } elseif (!$in_first_para) {
            return array(
                'id' => 'keyword_in_content',
                'title' => __('Keyword in Content', 'schemafy'),
                'status' => 'warning',
                'message' => sprintf(
                    // translators: %d is a value inserted at runtime.
                    __('Keyword found %d times but not in first paragraph.', 'schemafy'),
                    $keyword_count
                ),
                'weight' => $this->criteria['keyword_in_content']['weight']
            );
        } else {
            return array(
                'id' => 'keyword_in_content',
                'title' => __('Keyword in Content', 'schemafy'),
                'status' => 'good',
                'message' => sprintf(
                    // translators: %d is a value inserted at runtime.
                    __('Keyword found %d times including first paragraph.', 'schemafy'),
                    $keyword_count
                ),
                'weight' => $this->criteria['keyword_in_content']['weight']
            );
        }
    }

    /**
     * Check if post has schema markup
     *
     * @param string $post_url Post URL
     * @return array Check result
     */
    private function check_has_schema($post_url) {
        $has_schema = false;
        $schema_count = 0;

        if ($this->database && method_exists($this->database, 'get_schemas_by_url')) {
            $schemas = $this->database->get_schemas_by_url($post_url, true);
            $schema_count = is_array($schemas) ? count($schemas) : 0;
            $has_schema = $schema_count > 0;
        }

        return array(
            'id' => 'has_schema',
            'title' => __('Schema Markup', 'schemafy'),
            'status' => $has_schema ? 'good' : 'warning',
            'message' => $has_schema
                // translators: %d is a value inserted at runtime.
                ? sprintf(__('%d active schema(s) found for this page.', 'schemafy'), $schema_count)
                : __('No schema markup found. Add structured data for rich snippets.', 'schemafy'),
            'weight' => $this->criteria['has_schema']['weight']
        );
    }

    /**
     * Check if post has Open Graph tags
     *
     * @param int $post_id Post ID
     * @return array Check result
     */
    private function check_og_tags($post_id) {
        $og_title = get_post_meta($post_id, '_schemafy_og_title', true);
        $og_desc = get_post_meta($post_id, '_schemafy_og_description', true);
        $og_image = get_post_meta($post_id, '_schemafy_og_image', true);

        $has_all = !empty($og_title) && !empty($og_desc) && !empty($og_image);
        $has_some = !empty($og_title) || !empty($og_desc) || !empty($og_image);

        if ($has_all) {
            return array(
                'id' => 'has_og_tags',
                'title' => __('Open Graph Tags', 'schemafy'),
                'status' => 'good',
                'message' => __('All Open Graph tags are set for social sharing.', 'schemafy'),
                'weight' => $this->criteria['has_og_tags']['weight']
            );
        } elseif ($has_some) {
            $missing = array();
            if (empty($og_title)) $missing[] = 'title';
            if (empty($og_desc)) $missing[] = 'description';
            if (empty($og_image)) $missing[] = 'image';

            return array(
                'id' => 'has_og_tags',
                'title' => __('Open Graph Tags', 'schemafy'),
                'status' => 'warning',
                'message' => sprintf(
                    // translators: %s is a value inserted at runtime.
                    __('Missing OG %s for better social sharing.', 'schemafy'),
                    implode(', ', $missing)
                ),
                'weight' => $this->criteria['has_og_tags']['weight']
            );
        } else {
            return array(
                'id' => 'has_og_tags',
                'title' => __('Open Graph Tags', 'schemafy'),
                'status' => 'warning',
                'message' => __('No Open Graph tags set. Add them for better social sharing.', 'schemafy'),
                'weight' => $this->criteria['has_og_tags']['weight']
            );
        }
    }

    /**
     * Check if post has Twitter Card tags
     *
     * @param int $post_id Post ID
     * @return array Check result
     */
    private function check_twitter_tags($post_id) {
        $twitter_card = get_post_meta($post_id, '_schemafy_twitter_card', true);
        $twitter_title = get_post_meta($post_id, '_schemafy_twitter_title', true);
        $twitter_desc = get_post_meta($post_id, '_schemafy_twitter_description', true);

        // Twitter can fallback to OG tags, so check those too
        $og_title = get_post_meta($post_id, '_schemafy_og_title', true);
        $og_desc = get_post_meta($post_id, '_schemafy_og_description', true);

        $has_twitter = !empty($twitter_card) || !empty($twitter_title) || !empty($twitter_desc);
        $has_og_fallback = !empty($og_title) && !empty($og_desc);

        if ($has_twitter) {
            return array(
                'id' => 'has_twitter_tags',
                'title' => __('Twitter Card Tags', 'schemafy'),
                'status' => 'good',
                'message' => __('Twitter Card tags are configured.', 'schemafy'),
                'weight' => $this->criteria['has_twitter_tags']['weight']
            );
        } elseif ($has_og_fallback) {
            return array(
                'id' => 'has_twitter_tags',
                'title' => __('Twitter Card Tags', 'schemafy'),
                'status' => 'good',
                'message' => __('Twitter will use Open Graph tags as fallback.', 'schemafy'),
                'weight' => $this->criteria['has_twitter_tags']['weight']
            );
        } else {
            return array(
                'id' => 'has_twitter_tags',
                'title' => __('Twitter Card Tags', 'schemafy'),
                'status' => 'warning',
                'message' => __('No Twitter Card tags. Set OG tags or Twitter-specific tags.', 'schemafy'),
                'weight' => $this->criteria['has_twitter_tags']['weight']
            );
        }
    }

    /**
     * Get score label based on score
     *
     * @param int $score Overall score
     * @return string Score label
     */
    private function get_score_label($score) {
        if ($score >= 80) {
            return 'excellent';
        } elseif ($score >= 60) {
            return 'good';
        } elseif ($score >= 40) {
            return 'needs_improvement';
        } else {
            return 'poor';
        }
    }

    /**
     * Get summary of checks
     *
     * @param array $checks All checks
     * @return array Summary counts
     */
    private function get_summary($checks) {
        $summary = array(
            'good' => 0,
            'warning' => 0,
            'error' => 0,
            'total' => count($checks)
        );

        foreach ($checks as $check) {
            if (isset($summary[$check['status']])) {
                $summary[$check['status']]++;
            }
        }

        return $summary;
    }
}
