<?php
/**
 * Internal Links Analyzer and Manager
 *
 * @package ContentPerformanceAnalyzer
 * @since 1.0.0
 */

// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class CPAP_Internal_Links {

	/**
	 * Nonce constants for security
	 */
	const NONCE_ACTION = 'cpanalyzer_internal_links';
	const NONCE_NAME   = 'cpanalyzer_internal_links_nonce';

	/**
	 * Initialize the internal links functionality
	 */
	public function init() {
		// AJAX handlers
		add_action( 'wp_ajax_cpanalyzer_analyze_internal_links', array( $this, 'ajax_analyze_internal_links' ) );
		add_action( 'wp_ajax_cpanalyzer_get_link_analysis_status', array( $this, 'ajax_get_link_analysis_status' ) );
		add_action( 'wp_ajax_cpanalyzer_trigger_link_analysis', array( $this, 'ajax_trigger_link_analysis' ) );
		add_action( 'wp_ajax_cpanalyzer_debug_cron', array( $this, 'ajax_debug_cron' ) );
		add_action( 'wp_ajax_cpanalyzer_reset_link_analysis', array( $this, 'ajax_reset_link_analysis' ) );

		// Apply links AJAX handlers
		add_action( 'wp_ajax_cpanalyzer_apply_internal_links', array( $this, 'ajax_apply_internal_links' ) );
		add_action( 'wp_ajax_cpanalyzer_apply_single_internal_link', array( $this, 'ajax_apply_single_internal_link' ) );

		// Remove suggestion AJAX handler
		add_action( 'wp_ajax_cpanalyzer_remove_suggestion', array( $this, 'ajax_remove_suggestion' ) );

		// Enqueue admin scripts
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
	}

	/**
	 * Get all published content for analysis
	 *
	 * @return array Array of post objects with relevant data
	 */
	public function get_published_content() {
		global $wpdb;

		// Get all public post types
		$post_types = get_post_types( array( 'public' => true ), 'names' );

		// Remove attachment from post types as it's not relevant for internal linking
		unset( $post_types['attachment'] );

		if ( empty( $post_types ) ) {
			return array();
		}

		// Build IN clause with proper placeholders
		$post_types_placeholders = implode( ',', array_fill( 0, count( $post_types ), '%s' ) );

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$sql_query = "
            SELECT ID, post_title, post_content, post_type, post_name, post_date
            FROM {$wpdb->posts}
            WHERE post_status = 'publish'
            AND post_type IN ($post_types_placeholders)
            AND post_content != ''
            AND LENGTH(post_content) > 100
            ORDER BY post_date DESC
        ";

		// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.NotPrepared
		$sql = $wpdb->prepare( $sql_query, ...$post_types );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
		$posts = $wpdb->get_results( $sql );

		// Process posts to add URL and clean content
		foreach ( $posts as &$post ) {
			$post->url           = get_permalink( $post->ID );
			$post->clean_content = $this->clean_content_for_analysis( $post->post_content );
			$post->word_count    = str_word_count( wp_strip_all_tags( $post->clean_content, true ) );
			$post->excerpt       = $this->generate_excerpt( $post->clean_content, 150 );
		}

		return $posts;
	}

	/**
	 * Clean content for analysis by removing Gutenberg blocks markup and excluding headings
	 *
	 * @param string $content Raw post content
	 * @return string Cleaned content without headings or titles
	 */
	private function clean_content_for_analysis( $content ) {
		// Remove Gutenberg block comments
		$content = preg_replace( '/<!-- wp:.*? -->/', '', $content );
		$content = preg_replace( '/<!-- \/wp:.*? -->/', '', $content );

		// Remove heading tags and their content to exclude from linking suggestions
		$content = preg_replace( '/<h[1-6][^>]*>.*?<\/h[1-6]>/i', '', $content );

		// Remove title-like elements
		$content = preg_replace( '/<title[^>]*>.*?<\/title>/i', '', $content );

		// Remove other non-paragraph content
		$content = preg_replace( '/<(nav|header|footer|aside|menu)[^>]*>.*?<\/\1>/i', '', $content );

		// Remove remaining HTML tags but keep the text from paragraphs and content blocks
		$content = wp_strip_all_tags( $content );

		// Clean up whitespace
		$content = preg_replace( '/\s+/', ' ', $content );

		return trim( $content );
	}

	/**
	 * Generate excerpt from content
	 *
	 * @param string $content Content to excerpt
	 * @param int    $length Maximum length in characters
	 * @return string Generated excerpt
	 */
	private function generate_excerpt( $content, $length = 150 ) {
		if ( strlen( $content ) <= $length ) {
			return $content;
		}

		$excerpt    = substr( $content, 0, $length );
		$last_space = strrpos( $excerpt, ' ' );

		if ( $last_space !== false ) {
			$excerpt = substr( $excerpt, 0, $last_space );
		}

		return $excerpt . '...';
	}

	/**
	 * Analyze content for internal linking opportunities using AI
	 *
	 * @param array $posts Array of post objects to analyze
	 * @return array Array of linking suggestions
	 */
	public function analyze_linking_opportunities( $posts ) {
		$this->debug_log( '[CPANALYZER Internal Links] Starting analyze_linking_opportunities with ' . count( $posts ) . ' posts' );

		// Validate input
		if ( empty( $posts ) || ! is_array( $posts ) ) {
			$this->debug_log( '[CPANALYZER Internal Links] Invalid posts parameter' );
			return array(
				'success' => false,
				'message' => __( 'No posts provided for analysis.', 'content-performance-analyzer' ),
			);
		}

		// Get AI processor instance
		if ( ! class_exists( 'CPAP_AI_Processor' ) ) {
			$this->debug_log( '[CPANALYZER Internal Links] AI processor class not found' );
			return array(
				'success' => false,
				'message' => __( 'AI processor not available. Please check plugin installation.', 'content-performance-analyzer' ),
			);
		}

		try {
			$ai_processor = new CPAP_AI_Processor();
			$this->debug_log( '[CPANALYZER Internal Links] AI processor created successfully' );

			if ( ! method_exists( $ai_processor, 'is_configured' ) ) {
				$this->debug_log( '[CPANALYZER Internal Links] AI processor missing is_configured method' );
				return array(
					'success' => false,
					'message' => __( 'AI processor is outdated. Please update the plugin.', 'content-performance-analyzer' ),
				);
			}

			if ( ! $ai_processor->is_configured() ) {
				$this->debug_log( '[CPANALYZER Internal Links] AI processor not configured' );
				return array(
					'success' => false,
					'message' => __( 'OpenAI API key not configured. Please configure it in Settings.', 'content-performance-analyzer' ),
				);
			}

			$this->debug_log( '[CPANALYZER Internal Links] AI processor configured and ready' );

		} catch ( Exception $e ) {
			$this->debug_log( '[CPANALYZER Internal Links] Error creating AI processor: ' . $e->getMessage() );
			return array(
				'success' => false,
				/* translators: %s: error message */
				'message' => sprintf( __( 'Error initializing AI processor: %s', 'content-performance-analyzer' ), $e->getMessage() ),
			);
		}

		$suggestions     = array();
		$processed_count = 0;
		$total_posts     = count( $posts );

		// Update status only if not already set (to preserve status from AJAX call)
		$existing_status = get_option( 'cpanalyzer_link_analysis_status', array() );
		if ( empty( $existing_status ) || $existing_status['status'] !== 'analyzing' ) {
			update_option(
				'cpanalyzer_link_analysis_status',
				array(
					'status'     => 'analyzing',
					'processed'  => 0,
					'total'      => $total_posts,
					'started_at' => current_time( 'mysql' ),
				)
			);
		} else {
			// Update total if it was already set but might be different
			$existing_status['total'] = $total_posts;
			update_option( 'cpanalyzer_link_analysis_status', $existing_status );
		}

		try {
			foreach ( $posts as $index => $source_post ) {
				$this->debug_log( '[CPANALYZER Internal Links] Processing post ' . ( $index + 1 ) . '/' . $total_posts . ': ' . $source_post->post_title );

				// Validate post object
				if ( ! isset( $source_post->ID, $source_post->post_title, $source_post->word_count ) ) {
					$this->debug_log( '[CPANALYZER Internal Links] Invalid post object, skipping' );
					++$processed_count;
					continue;
				}

				// Skip if content is too short
				if ( $source_post->word_count < 100 ) {
					$this->debug_log( '[CPANALYZER Internal Links] Post too short (' . $source_post->word_count . ' words), skipping' );
					++$processed_count;
					continue;
				}

				try {
					// Create context of other posts for AI analysis
					$context_posts = array();
					foreach ( $posts as $target_post ) {
						if ( isset( $target_post->ID ) && $target_post->ID !== $source_post->ID ) {
							$context_posts[] = array(
								'id'      => $target_post->ID,
								'title'   => $target_post->post_title ?? 'Untitled',
								'excerpt' => $target_post->excerpt ?? '',
								'url'     => $target_post->url ?? '',
								'type'    => $target_post->post_type ?? 'post',
							);
						}
					}

					// Limit context to prevent token overflow (max 20 posts)
					$context_posts = array_slice( $context_posts, 0, 20 );

					$this->debug_log( '[CPANALYZER Internal Links] Created context with ' . count( $context_posts ) . ' posts' );

					// Get AI suggestions for this post
					$post_suggestions = $this->get_ai_linking_suggestions( $source_post, $context_posts );

					if ( ! empty( $post_suggestions ) ) {
						$suggestions[ $source_post->ID ] = array(
							'source_post' => array(
								'id'    => $source_post->ID,
								'title' => $source_post->post_title,
								'url'   => $source_post->url ?? '',
								'type'  => $source_post->post_type ?? 'post',
							),
							'suggestions' => $post_suggestions,
						);
						$this->debug_log( '[CPANALYZER Internal Links] Found ' . count( $post_suggestions ) . ' suggestions for post ' . $source_post->ID );
					} else {
						$this->debug_log( '[CPANALYZER Internal Links] No suggestions found for post ' . $source_post->ID );
					}
				} catch ( Exception $e ) {
					$this->debug_log( '[CPANALYZER Internal Links] Error processing post ' . $source_post->ID . ': ' . $e->getMessage() );
					// Continue with next post instead of failing completely
				}

				++$processed_count;

				// Update progress
				update_option(
					'cpanalyzer_link_analysis_status',
					array(
						'status'     => 'analyzing',
						'processed'  => $processed_count,
						'total'      => $total_posts,
						'started_at' => get_option( 'cpanalyzer_link_analysis_status' )['started_at'] ?? current_time( 'mysql' ),
					)
				);

				// Add small delay to prevent rate limiting
				usleep( 250000 ); // 0.25 seconds
			}
		} catch ( Exception $e ) {
			$this->debug_log( '[CPANALYZER Internal Links] Fatal error in processing loop: ' . $e->getMessage() );
			return array(
				'success' => false,
				/* translators: %s: error message */
				'message' => sprintf( __( 'Error during analysis: %s', 'content-performance-analyzer' ), $e->getMessage() ),
			);
		}

		// Store results and update status
		update_option( 'cpanalyzer_internal_link_suggestions', $suggestions );
		update_option(
			'cpanalyzer_link_analysis_status',
			array(
				'status'            => 'completed',
				'processed'         => $processed_count,
				'total'             => $total_posts,
				'completed_at'      => current_time( 'mysql' ),
				'suggestions_count' => count( $suggestions ),
			)
		);

		return array(
			'success'                => true,
			'suggestions'            => $suggestions,
			'total_posts'            => $total_posts,
			'posts_with_suggestions' => count( $suggestions ),
		);
	}

	/**
	 * Get AI-powered linking suggestions for a specific post
	 *
	 * @param object $source_post Post to analyze for linking opportunities
	 * @param array  $context_posts Array of other posts to consider for linking
	 * @return array Array of link suggestions
	 */
	private function get_ai_linking_suggestions( $source_post, $context_posts ) {
		// Ensure AI processor is available
		if ( ! class_exists( 'CPAP_AI_Processor' ) ) {
			$this->debug_log( '[CPANALYZER Internal Links] AI processor class not found' );
			return array();
		}

		try {
			$ai_processor = new CPAP_AI_Processor();
		} catch ( Exception $e ) {
			$this->debug_log( '[CPANALYZER Internal Links] Error creating AI processor in get_ai_linking_suggestions: ' . $e->getMessage() );
			return array();
		}

		require_once CPANALYZER_PLUGIN_DIR . 'includes/cpa-interlink-utils.php';

		// Prepare the prompt for AI analysis
		$context_text = '';
		foreach ( $context_posts as $post ) {
			$context_text .= sprintf(
				"- ID: %d, Title: \"%s\", Type: %s, Excerpt: \"%s\"\n",
				$post['id'],
				$post['title'],
				$post['type'],
				$post['excerpt']
			);
		}

		$prompt = sprintf(
			"Analyze the following article and suggest 2-4 natural internal linking opportunities from the available content list.\n\n" .
			"ARTICLE TO ANALYZE:\n" .
			"Title: \"%s\"\n" .
			"Content: \"%s\"\n\n" .
			"AVAILABLE CONTENT FOR LINKING:\n%s\n\n" .
			"CRITICAL REQUIREMENTS - FOLLOW EXACTLY:\n" .
			"1. ONLY suggest links for paragraph content - NEVER for headings, titles, or any heading text\n" .
			"2. The anchor_text MUST be exact words that appear in the article content above\n" .
			"3. The context MUST be an exact quote from the article content - DO NOT create or modify sentences\n" .
			"4. Find actual sentences in the content that contain relevant terms, don't invent new ones\n" .
			"5. Links should add genuine value and flow naturally\n\n" .
			"VALIDATION PROCESS:\n" .
			"1. Read through the article content carefully\n" .
			"2. Find sentences that mention topics related to the available content\n" .
			"3. Extract the EXACT sentence or phrase from the article\n" .
			"4. Identify the specific words within that sentence to use as anchor text\n" .
			"5. Verify the anchor_text appears exactly in the context quote\n\n" .
			"For each suggestion, provide:\n" .
			"1. target_id: The ID of the post to link to\n" .
			"2. anchor_text: Exact text from the article to be linked (2-5 words)\n" .
			"3. context: EXACT quote from the article containing the anchor_text (10+ words)\n" .
			"4. reason: Brief explanation why this link adds value\n\n" .
			"EXAMPLE OF CORRECT FORMAT:\n" .
			"If article contains: \"Project management software helps teams collaborate more effectively.\"\n" .
			"Then provide:\n" .
			"{\n" .
			"  \"anchor_text\": \"project management software\",\n" .
			"  \"context\": \"Project management software helps teams collaborate more effectively.\"\n" .
			"}\n\n" .
			"CRITICAL: Never create, modify, or invent sentences. Only use exact quotes from the article content.\n\n" .
			"Return ONLY a valid JSON array with this structure:\n" .
			"[\n" .
			"  {\n" .
			"    \"target_id\": 123,\n" .
			"    \"anchor_text\": \"exact words from article\",\n" .
			"    \"context\": \"exact sentence quote from article containing the anchor text\",\n" .
			"    \"reason\": \"why this link is valuable\"\n" .
			"  }\n" .
			"]\n\n" .
			'If no good linking opportunities exist, return an empty array: []',
			$source_post->post_title,
			$this->truncate_content_for_ai( $source_post->clean_content ),
			$context_text
		);

		try {
			$response = $ai_processor->call_openai_api(
				$prompt,
				array(
					'max_tokens'  => 800,
					'temperature' => 0.3,
				)
			);

			if ( empty( $response ) ) {
				return array();
			}

			// Parse JSON response
			$suggestions = json_decode( $response, true );

			if ( ! is_array( $suggestions ) ) {
				$this->debug_log( '[CPANALYZER Internal Links] Invalid JSON response from AI: ' . $response );
				return array();
			}

			// Validate and enrich suggestions.
			$valid_suggestions      = array();
			$context_posts_by_id    = array();
			$seen_suggestion_hashes = array();
			$normalized_source_text = $this->normalize_linking_text( $source_post->clean_content ?? '' );

			foreach ( $context_posts as $post ) {
				if ( isset( $post['id'] ) ) {
					$context_posts_by_id[ (int) $post['id'] ] = $post;
				}
			}

			foreach ( $suggestions as $suggestion ) {
				$validated = $this->validate_ai_link_suggestion(
					$suggestion,
					$source_post,
					$normalized_source_text,
					$context_posts_by_id,
					$seen_suggestion_hashes
				);

				if ( $validated ) {
					$valid_suggestions[] = $validated;
				}
			}

			return $valid_suggestions;

		} catch ( Exception $e ) {
			$this->debug_log( '[CPANALYZER Internal Links] AI analysis error: ' . $e->getMessage() );
			return array();
		}
	}

	/**
	 * Validate and normalize one AI-generated internal-link suggestion.
	 *
	 * @param array  $suggestion             Raw AI suggestion.
	 * @param object $source_post            Source post object.
	 * @param string $normalized_source_text Normalized paragraph-only source text.
	 * @param array  $context_posts_by_id    Available target posts keyed by ID.
	 * @param array  $seen_suggestion_hashes Deduplication map passed by reference.
	 * @return array|null
	 */
	private function validate_ai_link_suggestion( $suggestion, $source_post, $normalized_source_text, $context_posts_by_id, &$seen_suggestion_hashes ) {
		if ( ! is_array( $suggestion ) || ! isset( $suggestion['target_id'], $suggestion['anchor_text'], $suggestion['context'], $suggestion['reason'] ) ) {
			return null;
		}

		$target_id   = (int) $suggestion['target_id'];
		$anchor_text = sanitize_text_field( trim( (string) $suggestion['anchor_text'] ) );
		$context     = sanitize_text_field( trim( (string) $suggestion['context'] ) );
		$reason      = sanitize_text_field( trim( (string) $suggestion['reason'] ) );

		if ( ! $target_id || $target_id === (int) $source_post->ID || empty( $context_posts_by_id[ $target_id ] ) ) {
			$this->debug_log( '[CPANALYZER Links] INVALID TARGET: suggestion references unavailable target post.' );
			return null;
		}

		if ( '' === $anchor_text || '' === $context || '' === $reason ) {
			$this->debug_log( '[CPANALYZER Links] INVALID SUGGESTION: missing anchor, context, or reason.' );
			return null;
		}

		if ( $this->is_generic_anchor_text( $anchor_text ) ) {
			$this->debug_log( '[CPANALYZER Links] INVALID ANCHOR: generic anchor text rejected - "' . $anchor_text . '"' );
			return null;
		}

		$anchor_word_count  = str_word_count( $anchor_text );
		$context_word_count = str_word_count( $context );
		if ( $anchor_word_count < 1 || $anchor_word_count > 8 || $context_word_count < 6 ) {
			$this->debug_log( '[CPANALYZER Links] INVALID SUGGESTION: anchor/context length out of bounds.' );
			return null;
		}

		if ( $this->is_already_applied( $source_post->ID, $target_id, $anchor_text ) ) {
			return null;
		}

		$normalized_anchor  = $this->normalize_linking_text( $anchor_text );
		$normalized_context = $this->normalize_linking_text( $context );
		if ( '' === $normalized_anchor || '' === $normalized_context ) {
			return null;
		}

		if ( false === mb_stripos( $normalized_context, $normalized_anchor ) ) {
			$this->debug_log( '[CPANALYZER Links] INVALID ANCHOR: anchor text not found inside context.' );
			return null;
		}

		if ( false === mb_stripos( $normalized_source_text, $normalized_context ) ) {
			$this->debug_log( '[CPANALYZER Links] INVALID CONTEXT: suggested context does not exist in paragraph-only source text.' );
			return null;
		}

		if ( false === mb_stripos( $normalized_source_text, $normalized_anchor ) ) {
			$this->debug_log( '[CPANALYZER Links] INVALID ANCHOR: anchor text not found in paragraph-only source text.' );
			return null;
		}

		$target_post = $context_posts_by_id[ $target_id ];
		$target_obj  = get_post( $target_id );
		if ( ! $target_obj || 'publish' !== $target_obj->post_status ) {
			$this->debug_log( '[CPANALYZER Links] INVALID TARGET: target post is no longer published.' );
			return null;
		}

		$target_relevance_text = trim( (string) ( $target_post['title'] ?? '' ) . ' ' . (string) ( $target_post['excerpt'] ?? '' ) );
		if ( ! $this->has_meaningful_term_overlap( $anchor_text . ' ' . $context, $target_relevance_text ) ) {
			$this->debug_log( '[CPANALYZER Links] INVALID RELEVANCE: suggestion has weak overlap with target post.' );
			return null;
		}

		$suggestion_hash = md5( $source_post->ID . '|' . $target_id . '|' . mb_strtolower( $normalized_anchor ) );
		if ( isset( $seen_suggestion_hashes[ $suggestion_hash ] ) ) {
			return null;
		}
		$seen_suggestion_hashes[ $suggestion_hash ] = true;

		$this->debug_log( '[CPANALYZER Links] VALID SUGGESTION: "' . $anchor_text . '" -> target ' . $target_id );

		return array(
			'target_id'    => $target_id,
			'target_title' => $target_post['title'],
			'target_url'   => $target_post['url'],
			'target_type'  => $target_post['type'],
			'anchor_text'  => $anchor_text,
			'context'      => $context,
			'reason'       => $reason,
		);
	}

	/**
	 * Normalize text for internal-link validation.
	 *
	 * @param string $text Raw text.
	 * @return string
	 */
	private function normalize_linking_text( $text ) {
		if ( function_exists( 'cpanalyzer_interlink_normalize' ) ) {
			return cpanalyzer_interlink_normalize( $text );
		}

		$text = wp_strip_all_tags( $text );
		$text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
		$text = preg_replace( '/\s+/u', ' ', $text );

		return trim( (string) $text );
	}

	/**
	 * Reject generic anchors that are likely to feel spammy or unhelpful.
	 *
	 * @param string $anchor_text Candidate anchor text.
	 * @return bool
	 */
	private function is_generic_anchor_text( $anchor_text ) {
		$generic_anchors = array(
			'click here',
			'learn more',
			'read more',
			'view more',
			'this page',
			'this article',
			'here',
			'more',
			'link',
			'website',
		);

		return in_array( mb_strtolower( trim( $anchor_text ) ), $generic_anchors, true );
	}

	/**
	 * Ensure a suggested source phrase actually overlaps with the target content topic.
	 *
	 * @param string $left_text  Source-side text to analyze.
	 * @param string $right_text Target-side text to compare against.
	 * @return bool
	 */
	private function has_meaningful_term_overlap( $left_text, $right_text ) {
		$stopwords = array(
			'about', 'after', 'also', 'and', 'been', 'from', 'have', 'into', 'more', 'page',
			'that', 'their', 'them', 'they', 'this', 'will', 'with', 'your', 'plus', 'over',
			'just', 'than', 'then', 'what', 'when', 'where', 'which', 'while',
		);

		$normalize_terms = static function ( $text ) use ( $stopwords ) {
			$tokens = preg_split( '/\W+/u', mb_strtolower( (string) $text ), -1, PREG_SPLIT_NO_EMPTY );
			$tokens = array_filter(
				$tokens,
				static function ( $token ) use ( $stopwords ) {
					return strlen( $token ) > 3 && ! in_array( $token, $stopwords, true );
				}
			);

			return array_values( array_unique( $tokens ) );
		};

		$left_terms  = $normalize_terms( $left_text );
		$right_terms = $normalize_terms( $right_text );

		return ! empty( array_intersect( $left_terms, $right_terms ) );
	}

	/**
	 * Truncate content for AI analysis to prevent token overflow
	 *
	 * @param string $content Content to truncate
	 * @param int    $max_words Maximum number of words
	 * @return string Truncated content
	 */
	private function truncate_content_for_ai( $content, $max_words = 800 ) {
		$words = explode( ' ', $content );
		if ( count( $words ) <= $max_words ) {
			return $content;
		}

		return implode( ' ', array_slice( $words, 0, $max_words ) ) . '...';
	}

	/**
	 * Apply selected internal links to posts
	 *
	 * @param array $selected_links Array of selected links to apply
	 * @return array Result of link application
	 */
	public function apply_internal_links( $selected_links ) {
		if ( empty( $selected_links ) ) {
			return array(
				'success' => false,
				'message' => __( 'No links selected to apply.', 'content-performance-analyzer' ),
			);
		}

		$applied_count = 0;
		$failed_count  = 0;
		$errors        = array();
		$items         = array();

		foreach ( $selected_links as $link_data ) {
			$result = $this->apply_single_internal_link( $link_data );

			$item_result = array(
				'source_id'   => $link_data['source_id'],
				'target_id'   => $link_data['target_id'],
				'anchor_text' => $link_data['anchor_text'],
				'success'     => $result['success'],
				'message'     => $result['message'],
			);

			if ( $result['success'] ) {
				++$applied_count;
			} else {
				++$failed_count;
				$errors[] = $result['message'];
			}

			$items[] = $item_result;
		}

		return array(
			'success'       => $applied_count > 0,
			'items'         => $items,
			'totals'        => array(
				'ok'   => $applied_count,
				'fail' => $failed_count,
			),
			'applied_count' => $applied_count,
			'failed_count'  => $failed_count,
			'errors'        => $errors,
			'message'       => sprintf(
				/* translators: 1: number of links applied successfully; 2: number of links that failed */
				__( 'Applied %1$d links successfully. %2$d failed.', 'content-performance-analyzer' ),
				$applied_count,
				$failed_count
			),
		);
	}

	/**
	 * Apply a single internal link to a post
	 *
	 * @param array $link_data Link data containing source_id, target_id, anchor_text, context
	 * @return array Result of link application
	 */
	private function apply_single_internal_link( $link_data ) {
		$source_id   = (int) $link_data['source_id'];
		$target_id   = (int) $link_data['target_id'];
		$anchor_text = sanitize_text_field( $link_data['anchor_text'] );
		$context     = sanitize_text_field( $link_data['context'] );

		$this->debug_log( '[CPANALYZER Links] Applying link - Source: ' . $source_id . ', Target: ' . $target_id . ', Anchor: ' . $anchor_text );

		// Get the source post
		$post = get_post( $source_id );
		if ( ! $post ) {
			$this->debug_log( '[CPANALYZER Links] Source post not found: ' . $source_id );
			return array(
				'success' => false,
				/* translators: %d: source post ID */
				'message' => sprintf( __( 'Source post %d not found.', 'content-performance-analyzer' ), $source_id ),
			);
		}

		// Get target URL
		$target_url = get_permalink( $target_id );
		if ( ! $target_url ) {
			$this->debug_log( '[CPANALYZER Links] Target URL not found for post: ' . $target_id );
			return array(
				'success' => false,
				/* translators: %d: target post ID */
				'message' => sprintf( __( 'Target post %d not found.', 'content-performance-analyzer' ), $target_id ),
			);
		}

		$this->debug_log( '[CPANALYZER Links] Target URL: ' . $target_url );

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

		// Apply link to post content using context-aware insertion first.
		$original_content = $post->post_content;
		$updated_content  = $this->insert_link_in_gutenberg_content( $original_content, $anchor_text, $link_html, $context );

		if ( $updated_content === $original_content ) {
			$updated_content = $this->simple_link_insertion( $original_content, $anchor_text, $link_html );
		}

		if ( $updated_content === $original_content ) {
			$this->debug_log( '[CPANALYZER Links] No suitable location found for link in post ' . $source_id );
			$this->debug_log( '[CPANALYZER Links] Anchor text not found: "' . $anchor_text . '"' );
			return array(
				'success' => false,
				/* translators: 1: anchor text; 2: post ID */
				'message' => sprintf( __( 'Could not find anchor text "%1$s" in post %2$d. Try a different anchor text or context.', 'content-performance-analyzer' ), $anchor_text, $source_id ),
			);
		}

		$this->debug_log( '[CPANALYZER Links] Content updated successfully for post ' . $source_id );

		// Update the post
		$result = wp_update_post(
			array(
				'ID'           => $source_id,
				'post_content' => $updated_content,
			)
		);

		if ( is_wp_error( $result ) ) {
			$this->debug_log( '[CPANALYZER Links] WordPress update failed: ' . $result->get_error_message() );
			return array(
				'success' => false,
				/* translators: 1: post ID; 2: error message */
				'message' => sprintf( __( 'Failed to update post %1$d: %2$s', 'content-performance-analyzer' ), $source_id, $result->get_error_message() ),
			);
		}

		$this->debug_log( '[CPANALYZER Links] Post ' . $source_id . ' updated successfully in WordPress' );

		// Mark this link as applied to avoid re-suggesting it
		$this->mark_applied( $source_id, $target_id, $anchor_text );

		return array(
			'success' => true,
			/* translators: %d: post ID */
			'message' => sprintf( __( 'Link applied to post %d successfully.', 'content-performance-analyzer' ), $source_id ),
		);
	}

	/**
	 * Simple and fast link insertion method to avoid timeouts
	 *
	 * @param string $content Original post content
	 * @param string $anchor_text Text to replace with link
	 * @param string $link_html Complete link HTML
	 * @return string Updated content
	 */
	private function simple_link_insertion( $content, $anchor_text, $link_html ) {
		// Exact-anchor match only, and only in text that is outside HTML tags
		// and outside existing links — never inside attributes or block markup.
		$pattern = '/\b' . preg_quote( $anchor_text, '/' ) . '\b(?![^<]*>)(?![^<>]*<\/a>)/i';

		$updated = preg_replace( $pattern, $link_html, $content, 1 );

		return ( null === $updated ) ? $content : $updated;
	}

	/**
	 * Insert link in Gutenberg content, preserving block structure
	 * Enhanced for Kadence Blocks compatibility
	 *
	 * @param string $content Original post content
	 * @param string $anchor_text Text to replace with link
	 * @param string $link_html Complete link HTML
	 * @param string $context Context phrase to help locate insertion point
	 * @return string Updated content
	 */
	private function insert_link_in_gutenberg_content( $content, $anchor_text, $link_html, $context ) {
		// Ensure we don't break Gutenberg block structure
		$original_content = $content;

		// Strategy 1: Context-based replacement within paragraph blocks.
		if ( ! empty( $context ) ) {
			$content = $this->insert_link_in_paragraph_blocks( $original_content, $anchor_text, $link_html, $context );
			if ( $content !== $original_content && $this->is_valid_gutenberg_content( $content ) ) {
				return $content;
			}
		}

		// Strategy 2: Exact anchor replacement in paragraph-like blocks only.
		$content = $this->insert_link_in_paragraph_blocks( $original_content, $anchor_text, $link_html, $anchor_text );
		if ( $content !== $original_content && $this->is_valid_gutenberg_content( $content ) ) {
			return $content;
		}

		return $original_content; // Return unchanged if no suitable location found
	}

	/**
	 * Insert link specifically within paragraph blocks to maintain Gutenberg structure
	 *
	 * @param string $content Original content
	 * @param string $anchor_text Anchor text to replace
	 * @param string $link_html Link HTML
	 * @param string $context Context to help locate insertion point
	 * @return string Updated content
	 */
	private function insert_link_in_paragraph_blocks( $content, $anchor_text, $link_html, $context ) {
		// Match paragraph-like blocks only. Headings are intentionally excluded.
		$pattern = '/<!-- wp:(core\/paragraph|kadence\/advancedtext)[^>]*-->(.*?)<!-- \/wp:\1 -->/s';

		return preg_replace_callback(
			$pattern,
			function ( $matches ) use ( $anchor_text, $link_html, $context ) {
				$block_content  = $matches[2];
				$original_block = $matches[0];

				// Check if this block contains our context or anchor text
				if ( stripos( $block_content, $context ) !== false || stripos( $block_content, $anchor_text ) !== false ) {
					// Replace the exact anchor text only, outside tags and existing links
					if ( stripos( $block_content, $anchor_text ) !== false ) {
						$pattern               = '/\b' . preg_quote( $anchor_text, '/' ) . '\b(?![^<]*>)(?![^<>]*<\/a>)/i';
						$updated_block_content = preg_replace( $pattern, $link_html, $block_content, 1 );

						if ( null !== $updated_block_content && $updated_block_content !== $block_content ) {
							return str_replace( $block_content, $updated_block_content, $original_block );
						}
					}
				}

				return $original_block; // Return unchanged if no match
			},
			$content
		);
	}

	/**
	 * Validate that content still has valid Gutenberg block structure
	 *
	 * @param string $content Content to validate
	 * @return bool True if valid
	 */
	private function is_valid_gutenberg_content( $content ) {
		// Check for balanced block comments
		$open_blocks  = preg_match_all( '/<!-- wp:/', $content );
		$close_blocks = preg_match_all( '/<!-- \/wp:/', $content );

		// Basic validation - must have equal opening and closing blocks
		return $open_blocks === $close_blocks;
	}

	/**
	 * AJAX handler for analyzing internal links
	 */
	public function ajax_analyze_internal_links() {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'content-performance-analyzer' ) );
		}

		// Start background analysis
		$posts = $this->get_published_content();

		if ( empty( $posts ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'No published content found to analyze.', 'content-performance-analyzer' ),
				)
			);
		}

		// Set initial analysis status
		update_option(
			'cpanalyzer_link_analysis_status',
			array(
				'status'     => 'analyzing',
				'processed'  => 0,
				'total'      => count( $posts ),
				'started_at' => current_time( 'mysql' ),
			)
		);

		// Clear any previous suggestions
		delete_option( 'cpanalyzer_internal_link_suggestions' );

		// Store posts for analysis and start process
		update_option( 'cpanalyzer_posts_for_link_analysis', $posts );

		// Start analysis in background
		$scheduled = wp_schedule_single_event( time() + 5, 'cpanalyzer_process_link_analysis' );

		// Fallback: If WordPress cron might not work, also try to trigger via admin-ajax for immediate testing
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			// In debug mode, also schedule a more immediate check
			wp_schedule_single_event( time() + 2, 'cpanalyzer_process_link_analysis' );
		}

		wp_send_json_success(
			array(
				'message'     => __( 'Analysis started. Processing in background...', 'content-performance-analyzer' ),
				'total_posts' => count( $posts ),
			)
		);
	}


	/**
	 * AJAX handler for getting link analysis status
	 */
	public function ajax_get_link_analysis_status() {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'content-performance-analyzer' ) );
		}

		$status      = get_option( 'cpanalyzer_link_analysis_status', array() );
		$suggestions = get_option( 'cpanalyzer_internal_link_suggestions', array() );

		wp_send_json_success(
			array(
				'status'      => $status,
				'suggestions' => $suggestions,
			)
		);
	}

	/**
	 * Manual trigger for link analysis (fallback if cron doesn't work)
	 */
	public function ajax_trigger_link_analysis() {
		try {
			check_ajax_referer( self::NONCE_ACTION, 'nonce' );

			if ( ! current_user_can( 'manage_options' ) ) {
				wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'content-performance-analyzer' ) ) );
			}

			// Check if there are posts waiting for analysis
			$posts = get_option( 'cpanalyzer_posts_for_link_analysis', array() );

			// If no posts queued, get fresh posts for testing
			if ( empty( $posts ) ) {
				$posts = $this->get_published_content();
				if ( empty( $posts ) ) {
					wp_send_json_error(
						array(
							'message' => __( 'No published content found to analyze.', 'content-performance-analyzer' ),
						)
					);
				}
			}

			// For manual trigger, limit to 3 posts for quick testing
			$test_posts = array_slice( $posts, 0, 3 );

			// Additional validation
			if ( count( $test_posts ) === 0 ) {
				wp_send_json_error(
					array(
						'message' => __( 'No suitable posts found for analysis.', 'content-performance-analyzer' ),
					)
				);
			}

			// Check AI configuration before running analysis
			if ( ! cpanalyzer_ai_is_configured() ) {
				$cpa_ai_notice = class_exists( 'CPAP_AI_Processor' )
					? ( new CPAP_AI_Processor() )->get_not_configured_message()
					: __( 'AI is not configured yet.', 'content-performance-analyzer' );
				wp_send_json_error( array( 'message' => $cpa_ai_notice ) );
			}

			// Log the attempt
			$this->debug_log( '[CPANALYZER Manual Analysis] Starting analysis of ' . count( $test_posts ) . ' posts' );

			// Run the analysis directly on limited posts
			$result = $this->analyze_linking_opportunities( $test_posts );

			// Clean up queued posts
			delete_option( 'cpanalyzer_posts_for_link_analysis' );

			// Log the result
			$this->debug_log( '[CPANALYZER Manual Analysis] Result: ' . ( $result['success'] ? 'success' : 'failed' ) );
			if ( ! $result['success'] && isset( $result['message'] ) ) {
				$this->debug_log( '[CPANALYZER Manual Analysis] Error: ' . $result['message'] );
			}

			if ( $result['success'] ) {
				wp_send_json_success(
					array(
						'message'        => sprintf(
							/* translators: 1: number of posts processed; 2: number of suggestions found */
							__( 'Analysis completed successfully. Processed %1$d posts, found %2$d suggestions.', 'content-performance-analyzer' ),
							count( $test_posts ),
							count( $result['suggestions'] ?? array() )
						),
						'suggestions'    => $result['suggestions'],
						'posts_analyzed' => count( $test_posts ),
					)
				);
			} else {
				wp_send_json_error( $result );
			}
		} catch ( Exception $e ) {
			$this->debug_log( '[CPANALYZER Manual Analysis] Exception: ' . $e->getMessage() );
			$this->debug_log( '[CPANALYZER Manual Analysis] Stack trace: ' . $e->getTraceAsString() );

			wp_send_json_error(
				array(
					/* translators: %s: error message */
					'message' => sprintf( __( 'Error during analysis: %s', 'content-performance-analyzer' ), $e->getMessage() ),
				)
			);
		} catch ( Error $e ) {
			$this->debug_log( '[CPANALYZER Manual Analysis] Fatal Error: ' . $e->getMessage() );
			$this->debug_log( '[CPANALYZER Manual Analysis] Stack trace: ' . $e->getTraceAsString() );

			wp_send_json_error(
				array(
					/* translators: %s: error message */
					'message' => sprintf( __( 'Fatal error during analysis: %s', 'content-performance-analyzer' ), $e->getMessage() ),
				)
			);
		}
	}

	/**
	 * Debug WordPress cron status
	 */
	public function ajax_debug_cron() {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'content-performance-analyzer' ) );
		}

		$cron_info = array();

		// Check if cron is disabled
		if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
			$cron_info[] = '❌ WordPress cron is DISABLED (DISABLE_WP_CRON = true)';
		} else {
			$cron_info[] = '✅ WordPress cron is enabled';
		}

		// Check for scheduled events
		$scheduled = wp_next_scheduled( 'cpanalyzer_process_link_analysis' );
		if ( $scheduled ) {
			$cron_info[] = '⏰ Next analysis scheduled: ' . wp_date( 'Y-m-d H:i:s', $scheduled );
		} else {
			$cron_info[] = '❌ No analysis currently scheduled';
		}

		// Check cron queue
		$crons       = _get_cron_array();
		$link_events = 0;
		foreach ( $crons as $timestamp => $events ) {
			if ( isset( $events['cpanalyzer_process_link_analysis'] ) ) {
				++$link_events;
			}
		}
		$cron_info[] = 'Found ' . $link_events . ' link analysis events in cron queue';

		// Check for queued posts
		$queued_posts = get_option( 'cpanalyzer_posts_for_link_analysis', array() );
		$cron_info[]  = count( $queued_posts ) . ' posts queued for analysis';

		// Check analysis status
		$status = get_option( 'cpanalyzer_link_analysis_status', array() );
		if ( ! empty( $status ) ) {
			$cron_info[] = 'Current status: ' . ( $status['status'] ?? 'unknown' );
			$cron_info[] = 'Progress: ' . ( $status['processed'] ?? 0 ) . '/' . ( $status['total'] ?? 0 );
		}

		wp_send_json_success(
			array(
				'message' => implode( ' | ', $cron_info ),
			)
		);
	}

	/**
	 * Reset link analysis
	 */
	public function ajax_reset_link_analysis() {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Insufficient permissions.', 'content-performance-analyzer' ) );
		}

		// Clear all analysis data
		delete_option( 'cpanalyzer_link_analysis_status' );
		delete_option( 'cpanalyzer_internal_link_suggestions' );
		delete_option( 'cpanalyzer_posts_for_link_analysis' );

		// Clear scheduled events
		wp_clear_scheduled_hook( 'cpanalyzer_process_link_analysis' );

		wp_send_json_success(
			array(
				'message' => __( 'Analysis reset successfully.', 'content-performance-analyzer' ),
			)
		);
	}

	/**
	 * Enqueue admin scripts for internal links page
	 */
	public function enqueue_admin_scripts( $hook ) {
		// Debug: log the hook name to see what we're getting
		$this->debug_log( '[CPANALYZER Debug] Admin hook: ' . $hook );

		// Only load on internal links page - check multiple possible hook names
		if ( strpos( $hook, 'cpanalyzer-internal-links' ) === false &&
			strpos( $hook, 'internal-links' ) === false &&
			strpos( $hook, 'cpanalyzer_internal_links' ) === false ) {
			return;
		}

		$this->debug_log( '[CPANALYZER Debug] Loading internal links JS on hook: ' . $hook );

		// Enqueue the link application script
		$cpa_internal_links_js = CPANALYZER_PLUGIN_DIR . 'assets/js/cpa-internal-links.js';
		wp_enqueue_script(
			'cpanalyzer-internal-links',
			CPANALYZER_PLUGIN_URL . 'assets/js/cpa-internal-links.js',
			array( 'jquery' ),
			file_exists( $cpa_internal_links_js ) ? filemtime( $cpa_internal_links_js ) : CPANALYZER_VERSION,
			true
		);

		wp_localize_script(
			'cpanalyzer-internal-links',
			'CPANALYZER_INTERNAL_LINKS',
			array(
				'ajax_url'   => admin_url( 'admin-ajax.php' ),
				'nonce'      => wp_create_nonce( self::NONCE_ACTION ),
				'nonce_name' => self::NONCE_NAME,
				'strings'    => array(
					'applying'            => __( 'Applying...', 'content-performance-analyzer' ),
					'removing'            => __( 'Removing...', 'content-performance-analyzer' ),
					'applySuccess'        => __( 'Suggestion applied successfully.', 'content-performance-analyzer' ),
					'removeSuccess'       => __( 'Suggestion removed successfully.', 'content-performance-analyzer' ),
					'removeConfirm'       => __( 'Remove this suggestion from the review list?', 'content-performance-analyzer' ),
					'applyConfirm'        => __( 'Apply this internal link suggestion to the source post?', 'content-performance-analyzer' ),
					'emptyApplied'        => __( 'All reviewed suggestions are done. Run a new analysis to discover more opportunities.', 'content-performance-analyzer' ),
					'emptyRemoved'        => __( 'All suggestions have been cleared. Run a new analysis to generate fresh opportunities.', 'content-performance-analyzer' ),
					'networkError'        => __( 'Network error', 'content-performance-analyzer' ),
					'applyButton'         => __( 'Apply', 'content-performance-analyzer' ),
					'removeButton'        => __( 'Remove', 'content-performance-analyzer' ),
				),
			)
		);

		// Enqueue the analysis polling script
		$cpa_internal_links_analysis_js = CPANALYZER_PLUGIN_DIR . 'assets/js/internal-links-analysis.js';
		wp_enqueue_script(
			'cpanalyzer-internal-links-analysis',
			CPANALYZER_PLUGIN_URL . 'assets/js/internal-links-analysis.js',
			array( 'jquery', 'cpanalyzer-internal-links' ),
			file_exists( $cpa_internal_links_analysis_js ) ? filemtime( $cpa_internal_links_analysis_js ) : CPANALYZER_VERSION,
			true
		);
	}

	/**
	 * AJAX handler for applying multiple internal links
	 */
	public function ajax_apply_internal_links() {
		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', 'content-performance-analyzer' ) ), 403 );
		}

		check_ajax_referer( self::NONCE_ACTION, self::NONCE_NAME );

		$payload = isset( $_POST['items'] ) ? sanitize_textarea_field( wp_unslash( $_POST['items'] ) ) : '';
		$items   = json_decode( $payload, true );

		if ( ! is_array( $items ) || empty( $items ) ) {
			wp_send_json_error( array( 'message' => __( 'No items provided', 'content-performance-analyzer' ) ), 400 );
		}

		// Validate and sanitize each item
		$clean = array();
		foreach ( $items as $item ) {
			$source_id   = isset( $item['source_id'] ) ? (int) $item['source_id'] : 0;
			$target_id   = isset( $item['target_id'] ) ? (int) $item['target_id'] : 0;
			$anchor_text = isset( $item['anchor_text'] ) ? sanitize_text_field( $item['anchor_text'] ) : '';
			$context     = isset( $item['context'] ) ? sanitize_text_field( $item['context'] ) : '';

			if ( $source_id && $target_id && $anchor_text ) {
				// Check if user can edit this post
				if ( ! current_user_can( 'edit_post', $source_id ) ) {
					continue;
				}

				$clean[] = array(
					'source_id'   => $source_id,
					'target_id'   => $target_id,
					'anchor_text' => $anchor_text,
					'context'     => $context,
				);
			}
		}

		if ( empty( $clean ) ) {
			wp_send_json_error( array( 'message' => __( 'No valid items to apply', 'content-performance-analyzer' ) ), 400 );
		}

		$result = $this->apply_internal_links( $clean );

		wp_send_json_success( $result );
	}

	/**
	 * AJAX handler for applying a single internal link
	 */
	public function ajax_apply_single_internal_link() {
		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', 'content-performance-analyzer' ) ), 403 );
		}

		check_ajax_referer( self::NONCE_ACTION, self::NONCE_NAME );

		$source_id   = isset( $_POST['source_id'] ) ? (int) $_POST['source_id'] : 0;
		$target_id   = isset( $_POST['target_id'] ) ? (int) $_POST['target_id'] : 0;
		$anchor_text = isset( $_POST['anchor_text'] ) ? sanitize_text_field( wp_unslash( $_POST['anchor_text'] ) ) : '';
		$context     = isset( $_POST['context'] ) ? sanitize_text_field( wp_unslash( $_POST['context'] ) ) : '';

		if ( ! $source_id || ! $target_id || ! $anchor_text ) {
			wp_send_json_error( array( 'message' => __( 'Missing required parameters', 'content-performance-analyzer' ) ), 400 );
		}

		// Check if user can edit this post
		if ( ! current_user_can( 'edit_post', $source_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied for this post', 'content-performance-analyzer' ) ), 403 );
		}

		$link_data = array(
			'source_id'   => $source_id,
			'target_id'   => $target_id,
			'anchor_text' => $anchor_text,
			'context'     => $context,
		);

		$result = $this->apply_single_internal_link( $link_data );

		if ( ! empty( $result['success'] ) ) {
			wp_send_json_success( $result );
		} else {
			wp_send_json_error( $result );
		}
	}

	/**
	 * Mark a link as applied to avoid re-suggesting it
	 */
	private function mark_applied( $source_id, $target_id, $anchor_text ) {
		$key     = '_cpanalyzer_interlinks_applied';
		$hash    = md5( $target_id . '|' . mb_strtolower( $anchor_text ) );
		$applied = get_post_meta( $source_id, $key, true );

		if ( ! is_array( $applied ) ) {
			$applied = array();
		}

		if ( ! in_array( $hash, $applied, true ) ) {
			$applied[] = $hash;
			update_post_meta( $source_id, $key, $applied );
		}
	}

	/**
	 * Check if a link has already been applied
	 */
	private function is_already_applied( $source_id, $target_id, $anchor_text ) {
		$key     = '_cpanalyzer_interlinks_applied';
		$hash    = md5( $target_id . '|' . mb_strtolower( $anchor_text ) );
		$applied = get_post_meta( $source_id, $key, true );

		if ( ! is_array( $applied ) ) {
			return false;
		}

		return in_array( $hash, $applied, true );
	}

	/**
	 * AJAX handler for removing a suggestion from the list
	 */
	public function ajax_remove_suggestion() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', 'content-performance-analyzer' ) ), 403 );
		}

		check_ajax_referer( self::NONCE_ACTION, self::NONCE_NAME );

		$source_id   = isset( $_POST['source_id'] ) ? (int) $_POST['source_id'] : 0;
		$target_id   = isset( $_POST['target_id'] ) ? (int) $_POST['target_id'] : 0;
		$anchor_text = isset( $_POST['anchor_text'] ) ? sanitize_text_field( wp_unslash( $_POST['anchor_text'] ) ) : '';
		$context     = isset( $_POST['context'] ) ? sanitize_text_field( wp_unslash( $_POST['context'] ) ) : '';

		if ( ! $source_id || ! $target_id || ! $anchor_text ) {
			wp_send_json_error( array( 'message' => __( 'Missing required parameters', 'content-performance-analyzer' ) ), 400 );
		}

		// Get current suggestions
		$suggestions = get_option( 'cpanalyzer_internal_link_suggestions', array() );

		if ( ! isset( $suggestions[ $source_id ] ) || ! isset( $suggestions[ $source_id ]['suggestions'] ) ) {
			wp_send_json_error( array( 'message' => __( 'Suggestion not found', 'content-performance-analyzer' ) ), 404 );
		}

		// Find and remove the specific suggestion
		$post_suggestions = $suggestions[ $source_id ]['suggestions'];
		$found            = false;

		foreach ( $post_suggestions as $index => $suggestion ) {
			if ( isset( $suggestion['target_id'], $suggestion['anchor_text'] ) &&
				$suggestion['target_id'] == $target_id &&
				$suggestion['anchor_text'] === $anchor_text ) {

				// Remove this suggestion
				unset( $suggestions[ $source_id ]['suggestions'][ $index ] );

				// Re-index array
				$suggestions[ $source_id ]['suggestions'] = array_values( $suggestions[ $source_id ]['suggestions'] );

				// If no suggestions left for this post, remove the entire post entry
				if ( empty( $suggestions[ $source_id ]['suggestions'] ) ) {
					unset( $suggestions[ $source_id ] );
				}

				$found = true;
				break;
			}
		}

		if ( ! $found ) {
			wp_send_json_error( array( 'message' => __( 'Suggestion not found in list', 'content-performance-analyzer' ) ), 404 );
		}

		// Update the suggestions in database
		update_option( 'cpanalyzer_internal_link_suggestions', $suggestions );

		$this->debug_log( '[CPANALYZER Links] Suggestion removed: Source=' . $source_id . ', Target=' . $target_id . ', Anchor="' . $anchor_text . '"' );

		wp_send_json_success(
			array(
				'message'               => __( 'Suggestion removed successfully', 'content-performance-analyzer' ),
				'remaining_suggestions' => count( $suggestions ),
			)
		);
	}

	/**
	 * Debug logging helper that respects WP_DEBUG_LOG setting
	 *
	 * @param string $message The message to log
	 */
	private function debug_log( $message ) {
		if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( $message );
		}
	}
}
