<?php
/**
 * Internal Linking Utility Functions
 * Pure helper functions for text normalization, candidate generation, and link insertion
 *
 * @package ContentPerformanceAnalyzer
 * @since 2.0.0
 */

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

/**
 * Normalize text content for consistent processing
 * 
 * @param string $text Raw text content
 * @return string Normalized text
 */
function cpanalyzer_interlink_normalize($text) {
    // Strip all HTML tags
    $text = wp_strip_all_tags($text);
    
    // Decode HTML entities
    $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    
    // Normalize whitespace to single spaces
    $text = preg_replace('/\s+/u', ' ', $text);
    
    return trim($text);
}

/**
 * Extract plain text from post content, handling Gutenberg and Kadence blocks
 * 
 * @param int $post_id Post ID to extract text from
 * @return string Plain text content
 */
function cpanalyzer_interlink_get_plain_text($post_id) {
    $content = get_post_field('post_content', $post_id);
    
    // Ensure parse_blocks function is available
    if (!function_exists('parse_blocks')) {
        // Fallback: strip all HTML tags
        return wp_strip_all_tags($content);
    }
    
    $blocks = parse_blocks($content);
    $text = '';

    // Recursive walker to extract text from all blocks
    $walker = function($blocks) use (&$walker, &$text) {
        foreach ($blocks as $block) {
            // Extract text from innerHTML (Kadence and core blocks)
            if (!empty($block['innerHTML'])) {
                $text .= ' ' . wp_strip_all_tags($block['innerHTML']);
            }
            
            // Extract text from innerContent array (Gutenberg format)
            if (!empty($block['innerContent']) && is_array($block['innerContent'])) {
                foreach ($block['innerContent'] as $chunk) {
                    if (is_string($chunk)) {
                        $text .= ' ' . wp_strip_all_tags($chunk);
                    }
                }
            }
            
            // Process nested blocks recursively
            if (!empty($block['innerBlocks'])) {
                $walker($block['innerBlocks']);
            }
        }
    };
    
    $walker($blocks);

    // Fallback: if no text extracted from blocks, strip tags from raw content
    if (empty(trim($text))) {
        $text = wp_strip_all_tags($content);
    }

    return trim($text);
}

/**
 * Generate deterministic candidate anchor texts from source content
 * Only includes phrases that have semantic overlap with target hint
 * 
 * @param string $source_norm Normalized source text
 * @param string $hint Hint text (usually target title) to find relevant phrases
 * @return array Array of candidate objects with id, anchor_text, start, end
 */
function cpanalyzer_interlink_generate_candidates($source_norm, $hint) {
    // Extract terms from hint for overlap detection
    $hint_terms = preg_split('/\W+/u', mb_strtolower($hint), -1, PREG_SPLIT_NO_EMPTY);
    $hint_terms = array_filter($hint_terms, function($term) {
        return strlen($term) > 2; // Ignore very short words
    });
    
    $words = preg_split('/\s+/u', $source_norm);
    $candidates = array();
    
    // Generate 2-6 word phrases
    for ($i = 0; $i < count($words); $i++) {
        for ($length = 2; $length <= 6; $length++) {
            if ($i + $length > count($words)) {
                break;
            }
            
            $phrase = implode(' ', array_slice($words, $i, $length));
            $phrase_lower = mb_strtolower($phrase);
            
            // Check for semantic overlap with hint
            $phrase_terms = preg_split('/\W+/u', $phrase_lower, -1, PREG_SPLIT_NO_EMPTY);
            $overlap = array_intersect($hint_terms, $phrase_terms);
            
            // Skip phrases with no relevant terms
            if (empty($overlap)) {
                continue;
            }
            
            // Find exact position in normalized text
            $start_pos = mb_strpos($source_norm, $phrase);
            if ($start_pos === false) {
                continue;
            }
            
            $candidate_id = md5($phrase . $start_pos); // Unique ID for this phrase at this position
            
            $candidates[] = array(
                'id' => $candidate_id,
                'anchor_text' => $phrase,
                'start' => $start_pos,
                'end' => $start_pos + mb_strlen($phrase),
                'overlap_terms' => array_values($overlap)
            );
        }
    }
    
    // Remove duplicates by ID
    $unique_candidates = array();
    $seen_ids = array();
    
    foreach ($candidates as $candidate) {
        if (!in_array($candidate['id'], $seen_ids)) {
            $seen_ids[] = $candidate['id'];
            $unique_candidates[] = $candidate;
        }
        
        // Limit to prevent overwhelming the AI
        if (count($unique_candidates) >= 100) {
            break;
        }
    }
    
    return $unique_candidates;
}

/**
 * Validate that anchor text exists at specified offsets in source text
 * Critical function to prevent hallucinations
 * 
 * @param string $source_norm Normalized source text
 * @param string $anchor_text Proposed anchor text
 * @param int $start Start position
 * @param int $end End position
 * @return bool True if valid, false if hallucinated
 */
function cpanalyzer_interlink_validate_anchor($source_norm, $anchor_text, $start, $end) {
    // Basic boundary checks
    if ($start < 0 || $end <= $start || $end > mb_strlen($source_norm)) {
        return false;
    }
    
    // Extract text at specified position
    $extracted_text = mb_substr($source_norm, $start, $end - $start);
    
    // Must match exactly
    if ($extracted_text !== $anchor_text) {
        return false;
    }
    
    // Additional check: anchor text must exist somewhere in source
    if (mb_strpos($source_norm, $anchor_text) === false) {
        return false;
    }
    
    return true;
}

/**
 * Insert link into Gutenberg/Kadence blocks while preserving structure
 * 
 * @param array &$blocks Reference to blocks array (modified in place)
 * @param string $anchor_text Text to replace with link
 * @param string $target_url URL to link to
 * @return bool True if link was inserted, false if not found
 */
function cpanalyzer_interlink_insert_link_into_blocks(&$blocks, $anchor_text, $target_url) {
    $link_inserted = false;

    // Create the link HTML
    $link_html = sprintf('<a href="%s">%s</a>', esc_url($target_url), esc_html($anchor_text));

    // Word-boundary pattern that skips matches inside HTML tags or existing links
    $pattern = '/\b' . preg_quote($anchor_text, '/') . '\b(?![^<]*>)(?![^<>]*<\/a>)/u';

    // Recursive walker to process all blocks
    $walker = function(&$blocks) use (&$walker, $pattern, $link_html, &$link_inserted) {
        foreach ($blocks as &$block) {
            if ($link_inserted) {
                return;
            }

            // The match must land in innerContent — serialize_blocks() rebuilds
            // block markup from innerContent, so a link written only to
            // innerHTML is silently dropped on save.
            if (isset($block['innerContent']) && is_array($block['innerContent'])) {
                foreach ($block['innerContent'] as $key => $chunk) {
                    if (!is_string($chunk) || !preg_match($pattern, $chunk)) {
                        continue;
                    }

                    $block['innerContent'][$key] = preg_replace($pattern, $link_html, $chunk, 1);

                    // Mirror the replacement into innerHTML so the block stays consistent
                    if (isset($block['innerHTML']) && is_string($block['innerHTML'])) {
                        $block['innerHTML'] = preg_replace($pattern, $link_html, $block['innerHTML'], 1);
                    }

                    $link_inserted = true;
                    break;
                }
            }

            // Process nested blocks
            if (!$link_inserted && !empty($block['innerBlocks'])) {
                $walker($block['innerBlocks']);
            }
        }
    };

    $walker($blocks);

    return $link_inserted;
}

/**
 * Get post types that support internal linking
 * 
 * @return array Array of post type names
 */
function cpanalyzer_interlink_get_supported_post_types() {
    $post_types = get_post_types(array(
        'public' => true,
        'show_in_rest' => true, // Support for Gutenberg
    ), 'names');
    
    // Remove attachment as it's not relevant for internal linking
    unset($post_types['attachment']);
    
    return $post_types;
}

/**
 * Check if a post is suitable for internal linking
 * 
 * @param int $post_id Post ID to check
 * @return bool True if suitable
 */
function cpanalyzer_interlink_is_post_suitable($post_id) {
    $post = get_post($post_id);
    
    if (!$post) {
        return false;
    }
    
    // Must be published
    if ($post->post_status !== 'publish') {
        return false;
    }
    
    // Must have content
    if (empty($post->post_content) || strlen($post->post_content) < 100) {
        return false;
    }
    
    // Must be a supported post type
    $supported_types = cpanalyzer_interlink_get_supported_post_types();
    if (!in_array($post->post_type, $supported_types)) {
        return false;
    }
    
    return true;
}