<?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\Automation;

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

use WP_Error;
use Wisewand\Utils\Options;
use Wisewand\Logging\Logger;
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, WordPress.PHP.DevelopmentFunctions.error_log_print_r, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- error_log() and print_r() are only used within WP_DEBUG conditionals for debugging; table names derived from $wpdb->prefix are safe to interpolate

class Calendar_CRON {

	private $options;
	private $logger;
	private $wisewand_api;
	private $content_manager;
	private $entry_manager;
	private $run_summary        = array();
	private $last_api_call_time = 0; // Track API call timing for intelligent rate limiting

	public function __construct( Options $options, Logger $logger, Content_Manager $content_manager ) {
		$this->options         = $options;
		$this->logger          = $logger;
		$this->content_manager = $content_manager;

		$api_key = \Wisewand\Utils\Wisewand_API_Client::get_stored_api_key();
		if ( $api_key ) {
			$this->wisewand_api = new Wisewand_API_Client( $api_key, $logger );
		}

		if ( ! class_exists( 'Wisewand\\Calendar\\Calendar_Entry_Manager' ) ) {
			require_once WISEWAND_PLUGIN_DIR . 'includes/Calendar/Calendar_Entry_Manager.php';
		}
		if ( ! class_exists( 'Wisewand\\Utils\\Wisewand_Payload_Builder' ) ) {
			require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Wisewand_Payload_Builder.php';
		}

		$payload_builder     = new \Wisewand\Utils\Wisewand_Payload_Builder( $options, $logger );
		$this->entry_manager = new \Wisewand\Calendar\Calendar_Entry_Manager( $options, $logger, $payload_builder );
	}

	/**
	 * Run status check only (lightweight, runs every 15 minutes)
	 * Checks if generating articles are ready on Wisewand
	 * AND imports ready articles that are due for publication
	 *
	 * @param bool $force_retry If true, ignores exponential backoff and retries immediately
	 */
	public function run_status_check_only( $force_retry = false ) {
		if ( ! $this->options->get( 'wisewand_cron_enabled', true ) ) {
			return;
		}

		$this->logger->info( '=== Status Check CRON Started (15-min) ===' );

		$start_time = microtime( true );

		$this->run_summary = array(
			'generated'      => 0,
			'status_checked' => 0,
			'imported'       => 0,
			'retried'        => 0,
			'force_retry'    => $force_retry,
		);

		// Step 2: Check generation status
		$this->step_2_check_generation_status();

		// Step 3: Import ready articles for today AND past-due dates
		// This ensures articles are imported as soon as they're ready, not waiting for 2 AM daily CRON
		$this->step_3_import_ready_articles();

		// Step 3.5: Publish scheduled RSS articles (RSS v2.0)
		// Publishes RSS articles that were imported as drafts and are now due for publication
		$this->step_3_5_publish_scheduled_rss_articles();

		// Step 4: Retry failed articles (NEW - runs every 15 minutes)
		// This provides faster recovery from failures while respecting exponential backoff
		$this->step_4_retry_failed( $force_retry );

		$elapsed                      = round( microtime( true ) - $start_time, 2 );
		$this->run_summary['elapsed'] = $elapsed;

		$this->logger->info( '=== Status Check CRON Completed ===', $this->run_summary );

		// Update last status check time (used by dashboard)
		update_option( 'wisewand_cron_last_status_check', current_time( 'mysql' ) );
		update_option( 'wisewand_cron_last_status_summary', wp_json_encode( $this->run_summary ) );

		return $this->run_summary;
	}

	/**
	 * Run full daily CRON (heavy operations at 2 AM)
	 * Generates articles, imports ready ones, retries failed
	 *
	 * @param bool $force_retry If true, ignores exponential backoff and retries immediately
	 */
	public function run( $force_retry = false ) {
		if ( ! $this->options->get( 'wisewand_cron_enabled', true ) ) {
			$this->logger->info( 'Editorial calendar CRON is disabled' );
			return;
		}

		$this->logger->info( '=== Editorial Calendar CRON Started (Daily) ===' );

		$start_time = microtime( true );

		$this->run_summary = array(
			'generated'      => 0,
			'status_checked' => 0,
			'imported'       => 0,
			'retried'        => 0,
			'force_retry'    => $force_retry,
		);

		// Step 1: Generate new articles
		$this->step_1_generate_articles();

		// Step 2: Removed from daily CRON (now handled by 15-min CRON)
		// This avoids redundant status checks

		// Step 3: Import and publish ready articles
		$this->step_3_import_and_publish();

		// Step 4: Retry failed articles
		$this->step_4_retry_failed( $force_retry );

		$elapsed                      = round( microtime( true ) - $start_time, 2 );
		$this->run_summary['elapsed'] = $elapsed;

		$this->logger->info( '=== Editorial Calendar CRON Completed (Daily) ===', $this->run_summary );

		// Save last run info for Dashboard
		update_option( 'wisewand_cron_last_run', current_time( 'mysql' ) );
		update_option( 'wisewand_cron_last_summary', wp_json_encode( $this->run_summary ) );

		return $this->run_summary;
	}

	private function step_1_generate_articles() {
		$lead_time   = $this->options->get( 'wisewand_cron_lead_time_days', 1 );
		$cutoff_date = gmdate( 'Y-m-d', strtotime( "+{$lead_time} days" ) );

		// Generate articles for today, tomorrow, and any past-due entries
		// Uses <= instead of = to catch same-day and missed entries
		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for articles to generate
		$to_generate = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$table}
             WHERE publication_date <= %s
             AND wisewand_status = 'not_created'
             ORDER BY publication_date ASC, id ASC",
				$cutoff_date
			),
			ARRAY_A
		);

		$this->logger->info(
			'Step 1: Generate articles',
			array(
				'cutoff_date'    => $cutoff_date,
				'lead_time_days' => $lead_time,
				'count'          => count( $to_generate ),
				'includes'       => 'today, tomorrow, and past-due entries',
			)
		);

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 1: Generating articles for dates up to ' . $cutoff_date . ' (found ' . count( $to_generate ) . ' entries)' );
		}

		if ( empty( $to_generate ) ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 1: No articles to generate' );
			}
			return;
		}

		foreach ( $to_generate as $entry ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 1: Generating entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (scheduled for ' . $entry['publication_date'] . ')' );
			}

			$this->generate_article( $entry );
			++$this->run_summary['generated'];

			// Smart rate limiting: Wait only if we're going too fast
			$this->smart_rate_limit( 1.0 ); // Min 1 second between API calls
		}

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 1: Completed generating ' . count( $to_generate ) . ' articles' );
		}
	}

	private function generate_article( $entry ) {
		if ( ! $this->wisewand_api ) {
			$this->logger->error( 'Wisewand API not configured' );
			return;
		}

		try {
			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_status' => 'creating',
				)
			);

			$payload = json_decode( $entry['wisewand_payload'], true );

			if ( empty( $payload ) ) {
				throw new \Exception( 'Payload is empty or invalid JSON' );
			}

			// Determine API endpoint and article type from entry
			$endpoint     = $this->determine_endpoint( $entry['wisewand_type'] ?? 'blog' );
			$article_type = $payload['type'] ?? 'blog';

			// CRITICAL: Clean payload using same logic as manual "Generate Now"
			// This filters to only valid fields for the endpoint
			$clean_payload = $this->clean_payload_for_api( $payload, $endpoint, $article_type );

			// CRITICAL FIX: Validate and correct language/country combinations
			$lang    = $clean_payload['lang'] ?? 'en';
			$country = $clean_payload['country'] ?? 'us';

			// Fix invalid language/country combinations (Wisewand API validation)
			$valid_combos = array(
				'en' => array( 'us', 'gb', 'ca' ), // English: US, UK, Canada
				'fr' => array( 'fr', 'ca', 'be', 'ch' ), // French: France, Canada, Belgium, Switzerland
				'de' => array( 'de', 'at', 'ch' ), // German: Germany, Austria, Switzerland
				'es' => array( 'es', 'mx', 'ar' ), // Spanish: Spain, Mexico, Argentina
				'it' => array( 'it', 'ch' ), // Italian: Italy, Switzerland
			);

			if ( isset( $valid_combos[ $lang ] ) && ! in_array( $country, $valid_combos[ $lang ] ) ) {
				$old_country              = $country;
				$country                  = $valid_combos[ $lang ][0]; // Use first valid country for language
				$clean_payload['country'] = $country;
				if ( WP_DEBUG ) {
					error_log( 'CRON: Fixed invalid country for lang=' . $lang . ': ' . $old_country . ' → ' . $country );
				}
			}

			// Always override with current default persona (like manual does)
			$default_persona = $lang === 'fr'
				? $this->options->get( 'wisewand_default_persona_fr', '' )
				: $this->options->get( 'wisewand_default_persona_en', '' );

			if ( ! empty( $default_persona ) ) {
				$old_persona                         = $clean_payload['persona_id'] ?? 'none';
				$clean_payload['persona_id']         = $default_persona;
				$clean_payload['writing_style_mode'] = 'persona';
				if ( WP_DEBUG ) {
					error_log( 'CRON: Using current default persona_id: ' . $default_persona . ' (was: ' . $old_persona . ')' );
				}
			} else {
				// No persona configured - use auto mode
				$clean_payload['writing_style_mode'] = 'auto';
				unset( $clean_payload['persona_id'] );
				if ( WP_DEBUG ) {
					error_log( 'CRON: No persona configured, using auto writing mode' );
				}
			}

			// Always override with current default project (like manual does)
			$default_project = $this->options->get( 'wisewand_default_project_id', '' );
			if ( ! empty( $default_project ) ) {
				$old_project                 = $clean_payload['project_id'] ?? 'none';
				$clean_payload['project_id'] = $default_project;
				if ( WP_DEBUG ) {
					error_log( 'CRON: Using current default project_id: ' . $default_project . ' (was: ' . $old_project . ')' );
				}
			}

			// Enable webhook for instant notification when ready
			if ( ! class_exists( 'Wisewand\\API\\Webhook_Receiver' ) ) {
				require_once WISEWAND_PLUGIN_DIR . 'includes/API/Webhook_Receiver.php';
			}

			$clean_payload['use_webhook']           = true;
			$clean_payload['webhook_connection_id'] = \Wisewand\API\Webhook_Receiver::get_webhook_url();

			// DETAILED LOGGING: Log exact payload being sent (same as manual Generate Now)
			if ( WP_DEBUG ) {
				error_log( '=== CRON WISEWAND API REQUEST ===' );
				error_log( 'Entry ID: ' . $entry['id'] );
				error_log( 'Endpoint: ' . $endpoint );
				error_log( 'Keyword: ' . $entry['keyword'] );
				error_log( 'Article Type: ' . $article_type );
                // Reduced verbose payload logging for production cleanliness
				error_log( '===========================' );
			}

			$this->logger->info(
				'Creating Wisewand content (AUTOMATED CRON)',
				array(
					'entry_id'              => $entry['id'],
					'keyword'               => $entry['keyword'],
					'endpoint'              => $endpoint,
					'type'                  => $entry['wisewand_type'],
					'article_type'          => $article_type,
					'webhook_connection_id' => $clean_payload['webhook_connection_id'],
					'raw_fields'            => count( $payload ),
					'clean_fields'          => count( $clean_payload ),
				)
			);

			// Send CLEAN payload (same as manual Generate Now)
			$article = $this->wisewand_api->create_content( $endpoint, $clean_payload );

			if ( is_wp_error( $article ) ) {
				$error_msg  = $article->get_error_message();
				$error_data = $article->get_error_data();

				if ( WP_DEBUG ) {
					error_log( '=== CRON API ERROR ===' );
				}
				if ( WP_DEBUG ) {
					error_log( 'Error Message: ' . $error_msg );
				}
				if ( WP_DEBUG ) {
					error_log( 'Error Data: ' . print_r( $error_data, true ) );
				}
				if ( WP_DEBUG ) {
					error_log( 'Persona ID in payload: ' . ( $clean_payload['persona_id'] ?? 'MISSING' ) );
				}
				if ( WP_DEBUG ) {
					error_log( 'Project ID in payload: ' . ( $clean_payload['project_id'] ?? 'MISSING' ) );
				}
				if ( WP_DEBUG ) {
					error_log( 'Writing Style Mode: ' . ( $clean_payload['writing_style_mode'] ?? 'MISSING' ) );
				}
				if ( WP_DEBUG ) {
					error_log( '=====================' );
				}

				throw new \Exception( $error_msg . ' | Check if persona_id (' . ( $clean_payload['persona_id'] ?? 'missing' ) . ') and project_id (' . ( $clean_payload['project_id'] ?? 'missing' ) . ') are valid' );
			}

			$article_id = $article['id'] ?? $article['data']['id'] ?? null;

			if ( ! $article_id ) {
				throw new \Exception( 'No content ID returned from Wisewand' );
			}

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_article_id' => $article_id,
					'wisewand_status'     => 'creating',
				)
			);

			$this->logger->info(
				'Triggering content generation',
				array(
					'article_id' => $article_id,
					'endpoint'   => $endpoint,
				)
			);

			// Use generic method for running generation
			$run_result = $this->wisewand_api->run_content_generation( $endpoint, $article_id );

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

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_status'         => 'generating',
					'generation_triggered_at' => current_time( 'mysql' ),
				)
			);

			$this->logger->info(
				'Content generation triggered successfully',
				array(
					'entry_id'   => $entry['id'],
					'article_id' => $article_id,
					'endpoint'   => $endpoint,
				)
			);

		} catch ( \Exception $e ) {
			$this->logger->error(
				'Failed to generate content',
				array(
					'entry_id' => $entry['id'],
					'keyword'  => $entry['keyword'],
					'error'    => $e->getMessage(),
				)
			);

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_status'      => 'failed',
					'wisewand_error'       => $e->getMessage(),
					'wisewand_retry_count' => ( $entry['wisewand_retry_count'] ?? 0 ) + 1,
				)
			);
		}
	}

	/**
	 * Determine which API endpoint to use based on content type
	 *
	 * Maps wisewand_type values to their corresponding API endpoints.
	 * Handles legacy type names (e.g., 'categorypage' → 'categorypages').
	 * All article subtypes (blog, news, affiliation-*) route to articles endpoint.
	 *
	 * @since 1.0.0
	 * @since 1.4.1 Added support for discoverarticles endpoint routing.
	 *
	 * @param string $wisewand_type Content type from database (blog, categorypages, productpages, discoverarticles, etc.).
	 * @return string API endpoint name (articles, categorypages, productpages, discoverarticles).
	 */
	private function determine_endpoint( $wisewand_type ) {
		// Map old-style types to endpoints
		if ( $wisewand_type === 'categorypage' || $wisewand_type === 'categorypages' ) {
			return 'categorypages';
		}
		if ( $wisewand_type === 'productpage' || $wisewand_type === 'productpages' ) {
			return 'productpages';
		}
		if ( $wisewand_type === 'discoverarticles' ) {
			return 'discoverarticles';
		}

		// All article types (blog, news, affiliation-*) use articles endpoint
		return 'articles';
	}

	private function step_2_check_generation_status() {
		// Get ALL entries that might be generating (creating OR generating status)
		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for generating articles
		$generating = $wpdb->get_results(
			"SELECT * FROM {$table}
             WHERE wisewand_status IN ('creating', 'generating')
             AND wisewand_article_id IS NOT NULL
             AND wisewand_article_id != ''
             ORDER BY id ASC",
			ARRAY_A
		);

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 2: Found ' . count( $generating ) . ' entries to check (creating OR generating status)' );
		}

		$this->logger->info(
			'Step 2: Check generation status',
			array(
				'count'            => count( $generating ),
				'statuses_checked' => array( 'creating', 'generating' ),
			)
		);

		if ( empty( $generating ) ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 2: No entries found with creating/generating status and article_id' );
			}
			return;
		}

		foreach ( $generating as $entry ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 2: Checking entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (status: ' . $entry['wisewand_status'] . ', article_id: ' . $entry['wisewand_article_id'] . ')' );
			}

			$this->check_article_status( $entry );
			++$this->run_summary['status_checked'];

			// Minimal delay for status checks (read-only operations)
			$this->smart_rate_limit( 0.5 ); // Min 0.5 seconds between status checks
		}

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 2: Completed checking ' . count( $generating ) . ' entries' );
		}
	}

	private function check_article_status( $entry ) {
		if ( ! $this->wisewand_api ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON check_article_status: No Wisewand API client available' );
			}
			return;
		}

		if ( ! $entry['wisewand_article_id'] ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON check_article_status: Entry ' . $entry['id'] . ' has no wisewand_article_id' );
			}
			return;
		}

		try {
			// v1.4.1: Use content type from database to route to correct endpoint
			$content_type = $this->determine_endpoint( $entry['wisewand_type'] ?? 'blog' );

			if ( WP_DEBUG ) {
				error_log( sprintf(
					'CRON: Checking article status - ID: %s, Type: %s, Endpoint: %s',
					$entry['wisewand_article_id'],
					$entry['wisewand_type'] ?? 'blog',
					$content_type
				) );
			}

			// Primary attempt: Use database-indicated endpoint
			$article = $this->wisewand_api->get_content( $content_type, $entry['wisewand_article_id'] );

			// v1.4.1: Check if we got 404 due to wrong endpoint (fallback needed)
			if ( is_wp_error( $article ) ) {
				$error_code    = $article->get_error_code();
				$error_message = $article->get_error_message();

				// 404 might mean wrong endpoint - try fallback
				if ( $error_code === 'wisewand_resource_not_found' ||
				     strpos( $error_message, '404' ) !== false ||
				     strpos( $error_message, 'not found' ) !== false ) {

					if ( WP_DEBUG ) {
						error_log( sprintf(
							'CRON: Article not found at %s endpoint, trying fallback for ID: %s',
							$content_type,
							$entry['wisewand_article_id']
						) );
					}

					// Try fallback to find correct endpoint
					$fallback_result = $this->check_article_status_with_fallback(
						$entry,
						$content_type
					);

					if ( ! is_wp_error( $fallback_result ) ) {
						// Fallback succeeded - use that article
						$article = $fallback_result;
					}
					// If fallback also failed, $article remains the original error
				}
			}

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

			// COMPREHENSIVE LOGGING: See exact API response
			if ( WP_DEBUG ) {
				error_log( '=== CRON STATUS CHECK ===' );
			}
			if ( WP_DEBUG ) {
				error_log( 'Entry ID: ' . $entry['id'] );
			}
			if ( WP_DEBUG ) {
				error_log( 'Keyword: ' . $entry['keyword'] );
			}
			if ( WP_DEBUG ) {
				error_log( 'Article ID: ' . $entry['wisewand_article_id'] );
			}
			if ( WP_DEBUG ) {
				error_log( 'Full API Response: ' . wp_json_encode( $article, JSON_PRETTY_PRINT ) );
			}
			if ( WP_DEBUG ) {
				error_log( '=========================' );
			}

			$status = $article['status'] ?? $article['data']['status'] ?? 'unknown';

			if ( WP_DEBUG ) {
				error_log( 'CRON: Extracted status for entry ' . $entry['id'] . ': ' . $status );
			}

			// DIAGNOSTIC: Check exact status value and type
			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: Status value = "' . $status . '"' );
			}
			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: Status type = ' . gettype( $status ) );
			}
			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: Status length = ' . strlen( $status ) );
			}
			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: Trimmed status = "' . trim( $status ) . '"' );
			}
			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: Status === "success" ? ' . ( $status === 'success' ? 'YES' : 'NO' ) );
			}
			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: Status == "success" ? ' . ( $status == 'success' ? 'YES' : 'NO' ) );
			}
			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: strcasecmp = ' . strcasecmp( $status, 'success' ) );
			}

			$this->logger->debug(
				'Article status check',
				array(
					'entry_id'           => $entry['id'],
					'article_id'         => $entry['wisewand_article_id'],
					'status'             => $status,
					'full_response_keys' => array_keys( $article ),
				)
			);

			if ( WP_DEBUG ) {
				error_log( '🔍 DIAGNOSTIC: About to check if status === success...' );
			}

			if ( $status === 'success' ) {
				if ( WP_DEBUG ) {
					error_log( '🔍 DIAGNOSTIC: ✅ ENTERED if ($status === "success") block!' );
				}
				if ( WP_DEBUG ) {
					error_log( '✅ CRON: Article ready! Updating entry ' . $entry['id'] . ' (' . $entry['keyword'] . ') to READY status' );
				}

				// CRITICAL FIX: Set wordpress_status to 'scheduled' so Step 3 can find it!
				$update_result = $this->entry_manager->update_entry(
					$entry['id'],
					array(
						'wisewand_status'         => 'ready',
						'wordpress_status'        => 'scheduled',  // ← This allows Step 3 to find the entry
						'generation_completed_at' => current_time( 'mysql' ),
					)
				);

				if ( is_wp_error( $update_result ) ) {
					if ( WP_DEBUG ) {
						error_log( '❌ CRON: Failed to update entry to ready: ' . $update_result->get_error_message() );
					}
				} elseif ( WP_DEBUG ) {
						error_log( '✅ CRON: Successfully updated entry ' . $entry['id'] . ' to READY status with wordpress_status=scheduled' );
				}

				$this->logger->info(
					'Article generation completed',
					array(
						'entry_id'   => $entry['id'],
						'article_id' => $entry['wisewand_article_id'],
						'keyword'    => $entry['keyword'],
					)
				);
			} elseif ( $status === 'error' ) {
				if ( WP_DEBUG ) {
					error_log( '🔍 DIAGNOSTIC: ❌ ENTERED elseif ($status === "error") block!' );
					error_log( '❌ FULL WISEWAND API ERROR RESPONSE:' );
					error_log( wp_json_encode( $article, JSON_PRETTY_PRINT ) );
				}

				// Extract error message from multiple possible locations
				$error_msg = $article['error']
					?? $article['status_detail']
					?? $article['data']['error']
					?? $article['data']['error_message']
					?? $article['data']['status_detail']
					?? '[No error details provided by Wisewand API]';

				// Add Wisewand dashboard link for manual investigation
				$wisewand_url  = 'https://app.wisewand.ai/articles/' . $entry['wisewand_article_id'];
				$full_error    = $error_msg . ' | Check Wisewand Dashboard: ' . $wisewand_url;

				if ( WP_DEBUG ) {
					error_log( '❌ ERROR MESSAGE: ' . $full_error );
				}

				throw new \Exception( $full_error );
			} elseif ( $status === 'queued' || $status === 'running' ) {
				if ( WP_DEBUG ) {
					error_log( '🔍 DIAGNOSTIC: ⏳ ENTERED elseif (queued OR running) block! Status = ' . $status );
				}
				// Still generating, do nothing (will check again on next CRON run)
				$this->logger->debug(
					'Article still generating',
					array(
						'entry_id' => $entry['id'],
						'status'   => $status,
					)
				);
			} else {
				if ( WP_DEBUG ) {
					error_log( '🔍 DIAGNOSTIC: ⚠️ ENTERED else block (unknown status)! Status = "' . $status . '"' );
				}
				if ( WP_DEBUG ) {
					error_log( '🔍 DIAGNOSTIC: This status is not handled by any condition!' );
				}
			}
		} catch ( \Exception $e ) {
			$this->logger->error(
				'Failed to check article status',
				array(
					'entry_id' => $entry['id'],
					'error'    => $e->getMessage(),
				)
			);

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wisewand_status' => 'failed',
					'wisewand_error'  => $e->getMessage(),
				)
			);
		}
	}

	/**
	 * Fallback method to find article when primary endpoint returns 404
	 * Tries alternative endpoints and updates database with correct type if found
	 *
	 * @since 1.4.1
	 * @param array  $entry         Calendar entry data.
	 * @param string $tried_endpoint The endpoint that already returned 404.
	 * @return array|WP_Error Article data if found, error otherwise.
	 */
	private function check_article_status_with_fallback( $entry, $tried_endpoint ) {
		$article_id = $entry['wisewand_article_id'];

		// All possible endpoints
		$all_endpoints = array( 'articles', 'discoverarticles', 'productpages', 'categorypages' );

		// Remove the one we already tried
		$fallback_endpoints = array_diff( $all_endpoints, array( $tried_endpoint ) );

		if ( WP_DEBUG ) {
			error_log( sprintf(
				'CRON FALLBACK: Trying %d alternative endpoints for article: %s',
				count( $fallback_endpoints ),
				$article_id
			) );
		}

		foreach ( $fallback_endpoints as $fallback_endpoint ) {
			if ( WP_DEBUG ) {
				error_log( sprintf(
					'CRON FALLBACK: Trying %s endpoint...',
					$fallback_endpoint
				) );
			}

			// Small delay to avoid hammering API
			sleep( 1 );

			$article = $this->wisewand_api->get_content( $fallback_endpoint, $article_id );

			if ( ! is_wp_error( $article ) ) {
				// SUCCESS: Found it at different endpoint!
				if ( $this->logger ) {
					$this->logger->info(
						'Article found via fallback - database type mismatch corrected',
						array(
							'entry_id'          => $entry['id'],
							'article_id'        => $article_id,
							'wrong_endpoint'    => $tried_endpoint,
							'correct_endpoint'  => $fallback_endpoint,
							'stored_type'       => $entry['wisewand_type'] ?? 'NOT SET',
						)
					);
				}

				if ( WP_DEBUG ) {
					error_log( sprintf(
						'CRON FALLBACK: ✅ Found at %s endpoint! Correcting database...',
						$fallback_endpoint
					) );
				}

				// Update database with correct type (self-healing)
				$this->correct_wisewand_type( $entry['id'], $fallback_endpoint, $entry['wisewand_type'] ?? 'blog' );

				return $article;
			}
		}

		// Article not found on any endpoint
		if ( WP_DEBUG ) {
			error_log( sprintf(
				'CRON FALLBACK: ✗ Article not found on ANY endpoint: %s',
				$article_id
			) );
		}

		return new \WP_Error(
			'wisewand_article_not_found_anywhere',
			__( 'Article could not be found on any API endpoint', 'wisewand-ai-content-writer-seo-content-generator' ),
			array(
				'article_id'       => $article_id,
				'endpoints_tried'  => array_merge( array( $tried_endpoint ), $fallback_endpoints ),
			)
		);
	}

	/**
	 * Update wisewand_type in database when fallback discovers correct endpoint
	 * Self-healing logic to prevent future fallback attempts
	 *
	 * @since 1.4.1
	 * @param int    $entry_id       Calendar entry ID.
	 * @param string $correct_endpoint The endpoint where article was found.
	 * @param string $wrong_type     The incorrect type that was stored.
	 */
	private function correct_wisewand_type( $entry_id, $correct_endpoint, $wrong_type ) {
		// v1.4.1: Validate entry_id is positive integer
		$entry_id = absint( $entry_id );
		if ( $entry_id <= 0 ) {
			if ( $this->logger ) {
				$this->logger->error(
					'Invalid entry_id in correct_wisewand_type',
					array( 'entry_id' => $entry_id )
				);
			}
			return;
		}

		// Map endpoint back to wisewand_type value
		$endpoint_to_type = array(
			'categorypages'    => 'categorypages',
			'productpages'     => 'productpages',
			'discoverarticles' => 'discoverarticles',
			'articles'         => 'blog', // Default for articles endpoint
		);

		$correct_type = $endpoint_to_type[ $correct_endpoint ] ?? 'blog';

		// Update database
		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->update(
			$table,
			array( 'wisewand_type' => $correct_type ),
			array( 'id' => $entry_id ),
			array( '%s' ),
			array( '%d' )
		);

		if ( $result === false ) {
			if ( $this->logger ) {
				$this->logger->error(
					'Failed to correct wisewand_type in database',
					array(
						'entry_id'      => $entry_id,
						'correct_type'  => $correct_type,
						'db_error'      => $wpdb->last_error,
					)
				);
			}

			if ( WP_DEBUG ) {
				error_log( sprintf(
					'CRON: Failed to update wisewand_type for entry %d: %s',
					$entry_id,
					$wpdb->last_error
				) );
			}
		} else {
			if ( $this->logger ) {
				$this->logger->info(
					'Auto-corrected wisewand_type in database',
					array(
						'entry_id'   => $entry_id,
						'old_type'   => $wrong_type,
						'new_type'   => $correct_type,
					)
				);
			}

			if ( WP_DEBUG ) {
				error_log( sprintf(
					'CRON: ✅ Corrected wisewand_type: "%s" → "%s" for entry %d',
					$wrong_type,
					$correct_type,
					$entry_id
				) );
			}
		}
	}

	/**
	 * Step 3 for 15-minute CRON: Import ready articles for today and past-due dates
	 * Lightweight version that only imports articles that are due now
	 */
	private function step_3_import_ready_articles() {
		$today = gmdate( 'Y-m-d' );
		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

		if ( WP_DEBUG ) {
			error_log( '=== CRON STEP 3 (15-min): Import Ready Articles ===' );
		}
		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Today: ' . $today . ', Current time: ' . current_time( 'mysql' ) );
		}

		// Get ready articles for today OR past-due (publication_date <= today)
		// Exclude already imported articles (wordpress_post_id IS NOT NULL)
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for ready articles to import
		$to_import = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$table}
             WHERE wisewand_status = 'ready'
             AND publication_date <= %s
             AND (wordpress_post_id IS NULL OR wordpress_post_id = 0)
             AND (wordpress_status = 'scheduled' OR wordpress_status IS NULL)
             ORDER BY publication_date ASC, publication_time ASC",
				$today
			),
			ARRAY_A
		);

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Found ' . count( $to_import ) . ' ready articles due for publication (today or past-due)' );
		}

		if ( empty( $to_import ) ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 3: No articles to import at this time' );
			}
			return;
		}

		// Log details
		foreach ( $to_import as $entry ) {
			$days_overdue = ( strtotime( $today ) - strtotime( $entry['publication_date'] ) ) / 86400;
			$status       = $days_overdue > 0 ? 'OVERDUE by ' . floor( $days_overdue ) . ' days' : 'DUE TODAY';
			if ( WP_DEBUG ) {
				error_log( '  Entry #' . $entry['id'] . ': ' . $entry['keyword'] . ' - ' . $status );
			}
			if ( WP_DEBUG ) {
				error_log( '    Publication date: ' . $entry['publication_date'] );
			}
			if ( WP_DEBUG ) {
				error_log( '    Publication time: ' . ( $entry['publication_time'] ?? 'NOT SET' ) );
			}
		}

		// Import each article
		$imported_count = 0;
		foreach ( $to_import as $entry ) {
			// Check if it's time to publish (respects publication_time)
			if ( ! $this->is_time_to_publish( $entry ) ) {
				$pub_time = $entry['publication_time'] ?? 'NOT SET';
				if ( WP_DEBUG ) {
					error_log( '  ⏰ Entry #' . $entry['id'] . ' not time yet (scheduled: ' . $pub_time . ')' );
				}
				continue;
			}

			if ( WP_DEBUG ) {
				error_log( '  ✅ Entry #' . $entry['id'] . ' - Time to import! Starting...' );
			}

			$result = $this->import_and_publish_entry( $entry );

			if ( ! is_wp_error( $result ) ) {
				++$imported_count;
				++$this->run_summary['imported'];
				if ( WP_DEBUG ) {
					error_log( '  ✅ Entry #' . $entry['id'] . ' imported successfully' );
				}
			} elseif ( WP_DEBUG ) {
					error_log( '  ❌ Entry #' . $entry['id'] . ' import failed: ' . $result->get_error_message() );
			}

			// Smart rate limiting for imports (most intensive operation)
			$this->smart_rate_limit( 2.0 ); // Min 2 seconds between imports
		}

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Imported ' . $imported_count . ' articles' );
		}
		if ( WP_DEBUG ) {
			error_log( '=== CRON STEP 3 (15-min): Complete ===' );
		}
	}

	/**
	 * Step 3 for Daily CRON: Full import and publish with diagnostics
	 * Legacy method with comprehensive logging (kept for manual triggering)
	 */
	private function step_3_import_and_publish() {
		$today = gmdate( 'Y-m-d' );

		if ( WP_DEBUG ) {
			error_log( '=== CRON STEP 3 (Daily): Import and Publish ===' );
		}
		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Looking for entries to import' );
		}
		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Today date: ' . $today );
		}
		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Current time: ' . current_time( 'mysql' ) );
		}

		// DIAGNOSTIC: Check ALL ready entries regardless of wordpress_status
		global $wpdb;
		$table = $wpdb->prefix . 'wisewand_editorial_calendar';

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for all ready articles diagnostic
		$all_ready = $wpdb->get_results(
			"SELECT id, keyword, publication_date, publication_time,
                    wisewand_status, wordpress_status, wordpress_post_id
             FROM {$table}
             WHERE wisewand_status = 'ready'
             ORDER BY publication_date ASC, publication_time ASC",
			ARRAY_A
		);

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Found ' . count( $all_ready ) . ' entries with wisewand_status=ready' );
		}

		if ( ! empty( $all_ready ) ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 3: Details of ready entries:' );
			}

			// ONE-TIME FIX: Update any ready entries that don't have wordpress_status set
			$fixed_count = 0;
			foreach ( $all_ready as $entry ) {
				$already_imported = ! empty( $entry['wordpress_post_id'] ) ? 'YES (post_id: ' . $entry['wordpress_post_id'] . ')' : 'NO';
				if ( WP_DEBUG ) {
					error_log( '  Entry #' . $entry['id'] . ': ' . $entry['keyword'] );
				}
				if ( WP_DEBUG ) {
					error_log( '    publication_date: ' . $entry['publication_date'] . ' (today: ' . $today . ')' );
				}
				if ( WP_DEBUG ) {
					error_log( '    publication_time: ' . ( $entry['publication_time'] ?? 'NOT SET' ) );
				}
				if ( WP_DEBUG ) {
					error_log( '    wordpress_status: ' . $entry['wordpress_status'] );
				}
				if ( WP_DEBUG ) {
					error_log( '    already_imported: ' . $already_imported );
				}

				// Fix entries that are ready but wordpress_status is not 'scheduled' and not imported yet
				// IMPORTANT: Do NOT reset imported statuses - only fix entries stuck in invalid states
				$invalid_states  = array( 'importing', 'failed' );
				$imported_states = array( 'imported_draft', 'imported_published' );

				if ( empty( $entry['wordpress_post_id'] )
					&& $entry['wordpress_status'] !== 'scheduled'
					&& ! in_array( $entry['wordpress_status'], $imported_states ) ) {

					// Only reset if in invalid state, not if it's a legitimate non-scheduled state
					if ( in_array( $entry['wordpress_status'], $invalid_states ) || empty( $entry['wordpress_status'] ) ) {
						if ( WP_DEBUG ) {
							error_log( '  🔧 FIXING: Setting wordpress_status=scheduled for entry #' . $entry['id'] . ' (was: ' . $entry['wordpress_status'] . ')' );
						}
                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table update for fixing wordpress_status
						$wpdb->update(
							$table,
							array( 'wordpress_status' => 'scheduled' ),
							array( 'id' => $entry['id'] ),
							array( '%s' ),
							array( '%d' )
						);
						++$fixed_count;
					}
				}
			}

			if ( $fixed_count > 0 ) {
				if ( WP_DEBUG ) {
					error_log( '✅ CRON Step 3: Fixed ' . $fixed_count . ' ready entries by setting wordpress_status=scheduled' );
				}
			}
		}

		// Original query - restrictive
		$to_publish = $this->entry_manager->get_entries(
			array(
				'publication_date' => $today,
				'wisewand_status'  => 'ready',
				'wordpress_status' => 'scheduled',
			)
		);

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Restrictive query (today + ready + scheduled) found: ' . count( $to_publish ) . ' entries' );
		}

		if ( count( $to_publish ) === 0 && ! empty( $all_ready ) ) {
			if ( WP_DEBUG ) {
				error_log( '⚠️  CRON Step 3: WARNING - Ready entries exist but none match wordpress_status=scheduled' );
			}
			if ( WP_DEBUG ) {
				error_log( '⚠️  CRON Step 3: This is likely why imports are not happening!' );
			}

			// Try broader query - any ready entry for today that hasn't been imported
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for broader ready articles check
			$to_publish_broader = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$table}
                 WHERE publication_date = %s
                 AND wisewand_status = 'ready'
                 AND (wordpress_post_id IS NULL OR wordpress_post_id = 0)
                 ORDER BY publication_time ASC",
					$today
				),
				ARRAY_A
			);

			if ( WP_DEBUG ) {
				error_log( 'CRON Step 3: Broader query (today + ready + not imported) found: ' . count( $to_publish_broader ) . ' entries' );
			}

			if ( count( $to_publish_broader ) > 0 ) {
				if ( WP_DEBUG ) {
					error_log( '✅ CRON Step 3: Using broader query results to import entries' );
				}
				$to_publish = $to_publish_broader;
			}
		}

		$this->logger->info(
			'Step 3: Import and publish',
			array(
				'today'           => $today,
				'count'           => count( $to_publish ),
				'all_ready_count' => count( $all_ready ),
			)
		);

		if ( empty( $to_publish ) ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 3: No entries to import' );
			}
			if ( WP_DEBUG ) {
				error_log( '=== CRON STEP 3: Complete ===' );
			}
			return;
		}

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Processing ' . count( $to_publish ) . ' entries for import' );
		}

		foreach ( $to_publish as $entry ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 3: Checking entry #' . $entry['id'] . ' - ' . $entry['keyword'] );
			}
			if ( WP_DEBUG ) {
				error_log( '  Publication time: ' . ( $entry['publication_time'] ?? 'NOT SET' ) );
			}
			if ( WP_DEBUG ) {
				error_log( '  Publication time range: ' . ( $entry['publication_time_range'] ?? 'NOT SET' ) );
			}

			if ( ! $this->is_time_to_publish( $entry ) ) {
				$pub_time = $entry['publication_time'] ?? 'NOT SET';
				$now      = current_time( 'mysql' );
				if ( WP_DEBUG ) {
					error_log( '  ⏰ NOT time to publish yet (scheduled: ' . $pub_time . ', now: ' . $now . ')' );
				}
				continue;
			}

			if ( WP_DEBUG ) {
				error_log( '  ✅ Time to publish! Starting import...' );
			}
			$this->import_and_publish_entry( $entry );
			++$this->run_summary['imported'];
			if ( WP_DEBUG ) {
				error_log( '  ✅ Import completed for entry #' . $entry['id'] );
			}

			// Smart rate limiting for imports
			$this->smart_rate_limit( 2.0 ); // Min 2 seconds between imports
		}

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3: Imported ' . $this->run_summary['imported'] . ' entries' );
		}
		if ( WP_DEBUG ) {
			error_log( '=== CRON STEP 3: Complete ===' );
		}
	}

	private function is_time_to_publish( $entry ) {
		if ( empty( $entry['publication_time'] ) ) {
			$random_time = $this->generate_random_time( $entry['publication_time_range'] );

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'publication_time' => $random_time,
				)
			);

			$entry['publication_time'] = $random_time;
		}

		$publish_datetime = strtotime( $entry['publication_date'] . ' ' . $entry['publication_time'] );
		$current_time     = current_time( 'timestamp' );

		return $current_time >= $publish_datetime;
	}

	private function generate_random_time( $range ) {
		$ranges = array(
			'random'    => array( '09:00:00', '20:00:00' ),
			'morning'   => array( '09:00:00', '12:00:00' ),
			'afternoon' => array( '12:00:00', '17:00:00' ),
			'evening'   => array( '17:00:00', '20:00:00' ),
		);

		[$start, $end] = $ranges[ $range ] ?? $ranges['random'];

		$start_seconds = strtotime( $start ) - strtotime( '00:00:00' );
		$end_seconds   = strtotime( $end ) - strtotime( '00:00:00' );

		$random_seconds = wp_rand( $start_seconds, $end_seconds );

		return gmdate( 'H:i:s', $random_seconds );
	}

	private function import_and_publish_entry( $entry ) {
		if ( WP_DEBUG ) {
			error_log( '=== IMPORT ENTRY #' . $entry['id'] . ' START ===' );
		}
		if ( WP_DEBUG ) {
			error_log( 'IMPORT: Entry keyword: ' . $entry['keyword'] );
		}
		if ( WP_DEBUG ) {
			error_log( 'IMPORT: Article ID: ' . $entry['wisewand_article_id'] );
		}
		if ( WP_DEBUG ) {
			error_log( 'IMPORT: Entry type: ' . ( $entry['wisewand_type'] ?? 'NOT SET' ) );
		}

		if ( ! $this->wisewand_api ) {
			if ( WP_DEBUG ) {
				error_log( '❌ IMPORT: No Wisewand API client available!' );
			}
			return;
		}

		try {
			// Load Content_Importer utility (shared with manual import)
			if ( ! class_exists( 'Wisewand\\Utils\\Content_Importer' ) ) {
				require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Content_Importer.php';
			}

			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Using shared Content_Importer utility (same as manual import!)' );
			}

			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Step 1 - Updating entry status to importing' );
			}
			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wordpress_status'  => 'importing',
					'import_started_at' => current_time( 'mysql' ),
				)
			);
			if ( WP_DEBUG ) {
				error_log( '✅ IMPORT: Status updated to importing' );
			}

			// Prepare import options from calendar entry
			// Use global default_post_status setting (Change 2: Simplified post status control)
			$default_status = $this->options->get( 'wisewand_default_post_status', 'draft' );
			$import_options = array(
				'post_status' => $default_status,
			);

			// Add author if specified
			if ( ! empty( $entry['created_by'] ) ) {
				$import_options['author_id'] = $entry['created_by'];
				if ( WP_DEBUG ) {
					error_log( 'IMPORT: Author from entry: ' . $entry['created_by'] );
				}
			}

			// Add categories if specified
			if ( ! empty( $entry['wordpress_categories'] ) ) {
				$categories = is_array( $entry['wordpress_categories'] )
					? $entry['wordpress_categories']
					: json_decode( $entry['wordpress_categories'], true );

				if ( ! empty( $categories ) && is_array( $categories ) ) {
					$import_options['categories'] = $categories;
					if ( WP_DEBUG ) {
						error_log( 'IMPORT: Categories from entry: ' . implode( ', ', $categories ) );
					}
				}
			}

			// Determine content type for API
			$content_type = $this->determine_endpoint( $entry['wisewand_type'] ?? 'blog' );
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Step 2 - Content type determined: ' . $content_type );
			}
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Import options: ' . wp_json_encode( $import_options ) );
			}

			// Use shared import utility - SAME LOGIC AS MANUAL IMPORT!
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Step 3 - Calling Content_Importer::import_content()...' );
			}
			$result = \Wisewand\Utils\Content_Importer::import_content(
				$entry['wisewand_article_id'],
				$content_type,
				$import_options,
				$this->wisewand_api,
				$this->content_manager
			);

			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Step 4 - Content_Importer returned' );
			}

			if ( is_wp_error( $result ) ) {
				if ( WP_DEBUG ) {
					error_log( '❌ IMPORT: Content_Importer returned WP_Error: ' . $result->get_error_message() );
				}
				throw new \Exception( $result->get_error_message() );
			}

			if ( WP_DEBUG ) {
				error_log( '✅ IMPORT: Content_Importer succeeded' );
			}
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Result keys: ' . ( is_array( $result ) ? implode( ', ', array_keys( $result ) ) : 'NOT ARRAY' ) );
			}

			// Verify import completed correctly
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Step 5 - Verifying import...' );
			}
			$verification = \Wisewand\Utils\Content_Importer::verify_import( $result, $entry['wisewand_article_id'] );

			if ( ! $verification['success'] ) {
				$error_msg = 'Import verification failed: ' . implode( ', ', $verification['errors'] );
				if ( WP_DEBUG ) {
					error_log( '❌ IMPORT: ' . $error_msg );
				}
				throw new \Exception( $error_msg );
			}

			if ( ! empty( $verification['warnings'] ) ) {
				if ( WP_DEBUG ) {
					error_log( '⚠️  IMPORT: Warnings - ' . implode( ', ', $verification['warnings'] ) );
				}
			}

			if ( WP_DEBUG ) {
				error_log( '✅ IMPORT: Verification passed' );
			}

			// Extract post_id or category_id
			$post_id     = $result['post_id'] ?? null;
			$category_id = $result['category_id'] ?? null;

			if ( ! $post_id && ! $category_id ) {
				if ( WP_DEBUG ) {
					error_log( '❌ IMPORT: No post_id or category_id in result' );
				}
				throw new \Exception( 'No post_id or category_id returned after import' );
			}

			if ( WP_DEBUG ) {
				error_log( '✅ IMPORT: Post/Category ID obtained: ' . ( $post_id ?? $category_id ) );
			}

			// IMPORTANT: Determine sub-status based on actual WordPress post status
			// This implements the new "Imported" status with draft/published sub-statuses
			// Change 2: Use default_post_status setting as fallback
			$actual_post_status = $result['post_status'] ?? null;

			if ( $actual_post_status === 'publish' ) {
				$final_status = 'imported_published';
			} elseif ( $actual_post_status === 'draft' || $actual_post_status === 'pending' ) {
				$final_status = 'imported_draft';
			} else {
				// Fallback to default_post_status if post_status not available
				$default_status = $this->options->get( 'wisewand_default_post_status', 'draft' );
				$final_status   = ( $default_status === 'publish' ) ? 'imported_published' : 'imported_draft';
			}

			if ( WP_DEBUG ) {
				error_log( 'IMPORT: Step 6 - Updating entry in calendar' );
			}
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: actual_post_status from result: ' . ( $actual_post_status ?? 'NOT SET' ) );
			}
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: final_status: ' . $final_status );
			}
			if ( WP_DEBUG ) {
				error_log( 'IMPORT: default_post_status setting: ' . $this->options->get( 'wisewand_default_post_status', 'draft' ) );
			}

			$update_data = array(
				'wordpress_status' => $final_status,
				'published_at'     => current_time( 'mysql' ),
			);

			if ( $post_id ) {
				$update_data['wordpress_post_id'] = $post_id;
			}

			// CRITICAL: Check if status update succeeds
			$update_result = $this->entry_manager->update_entry( $entry['id'], $update_data );

			if ( is_wp_error( $update_result ) ) {
				if ( WP_DEBUG ) {
					error_log( '❌ IMPORT: Failed to update calendar entry status!' );
				}
				if ( WP_DEBUG ) {
					error_log( '❌ IMPORT: Error: ' . $update_result->get_error_message() );
				}
				if ( WP_DEBUG ) {
					error_log( '❌ IMPORT: Update data: ' . wp_json_encode( $update_data ) );
				}
				throw new \Exception( 'Failed to update calendar entry status: ' . $update_result->get_error_message() );
			}

			if ( WP_DEBUG ) {
				error_log( '✅ IMPORT: Entry updated in calendar with status: ' . $final_status );
			}
			if ( WP_DEBUG ) {
				error_log( '✅ IMPORT: wordpress_post_id saved: ' . ( $post_id ?? 'NONE' ) );
			}

			$this->logger->info(
				'Content imported successfully',
				array(
					'entry_id'    => $entry['id'],
					'post_id'     => $post_id,
					'category_id' => $category_id,
					'status'      => $final_status,
				)
			);

			// Sync published status to Wisewand API
			if ( ! empty( $entry['wisewand_article_id'] ) ) {
				$is_published = ( $final_status === 'imported_published' );
				$this->update_wisewand_published_status( $entry['wisewand_article_id'], $is_published );
			}

			if ( WP_DEBUG ) {
				error_log( '=== IMPORT ENTRY #' . $entry['id'] . ' SUCCESS ===' );
			}
			if ( WP_DEBUG ) {
				error_log( '' );
			}

			return $result;

		} catch ( \Exception $e ) {
			if ( WP_DEBUG ) {
				error_log( '❌❌❌ IMPORT EXCEPTION ❌❌❌' );
			}
			if ( WP_DEBUG ) {
				error_log( 'Entry #' . $entry['id'] . ' - ' . $entry['keyword'] );
			}
			if ( WP_DEBUG ) {
				error_log( 'Error: ' . $e->getMessage() );
			}
			if ( WP_DEBUG ) {
				error_log( 'Stack trace: ' . $e->getTraceAsString() );
			}
			if ( WP_DEBUG ) {
				error_log( '=== IMPORT ENTRY #' . $entry['id'] . ' FAILED ===' );
			}
			if ( WP_DEBUG ) {
				error_log( '' );
			}

			$this->logger->error(
				'Failed to import content',
				array(
					'entry_id' => $entry['id'],
					'error'    => $e->getMessage(),
				)
			);

			$this->entry_manager->update_entry(
				$entry['id'],
				array(
					'wordpress_status' => 'failed',
					'wordpress_error'  => $e->getMessage(),
				)
			);

			// Return error but don't throw - allow other entries to process
			return new \WP_Error( 'import_failed', $e->getMessage() );
		}
	}

	/**
	 * Update is_published field in Wisewand API
	 * Syncs WordPress publish status back to Wisewand for analytics and tracking
	 *
	 * @param string $article_id Wisewand article ID
	 * @param bool   $is_published Publication status (true = published, false = draft/unpublished)
	 */
	private function update_wisewand_published_status( $article_id, $is_published ) {
		if ( ! $this->wisewand_api ) {
			if ( WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: No API client available, skipping is_published update' );
			}
			return;
		}

		try {
			if ( WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: Updating article ' . $article_id . ' with is_published = ' . ( $is_published ? 'true' : 'false' ) );
			}

			$result = $this->wisewand_api->update_article(
				$article_id,
				array(
					'is_published' => $is_published,
				)
			);

			if ( is_wp_error( $result ) ) {
				$this->logger->warning(
					'Failed to update Wisewand is_published status',
					array(
						'article_id'   => $article_id,
						'is_published' => $is_published,
						'error'        => $result->get_error_message(),
					)
				);
				if ( WP_DEBUG ) {
					error_log( 'WISEWAND_SYNC: PATCH failed - ' . $result->get_error_message() );
				}
				return;
			}

			$this->logger->info(
				'Updated Wisewand is_published status',
				array(
					'article_id'   => $article_id,
					'is_published' => $is_published,
				)
			);

			if ( WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: ✅ Successfully updated article ' . $article_id . ' is_published = ' . ( $is_published ? 'true' : 'false' ) );
			}
		} catch ( \Exception $e ) {
			$this->logger->error(
				'Exception updating Wisewand is_published status',
				array(
					'article_id'   => $article_id,
					'is_published' => $is_published,
					'error'        => $e->getMessage(),
				)
			);
			if ( WP_DEBUG ) {
				error_log( 'WISEWAND_SYNC: Exception - ' . $e->getMessage() );
			}
		}
	}

	private function update_existing_post( $entry, $content ) {
		if ( WP_DEBUG ) {
			error_log( 'UPDATE_POST: Starting update for post ID ' . $entry['existing_post_id'] );
		}
		if ( WP_DEBUG ) {
			error_log( 'UPDATE_POST: Content keys available: ' . implode( ', ', array_keys( $content ) ) );
		}
		if ( WP_DEBUG ) {
			error_log( 'UPDATE_POST: Content title: ' . ( $content['title'] ?? 'MISSING' ) );
		}
		if ( WP_DEBUG ) {
			error_log( 'UPDATE_POST: Content content length: ' . ( isset( $content['content'] ) ? strlen( $content['content'] ) : 0 ) );
		}

		// Validate we have content to update
		if ( empty( $content['content'] ) && empty( $content['title'] ) ) {
			if ( WP_DEBUG ) {
				error_log( '❌ UPDATE_POST: Both content and title are empty!' );
			}
			throw new \Exception( 'Content, title, and excerpt are empty.' );
		}

		// Change 2: Use default_post_status instead of deprecated publish_as_draft
		$default_status = $this->options->get( 'wisewand_default_post_status', 'draft' );
		$post_data      = array(
			'ID'          => $entry['existing_post_id'],
			'post_status' => $default_status,
		);

		$strategy = $entry['content_merge_strategy'] ?? 'replace';
		if ( WP_DEBUG ) {
			error_log( 'UPDATE_POST: Merge strategy: ' . $strategy );
		}

		if ( $strategy === 'replace' ) {
			$post_data['post_content'] = $content['content'] ?? '';
			$post_data['post_title']   = $content['title'] ?? '';
			if ( WP_DEBUG ) {
				error_log( 'UPDATE_POST: Replace mode - Title: "' . $post_data['post_title'] . '"' );
			}
			if ( WP_DEBUG ) {
				error_log( 'UPDATE_POST: Replace mode - Content length: ' . strlen( $post_data['post_content'] ) );
			}
		} elseif ( $strategy === 'append' ) {
			$existing                  = get_post( $entry['existing_post_id'] );
			$post_data['post_content'] = $existing->post_content . "\n\n" . ( $content['content'] ?? '' );
			if ( WP_DEBUG ) {
				error_log( 'UPDATE_POST: Append mode - keeping existing title' );
			}
		} elseif ( $strategy === 'prepend' ) {
			$existing                  = get_post( $entry['existing_post_id'] );
			$post_data['post_content'] = ( $content['content'] ?? '' ) . "\n\n" . $existing->post_content;
			if ( WP_DEBUG ) {
				error_log( 'UPDATE_POST: Prepend mode - keeping existing title' );
			}
		}

		if ( WP_DEBUG ) {
			error_log( 'UPDATE_POST: Final post_data keys: ' . implode( ', ', array_keys( $post_data ) ) );
		}
		if ( WP_DEBUG ) {
			error_log( 'UPDATE_POST: Calling wp_update_post...' );
		}

		$result = wp_update_post( $post_data, true );

		if ( is_wp_error( $result ) ) {
			if ( WP_DEBUG ) {
				error_log( '❌ UPDATE_POST: wp_update_post returned error: ' . $result->get_error_message() );
			}
			throw new \Exception( esc_html( $result->get_error_message() ) );
		}

		if ( WP_DEBUG ) {
			error_log( '✅ UPDATE_POST: Post updated successfully, ID: ' . $result );
		}

		return $entry['existing_post_id'];
	}

	/**
	 * Step 3.5: Publish scheduled RSS articles (RSS v2.0)
	 *
	 * Checks RSS articles that are imported as drafts and waiting for their scheduled
	 * publication time. When scheduled_publish_time is reached, publishes the article.
	 *
	 * This respects publication time restrictions (morning/afternoon/evening) and
	 * allowed days (Mon-Fri, etc) configured in Calendar or per-feed settings.
	 */
	private function step_3_5_publish_scheduled_rss_articles() {
		global $wpdb;
		$articles_table = $wpdb->prefix . 'wisewand_rss_articles';
		$now            = current_time( 'mysql' );

		// Diagnostic logging to identify which CRON condition is blocking publication
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			// Log total articles in each status
			$status_breakdown = $wpdb->get_results(
				"SELECT
					import_status,
					wordpress_post_status,
					COUNT(*) as count
				FROM {$articles_table}
				GROUP BY import_status, wordpress_post_status",
				ARRAY_A
			);

			$this->logger->debug(
				'CRON_PUBLISH_STATUS: Article status breakdown',
				array( 'breakdown' => $status_breakdown )
			);

			// Log articles that fail each condition
			$cond1 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE import_status != 'imported'" );
			$cond2 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE wordpress_post_status != 'draft'" );
			$cond3 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE scheduled_publish_time IS NULL" );
			$cond4 = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$articles_table} WHERE scheduled_publish_time > %s", current_time( 'mysql' ) ) );
			$cond5 = $wpdb->get_var( "SELECT COUNT(*) FROM {$articles_table} WHERE wordpress_post_id IS NULL OR wordpress_post_id = 0" );

			$this->logger->debug(
				'CRON_PUBLISH_CONDITIONS: Articles failing each condition',
				array(
					'not_imported'         => $cond1,
					'not_draft'            => $cond2,
					'no_schedule'          => $cond3,
					'schedule_in_future'   => $cond4,
					'no_post_id'           => $cond5,
				)
			);
		}

		// Find RSS articles ready to publish
		$ready_to_publish = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM {$articles_table}
             WHERE import_status = 'imported'
             AND wordpress_post_status = 'draft'
             AND scheduled_publish_time IS NOT NULL
             AND scheduled_publish_time <= %s
             AND wordpress_post_id IS NOT NULL
             AND wordpress_post_id > 0",
				$now
			),
			ARRAY_A
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( 'CRON Step 3.5 (RSS Publish): Checking for scheduled RSS articles to publish' );
			error_log( 'CRON Step 3.5: Current time: ' . $now );
			error_log( 'CRON Step 3.5: Found ' . count( $ready_to_publish ) . ' RSS article(s) ready to publish' );
		}

		if ( empty( $ready_to_publish ) ) {
			return; // Nothing to publish
		}

		$published_count = 0;
		$failed_count    = 0;

		foreach ( $ready_to_publish as $article ) {
			// Log each article being processed
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				$this->logger->debug(
					'CRON_PUBLISH_ATTEMPT: Processing article for publication',
					array(
						'article_id'        => $article['id'],
						'wordpress_post_id' => $article['wordpress_post_id'],
						'scheduled_time'    => $article['scheduled_publish_time'],
						'current_time'      => $now,
						'current_status'    => $article['wordpress_post_status'],
						'title'             => $article['article_title'],
					)
				);
			}

			try {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'CRON Step 3.5: Publishing RSS article #' . $article['wordpress_post_id'] . ' - ' . $article['article_title'] );
					error_log( 'CRON Step 3.5: Scheduled time: ' . $article['scheduled_publish_time'] . ', Current: ' . $now );
				}

				// VALIDATION: Check post exists before attempting to publish
				$post = get_post( $article['wordpress_post_id'] );
				if ( ! $post ) {
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON Step 3.5: ⚠️ Post #' . $article['wordpress_post_id'] . ' not found (deleted?), cleaning up tracking' );
					}

					// Update tracking record to reflect post deletion
					$wpdb->update(
						$articles_table,
						array(
							'wordpress_post_status' => 'deleted',
							'import_status'         => 'failed',
							'import_error'          => 'WordPress post was deleted',
						),
						array( 'id' => $article['id'] ),
						array( '%s', '%s', '%s' ),
						array( '%d' )
					);

					$this->logger->warning(
						'RSS article post not found (deleted?), cleaned up tracking',
						array(
							'article_id'        => $article['id'],
							'wordpress_post_id' => $article['wordpress_post_id'],
							'article_title'     => $article['article_title'],
						)
					);

					++$failed_count;
					continue; // Skip to next article
				}

				// VALIDATION: Verify post is in draft status
				if ( $post->post_status !== 'draft' ) {
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'CRON Step 3.5: ⚠️ Post #' . $article['wordpress_post_id'] . ' is already "' . $post->post_status . '", skipping' );
					}

					// Update tracking to match actual post status
					$wpdb->update(
						$articles_table,
						array( 'wordpress_post_status' => $post->post_status ),
						array( 'id' => $article['id'] ),
						array( '%s' ),
						array( '%d' )
					);

					// Don't count as failure - just already published
					continue;
				}

				// Update WordPress post status to publish
				$updated = wp_update_post(
					array(
						'ID'          => $article['wordpress_post_id'],
						'post_status' => 'publish',
					),
					true
				); // Return WP_Error on failure

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

				if ( ! $updated || $updated === 0 ) {
					throw new \Exception( 'Failed to update post status' );
				}

				// Update tracking in database
				$wpdb->update(
					$articles_table,
					array( 'wordpress_post_status' => 'publish' ),
					array( 'id' => $article['id'] ),
					array( '%s' ),
					array( '%d' )
				);

				// Sync to Wisewand API
				if ( ! class_exists( 'Wisewand\\Utils\\Wisewand_API_Client' ) ) {
					require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Wisewand_API_Client.php';
				}

				$api_client = new \Wisewand\Utils\Wisewand_API_Client( $this->options->get( 'wisewand_api_key' ), $this->logger );
				$api_client->update_article( $article['wisewand_article_id'], array( 'is_published' => true ) );

				++$published_count;

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( "CRON Step 3.5: ✅ Published RSS article #{$article['wordpress_post_id']} successfully" );
				}
			} catch ( \Exception $e ) {
				++$failed_count;

				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( "CRON Step 3.5: ❌ Failed to publish article #{$article['wordpress_post_id']}: " . $e->getMessage() );
				}

				$this->logger->error(
					'Failed to publish scheduled RSS article',
					array(
						'article_id'        => $article['id'],
						'wordpress_post_id' => $article['wordpress_post_id'],
						'error'             => $e->getMessage(),
					)
				);
			}
		}

		$this->logger->info(
			'Step 3.5: Published scheduled RSS articles',
			array(
				'ready_to_publish' => count( $ready_to_publish ),
				'published'        => $published_count,
				'failed'           => $failed_count,
			)
		);

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 3.5: Summary - Published: ' . $published_count . ', Failed: ' . $failed_count );
		}
	}

	private function step_4_retry_failed( $force_retry = false ) {
		// POLICY: Only retry 2 times max
		// After 2 failures, it means Wisewand API has an issue - user should contact support
		$max_retries = $this->options->get( 'wisewand_cron_max_retries', 2 );

		$failed = $this->entry_manager->get_entries(
			array(
				'wisewand_status' => 'failed',
			)
		);

		if ( $force_retry ) {
			if ( WP_DEBUG ) {
				error_log( 'CRON Step 4: FORCE RETRY MODE - resetting retry counters and ignoring backoff for ' . count( $failed ) . ' failed entries' );
			}

			// Reset retry counters when manually triggered (give failed entries a fresh start)
			foreach ( $failed as $entry ) {
				if ( ! empty( $entry['wisewand_article_id'] ) ) {
					$this->entry_manager->update_entry(
						$entry['id'],
						array(
							'wisewand_retry_count' => 0, // Reset counter
							'wisewand_error'       => null,
						)
					);
					if ( WP_DEBUG ) {
						error_log( 'CRON Step 4: Reset retry counter for entry ' . $entry['id'] );
					}
				}
			}

			// Reload entries after reset
			$failed = $this->entry_manager->get_entries(
				array(
					'wisewand_status' => 'failed',
				)
			);
		} elseif ( WP_DEBUG ) {
				error_log( 'CRON Step 4: Found ' . count( $failed ) . ' failed entries to check for retry (with exponential backoff)' );
		}

		$retried = 0;
		$skipped = 0;

		foreach ( $failed as $entry ) {
			$retry_count = $entry['wisewand_retry_count'] ?? 0;

			// In force retry mode, ignore max retries limit (user explicitly requesting retry)
			if ( ! $force_retry && $retry_count >= $max_retries ) {
				if ( WP_DEBUG ) {
					error_log( 'CRON Step 4: Skipping entry ' . $entry['id'] . ' - max retries reached (' . $retry_count . '/' . $max_retries . ')' );
				}

				// Mark as permanently failed - needs Wisewand support
				$this->entry_manager->update_entry(
					$entry['id'],
					array(
						'wisewand_error' => $entry['wisewand_error'] . ' | Max retries reached (' . $max_retries . '). Please contact Wisewand support at https://wisewand.ai/support',
						'notes'          => 'PERMANENTLY FAILED after ' . $max_retries . ' automatic retries. The issue persists on Wisewand API side. User should contact Wisewand support for assistance.',
					)
				);

				$this->logger->warning(
					'Entry permanently failed - needs Wisewand support',
					array(
						'entry_id'    => $entry['id'],
						'keyword'     => $entry['keyword'],
						'retry_count' => $retry_count,
						'error'       => $entry['wisewand_error'],
					)
				);

				++$skipped;
				continue;
			}

			// Check if it's time to retry (or force retry)
			$wait_hours       = pow( 2, $retry_count );
			$next_retry_time  = strtotime( $entry['updated_at'] ) + ( $wait_hours * 3600 );
			$time_until_retry = $next_retry_time - time();
			$should_retry     = time() >= $next_retry_time;

			// FORCE RETRY: Ignore exponential backoff if manually triggered
			if ( $force_retry ) {
				$should_retry = true;
				if ( WP_DEBUG ) {
					error_log( 'CRON Step 4: FORCE retrying entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (ignoring ' . ceil( $time_until_retry / 3600 ) . 'h wait)' );
				}
			}

			if ( $should_retry ) {
				if ( WP_DEBUG ) {
					error_log( 'CRON Step 4: Retrying entry ' . $entry['id'] . ' - ' . $entry['keyword'] . ' (attempt ' . ( $retry_count + 1 ) . '/' . $max_retries . ')' );
				}

				$this->logger->info(
					'Retrying failed entry',
					array(
						'entry_id'    => $entry['id'],
						'retry_count' => $retry_count + 1,
					)
				);

				// Check if article already exists on Wisewand
				if ( ! empty( $entry['wisewand_article_id'] ) ) {
					// Article already created - just check status instead of recreating
					if ( WP_DEBUG ) {
						error_log( 'CRON Step 4: Entry ' . $entry['id'] . ' already has article_id (' . $entry['wisewand_article_id'] . '), checking status instead of recreating' );
					}

					$this->entry_manager->update_entry(
						$entry['id'],
						array(
							'wisewand_status' => 'generating', // Set to generating to be picked up by status check
							'wisewand_error'  => null,
						)
					);

					// Let status check CRON handle it (don't recreate!)
					++$skipped;
				} else {
					// No article_id - safe to create new article
					if ( WP_DEBUG ) {
						error_log( 'CRON Step 4: Entry ' . $entry['id'] . ' has no article_id, creating new article' );
					}

					$this->entry_manager->update_entry(
						$entry['id'],
						array(
							'wisewand_status' => 'not_created',
							'wisewand_error'  => null,
						)
					);

					$this->generate_article( $entry );
					++$retried;
					++$this->run_summary['retried'];

					// Smart rate limiting for retries
					$this->smart_rate_limit( 1.0 ); // Min 1 second between retries
				}
			} else {
				$hours_remaining = ceil( $time_until_retry / 3600 );
				if ( WP_DEBUG ) {
					error_log( 'CRON Step 4: Skipping entry ' . $entry['id'] . ' - must wait ' . $hours_remaining . 'h before retry (exponential backoff)' );
				}
				++$skipped;
			}
		}

		if ( WP_DEBUG ) {
			error_log( 'CRON Step 4: Retried ' . $retried . ', Skipped ' . $skipped . ' (waiting for backoff time)' );
		}

		if ( $retried > 0 ) {
			$this->logger->info( 'Step 4: Retried failed entries', array( 'count' => $retried ) );
		}
	}

	public function get_cron_summary() {
		$stats = $this->entry_manager->get_statistics();

		return array_merge(
			$this->run_summary,
			array(
				'total_scheduled' => $stats['total_scheduled'] ?? 0,
				'not_created'     => $stats['by_wisewand_status']['not_created'] ?? 0,
				'generating'      => $stats['by_wisewand_status']['generating'] ?? 0,
				'ready'           => $stats['by_wisewand_status']['ready'] ?? 0,
				'failed'          => $stats['by_wisewand_status']['failed'] ?? 0,
				'published'       => $stats['by_wordpress_status']['published'] ?? 0,
				'upcoming_7_days' => $stats['upcoming_7_days'] ?? 0,
			)
		);
	}

	/**
	 * Clean payload for API submission
	 * Removes invalid fields based on endpoint/article type
	 * Same logic as manual "Generate Now" to ensure consistency
	 *
	 * @param array  $payload Raw payload from database
	 * @param string $endpoint API endpoint (articles, categorypages, etc.)
	 * @param string $article_type Article type (blog, affiliation-*, etc.)
	 * @return array Cleaned payload with only valid fields
	 */
	private function clean_payload_for_api( $payload, $endpoint, $article_type ) {
		// Define valid fields per endpoint (based on Editorial_Calendar.php logic)
		$valid_fields = $this->get_valid_fields_for_endpoint( $endpoint, $article_type );

		$clean = array();

		foreach ( $valid_fields as $field ) {
			if ( ! isset( $payload[ $field ] ) ) {
				continue;
			}

			$value = $payload[ $field ];

			// Skip empty values for optional fields
			$required = array( 'subject', 'title', 'target_keyword', 'lang', 'country' );
			if ( ( $value === '' || $value === null ) && ! in_array( $field, $required ) ) {
				continue;
			}

			// Convert boolean fields to proper boolean type (critical for API validation!)
			$boolean_fields = array(
				'use_toc',
				'use_summary',
				'use_bulletlist',
				'use_blockquotes',
				'use_infotable',
				'use_boldkeywords',
				'use_externalsources',
				'use_faq',
				'use_cta',
				'use_social',
				'use_image',
				'use_inlineimages',
				'use_audio',
				'use_internallinks',
				'use_indexed_pages_internal_links',
				'use_wp_internal_links',
				'use_app_internal_links',
				'use_list_internal_links',
				'keep_title',
				'image_use_palette',
				'inline_images_use_palette',
				'use_webhook',
			);

			if ( in_array( $field, $boolean_fields ) ) {
				// Convert to proper boolean (not string "true" or integer 1)
				$value = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
			}

			// Integer fields needing type preservation
			$integer_fields = array( 'inlineimages_count' );
			if ( in_array( $field, $integer_fields ) && is_numeric( $value ) ) {
				$value = absint( $value );
			}

			// Handle arrays - filter empty values
			if ( is_array( $value ) ) {
				$value = array_filter( $value, fn( $v ) => $v !== '' && $v !== null && $v !== false );

				// Skip empty arrays (except required ones)
				if ( empty( $value ) && ! in_array( $field, array( 'social_networks', 'internal_links_wp_status' ) ) ) {
					continue;
				}

				// Re-index array
				$value = array_values( $value );
			}

			$clean[ $field ] = $value;
		}

		return $clean;
	}

	/**
	 * Get valid fields for endpoint/article type
	 * Based on Wisewand API specifications
	 */
	private function get_valid_fields_for_endpoint( $endpoint, $article_type ) {
		// Common fields for all endpoints
		$common = array(
			'subject',
			'title',
			'target_keyword',
			'lang',
			'country',
			'project_id',
			'persona_id',
			'writing_style_mode',
			'length',
			'type',
			'use_toc',
			'use_summary',
			'use_bulletlist',
			'use_blockquotes',
			'use_infotable',
			'use_boldkeywords',
			'use_externalsources',
			'use_faq',
			'faq_questions',
			'use_cta',
			'ctas',
			'use_social',
			'social_networks',
			'use_image',
			'image_ratio',
			'image_use_palette',
			'image_primary_color',
			'image_secondary_color',
			'image_prompt',
			'use_inlineimages',
			'inlineimages_count',
			'inline_images_ratio',
			'inline_images_use_palette',
			'inline_images_primary_color',
			'inline_images_secondary_color',
			'use_audio',
			'audio_voice_id',
			'use_internallinks',
			'use_indexed_pages_internal_links',
			'use_wp_internal_links',
			'use_app_internal_links',
			'use_list_internal_links',
			'internal_links_list',
			'internal_links_wp_status',
			'use_webhook',
			'webhook_connection_id',
		);

		// Articles-specific fields
		if ( $endpoint === 'articles' ) {
			$common[] = 'keep_title';
			$common[] = 'keywords_secondary';
			$common[] = 'search_intention_answer';
			$common[] = 'avoid_information';
			$common[] = 'additional_information';

			// Affiliation fields for affiliation article types
			if ( strpos( $article_type, 'affiliation-' ) === 0 ) {
				$common = array_merge(
					$common,
					array(
						'affiliation_product_name',
						'affiliation_product_review_urls',
						'affiliation_links',
						'affiliation_promo_code',
						'affiliation_promo_percentage',
					)
				);
			}
		}

		// Category/Product pages specific
		if ( $endpoint === 'categorypages' || $endpoint === 'productpages' ) {
			$common[] = 'website';
			$common[] = 'writing_style';
		}

		return $common;
	}

	/**
	 * Smart rate limiting that only delays if calls are too frequent
	 * More efficient than fixed sleep() as it adapts to actual call timing
	 *
	 * @param float $min_seconds Minimum seconds between API calls
	 */
	private function smart_rate_limit( $min_seconds = 1.0 ) {
		if ( $this->last_api_call_time === 0 ) {
			// First call - no delay needed
			$this->last_api_call_time = microtime( true );
			return;
		}

		$elapsed       = microtime( true ) - $this->last_api_call_time;
		$required_wait = $min_seconds - $elapsed;

		if ( $required_wait > 0 ) {
			// We're going too fast - delay
			$wait_microseconds = (int) ( $required_wait * 1000000 );
			usleep( $wait_microseconds );

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				$this->logger->debug(
					'Rate limit delay applied',
					array(
						'elapsed_since_last_call' => round( $elapsed, 2 ),
						'delay_applied'           => round( $required_wait, 2 ),
					)
				);
			}
		}

		// Update last call time
		$this->last_api_call_time = microtime( true );
	}
}
