<?php
/**
 * WP Blog Agent Topic Scout
 *
 * Analyzes existing blog posts and generates new topic suggestions via Claude API.
 *
 * @package WP_Blog_Agent
 * @since 1.0.0
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * WBAG_Topic_Scout class for analyzing existing posts and generating topic suggestions
 *
 * @since 1.0.0
 */
class WBAG_Topic_Scout {

	/**
	 * Main method called by AJAX handler
	 *
	 * Gathers existing posts, builds context, calls Claude API for topic generation,
	 * and stores results in the topic queue.
	 *
	 * @return array|WP_Error Array of generated topics or WP_Error on failure.
	 * @since 1.0.0
	 */
	/**
	 * Run the Topic Scout.
	 *
	 * @param array $rss_news Optional. Pre-fetched RSS news items (already keyword-filtered).
	 *                        When provided, the generated topics will be based on these headlines.
	 * @return array|WP_Error
	 */
	public function run( $rss_news = array() ) {
		// Load settings
		$settings = get_option( 'wbag_settings', array() );

		// Validate required settings
		if ( empty( $settings['api_key'] ) ) {
			return new WP_Error(
				'wbag_missing_api_key',
				__( 'API key not configured. Please set up your Claude API key first.', 'wp-blog-agent' )
			);
		}

		// Gather existing posts
		$existing_posts = $this->gather_existing_posts();

		if ( is_wp_error( $existing_posts ) ) {
			return $existing_posts;
		}

		// Gather competitor topics if competitor URLs are configured
		$competitor_topics = array();
		if ( ! empty( $settings['competitor_urls'] ) ) {
			$competitor_urls = array_filter( array_map( 'trim', explode( "\n", $settings['competitor_urls'] ) ) );
			$competitor_topics = $this->gather_competitor_topics( $competitor_urls );
		}

		// Build context string for Claude
		$context = $this->build_context( $existing_posts, $competitor_topics, $settings, $rss_news );

		// Call Claude API to generate topics
		$topics = WBAG_Api::get_instance()->generate_topics( $context );

		if ( is_wp_error( $topics ) ) {
			return $topics;
		}

		// Format topics with metadata
		$source = ! empty( $rss_news ) ? 'autopilot' : 'topic_scout';
		$formatted_topics = $this->format_topics( $topics, $source );

		// Filter out duplicates against existing posts, queue, and dismissed topics
		$formatted_topics = $this->filter_duplicates( $formatted_topics, $existing_posts );

		// Get current queue and merge with new topics
		$current_queue = self::get_queue();
		$merged_queue = array_merge( $current_queue, $formatted_topics );

		// Store in option
		update_option( 'wbag_topic_queue', $merged_queue );

		return $formatted_topics;
	}

	/**
	 * Gather existing published blog posts
	 *
	 * Uses WP_Query to get the last 50 published posts and extract relevant data.
	 *
	 * @return array|WP_Error Array of post summaries or WP_Error on failure.
	 * @since 1.0.0
	 * @access private
	 */
	private function gather_existing_posts() {
		$args = array(
			'post_type'      => 'post',
			'posts_per_page' => 50,
			'post_status'    => 'publish',
			'orderby'        => 'date',
			'order'          => 'DESC',
			'fields'         => 'ids',
		);

		$query = new WP_Query( $args );

		if ( ! $query->have_posts() ) {
			return array();
		}

		$posts = array();

		foreach ( $query->posts as $post_id ) {
			$post = get_post( $post_id );

			if ( ! $post ) {
				continue;
			}

			// Get categories
			$categories = wp_get_post_categories( $post_id, array( 'fields' => 'names' ) );

			// Get tags
			$tags = wp_get_post_tags( $post_id, array( 'fields' => 'names' ) );

			$posts[] = array(
				'title'    => $post->post_title,
				'slug'     => $post->post_name,
				'date'     => $post->post_date,
				'excerpt'  => $post->post_excerpt ? $post->post_excerpt : substr( wp_strip_all_tags( $post->post_content ), 0, 150 ),
				'categories' => $categories,
				'tags'     => $tags,
			);
		}

		wp_reset_postdata();

		return $posts;
	}

	/**
	 * Gather topics from competitor websites
	 *
	 * Fetches competitor pages and extracts post titles from HTML.
	 *
	 * @param array $competitor_urls Array of competitor URLs.
	 *
	 * @return array Array of competitor topic strings.
	 * @since 1.0.0
	 * @access private
	 */
	private function gather_competitor_topics( array $competitor_urls ) {
		$topics = array();
		$timeout = 15;

		foreach ( $competitor_urls as $url ) {
			// Validate URL
			if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
				continue;
			}

			// Extract domain for source attribution.
			$domain = wp_parse_url( $url, PHP_URL_HOST );
			$domain = preg_replace( '/^www\./', '', $domain );

			// Fetch the page
			$response = wp_remote_get(
				$url,
				array(
					'timeout'   => $timeout,
					'sslverify' => true,
					'user-agent' => 'Mozilla/5.0 (WP Blog Agent)',
				)
			);

			// Handle errors
			if ( is_wp_error( $response ) ) {
				continue;
			}

			$body = wp_remote_retrieve_body( $response );
			$http_code = wp_remote_retrieve_response_code( $response );

			if ( 200 !== $http_code || empty( $body ) ) {
				continue;
			}

			// Extract titles from HTML
			$extracted = $this->extract_titles_from_html( $body );

			if ( is_array( $extracted ) ) {
				foreach ( $extracted as $title ) {
					$topics[] = array(
						'title'  => $title,
						'source' => $domain,
					);
				}
			}
		}

		// Remove duplicates by title.
		$seen   = array();
		$unique = array();
		foreach ( $topics as $t ) {
			$norm = mb_strtolower( $t['title'], 'UTF-8' );
			if ( ! isset( $seen[ $norm ] ) ) {
				$seen[ $norm ] = true;
				$unique[]      = $t;
			}
		}

		return $unique;
	}

	/**
	 * Extract post titles from HTML content
	 *
	 * Looks for h1, h2, and article titles in HTML.
	 *
	 * @param string $html HTML content.
	 *
	 * @return array Array of extracted titles.
	 * @since 1.0.0
	 * @access private
	 */
	private function extract_titles_from_html( $html ) {
		$titles = array();

		// Extract h1 titles
		if ( preg_match_all( '/<h1[^>]*>(.+?)<\/h1>/i', $html, $matches ) ) {
			foreach ( $matches[1] as $title ) {
				$clean_title = trim( wp_strip_all_tags( $title ) );
				if ( ! empty( $clean_title ) && strlen( $clean_title ) < 200 ) {
					$titles[] = $clean_title;
				}
			}
		}

		// Extract h2 titles
		if ( preg_match_all( '/<h2[^>]*>(.+?)<\/h2>/i', $html, $matches ) ) {
			foreach ( $matches[1] as $title ) {
				$clean_title = trim( wp_strip_all_tags( $title ) );
				if ( ! empty( $clean_title ) && strlen( $clean_title ) < 200 ) {
					$titles[] = $clean_title;
				}
			}
		}

		return array_unique( $titles );
	}

	/**
	 * Build comprehensive context string for Claude API
	 *
	 * Includes niche, target audience, existing posts, competitor topics, and settings.
	 *
	 * @param array $existing_posts   Array of existing post data.
	 * @param array $competitor_topics Array of competitor topic strings.
	 * @param array $settings         Plugin settings.
	 *
	 * @return string Context string for Claude API.
	 * @since 1.0.0
	 * @access private
	 */
	private function build_context( array $existing_posts, array $competitor_topics, array $settings, array $rss_news = array() ) {
		$context = '';

		// Add niche information
		if ( ! empty( $settings['niche'] ) ) {
			$context .= "BLOG NICHE: " . sanitize_text_field( $settings['niche'] ) . "\n\n";
		}

		// Add RSS news context (when available, topics MUST be inspired by these headlines)
		if ( ! empty( $rss_news ) ) {
			$context .= "AKTUELLE NACHRICHTEN AUS RSS-FEEDS (heute abgerufen):\n";
			foreach ( $rss_news as $news_item ) {
				$context .= "- [" . $news_item['source'] . ", " . $news_item['date'] . "] " . $news_item['title'];
				if ( ! empty( $news_item['summary'] ) ) {
					$context .= " — " . mb_substr( $news_item['summary'], 0, 120 );
				}
				if ( ! empty( $news_item['matched_keyword'] ) ) {
					$context .= " [Keyword: " . $news_item['matched_keyword'] . "]";
				}
				$context .= "\n";
			}
			// Collect the matched keywords for emphasis.
			$matched_kws = array();
			foreach ( $rss_news as $ni ) {
				if ( ! empty( $ni['matched_keyword'] ) ) {
					$matched_kws[ mb_strtolower( trim( $ni['matched_keyword'] ), 'UTF-8' ) ] = $ni['matched_keyword'];
				}
			}
			$kw_hint = '';
			if ( ! empty( $matched_kws ) ) {
				$kw_hint = " Der Benutzer hat gezielt nach diesen Schlagwörtern gefiltert: " . implode( ', ', $matched_kws ) . ".";
			}

			$context .= "\n=== PFLICHT-ANWEISUNG FÜR THEMENVORSCHLÄGE ===\n"
				. "Die oben aufgelisteten Nachrichten wurden vom Benutzer per Schlagwortfilter vorselektiert." . $kw_hint . "\n"
				. "STRIKTE REGELN:\n"
				. "1. Jeder generierte Themenvorschlag MUSS sich DIREKT und ERKENNBAR auf eine oder mehrere der obigen Nachrichten beziehen.\n"
				. "2. Themenvorschläge, die KEINEN klaren Bezug zu den gelisteten Nachrichten haben, sind VERBOTEN.\n"
				. "3. Nutze die Nachrichten als Aufhänger und leite daraus eigenständige, wertvolle Blog-Beiträge ab.\n"
				. "4. Der Titel jedes Themenvorschlags muss erkennen lassen, auf welche Nachricht(en) er sich bezieht.\n"
				. "=== ENDE PFLICHT-ANWEISUNG ===\n\n";
		}

		// Add subtopics
		if ( ! empty( $settings['subtopics'] ) ) {
			$context .= "SUBTOPICS/CATEGORIES:\n";
			$subtopics = array_filter( array_map( 'trim', explode( ',', $settings['subtopics'] ) ) );
			foreach ( $subtopics as $subtopic ) {
				$context .= "- " . sanitize_text_field( $subtopic ) . "\n";
			}
			$context .= "\n";
		}

		// Add target audience
		if ( ! empty( $settings['target_audience'] ) ) {
			$context .= "TARGET AUDIENCE: " . sanitize_text_field( $settings['target_audience'] ) . "\n\n";
		}

		// Add existing posts
		if ( ! empty( $existing_posts ) ) {
			$context .= "EXISTING BLOG POSTS (" . count( $existing_posts ) . "):\n";
			foreach ( $existing_posts as $post ) {
				$context .= "- " . $post['title'] . " (Published: " . date( 'Y-m-d', strtotime( $post['date'] ) ) . ")\n";
				if ( ! empty( $post['categories'] ) ) {
					$context .= "  Categories: " . implode( ', ', $post['categories'] ) . "\n";
				}
			}
			$context .= "\n";
		}

		// Add competitor topics (with source domain)
		if ( ! empty( $competitor_topics ) ) {
			$context .= "COMPETITOR TOPICS FOUND (" . count( $competitor_topics ) . "):\n";
			foreach ( array_slice( $competitor_topics, 0, 30 ) as $ct ) {
				if ( is_array( $ct ) && ! empty( $ct['title'] ) ) {
					$context .= "- " . sanitize_text_field( $ct['title'] ) . " [Quelle: " . sanitize_text_field( $ct['source'] ) . "]\n";
				} elseif ( is_string( $ct ) ) {
					$context .= "- " . sanitize_text_field( $ct ) . "\n";
				}
			}
			$context .= "\n";
		}

		// Add dismissed/rejected topics (blacklist)
		$dismissed = get_option( 'wbag_dismissed_topics', array() );
		if ( ! empty( $dismissed ) && is_array( $dismissed ) ) {
			$context .= "REJECTED/DISMISSED TOPICS (do NOT suggest these or very similar ones):\n";
			foreach ( $dismissed as $d ) {
				$context .= "- " . ( $d['title'] ?? '' ) . "\n";
			}
			$context .= "\n";
		}

		// Add topics already in the queue
		$current_queue = self::get_queue();
		if ( ! empty( $current_queue ) ) {
			$context .= "TOPICS ALREADY IN QUEUE (do NOT suggest duplicates):\n";
			foreach ( $current_queue as $qt ) {
				$context .= "- " . ( $qt['title'] ?? '' ) . "\n";
			}
			$context .= "\n";
		}

		// Add generation parameters
		$batch_size = ! empty( $settings['topic_count'] ) ? intval( $settings['topic_count'] ) : 5;
		$context .= "GENERATION PARAMETERS:\n";
		$context .= "- Number of topics to generate: " . $batch_size . "\n";
		$context .= "- Language: " . ( ! empty( $settings['language'] ) ? sanitize_text_field( $settings['language'] ) : 'en' ) . "\n";
		$context .= "\nIMPORTANT RULES:\n";
		$context .= "- NEVER suggest topics that are identical or very similar to existing blog posts, rejected topics, or topics already in queue.\n";
		$context .= "- Each suggestion must cover a clearly different angle or subject.\n";
		$context .= "- Each topic MUST use a different content format (e.g. how-to, comparison, listicle, myth-busting, case study, trend analysis, deep-dive, opinion).\n";
		$context .= "- Titles must be varied in structure — never start multiple titles with the same word or pattern.\n";
		$context .= "- Be creative and specific: use concrete details, numbers, tool names, or provocative questions instead of generic phrasing.\n";

		return $context;
	}

	/**
	 * Format API response topics with metadata
	 *
	 * Adds created_at timestamp and status to each topic.
	 *
	 * @param array $topics Array of topics from API.
	 *
	 * @return array Array of formatted topics with metadata.
	 * @since 1.0.0
	 * @access private
	 */
	private function format_topics( array $topics, $source = 'topic_scout' ) {
		$current_time = current_time( 'mysql' );
		$formatted = array();

		foreach ( $topics as $topic ) {
			// Ensure required fields exist
			if ( ! isset( $topic['title'] ) || ! isset( $topic['primary_keyword'] ) ) {
				continue;
			}

			// Build slug if not provided
			if ( ! isset( $topic['slug'] ) ) {
				$topic['slug'] = sanitize_title( $topic['title'] );
			}

			// Add metadata
			$topic['status'] = 'pending';
			$topic['created_at'] = $current_time;
			$topic['source'] = $source;

			// Ensure all expected fields have values
			$topic['search_intent'] = $topic['search_intent'] ?? 'informational';
			$topic['content_gap'] = $topic['content_gap'] ?? '';
			$topic['subtopic'] = $topic['subtopic'] ?? '';
			$topic['difficulty'] = $topic['difficulty'] ?? 50;
			$topic['priority_score'] = $topic['priority_score'] ?? 5;
			$topic['description'] = $topic['description'] ?? '';
			$topic['content_type'] = $topic['content_type'] ?? '';
			$topic['competitor_inspired'] = $topic['competitor_inspired'] ?? '';

			$formatted[] = $topic;
		}

		return $formatted;
	}

	/**
	 * Filter out duplicate topics
	 *
	 * Compares new topics against existing posts, current queue, and dismissed topics.
	 * Uses normalized title comparison with a similarity threshold.
	 *
	 * @param array $new_topics     Array of new formatted topics.
	 * @param array $existing_posts Array of existing post data.
	 *
	 * @return array Filtered topics with duplicates removed.
	 * @since 1.3.36
	 * @access private
	 */
	private function filter_duplicates( array $new_topics, array $existing_posts ) {
		// Build list of all known titles to check against
		$known_titles = array();

		// Add existing published post titles
		foreach ( $existing_posts as $post ) {
			if ( ! empty( $post['title'] ) ) {
				$known_titles[] = $this->normalize_title( $post['title'] );
			}
		}

		// Add current queue titles
		$current_queue = self::get_queue();
		foreach ( $current_queue as $qt ) {
			if ( ! empty( $qt['title'] ) ) {
				$known_titles[] = $this->normalize_title( $qt['title'] );
			}
		}

		// Add dismissed/blacklisted titles
		$dismissed = get_option( 'wbag_dismissed_topics', array() );
		if ( is_array( $dismissed ) ) {
			foreach ( $dismissed as $d ) {
				if ( ! empty( $d['title'] ) ) {
					$known_titles[] = $this->normalize_title( $d['title'] );
				}
			}
		}

		// Filter new topics
		$filtered = array();
		foreach ( $new_topics as $topic ) {
			$normalized = $this->normalize_title( $topic['title'] ?? '' );
			if ( empty( $normalized ) ) {
				continue;
			}

			$is_duplicate = false;
			foreach ( $known_titles as $known ) {
				// Check for exact match or high similarity (> 75%)
				if ( $normalized === $known ) {
					$is_duplicate = true;
					break;
				}
				similar_text( $normalized, $known, $percent );
				if ( $percent > 75 ) {
					$is_duplicate = true;
					break;
				}
			}

			if ( ! $is_duplicate ) {
				$filtered[] = $topic;
				// Also add to known list to prevent duplicates within the batch
				$known_titles[] = $normalized;
			}
		}

		return $filtered;
	}

	/**
	 * Normalize a title for comparison
	 *
	 * Lowercases, removes punctuation, and trims whitespace.
	 *
	 * @param string $title The title to normalize.
	 *
	 * @return string Normalized title.
	 * @since 1.3.36
	 * @access private
	 */
	private function normalize_title( $title ) {
		$title = mb_strtolower( $title, 'UTF-8' );
		$title = preg_replace( '/[^\p{L}\p{N}\s]/u', '', $title );
		$title = preg_replace( '/\s+/', ' ', $title );
		return trim( $title );
	}

	/**
	 * Get current topic queue
	 *
	 * Static method that returns the topic queue from options.
	 *
	 * @return array Topic queue array.
	 * @since 1.0.0
	 */
	public static function get_queue() {
		$queue = get_option( 'wbag_topic_queue', array() );

		return is_array( $queue ) ? $queue : array();
	}

	/**
	 * Approve a topic in the queue
	 *
	 * Static method that marks a topic as 'approved' and updates the option.
	 *
	 * @param int $index Index of the topic to approve.
	 *
	 * @return bool True if successful, false on failure.
	 * @since 1.0.0
	 */
	public static function approve_topic( $index ) {
		$queue = self::get_queue();
		$index = intval( $index );

		// Validate index
		if ( ! isset( $queue[ $index ] ) ) {
			return false;
		}

		// Update status
		$queue[ $index ]['status'] = 'approved';

		// Update option
		return update_option( 'wbag_topic_queue', $queue );
	}

	/**
	 * Delete a topic from the queue
	 *
	 * Static method that removes a topic and re-indexes the array.
	 *
	 * @param int $index Index of the topic to delete.
	 *
	 * @return bool True if successful, false on failure.
	 * @since 1.0.0
	 */
	public static function delete_topic( $index ) {
		$queue = self::get_queue();
		$index = intval( $index );

		// Validate index
		if ( ! isset( $queue[ $index ] ) ) {
			return false;
		}

		// Remove topic
		unset( $queue[ $index ] );

		// Re-index array
		$queue = array_values( $queue );

		// Update option
		return update_option( 'wbag_topic_queue', $queue );
	}

	/**
	 * Get the next approved topic from the queue
	 *
	 * Static method that returns the first topic with 'approved' status.
	 *
	 * @return array|null Topic array or null if no approved topics found.
	 * @since 1.0.0
	 */
	public static function get_next_approved() {
		$queue = self::get_queue();

		foreach ( $queue as $topic ) {
			if ( isset( $topic['status'] ) && 'approved' === $topic['status'] ) {
				return $topic;
			}
		}

		return null;
	}
}
