<?php
/**
 * This file is part of the Wisewand WordPress plugin.
 *
 * @package Wisewand
 * @since 1.0.0
 * @license GPL-2.0-or-later
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
namespace Wisewand\RSS;

use Wisewand\Utils\Publish_Mode_Migrator;

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

use Wisewand\Logging\Logger;
use Wisewand\Utils\Options;
use Wisewand\Utils\Wisewand_API_Client;
use Wisewand\Content\Content_Manager;
use Wisewand\Calendar\Calendar_Entry_Manager;

// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log -- error_log() used within WP_DEBUG conditionals

class RSS_Sync_Engine {

	private $api_client;
	private $feed_manager;
	private $article_manager;
	private $content_manager;
	private $calendar_manager;
	private $calendar_bridge;
	private $logger;
	private $options;
	private $rate_limiter;

	/**
	 * Collected sync logs for UI display
	 *
	 * @since 1.2.0
	 * @var array
	 */
	private $sync_logs = array();

	public function __construct(
		Wisewand_API_Client $api_client,
		RSS_Feed_Manager $feed_manager,
		RSS_Article_Manager $article_manager,
		Content_Manager $content_manager,
		Calendar_Entry_Manager $calendar_manager,
		RSS_Calendar_Bridge $calendar_bridge,
		Logger $logger,
		Options $options,
		$rate_limiter = null
	) {
		$this->api_client       = $api_client;
		$this->feed_manager     = $feed_manager;
		$this->article_manager  = $article_manager;
		$this->content_manager  = $content_manager;
		$this->calendar_manager = $calendar_manager;
		$this->calendar_bridge  = $calendar_bridge;
		$this->logger           = $logger;
		$this->options          = $options;
		$this->rate_limiter     = $rate_limiter ?? new \Wisewand\Utils\Rate_Limiter( $this->logger );
	}

	/**
	 * Add log entry to sync logs (for UI display)
	 *
	 * @since 1.2.0
	 * @param string $message Log message.
	 * @param string $level   Log level (info, warning, error, success).
	 */
	private function add_sync_log( $message, $level = 'info' ) {
		$this->sync_logs[] = array(
			'time'    => current_time( 'H:i:s' ),
			'message' => $message,
			'level'   => $level,
		);
	}

	/**
	 * Get collected sync logs
	 *
	 * @since 1.2.0
	 * @return array Sync logs.
	 */
	public function get_sync_logs() {
		return $this->sync_logs;
	}

	/**
	 * Clear sync logs
	 *
	 * @since 1.2.0
	 */
	public function clear_sync_logs() {
		$this->sync_logs = array();
	}

	/**
	 * Sync single feed: fetch metadata, discover articles, import pending
	 *
	 * @param int  $feed_id Local feed ID
	 * @param bool $force_import Force import even if not scheduled
	 * @return array Sync results
	 */
	public function sync_feed( $feed_id, $force_import = false ) {
		$feed = $this->feed_manager->get_feed( $feed_id );
		if ( ! $feed ) {
			return new \WP_Error( 'feed_not_found', __( 'Feed not found', 'wisewand-ai-content-writer-seo-content-generator' ) );
		}

		$wisewand_feed_id = $feed['wisewand_feed_id'];

		// v1.2.0: Add log for UI display.
		$this->add_sync_log( "Starting sync for {$feed['feed_name']}", 'info' );

		$this->logger->info(
			'Starting feed sync',
			array(
				'feed_id'          => $feed_id,
				'wisewand_feed_id' => $wisewand_feed_id,
				'feed_name'        => $feed['feed_name'],
				'force_import'     => $force_import,
			)
		);

		$results = array(
			'feed_id'             => $feed_id,
			'feed_name'           => $feed['feed_name'],
			'articles_discovered' => 0,
			'articles_imported'   => 0,
			'articles_failed'     => 0,
			'errors'              => array(),
		);

		try {
			// Step 1: Refresh feed metadata from Wisewand
			$wisewand_feed = $this->api_client->get_feed( $wisewand_feed_id );
			if ( is_wp_error( $wisewand_feed ) ) {
				throw new \Exception( $wisewand_feed->get_error_message() );
			}

			$this->feed_manager->sync_feed_from_wisewand( $wisewand_feed );

			// Step 2: Fetch articles from this feed
			// v1.2.0: Reduced default batch size from 3 to 2 articles.
			// Optimized for exponential backoff testing (2s → 5s → 15s).
			// With smaller batches and longer delays (5s inter-article):
			// - Faster iteration during v1.2.0 testing phase
			// - Better rate limit compliance with new backoff strategy
			// - Can be increased after confirming stability
			// Users can override via feed settings if their server allows larger batches.
			$max_articles  = $feed['max_articles_per_sync'] ?? 2;
			$status_filter = $feed['import_status_filter'] ?? 'success';

			// Route to correct API endpoint based on feed_type
			$feed_type = $feed['feed_type'] ?? 'articles';

			// Validate feed_type
			$valid_types = array( 'articles', 'discoverarticles' );
			if ( ! in_array( $feed_type, $valid_types, true ) ) {
				$this->logger->warning(
					'Invalid feed_type encountered, defaulting to articles',
					array(
						'feed_id'          => $feed_id,
						'feed_type'        => $feed_type,
						'wisewand_feed_id' => $wisewand_feed_id,
					)
				);
				$feed_type = 'articles';
			}

			// Call appropriate API method
			// Note: Removed 'published' filter - we track import status in our own database
			// This allows syncing articles even if they were published in Wisewand
			if ( 'discoverarticles' === $feed_type ) {
				// IMPORTANT: discoverarticles don't have a status field - they are discovery suggestions
				// Don't apply status filtering for discoverarticles
				$articles_response = $this->api_client->get_feed_discoverarticles(
					$wisewand_feed_id,
					0,
					$max_articles
				);
			} else {
				$articles_response = $this->api_client->get_feed_articles(
					$wisewand_feed_id,
					0,
					$max_articles,
					array(
						'status' => $status_filter,
					)
				);
			}

			if ( is_wp_error( $articles_response ) ) {
				throw new \Exception( $articles_response->get_error_message() );
			}

			// Log which endpoint was used

			$total_available = $articles_response['count'] ?? 0;
			$articles        = $articles_response['items'] ?? array();

			// v1.2.0: Add log for UI display.
			$this->add_sync_log( "Found {$total_available} articles - processing " . count( $articles ) . ' items', 'info' );

			// Step 3: Discover new articles (add to tracking table)
			foreach ( $articles as $article ) {
				$article_id = $article['id'] ?? 'NO_ID';

				// FILTER: Skip discoverarticles without generated output.
				// Prevents tracking articles that cannot be imported yet.
				if ( 'discoverarticles' === $feed_type && empty( $article['last_output_id'] ) ) {

					continue; // Skip to next article.
				}

				// Check if article exists and get its status.
				$existing_article = $this->article_manager->check_article_exists( $article_id );

				if ( $existing_article ) {
					// Determine if we should skip or refresh this article.
					$should_skip = $this->should_skip_article( $existing_article, $force_import );

					if ( $should_skip ) {

						continue;
					}

					// Article needs refresh (pending, failed, or force_import).

				} else {
					// New article being discovered.

					// v1.2.0: Add log for UI display.
					$this->add_sync_log( "Discovering NEW article {$article_id}", 'info' );

				}

				$this->article_manager->discover_article( $article, $feed_id, $wisewand_feed_id );
				++$results['articles_discovered'];
			}

			// Step 4: Import pending articles (if auto-import enabled or forced)
			// DEBUG: Log import gate values to diagnose blocking issue

			if ( $feed['auto_import_enabled'] || $force_import ) {
				$pending_articles = $this->article_manager->get_pending_articles_for_feed( $feed_id, $max_articles );

				foreach ( $pending_articles as $index => $article_data ) {
					// v1.2.0: Inter-article delay increased to 5 seconds (from 2s).
					// Prevents cascading rate limits by allowing API cooldown between articles.
					// With exponential backoff (2s → 5s → 15s), this base delay reduces
					// likelihood of hitting rate limits on subsequent articles.
					if ( $index > 0 ) {
						$this->logger->debug(
							'Inter-article delay',
							array(
								'delay_seconds' => 5,
								'article_index' => $index,
								'feed_id'       => $feed_id,
								'reason'        => 'v1.2.0: Prevent cascading rate limits (API: 60 req/min)',
							)
						);
						$this->rate_limiter->smart_delay( 'wisewand_import', 5 );
					}

					// v1.2.0: Add log for UI display.
					$this->add_sync_log( "Importing article: {$article_data['article_title']}", 'info' );

					$import_result = $this->import_article( $article_data, $feed );

					if ( is_wp_error( $import_result ) ) {
						++$results['articles_failed'];
						// v1.2.0: Add log for UI display.
						$this->add_sync_log( "✗ Failed: {$article_data['article_title']} - {$import_result->get_error_message()}", 'error' );

						$results['errors'][] = $import_result->get_error_message();
						// v1.2.0: Add log for UI display.
						$this->add_sync_log( "✓ Imported: {$article_data['article_title']}", 'success' );

					} else {
						++$results['articles_imported'];
					}
				}
			}

			// Step 5: Update feed statistics
			$stats = $this->article_manager->get_feed_statistics( $feed_id );
			$this->feed_manager->update_feed_statistics(
				$feed_id,
				array(
					'total_articles'    => $stats['total'],
					'articles_imported' => $stats['imported'],
					'articles_pending'  => $stats['pending'],
					'articles_failed'   => $stats['failed'],
					'last_sync_at'      => current_time( 'mysql' ),
				)
			);

			if ( $results['articles_imported'] > 0 ) {
				$this->feed_manager->update_feed_statistics(
					$feed_id,
					array(
						'last_import_at' => current_time( 'mysql' ),
					)
				);
			}

			// Step 6: Calculate next sync time
			$this->feed_manager->calculate_next_sync_time( $feed_id );

			// Auto-purge any remaining deleted articles for this feed.
			// This catches 404s that were marked deleted in previous syncs.
			if ( apply_filters( 'wisewand_rss_auto_purge_deleted', true ) ) {
				$purge_stats = $this->article_manager->purge_deleted_articles( $feed_id );

				if ( $purge_stats['deleted_count'] > 0 ) {
					$this->logger->info(
						'Auto-purged deleted articles after feed sync',
						array(
							'feed_id'         => $feed_id,
							'articles_purged' => $purge_stats['deleted_count'],
						)
					);

					$results['auto_cleanup'] = $purge_stats;
				}
			}

			$this->logger->info(
				'Feed sync completed',
				array(
					'feed_id'    => $feed_id,
					'discovered' => $results['articles_discovered'],
					'imported'   => $results['articles_imported'],
					'failed'     => $results['articles_failed'],
				)
			);

			// v1.2.0: Add final completion log for UI display.
			$this->add_sync_log(
				"✅ Feed sync completed: {$results['articles_discovered']} discovered, {$results['articles_imported']} imported, {$results['articles_failed']} failed",
				'success'
			);

			return $results;

		} catch ( \Exception $e ) {
			$this->logger->error(
				'Feed sync failed',
				array(
					'feed_id' => $feed_id,
					'error'   => $e->getMessage(),
				)
			);

			return new \WP_Error( 'sync_failed', $e->getMessage() );
		}
	}

	/**
	 * Discover new articles from feed without importing
	 *
	 * @since 2.2.0
	 * @param int $feed_id Feed ID.
	 * @return array|WP_Error Result with new_articles count.
	 */
	public function discover_articles( $feed_id ) {
		$feed = $this->feed_manager->get_feed( $feed_id );
		if ( ! $feed ) {
			return new \WP_Error(
				'feed_not_found',
				__( 'Feed not found', 'wisewand-ai-content-writer-seo-content-generator' )
			);
		}

		$wisewand_feed_id = $feed['wisewand_feed_id'];

		$this->logger->info(
			'Starting article discovery',
			array(
				'feed_id'          => $feed_id,
				'wisewand_feed_id' => $wisewand_feed_id,
				'feed_name'        => $feed['feed_name'],
			)
		);

		$result = array(
			'feed_id'      => $feed_id,
			'feed_name'    => $feed['feed_name'],
			'new_articles' => 0,
			'errors'       => array(),
		);

		try {
			// Step 1: Refresh feed metadata from Wisewand
			$wisewand_feed = $this->api_client->get_feed( $wisewand_feed_id );
			if ( is_wp_error( $wisewand_feed ) ) {
				throw new \Exception( $wisewand_feed->get_error_message() );
			}

			$this->feed_manager->sync_feed_from_wisewand( $wisewand_feed );

			// Step 2: Fetch articles from Wisewand API
			$max_articles  = $feed['max_articles_per_sync'] ?? 2;
			$status_filter = $feed['import_status_filter'] ?? 'success';
			$feed_type     = $feed['feed_type'] ?? 'articles';

			// Validate feed_type
			$valid_types = array( 'articles', 'discoverarticles' );
			if ( ! in_array( $feed_type, $valid_types, true ) ) {
				$feed_type = 'articles';
			}

			// Call appropriate API method
			if ( 'discoverarticles' === $feed_type ) {
				$articles_response = $this->api_client->get_feed_discoverarticles(
					$wisewand_feed_id,
					0,
					$max_articles
				);
			} else {
				$articles_response = $this->api_client->get_feed_articles(
					$wisewand_feed_id,
					0,
					$max_articles,
					array( 'status' => $status_filter )
				);
			}

			if ( is_wp_error( $articles_response ) ) {
				throw new \Exception( $articles_response->get_error_message() );
			}

			$articles = $articles_response['items'] ?? array();

			// Step 3: Save new articles with status='discovered'
			foreach ( $articles as $article ) {
				$article_id = $article['id'] ?? 'NO_ID';

				// Skip discoverarticles without generated output
				if ( 'discoverarticles' === $feed_type && empty( $article['last_output_id'] ) ) {
					continue;
				}

				// Check if article already exists
				$existing_article = $this->article_manager->check_article_exists( $article_id );

				if ( $existing_article ) {
					// Article already discovered/imported, skip
					continue;
				}

				// New article - save with status='discovered'
				$this->article_manager->discover_article( $article, $feed_id, $wisewand_feed_id );
				++$result['new_articles'];
			}

			$this->logger->info(
				'Article discovery completed',
				array(
					'feed_id'      => $feed_id,
					'new_articles' => $result['new_articles'],
				)
			);

			return $result;

		} catch ( \Exception $e ) {
			$this->logger->error(
				'Article discovery failed',
				array(
					'feed_id' => $feed_id,
					'error'   => $e->getMessage(),
				)
			);

			return new \WP_Error( 'discovery_failed', $e->getMessage() );
		}
	}

	/**
	 * Import discovered articles for a feed
	 *
	 * @since 2.2.0
	 * @param int $feed_id Feed ID.
	 * @return array|WP_Error Result with imported_count.
	 */
	public function import_pending_articles( $feed_id ) {
		$feed = $this->feed_manager->get_feed( $feed_id );
		if ( ! $feed ) {
			return new \WP_Error(
				'feed_not_found',
				__( 'Feed not found', 'wisewand-ai-content-writer-seo-content-generator' )
			);
		}

		$this->logger->info(
			'Starting article import',
			array(
				'feed_id'   => $feed_id,
				'feed_name' => $feed['feed_name'],
			)
		);

		$result = array(
			'feed_id'        => $feed_id,
			'feed_name'      => $feed['feed_name'],
			'imported_count' => 0,
			'failed_count'   => 0,
			'errors'         => array(),
		);

		try {
			// Get articles with status='discovered'
			$max_articles     = $feed['max_articles_per_sync'] ?? 2;
			$pending_articles = $this->article_manager->get_pending_articles_for_feed( $feed_id, $max_articles );

			if ( empty( $pending_articles ) ) {
				$this->logger->info(
					'No pending articles to import',
					array( 'feed_id' => $feed_id )
				);
				return $result;
			}

			// Import each article
			foreach ( $pending_articles as $index => $article_data ) {
				// Inter-article delay (prevent rate limits)
				if ( $index > 0 ) {
					$this->logger->debug(
						'Inter-article delay',
						array(
							'delay_seconds' => 5,
							'article_index' => $index,
							'feed_id'       => $feed_id,
						)
					);
					$this->rate_limiter->smart_delay( 'wisewand_import', 5 );
				}

				$import_result = $this->import_article( $article_data, $feed );

				if ( is_wp_error( $import_result ) ) {
					++$result['failed_count'];
					$result['errors'][] = $import_result->get_error_message();
				} else {
					++$result['imported_count'];
				}
			}

			// Update feed statistics
			$stats = $this->article_manager->get_feed_statistics( $feed_id );
			$this->feed_manager->update_feed_statistics(
				$feed_id,
				array(
					'total_articles'    => $stats['total'],
					'articles_imported' => $stats['imported'],
					'articles_pending'  => $stats['pending'],
					'articles_failed'   => $stats['failed'],
					'last_sync_at'      => current_time( 'mysql' ),
				)
			);

			if ( $result['imported_count'] > 0 ) {
				$this->feed_manager->update_feed_statistics(
					$feed_id,
					array( 'last_import_at' => current_time( 'mysql' ) )
				);
			}

			// Calculate next sync time
			$this->feed_manager->calculate_next_sync_time( $feed_id );

			$this->logger->info(
				'Article import completed',
				array(
					'feed_id'  => $feed_id,
					'imported' => $result['imported_count'],
					'failed'   => $result['failed_count'],
				)
			);

			return $result;

		} catch ( \Exception $e ) {
			$this->logger->error(
				'Article import failed',
				array(
					'feed_id' => $feed_id,
					'error'   => $e->getMessage(),
				)
			);

			return new \WP_Error( 'import_failed', $e->getMessage() );
		}
	}

	/**
	 * Check if exception is a rate limit error
	 *
	 * @since 1.0.10
	 * @param \Exception $e Exception object.
	 * @return bool True if rate limit error.
	 */
	private function is_rate_limit_exception( $e ) {
		$message = strtolower( $e->getMessage() );

		$rate_limit_indicators = array(
			'rate limit',
			'too many requests',
			'429',
			'slow down',
			'quota exceeded',
			'throttle',
			'try again later',
			'please wait',        // NEW: Catches "Please wait X seconds before retrying"
			'before retrying',    // NEW: Catches "X seconds before retrying"
		);

		foreach ( $rate_limit_indicators as $indicator ) {
			if ( strpos( $message, $indicator ) !== false ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Extract wait time from rate limit error message with exponential backoff
	 *
	 * Implements exponential backoff strategy for retries:
	 * - Attempt 1: Uses API-suggested wait time with 5s minimum (increased from 3s)
	 * - Attempt 2: 10 seconds (increased from 5s based on observed API behavior)
	 * - Attempt 3: 20 seconds (increased from 15s)
	 * - Attempt 4: 40 seconds (new - final attempt)
	 *
	 * Based on production logs showing API needs longer cooldown periods.
	 *
	 * @since 1.0.10
	 * @since 1.2.0 Added exponential backoff with attempt parameter.
	 * @since 1.2.1 Increased backoff times based on observed API rate limit behavior.
	 *
	 * @param \Exception $e Exception object.
	 * @param int        $attempt Current attempt number (1-4).
	 * @return int Wait time in seconds with exponential backoff.
	 */
	private function extract_wait_time( $e, $attempt = 1 ) {
		// v1.2.1: Increased exponential backoff based on observed API behavior.
		// Production logs show API needs longer cooldown than initially expected.

		if ( $attempt === 2 ) {
			return 10;  // Second attempt: 10 seconds (increased from 5).
		}

		if ( $attempt === 3 ) {
			return 20; // Third attempt: 20 seconds (increased from 15).
		}

		if ( $attempt >= 4 ) {
			return 40; // Fourth attempt: 40 seconds (new).
		}

		// First attempt: try to extract API-suggested wait time with higher minimum.
		$message = $e->getMessage();

		// Look for "wait X seconds" or "retry after X seconds".
		if ( preg_match( '/wait\s+(\d+)\s+seconds?/i', $message, $matches ) ) {
			$wait_time = absint( $matches[1] );
			// v1.2.1: Increased minimum from 2s to 5s based on API behavior.
			return max( min( $wait_time, 60 ), 5 );
		}

		if ( preg_match( '/retry\s+after\s+(\d+)\s+seconds?/i', $message, $matches ) ) {
			$wait_time = absint( $matches[1] );
			return max( min( $wait_time, 60 ), 5 );
		}

		// Look for standalone numbers in rate limit messages.
		if ( preg_match( '/(\d+)\s+seconds?/i', $message, $matches ) ) {
			$wait_time = absint( $matches[1] );
			return max( min( $wait_time, 60 ), 5 );
		}

		// v1.2.1: Increased default from 2s to 5s for first attempt.
		return 5;
	}

	/**
	 * Determine if article should be skipped during discovery phase
	 *
	 * Skip logic:
	 * - SKIP if imported successfully AND has valid WordPress post (unless force_import)
	 * - SKIP if status is 'skipped' or 'deleted'
	 * - DO NOT SKIP if status is 'pending' (needs import)
	 * - DO NOT SKIP if status is 'failed' (needs retry)
	 * - DO NOT SKIP if force_import is true (re-import everything)
	 *
	 * @since 1.0.11
	 * @param array $existing_article Article data from database.
	 *                                Keys: import_status, wordpress_post_id
	 * @param bool  $force_import     Whether force import flag is set.
	 * @return bool True if should skip, false if should process.
	 */
	private function should_skip_article( $existing_article, $force_import ) {
		$import_status = $existing_article['import_status'] ?? '';
		$wp_post_id    = $existing_article['wordpress_post_id'] ?? null;

		// Never skip if force_import is enabled.
		if ( $force_import ) {
			return false;
		}

		// Skip if permanently excluded.
		if ( in_array( $import_status, array( 'skipped', 'deleted' ), true ) ) {
			return true;
		}

		// Skip only if successfully imported with valid WordPress post.
		if ( 'imported' === $import_status && ! empty( $wp_post_id ) && is_numeric( $wp_post_id ) ) {
			// Verify the WordPress post still exists.
			$post_exists = get_post( $wp_post_id );
			if ( $post_exists ) {
				return true;
			}

			// Post was deleted, need to re-import.

			return false;
		}

		// Do NOT skip pending, failed, importing, or articles without valid WP post.
		// These need to proceed to import phase.
		return false;
	}

	/**
	 * Import single article using existing Content_Importer
	 *
	 * @param array $article_data Article tracking data.
	 * @param array $feed Feed settings.
	 * @param int   $attempt Current attempt number (1-based).
	 * @param int   $max_attempts Maximum retry attempts.
	 * @return int|WP_Error WordPress post ID or error
	 */
	private function import_article( $article_data, $feed, $attempt = 1, $max_attempts = 4 ) {
		$wisewand_article_id = $article_data['wisewand_article_id'];
		$article_tracking_id = $article_data['id'];

		// Mark as importing
		$this->article_manager->mark_as_importing( $article_tracking_id );

		try {
			// RSS v2.1.4: publish_mode already resolved by RSS_Feed_Manager
			$publish_mode = $feed['publish_mode'] ?? 'draft';

			// Build import options.
			$import_options = array();

			// Initialize scheduled_time (used in calendar and logging).
			$scheduled_time = null;

			// v2.1.3: Check if new simple scheduling is enabled (global publish_mode option).
			// If enabled, use calculate_simple_schedule() instead of complex time ranges.
			$global_rss_publish_mode = get_option( 'wisewand_rss_publish_mode', null );
			// Translate old values to new values for backward compatibility
			if ( null !== $global_rss_publish_mode ) {
				$global_rss_publish_mode = \Wisewand\Utils\Publish_Mode_Migrator::translate_old_to_new( $global_rss_publish_mode );
			}

			if ( null !== $global_rss_publish_mode ) {
				// NEW SCHEDULING SYSTEM (v2.1.3): Simple time-based scheduling.
				if ( ! class_exists( 'Wisewand\\RSS\\RSS_Publication_Scheduler' ) ) {
					require_once WISEWAND_PLUGIN_DIR . 'includes/RSS/RSS_Publication_Scheduler.php';
				}

				$scheduler      = new \Wisewand\RSS\RSS_Publication_Scheduler( $this->options );
				$scheduled_time = $scheduler->calculate_simple_schedule( $feed );

				if ( $scheduled_time ) {
					// Scheduled mode - article scheduled for specific time.
					$import_options['post_date']   = $scheduled_time;
					$import_options['post_status'] = 'future';

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( sprintf(
							'Wisewand RSS Sync: Article scheduled for %s (feed_id=%d, mode=%s)',
							$scheduled_time,
							$feed['id'] ?? 0,
							$global_rss_publish_mode
						) );
					}
				} elseif ( 'immediate' === $global_rss_publish_mode ) {
					// Immediate mode.
					$scheduled_time                = current_time( 'mysql' );
					$import_options['post_date']   = $scheduled_time;
					$import_options['post_status'] = 'publish';

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( sprintf(
							'Wisewand RSS Sync: Article published immediately (feed_id=%d)',
							$feed['id'] ?? 0
						) );
					}
				} else {
					// Draft mode.
					$scheduled_time                = current_time( 'mysql' );
					$import_options['post_date']   = $scheduled_time;
					$import_options['post_status'] = 'draft';

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( sprintf(
							'Wisewand RSS Sync: Article saved as draft (feed_id=%d)',
							$feed['id'] ?? 0
						) );
					}
				}
			} else {
				// LEGACY SCHEDULING SYSTEM: Complex time range/day-based scheduling.
				// v1.4.0: Three publish modes.
				if ( 'schedule' === $publish_mode ) {
					// EXISTING BEHAVIOR: Schedule for future publication.
					if ( ! class_exists( 'Wisewand\\RSS\\RSS_Publication_Scheduler' ) ) {
						require_once WISEWAND_PLUGIN_DIR . 'includes/RSS/RSS_Publication_Scheduler.php';
					}

					$scheduler      = new \Wisewand\RSS\RSS_Publication_Scheduler( $this->options );
					$scheduled_time = $scheduler->calculate_publication_time( $feed, current_time( 'mysql' ) );

					$import_options['post_date']   = $scheduled_time;
					$import_options['post_status'] = 'future'; // WordPress auto-sets based on post_date.

				} elseif ( $publish_mode === 'publish' ) {
					// NEW: Immediate publication.
					$scheduled_time                = current_time( 'mysql' ); // Same as publication time.
					$import_options['post_date']   = $scheduled_time;
					$import_options['post_status'] = 'publish';

				} elseif ( $publish_mode === 'draft' ) {
					// NEW: Import as draft for review.
					$scheduled_time                = current_time( 'mysql' ); // Set to current time for tracking.
					$import_options['post_date']   = $scheduled_time;
					$import_options['post_status'] = 'draft';

				} else {
					// Fallback to schedule mode for safety.
					if ( ! class_exists( 'Wisewand\\RSS\\RSS_Publication_Scheduler' ) ) {
						require_once WISEWAND_PLUGIN_DIR . 'includes/RSS/RSS_Publication_Scheduler.php';
					}

					$scheduler      = new \Wisewand\RSS\RSS_Publication_Scheduler( $this->options );
					$scheduled_time = $scheduler->calculate_publication_time( $feed, current_time( 'mysql' ) );

					$import_options['post_date']   = $scheduled_time;
					$import_options['post_status'] = 'future';

				}
			}

			// Add author if specified
			if ( ! empty( $feed['target_author'] ) ) {
				$import_options['author_id'] = $feed['target_author'];
			}

			// v1.3.16: CRITICAL FIX - Validate categories before assigning
			$has_manual_cats = $this->has_manual_categories( $feed );
			$ai_enabled      = ! empty( $feed['use_ai_category_matching'] );

			if ( $has_manual_cats ) {
				// Validate manual categories BEFORE using them
				$validation = $this->validate_category_ids(
					$feed['target_categories'],
					'auto-import',
					$feed['id']
				);

				if ( ! empty( $validation['valid'] ) ) {
					// PRIORITY 1: Use validated manual categories
					$import_options['categories']               = $validation['valid'];
					$import_options['use_ai_category_matching'] = false;

					// Warn if some categories were invalid
					if ( ! empty( $validation['invalid'] ) ) {
						$invalid_ids = implode( ', ', $validation['invalid'] );
						$this->logger->warning(
							'Some manual categories are invalid and will be skipped',
							array(
								'feed_id'     => $feed['id'],
								'feed_name'   => $feed['feed_name'],
								'invalid_ids' => $validation['invalid'],
								'valid_ids'   => $validation['valid'],
							)
						);

					}
				} else {
					// All manual categories are invalid - fallback to AI or default
					$this->logger->error(
						'All manual categories are invalid - falling back to AI/default',
						array(
							'feed_id'     => $feed['id'],
							'feed_name'   => $feed['feed_name'],
							'invalid_ids' => $validation['invalid'],
						)
					);

					// Enable AI matching as fallback if available
					if ( $ai_enabled ) {
						$import_options['use_ai_category_matching'] = true;
					}
					// Otherwise will use WordPress default_category
				}
			} elseif ( $ai_enabled ) {
				// PRIORITY 2: AI Category Matching
				$import_options['use_ai_category_matching'] = true;
			}
			// PRIORITY 3: No categories set - will use WordPress default_category

			// Use existing Content_Importer (same as manual import!)
			if ( ! class_exists( 'Wisewand\\Utils\\Content_Importer' ) ) {
				require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Content_Importer.php';
			}

			// Use feed's content type (discoverarticles or articles)
			$content_type = $feed['feed_type'] ?? 'articles';

			// Validate it's a known type (security check)
			$valid_types = array( 'articles', 'discoverarticles' );
			if ( ! in_array( $content_type, $valid_types, true ) ) {

				$content_type = 'articles';
			}

			$result = \Wisewand\Utils\Content_Importer::import_content(
				$wisewand_article_id,
				$content_type,
				$import_options,
				$this->api_client,
				$this->content_manager
			);

			if ( is_wp_error( $result ) ) {
				throw new \Exception( $result->get_error_message() );
			}

			$post_id = $result['post_id'] ?? null;

			if ( ! $post_id ) {
				throw new \Exception( __( 'No post ID returned from import', 'wisewand-ai-content-writer-seo-content-generator' ) );
			}

			// Mark as imported with actual post status
			$current_post_status = get_post_status( $post_id );
			$this->article_manager->mark_as_imported( $article_tracking_id, $post_id, $current_post_status );

			// Update with scheduled publication time
			global $wpdb;
			$update_result = $wpdb->update(
				$wpdb->prefix . 'wisewand_rss_articles',
				array( 'scheduled_publish_time' => $scheduled_time ),
				array( 'id' => $article_tracking_id ),
				array( '%s' ),
				array( '%d' )
			);

			if ( $update_result === false ) {
				$this->logger->error(
					'Failed to update scheduled_publish_time in database',
					array(
						'article_id'      => $article_tracking_id,
						'scheduled_time'  => $scheduled_time,
						'wpdb_last_error' => $wpdb->last_error,
					)
				);
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log(
					sprintf(
						'RSS_IMPORT: ✅ Article imported - WordPress post #%d (status: %s, scheduled: %s)',
						$post_id,
						$current_post_status,
						$scheduled_time
					)
				);
			}

			$this->logger->info(
				'RSS article imported successfully',
				array(
					'article_tracking_id'    => $article_tracking_id,
					'wisewand_article_id'    => $wisewand_article_id,
					'wordpress_post_id'      => $post_id,
					'post_status'            => $current_post_status,
					'scheduled_publish_time' => $scheduled_time,
					'feed_id'                => $feed['id'],
				)
			);

			// Reload article data for calendar integration
			$article_data = $this->article_manager->get_article( $article_tracking_id );

			if ( ! $article_data ) {
				$this->logger->error(
					'Failed to reload article data after scheduling',
					array( 'article_id' => $article_tracking_id )
				);
				return $post_id;
			}

			// Validate article data before creating calendar entry
			$missing_fields = array();
			if ( empty( $article_data['id'] ) ) {
				$missing_fields[] = 'id';
			}
			if ( empty( $article_data['feed_id'] ) ) {
				$missing_fields[] = 'feed_id';
			}
			if ( empty( $article_data['scheduled_publish_time'] ) ) {
				$missing_fields[] = 'scheduled_publish_time';
			}

			if ( ! empty( $missing_fields ) ) {
				$this->logger->error(
					'Cannot create calendar entry - missing required fields',
					array(
						'article_id'     => $article_tracking_id,
						'missing_fields' => $missing_fields,
					)
				);
				return $post_id;
			}

			// Create calendar entry for RSS article
			try {
				$article_entry_id = $this->calendar_bridge->create_calendar_entry_for_rss_article(
					$article_data,
					$feed
				);

				if ( is_wp_error( $article_entry_id ) ) {
					$this->logger->warning(
						'Failed to create calendar entry for RSS article',
						array(
							'article_id' => $article_tracking_id,
							'post_id'    => $post_id,
							'error'      => $article_entry_id->get_error_message(),
						)
					);
				} else {
					$this->logger->info(
						'Created calendar entry for RSS article',
						array(
							'article_id'       => $article_tracking_id,
							'post_id'          => $post_id,
							'calendar_entry'   => $article_entry_id,
							'publication_date' => $scheduled_time,
						)
					);

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( "RSS_IMPORT: 📅 Added to editorial calendar (Entry #{$article_entry_id})" );
					}
				}
			} catch ( \Exception $calendar_exception ) {
				$this->logger->warning(
					'Exception creating calendar entry for RSS article',
					array(
						'article_id' => $article_tracking_id,
						'error'      => $calendar_exception->getMessage(),
					)
				);
			}

			return $post_id;

		} catch ( \Exception $e ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'RSS_DEBUG: ❌❌ OUTER EXCEPTION caught - import failed: ' . $e->getMessage() );
				error_log( 'RSS_DEBUG: Exception file: ' . $e->getFile() . ' line: ' . $e->getLine() );
			}

			// Check if this is a rate limit exception and we have retries left.
			if ( $this->is_rate_limit_exception( $e ) && $attempt <= $max_attempts ) {
				// v1.2.0: Extract wait time with exponential backoff based on attempt number.
				$wait_time = $this->extract_wait_time( $e, $attempt );

				$this->logger->warning(
					'Rate limit detected during article import, retrying',
					array(
						'article_tracking_id' => $article_tracking_id,
						'wisewand_article_id' => $wisewand_article_id,
						'attempt'             => $attempt,
						'max_attempts'        => $max_attempts,
						'wait_time'           => $wait_time,
						'error'               => $e->getMessage(),
					)
				);

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log(
						sprintf(
							'RSS_IMPORT: ⏳ Rate limit hit (attempt %d/%d) - waiting %d seconds before retry',
							$attempt,
							$max_attempts,
							$wait_time
						)
					);

					// v1.2.0: Add log for UI display.
					$this->add_sync_log( "⏳ Rate limit hit (attempt {$attempt}/{$max_attempts}) - waiting {$wait_time}s", 'warning' );

				}

				// Wait before retry.
				$this->rate_limiter->smart_delay( 'wisewand_import', $wait_time );

				// Recursive retry with incremented attempt.
				return $this->import_article( $article_data, $feed, $attempt + 1, $max_attempts );
			}

			// Check if exception message indicates article was deleted (404).
			$is_deleted = (
				strpos( $e->getMessage(), 'no longer exists' ) !== false ||
				strpos( $e->getMessage(), 'deleted or expired' ) !== false ||
				strpos( $e->getMessage(), 'Article not found' ) !== false
			);

			if ( $is_deleted ) {
				// Article deleted from Wisewand (404) - permanently remove from database.
				// This prevents retry loops and keeps database clean.
				$deletion_reason = sprintf(
					'Article no longer exists in Wisewand (404): %s',
					$e->getMessage()
				);

				$deleted = $this->article_manager->delete_article_permanently(
					$article_tracking_id,
					$deletion_reason
				);

				if ( $deleted ) {
					$this->logger->info(
						'Automatically removed 404-deleted article from database',
						array(
							'article_tracking_id' => $article_tracking_id,
							'wisewand_article_id' => $wisewand_article_id,
							'reason'              => 'Article returned 404 from Wisewand API',
						)
					);

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log(
							sprintf(
								'RSS_IMPORT: 🗑️  Article permanently deleted (404): %s',
								$wisewand_article_id
							)
						);
					}
				} else {
					// Fallback: mark as deleted if permanent deletion fails.
					$this->article_manager->mark_as_deleted( $article_tracking_id, $e->getMessage() );

					$this->logger->warning(
						'Failed to permanently delete article, marked as deleted instead',
						array(
							'article_tracking_id' => $article_tracking_id,
							'wisewand_article_id' => $wisewand_article_id,
						)
					);

					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'RSS_IMPORT: ⚠️  Failed to delete permanently, marked as deleted: ' . $wisewand_article_id );
					}
				}
			} else {
				// Temporary error - mark as failed with retry capability.
				$this->article_manager->mark_as_failed( $article_tracking_id, $e->getMessage() );

				$this->logger->error(
					'RSS article import failed',
					array(
						'article_tracking_id' => $article_tracking_id,
						'wisewand_article_id' => $wisewand_article_id,
						'attempt'             => $attempt,
						'max_attempts'        => $max_attempts,
						'error'               => $e->getMessage(),
					)
				);

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log(
						sprintf(
							'RSS_IMPORT: ❌ Article import failed (attempt %d/%d) - %s',
							$attempt,
							$max_attempts,
							$e->getMessage()
						)
					);
				}
			}

			return new \WP_Error( 'import_failed', $e->getMessage() );
		}
	}

	/**
	 * Sync all active feeds (batch operation)
	 *
	 * @param bool $force_import Force import even if not scheduled
	 * @return array Batch sync results
	 */
	public function sync_all_feeds( $force_import = false ) {
		$feeds = $force_import
			? $this->feed_manager->get_all_feeds( array( 'auto_import_enabled' => 1 ) )
			: $this->feed_manager->get_feeds_due_for_sync();

		$results = array(
			'feeds_processed'  => 0,
			'total_discovered' => 0,
			'total_imported'   => 0,
			'total_failed'     => 0,
			'feed_results'     => array(),
		);

		foreach ( $feeds as $feed ) {
			$feed_result = $this->sync_feed( $feed['id'], $force_import );

			if ( ! is_wp_error( $feed_result ) ) {
				++$results['feeds_processed'];
				$results['total_discovered'] += $feed_result['articles_discovered'];
				$results['total_imported']   += $feed_result['articles_imported'];
				$results['total_failed']     += $feed_result['articles_failed'];
				$results['feed_results'][]    = $feed_result;
			}
		}

		$this->logger->info( 'Batch feed sync completed', $results );

		return $results;
	}

	/**
	 * Manual import of specific article (user-triggered)
	 *
	 * @param string $wisewand_article_id Wisewand article ID
	 * @param int    $feed_id Feed ID
	 * @param array  $import_options Optional import options (override feed settings)
	 * @return int|WP_Error WordPress post ID or error
	 */
	public function import_article_manual( $wisewand_article_id, $feed_id, $import_options = array() ) {
		// PHASE 1: Comprehensive logging - Entry point
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '🔵 MANUAL_IMPORT: Entry point - article_id: ' . $wisewand_article_id . ', feed_id: ' . $feed_id );
			error_log( 'MANUAL_IMPORT: Import options: ' . wp_json_encode( $import_options ) );
		}

		try {
			// PHASE 1: Step 1 - Get article by wisewand_id
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Step 1 - Getting article by wisewand_id...' );
			}

			$article = $this->article_manager->get_article_by_wisewand_id( $wisewand_article_id );

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Article found: ' . ( $article ? 'YES (id=' . $article['id'] . ')' : 'NO' ) );
			}

			if ( ! $article ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'MANUAL_IMPORT: Step 1b - Article not in database, fetching from Wisewand API...' );
				}

				// Fetch article from Wisewand to create tracking record
				$wisewand_article = $this->api_client->get_article( $wisewand_article_id );
				if ( is_wp_error( $wisewand_article ) ) {
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( '❌ MANUAL_IMPORT: API fetch failed: ' . $wisewand_article->get_error_message() );
					}
					return $wisewand_article;
				}

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'MANUAL_IMPORT: Step 1c - Creating tracking record...' );
				}

				$feed = $this->feed_manager->get_feed( $feed_id );
				$this->article_manager->discover_article( $wisewand_article, $feed_id, $feed['wisewand_feed_id'] );
				$article = $this->article_manager->get_article_by_wisewand_id( $wisewand_article_id );

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'MANUAL_IMPORT: Tracking record created: ' . ( $article ? 'YES' : 'NO' ) );
				}
			}

			// PHASE 1: Check if already imported
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Checking if already imported - status: ' . ( $article['import_status'] ?? 'UNKNOWN' ) . ', wp_post_id: ' . ( $article['wordpress_post_id'] ?? 'NONE' ) );
			}

			if ( $article['import_status'] === 'imported' && ! empty( $article['wordpress_post_id'] ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '⚠️ MANUAL_IMPORT: Already imported - post_id: ' . $article['wordpress_post_id'] );
				}
				return new \WP_Error(
					'already_imported',
					__( 'Article already imported', 'wisewand-ai-content-writer-seo-content-generator' ),
					array(
						'post_id' => $article['wordpress_post_id'],
					)
				);
			}

			// PHASE 1: Step 2 - Get feed settings
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Step 2 - Getting feed settings...' );
			}

			$feed = $this->feed_manager->get_feed( $feed_id );

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Feed found: ' . ( $feed ? 'YES (name: ' . $feed['feed_name'] . ', type: ' . ( $feed['feed_type'] ?? 'UNKNOWN' ) . ')' : 'NO' ) );
			}

			// PHASE 1: Step 3 - Build import options
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Step 3 - Building import options...' );
			}

			// v1.3.16: CRITICAL FIX - Validate categories before manual import
			$categories_raw       = json_decode( $feed['target_categories'], true );
			$categories_validated = array();

			if ( ! empty( $categories_raw ) ) {
				$validation = $this->validate_category_ids(
					$categories_raw,
					'manual-import',
					$feed['id']
				);

				$categories_validated = $validation['valid'];

				if ( WP_DEBUG && ! empty( $validation['names'] ) ) {
					$cat_names = implode( ', ', $validation['names'] );
					error_log( "RSS_SYNC: Manual import using categories: {$cat_names}" );
				}
			}

			$merged_options = array_merge(
				array(
					'post_status'     => $feed['target_post_status'],
					'author_id'       => $feed['target_author'],
					'categories'      => $categories_validated, // Use validated categories
					'target_language' => $feed['target_language'] ?? null, // WPML: Language for imported articles
				),
				$import_options
			);

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Merged options: ' . wp_json_encode( $merged_options ) );
			}

			// Use Content_Importer (same as manual import from Articles page!)
			if ( ! class_exists( 'Wisewand\\Utils\\Content_Importer' ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'MANUAL_IMPORT: Loading Content_Importer class...' );
				}
				require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Content_Importer.php';
			}

			// Use feed's content type (discoverarticles or articles)
			$content_type = $feed['feed_type'] ?? 'articles';

			// Validate it's a known type (security check)
			$valid_types = array( 'articles', 'discoverarticles' );
			if ( ! in_array( $content_type, $valid_types, true ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '⚠️ MANUAL_IMPORT: Invalid feed_type "' . $content_type . '", defaulting to articles' );
				}
				$content_type = 'articles';
			}

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Using content_type: ' . $content_type . ' for feed: ' . $feed['feed_name'] );
			}

			// PHASE 1: Step 4 - Mark as importing
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Step 4 - Marking article as importing...' );
			}

			$this->article_manager->mark_as_importing( $article['id'] );

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Article marked as importing' );
			}

			// PHASE 1: Step 5 - Call Content_Importer
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Step 5 - Calling Content_Importer::import_content()...' );
				error_log( 'MANUAL_IMPORT: Parameters - article_id: ' . $wisewand_article_id . ', content_type: ' . $content_type );
			}

			$result = \Wisewand\Utils\Content_Importer::import_content(
				$wisewand_article_id,
				$content_type,
				$merged_options,
				$this->api_client,
				$this->content_manager
			);

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Content_Importer returned - type: ' . ( is_wp_error( $result ) ? 'WP_Error' : ( is_array( $result ) ? 'array' : gettype( $result ) ) ) );
				if ( is_array( $result ) ) {
					error_log( 'MANUAL_IMPORT: Result keys: ' . implode( ', ', array_keys( $result ) ) );
					error_log( 'MANUAL_IMPORT: Result values: ' . wp_json_encode( $result ) );
				}
			}

			if ( is_wp_error( $result ) ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( '❌ MANUAL_IMPORT: Content_Importer returned error: ' . $result->get_error_message() );
					error_log( 'MANUAL_IMPORT: Error code: ' . $result->get_error_code() );
					$error_data = $result->get_error_data();
					if ( $error_data ) {
						error_log( 'MANUAL_IMPORT: Error data: ' . wp_json_encode( $error_data ) );
					}
				}
				$this->article_manager->mark_as_failed( $article['id'], $result->get_error_message() );
				return $result;
			}

			// PHASE 1: Step 6 - Extract post_id from result
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Step 6 - Extracting post_id from result...' );
			}

			if ( ! is_array( $result ) || ! isset( $result['post_id'] ) ) {
				return new \WP_Error(
					'invalid_result',
					__( 'Import returned invalid result structure', 'wisewand-ai-content-writer-seo-content-generator' ),
					array(
						'result_type' => gettype( $result ),
						'result_data' => is_scalar( $result ) ? $result : 'non-scalar',
					)
				);
			}

			$post_id     = $result['post_id'];
			$post_status = $result['post_status'] ?? $merged_options['post_status'];

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Extracted post_id: ' . $post_id . ', post_status: ' . $post_status );
			}

			// PHASE 1: Step 7 - Mark as imported
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Step 7 - Marking article as imported...' );
			}

			$this->article_manager->mark_as_imported( $article['id'], $post_id, $post_status );

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'MANUAL_IMPORT: Article marked as imported' );
			}

			// PHASE 1: Step 8 - Sync is_published to Wisewand
			if ( 'publish' === $post_status ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'MANUAL_IMPORT: Step 8 - Syncing is_published=true to Wisewand...' );
				}
				$this->api_client->update_article( $wisewand_article_id, array( 'is_published' => true ) );
			}

			// PHASE 1: Success!
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '✅ MANUAL_IMPORT: Success - post_id: ' . $post_id );
			}

			return $post_id;

		} catch ( \Exception $e ) {
			// PHASE 1: Exception handling with stack trace
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( '❌ MANUAL_IMPORT: EXCEPTION CAUGHT!' );
				error_log( 'MANUAL_IMPORT: Exception message: ' . $e->getMessage() );
				error_log( 'MANUAL_IMPORT: Exception file: ' . $e->getFile() . ':' . $e->getLine() );
				error_log( 'MANUAL_IMPORT: Stack trace: ' . $e->getTraceAsString() );
			}
			return new \WP_Error(
				'import_exception',
				/* translators: %s: Exception error message */
				sprintf( __( 'Import failed with exception: %s', 'wisewand-ai-content-writer-seo-content-generator' ), $e->getMessage() ),
				array(
					'file' => $e->getFile(),
					'line' => $e->getLine(),
				)
			);
		}
	}

	/**
	 * Check if feed has manual categories configured
	 *
	 * @since 1.2.0
	 * @param array $feed Feed data array.
	 * @return bool True if feed has manual categories.
	 */
	private function has_manual_categories( $feed ) {
		if ( empty( $feed['target_categories'] ) ) {
			return false;
		}

		$categories = json_decode( $feed['target_categories'], true );

		// Valid if it's a non-empty array
		return is_array( $categories ) && count( $categories ) > 0;
	}

	/**
	 * Validate that category IDs exist in WordPress.
	 *
	 * @since 1.3.16
	 * @param array|string $category_ids Category IDs (array or JSON string).
	 * @param string       $context      Context for logging (e.g., 'auto-import', 'manual-import').
	 * @param int          $feed_id      Feed ID for logging.
	 * @return array {
	 *     @type array $valid       Valid category IDs that exist in WordPress.
	 *     @type array $invalid     Invalid category IDs that don't exist.
	 *     @type bool  $all_valid   Whether all IDs are valid.
	 *     @type array $names       Category names for valid IDs (ID => name map).
	 * }
	 */
	private function validate_category_ids( $category_ids, $context = 'unknown', $feed_id = 0 ) {
		// Handle JSON string input
		if ( is_string( $category_ids ) ) {
			$decoded = json_decode( $category_ids, true );

			if ( json_last_error() !== JSON_ERROR_NONE ) {
				$this->logger->error(
					'Category validation: Invalid JSON',
					array(
						'feed_id'    => $feed_id,
						'context'    => $context,
						'json_error' => json_last_error_msg(),
						'raw_value'  => substr( $category_ids, 0, 200 ),
					)
				);
				return array(
					'valid'     => array(),
					'invalid'   => array(),
					'all_valid' => false,
					'names'     => array(),
				);
			}

			$category_ids = is_array( $decoded ) ? $decoded : array();
		}

		// Ensure array
		if ( ! is_array( $category_ids ) ) {
			$category_ids = array();
		}

		$valid   = array();
		$invalid = array();
		$names   = array();

		foreach ( $category_ids as $cat_id ) {
			$cat_id = absint( $cat_id );

			if ( $cat_id === 0 ) {
				continue; // Skip invalid integers
			}

			// Check if category exists in WordPress
			$term = get_term( $cat_id, 'category' );

			if ( ! is_wp_error( $term ) && $term ) {
				$valid[]          = $cat_id;
				$names[ $cat_id ] = $term->name;

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log(
						sprintf(
							'RSS_SYNC [%s]: Category #%d "%s" VALID ✓',
							$context,
							$cat_id,
							$term->name
						)
					);
				}
			} else {
				$invalid[] = $cat_id;

				$this->logger->warning(
					'Category validation: Category does not exist',
					array(
						'feed_id'     => $feed_id,
						'context'     => $context,
						'category_id' => $cat_id,
						'action'      => 'will_skip_this_category',
					)
				);

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log(
						sprintf(
							'RSS_SYNC [%s]: Category #%d INVALID ✗ (does not exist in WordPress)',
							$context,
							$cat_id
						)
					);
				}
			}
		}

		$result = array(
			'valid'     => $valid,
			'invalid'   => $invalid,
			'all_valid' => empty( $invalid ),
			'names'     => $names,
		);

		// Summary logging
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log(
				sprintf(
					'RSS_SYNC [%s]: Category validation complete - %d valid, %d invalid',
					$context,
					count( $valid ),
					count( $invalid )
				)
			);
		}

		return $result;
	}

	/**
	 * Get sync summary for a feed (for UI display)
	 *
	 * @param int $feed_id Feed ID
	 * @return array Sync summary
	 */
	public function get_feed_sync_summary( $feed_id ) {
		$feed = $this->feed_manager->get_feed( $feed_id );
		if ( ! $feed ) {
			return array();
		}

		$stats = $this->article_manager->get_feed_statistics( $feed_id );

		// Calculate sync status
		$last_sync = $feed['last_sync_at'];
		$next_sync = $feed['next_sync_at'];

		$sync_status    = 'never';
		$next_sync_text = __( 'Not scheduled', 'wisewand-ai-content-writer-seo-content-generator' );

		if ( $next_sync ) {
			$next_timestamp = strtotime( $next_sync );
			$now            = current_time( 'timestamp' );

			if ( $next_timestamp > $now ) {
				$diff        = $next_timestamp - $now;
				$sync_status = 'scheduled';

				if ( $diff < 3600 ) {
					/* translators: %d: minutes */
					$next_sync_text = sprintf( __( 'in %d minutes', 'wisewand-ai-content-writer-seo-content-generator' ), ceil( $diff / 60 ) );
				} elseif ( $diff < 86400 ) {
					/* translators: %d: hours */
					$next_sync_text = sprintf( __( 'in %d hours', 'wisewand-ai-content-writer-seo-content-generator' ), ceil( $diff / 3600 ) );
				} else {
					/* translators: %d: days */
					$next_sync_text = sprintf( __( 'in %d days', 'wisewand-ai-content-writer-seo-content-generator' ), ceil( $diff / 86400 ) );
				}
			} else {
				$sync_status    = 'overdue';
				$next_sync_text = __( 'Overdue', 'wisewand-ai-content-writer-seo-content-generator' );
			}
		}

		if ( $last_sync ) {
			$last_timestamp = strtotime( $last_sync );
			$now            = current_time( 'timestamp' );
			$diff           = $now - $last_timestamp;

			if ( $diff < 3600 ) {
				/* translators: %d: minutes */
				$last_sync_text = sprintf( __( '%d minutes ago', 'wisewand-ai-content-writer-seo-content-generator' ), ceil( $diff / 60 ) );
			} elseif ( $diff < 86400 ) {
				/* translators: %d: hours */
				$last_sync_text = sprintf( __( '%d hours ago', 'wisewand-ai-content-writer-seo-content-generator' ), ceil( $diff / 3600 ) );
			} else {
				/* translators: %d: days */
				$last_sync_text = sprintf( __( '%d days ago', 'wisewand-ai-content-writer-seo-content-generator' ), ceil( $diff / 86400 ) );
			}
		} else {
			$last_sync_text = __( 'Never', 'wisewand-ai-content-writer-seo-content-generator' );
		}

		return array(
			'feed_id'             => $feed_id,
			'wisewand_feed_id'    => $feed['wisewand_feed_id'],
			'feed_name'           => $feed['feed_name'],
			'auto_import_enabled' => (bool) $feed['auto_import_enabled'],
			'sync_status'         => $sync_status,
			'last_sync'           => $last_sync,
			'last_sync_text'      => $last_sync_text,
			'next_sync'           => $next_sync,
			'next_sync_text'      => $next_sync_text,
			'statistics'          => $stats,
		);
	}
}
