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

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

use WP_Error;
use WP_Post;
use Wisewand\Utils\Options;
use Wisewand\Logging\Logger;
use Wisewand\Media\Media_Manager;

// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- error_log() used within WP_DEBUG conditionals; dynamic placeholder generation for IN clauses; table names derived from $wpdb->prefix are safe to interpolate; direct database calls required for custom plugin table operations

class Content_Manager {

	private $options;
	private $logger;
	private $media_manager;
	private $pull_queue_table;

	/**
	 * Taxonomy processing delegate.
	 *
	 * @since 2.8.2
	 * @var Taxonomy_Processor
	 */
	private $taxonomy_processor;

	/**
	 * Pull content and queue management delegate.
	 *
	 * @since 2.8.2
	 * @var Pull_Content_Handler
	 */
	private $pull_content_handler;

	/**
	 * Category creation and update delegate.
	 *
	 * @since 2.8.2
	 * @var Category_Creator
	 */
	private $category_creator;

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

		global $wpdb;
		$this->pull_queue_table = $wpdb->prefix . 'wisewand_pull_queue';

		$this->taxonomy_processor   = new Taxonomy_Processor( $this->options, $this->logger );
		$this->pull_content_handler = new Pull_Content_Handler( $this->options, $this->logger, $this->pull_queue_table );
		$this->category_creator     = new Category_Creator( $this->options, $this->logger );

		$this->taxonomy_processor->set_content_manager( $this );
		$this->pull_content_handler->set_content_manager( $this );
		$this->category_creator->set_content_manager( $this );
	}

	public function process_content( array $payload ) {
		$this->logger->debug(
			'Processing content payload',
			array(
				'external_id'  => $payload['external_id'] ?? 'unknown',
				'payload_keys' => array_keys( $payload ),
			)
		);

		// Validate required fields
		$validation_result = $this->validate_payload( $payload );
		if ( is_wp_error( $validation_result ) ) {
			return $validation_result;
		}

		// Determine if this is an update or create operation
		$existing_post = $this->find_existing_post( $payload );
		$operation     = $existing_post ? 'update' : 'create';

		// Extract categories early so they're available for debug logging later
		$category_ids = $this->taxonomy_processor->extract_and_prepare_categories( $payload );

		// Prepare post data (passing extracted categories)
		$post_data = $this->prepare_post_data( $payload, $existing_post, $category_ids );
		if ( is_wp_error( $post_data ) ) {
			return $post_data;
		}

		// Handle special post types (categories vs regular posts)
		$target_post_type = $this->category_creator->determine_target_post_type( $payload );

		if ( 'category' === $target_post_type ) {
			// Special handling for WordPress categories (taxonomies, not posts)
			$category_id = $this->category_creator->handle_category_creation( $payload );

			if ( is_wp_error( $category_id ) ) {
				$this->logger->error(
					'Failed to create/update category',
					array(
						'error' => $category_id->get_error_message(),
					)
				);
				return $category_id;
			}

			$language = $this->taxonomy_processor->process_language_for_taxonomy( $category_id, $payload );

			return array(
				'post_id'     => null,
				'category_id' => $category_id,
				'operation'   => 'category_' . ( ! empty( $payload['update_existing_category'] ) ? 'update' : 'create' ),
				'language'    => $language,
				'warnings'    => array(),
				'taxonomy'    => $this->taxonomy_processor->get_taxonomy_for_post_type( $payload['target_post_type'] ?? 'category' ),
			);
		}

		// Regular post/page/product creation
		if ( WP_DEBUG ) {
			error_log( 'CREATE_POST: Operation: ' . $operation );
			error_log( 'CREATE_POST: Post data summary:' );
			error_log( '  - Title: "' . $post_data['post_title'] . '"' );
			error_log( '  - Status: ' . $post_data['post_status'] );
			error_log( '  - Type: ' . $post_data['post_type'] );
			error_log( '  - Author: ' . $post_data['post_author'] );
			error_log( '  - Content length: ' . strlen( $post_data['post_content'] ) );
		}

		// Disable KSES content filters before insert/update.
		// Content is already sanitised by extract_content() with our expanded
		// allowlist. WordPress's internal wp_filter_post_kses would run a
		// second pass with the standard 'post' allowlist, stripping widget
		// HTML (input, style, link, iframe style) that we explicitly preserved.
		kses_remove_filters();

		if ( 'create' === $operation ) {
			if ( WP_DEBUG ) {
				error_log( 'CREATE_POST: Calling wp_insert_post...' );
			}
			$post_id = wp_insert_post( $post_data, true );
		} else {
			$post_data['ID'] = $existing_post->ID;
			if ( WP_DEBUG ) {
				error_log( 'CREATE_POST: Calling wp_update_post for ID: ' . $existing_post->ID );
			}
			$post_id = wp_update_post( $post_data, true );
		}

		// Re-enable KSES filters for subsequent operations.
		kses_init_filters();

		if ( is_wp_error( $post_id ) ) {
			if ( WP_DEBUG ) {
				error_log( '❌ CREATE_POST: Failed - ' . $post_id->get_error_message() );
			}
			$this->logger->error(
				'Failed to save post',
				array(
					'operation' => $operation,
					'post_type' => $target_post_type,
					'error'     => $post_id->get_error_message(),
				)
			);
			return $post_id;
		}

		if ( WP_DEBUG ) {
			error_log( '✅ CREATE_POST: Post created/updated - ID: ' . $post_id );

			// CRITICAL DEBUG: Check what categories WordPress actually assigned
			$wordpress_assigned_categories = wp_get_post_categories( $post_id );
			error_log( 'CREATE_POST: WordPress assigned categories after wp_insert_post:' );
			error_log( '  - Count: ' . count( $wordpress_assigned_categories ) );
			error_log( '  - IDs: ' . implode( ', ', $wordpress_assigned_categories ) );

			if ( count( $wordpress_assigned_categories ) < count( $category_ids ) ) {
				error_log( '❌ CREATE_POST: CATEGORY BUG - WordPress only assigned ' . count( $wordpress_assigned_categories ) . ' out of ' . count( $category_ids ) . ' requested categories!' );
				error_log( '  - Requested: ' . implode( ', ', $category_ids ) );
				error_log( '  - Actually assigned: ' . implode( ', ', $wordpress_assigned_categories ) );
			}

			// Log category names
			foreach ( $wordpress_assigned_categories as $cat_id ) {
				$cat = get_category( $cat_id );
				if ( $cat && ! is_wp_error( $cat ) ) {
					error_log( '  - Assigned: ' . $cat->name . ' (ID: ' . $cat_id . ')' );
				}
			}
		}

		// Verify post was created correctly
		$created_post = get_post( $post_id );
		if ( $created_post ) {
			if ( WP_DEBUG ) {
				error_log( 'CREATE_POST: Verification - Post exists' );
				error_log( '  - Actual status: ' . $created_post->post_status );
				error_log( '  - Actual type: ' . $created_post->post_type );
				error_log( '  - Actual title: ' . $created_post->post_title );
				error_log( '  - Actual slug: ' . $created_post->post_name );
			}
		} elseif ( WP_DEBUG ) {
				error_log( '❌ CREATE_POST: CRITICAL - Post not found after creation!' );
		}

		$warnings = array();

		// Store Wisewand metadata first
		if ( WP_DEBUG ) {
			error_log( 'CREATE_POST: Storing Wisewand metadata...' );
		}
		$this->store_wisewand_metadata( $post_id, $payload );

		// Process taxonomies (categories, tags, custom taxonomies)
		if ( WP_DEBUG ) {
			error_log( 'CREATE_POST: Processing taxonomies...' );
		}
		$taxonomy_result = $this->taxonomy_processor->process_taxonomies( $post_id, $payload );
		if ( is_wp_error( $taxonomy_result ) ) {
			$warnings[] = 'Taxonomy processing failed: ' . $taxonomy_result->get_error_message();
			if ( WP_DEBUG ) {
				error_log( 'CREATE_POST: Taxonomy warning: ' . $taxonomy_result->get_error_message() );
			}
		}

		// Process media FIRST (before content processing to ensure URLs are available)
		$media_warnings = $this->process_media( $post_id, $payload );
		$warnings       = array_merge( $warnings, $media_warnings );

		// Process custom fields
		$fields_result = $this->process_custom_fields( $post_id, $payload );
		if ( is_wp_error( $fields_result ) ) {
			$warnings[] = 'Custom fields processing failed: ' . $fields_result->get_error_message();
		}

		// Handle WPML/multilingual BEFORE SEO processing (language affects SEO)
		$language = $this->process_language( $post_id, $payload );

		// Process SEO data AFTER media and language are set
		$seo_result = $this->process_seo_data( $post_id, $payload );
		if ( is_wp_error( $seo_result ) ) {
			$warnings[] = 'SEO data processing failed: ' . $seo_result->get_error_message();
		}

		// Enhanced post-processing for API imports
		if ( isset( $payload['output'] ) || isset( $payload['data']['output'] ) ) {
			if ( WP_DEBUG ) {
				error_log( 'CREATE_POST: Running import enhancement...' );
			}
			$enhancement_warnings = $this->run_import_enhancement( $post_id, $payload );
			$warnings             = array_merge( $warnings, $enhancement_warnings );
		}

		// FINAL VERIFICATION: Check permalink and accessibility
		if ( WP_DEBUG ) {
			error_log( 'CREATE_POST: Final verification...' );
		}

		// Clean post cache to ensure fresh permalink generation
		clean_post_cache( $post_id );

		// Get the actual permalink WordPress generated
		$permalink = get_permalink( $post_id );
		if ( WP_DEBUG ) {
			error_log( '✅ CREATE_POST: Final permalink: ' . $permalink );
		}

		// Verify categories (critical for /%category%/ permalink structure)
		$final_categories = wp_get_post_categories( $post_id );
		if ( empty( $final_categories ) ) {
			if ( WP_DEBUG ) {
				error_log( '❌ CREATE_POST: WARNING - Post has NO categories after all processing!' );
			}
			$warnings[] = 'Post created without categories - may cause 404 with current permalink structure';
		} elseif ( WP_DEBUG ) {
				error_log( '✅ CREATE_POST: Final categories: ' . implode( ', ', $final_categories ) );
		}

		// Verify featured image
		if ( has_post_thumbnail( $post_id ) ) {
			$thumb_id = get_post_thumbnail_id( $post_id );
			if ( WP_DEBUG ) {
				error_log( '✅ CREATE_POST: Featured image set (ID: ' . $thumb_id . ')' );
			}
		} elseif ( WP_DEBUG ) {
				error_log( '⚠️  CREATE_POST: No featured image set' );
		}

		// Final summary log
		if ( WP_DEBUG ) {
			error_log( '=== CREATE_POST: COMPLETE ===' );
			error_log( 'Post ID: ' . $post_id );
			error_log( 'Title: ' . get_the_title( $post_id ) );
			error_log( 'Status: ' . get_post_status( $post_id ) );
			error_log( 'Type: ' . get_post_type( $post_id ) );
			error_log( 'Permalink: ' . $permalink );
			error_log( 'Categories: ' . count( $final_categories ) );
			error_log( 'Warnings: ' . count( $warnings ) );
			error_log( '============================' );
		}

		$this->logger->info(
			'Content processed successfully',
			array(
				'post_id'        => $post_id,
				'operation'      => $operation,
				'language'       => $language,
				'permalink'      => $permalink,
				'warnings_count' => count( $warnings ),
			)
		);

		return array(
			'post_id'   => $post_id,
			'operation' => $operation,
			'language'  => $language,
			'permalink' => $permalink,  // Add permalink to result
			'warnings'  => $warnings,
		);
	}

	private function run_import_enhancement( $post_id, $payload ) {
		// Load Import_Enhancer if not already loaded
		if ( ! class_exists( 'Wisewand\\Utils\\Import_Enhancer' ) ) {
			$enhancer_file = WISEWAND_PLUGIN_DIR . 'includes/Utils/Import_Enhancer.php';
			if ( file_exists( $enhancer_file ) ) {
				require_once $enhancer_file;
			}
		}

		if ( class_exists( 'Wisewand\\Utils\\Import_Enhancer' ) ) {
			$enhancer = new \Wisewand\Utils\Import_Enhancer( $this->logger, $this->options );
			return $enhancer->enhance_post_import( $post_id, $payload );
		}

		return array( 'Import enhancer not available' );
	}

	/**
	 * Initiates or checks the status of a pull operation for a given article.
	 *
	 * Delegates to Pull_Content_Handler.
	 *
	 * @since 2.8.2
	 *
	 * @param string $article_id   External article ID to pull.
	 * @param int    $output_index Index of the output variant to use. Default 0.
	 * @return array|WP_Error Result array with status key, or WP_Error on failure.
	 */
	public function pull_content( $article_id, $output_index = 0 ) {
		return $this->pull_content_handler->pull_content( $article_id, $output_index );
	}

	/**
	 * Processes a scheduled pull retry for a queued article.
	 *
	 * Delegates to Pull_Content_Handler.
	 *
	 * @since 2.8.2
	 *
	 * @param string $article_id External article ID to retry.
	 */
	public function process_pull_retry( $article_id ) {
		$this->pull_content_handler->process_pull_retry( $article_id );
	}

	private function validate_payload( array $payload ) {
		$required_fields = array( 'external_id' );

		foreach ( $required_fields as $field ) {
			if ( empty( $payload[ $field ] ) ) {
				return new WP_Error(
					'wisewand_missing_field',
					sprintf(
						/* translators: %s: Field name that is missing from the payload */
						__( 'Required field missing: %s', 'wisewand-ai-content-writer-seo-content-generator' ),
						$field
					)
				);
			}
		}

		// Validate post type if provided
		if ( isset( $payload['post_type'] ) ) {
			$allowed_types = $this->options->get_content_settings()['allowed_post_types'];
			if ( ! in_array( $payload['post_type'], $allowed_types, true ) ) {
				return new WP_Error(
					'wisewand_invalid_post_type',
					sprintf(
						/* translators: %s: Post type that is not allowed */
						__( 'Post type not allowed: %s', 'wisewand-ai-content-writer-seo-content-generator' ),
						$payload['post_type']
					)
				);
			}
		}

		return true;
	}

	private function find_existing_post( array $payload ) {
		global $wpdb;

		$external_id = sanitize_text_field( $payload['external_id'] );
		$language    = $this->determine_language( $payload );

		// Build query to find existing post
		$sql = "
            SELECT p.* FROM %i p
            INNER JOIN %i pm ON p.ID = pm.post_id
            WHERE pm.meta_key = '_wisewand_external_id'
            AND pm.meta_value = %s
        ";

		$args = array( $wpdb->posts, $wpdb->postmeta, $external_id );

		// Add language condition if WPML is active
		if ( $this->options->is_wpml_active() && $language ) {
			$sql   .= " AND p.ID IN (
                SELECT element_id FROM %i
                WHERE language_code = %s AND element_type LIKE 'post_%'
            )";
			$args[] = $wpdb->prefix . 'icl_translations';
			$args[] = $language;
		}

		$sql .= ' LIMIT 1';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- JOIN query more efficient than WP_Query for external ID lookup
		$post = $wpdb->get_row( $wpdb->prepare( $sql, $args ) );

		return $post ? new WP_Post( $post ) : null;
	}

	private function prepare_post_data( array $payload, ?WP_Post $existing_post = null, array $category_ids = array() ) {
		$content_settings = $this->options->get_content_settings();

		// Handle post type selection (user override vs smart default)
		$post_type = $this->determine_target_post_type( $payload );

		// Handle existing content updates
		$existing_content_id = $this->find_existing_content_for_upgmdate( $payload );

		// Categories are now passed as parameter (extracted early in process_content)
		// This ensures they're available for both wp_insert_post and debug logging

		// Start with payload data
		$post_data = array(
			'post_type'     => $post_type,
			'post_status'   => $payload['post_status'] ?? $content_settings['default_post_status'],
			'post_title'    => $this->extract_title( $payload ),
			'post_content'  => $this->extract_content( $payload ),
			'post_excerpt'  => $this->extract_excerpt( $payload ),
			'post_author'   => $this->determine_author( $payload ),
			// FIX #1: Include categories in wp_insert_post() to prevent auto-assignment
			'post_category' => $category_ids,
		);

		// CRITICAL DEBUG: Log exactly what we're passing to wp_insert_post
		if ( WP_DEBUG ) {
			error_log( 'PREPARE_POST_DATA: About to call wp_insert_post with:' );
			if ( is_array( $category_ids ) ) {
				error_log( '  - post_category count: ' . count( $category_ids ) );
				error_log( '  - post_category IDs: ' . implode( ', ', $category_ids ) );
				error_log( '  - post_category is_array: YES' );
			} else {
				error_log( '  - post_category is_array: NO - Type: ' . gettype( $category_ids ) );
			}
			error_log( '  - post_category raw: ' . wp_json_encode( $category_ids ) );
		}

		// If updating existing content, set the ID
		if ( $existing_content_id ) {
			$post_data['ID'] = $existing_content_id;
		}

		// Use Wisewand URL as post slug (priority over post_name)
		if ( isset( $payload['output']['url'] ) && ! empty( $payload['output']['url'] ) ) {
			$url = $payload['output']['url'];

			// Extract just the slug from the URL (remove domain and protocol)
			$parsed = wp_parse_url( $url );
			if ( isset( $parsed['path'] ) ) {
				// Get the path and remove leading/trailing slashes
				$path          = trim( $parsed['path'], '/' );
				$wisewand_slug = sanitize_title( $path );
			} else {
				// Fallback: sanitize the whole URL (shouldn't happen)
				$wisewand_slug = sanitize_title( $url );
			}

			if ( ! empty( $wisewand_slug ) ) {
				$post_data['post_name'] = $wisewand_slug;
				$this->logger->debug(
					'Using Wisewand URL as post slug',
					array(
						'original_url'   => $url,
						'extracted_path' => $path ?? 'N/A',
						'sanitized_slug' => $wisewand_slug,
					)
				);
			}
		} elseif ( isset( $payload['post_name'] ) && $content_settings['allow_slug_override'] ) {
			$post_data['post_name'] = sanitize_title( $payload['post_name'] );
		}

		if ( isset( $payload['post_date'] ) ) {
			// v1.2.6: post_date comes in as local WordPress time (e.g., "2025-11-11 19:39:37")
			// WordPress handles GMT conversion internally via get_gmt_from_date()
			$post_data['post_date'] = $payload['post_date'];

			// Set status to future if date is in the future
			if ( strtotime( $payload['post_date'] ) > current_time( 'timestamp' ) ) {
				$post_data['post_status'] = 'future';

				if ( WP_DEBUG ) {
					error_log(
						sprintf(
							'CONTENT_MANAGER: Setting status=future (post_date %s is in future)',
							$payload['post_date']
						)
					);
				}
			}
		}

		// Additional optional fields
		foreach ( array( 'comment_status', 'ping_status', 'post_parent', 'menu_order', 'post_password' ) as $field ) {
			if ( isset( $payload[ $field ] ) ) {
				$post_data[ $field ] = $payload[ $field ];
			}
		}

		// Handle sticky posts
		if ( isset( $payload['sticky'] ) && $payload['sticky'] ) {
			stick_post( $post_data['ID'] ?? 0 );
		}

		return $post_data;
	}

	public function extract_title( array $payload ) {
		// Priority: output.h1 (without HTML tags) > input.title > fallback
		if ( isset( $payload['output']['h1'] ) ) {
			return wp_strip_all_tags( $payload['output']['h1'] );
		}

		if ( isset( $payload['input']['title'] ) ) {
			return sanitize_text_field( $payload['input']['title'] );
		}

		if ( isset( $payload['title'] ) ) {
			return sanitize_text_field( $payload['title'] );
		}

		return __( 'Untitled', 'wisewand-ai-content-writer-seo-content-generator' );
	}

	public function extract_content( array $payload ) {
		$content = '';

		if ( isset( $payload['output']['content'] ) ) {
			$content = $payload['output']['content'];
		} elseif ( isset( $payload['content'] ) ) {
			$content = $payload['content'];
		}

		// Clean content using Content_Cleaner (H1 removal, heading hierarchy, etc.).
		if ( ! class_exists( 'Wisewand\\Utils\\Content_Cleaner' ) ) {
			$cleaner_file = WISEWAND_PLUGIN_DIR . 'includes/Utils/Content_Cleaner.php';
			if ( file_exists( $cleaner_file ) ) {
				require_once $cleaner_file;
			}
		}

		if ( class_exists( 'Wisewand\\Utils\\Content_Cleaner' ) ) {
			$post_type = $payload['post_type'] ?? 'post';
			$content   = \Wisewand\Utils\Content_Cleaner::format_for_post_type( $content, $post_type );
		}

		// Remove the API-injected Alpine.js CDN script — the plugin loads
		// its own bundled Alpine.js CSP build via enqueue_alpine().
		$content = preg_replace(
			'/<script\s+src=["\'][^"\']*alpinejs[^"\']*["\'][^>]*><\/script>\s*/i',
			'',
			$content
		);

		// Content from the WiseWand API is trusted (authenticated API call
		// to the user's own account). We intentionally do NOT run wp_kses()
		// here because it mangles widget HTML:
		//   - Strips <style>/<link> tags needed for widget CSS
		//   - Entity-encodes && in Alpine.js x-show expressions
		//   - Strips <input>/<label> form elements for interactive widgets
		//   - Strips iframe style/allow attributes for responsive video
		// Content_Cleaner above handles the structural cleaning we need.

		return $content;
	}

	public function extract_excerpt( array $payload ) {
		if ( isset( $payload['excerpt'] ) ) {
			return sanitize_textarea_field( $payload['excerpt'] );
		}

		if ( isset( $payload['output']['meta_description'] ) ) {
			return sanitize_textarea_field( $payload['output']['meta_description'] );
		}

		return '';
	}

	/**
	 * Extract and prepare categories BEFORE wp_insert_post()
	 *
	 * CRITICAL FIX: This prevents WordPress from auto-assigning default_category
	 * By including post_category in wp_insert_post(), we tell WordPress exactly
	 * which categories to use, preventing the "Assurance" bug.
	 *
	 * PRIORITY CASCADE:
	 * 1. Manual categories (RSS feed target_categories)
	 * 2. API categories (publishwordpress_category)
	 * 3. AI matching (if enabled and no manual)
	 * 4. Default category (last resort)
	 *
	 * @param array $payload Content payload with categories
	 * @return array Category IDs ready for wp_insert_post()
	 */
	/**
	 * Delegates to Taxonomy_Processor::extract_and_prepare_categories().
	 *
	 * @since 2.8.2
	 *
	 * @param array $payload Content payload.
	 * @return array Category IDs.
	 */
	private function extract_and_prepare_categories( array $payload ) {
		return $this->taxonomy_processor->extract_and_prepare_categories( $payload );
	}

	/**
	 * Delegates to Taxonomy_Processor::ensure_hierarchical_parents_included().
	 *
	 * @since 2.8.2
	 *
	 * @param array $category_ids Category IDs.
	 * @return array Category IDs with parents included.
	 */
	private function ensure_hierarchical_parents_included( array $category_ids ) {
		return $this->taxonomy_processor->ensure_hierarchical_parents_included( $category_ids );
	}

	/**
	 * Delegates to Taxonomy_Processor::match_article_to_categories().
	 *
	 * @since 2.8.2
	 *
	 * @param array $payload Content payload.
	 * @return array|WP_Error Category IDs or error.
	 */
	private function match_article_to_categories( array $payload ) {
		return $this->taxonomy_processor->match_article_to_categories( $payload );
	}

	private function determine_author( array $payload ) {
		$content_settings = $this->options->get_content_settings();

		// Priority 1: Explicit author_id in payload
		if ( isset( $payload['author_id'] ) && is_numeric( $payload['author_id'] ) ) {
			$user = get_user_by( 'id', (int) $payload['author_id'] );
			if ( $user ) {
				return $user->ID;
			}
		}

		// Priority 2: Explicit author_email in payload
		if ( isset( $payload['author_email'] ) ) {
			$user = get_user_by( 'email', sanitize_email( $payload['author_email'] ) );
			if ( $user ) {
				return $user->ID;
			}
		}

		// Priority 3: Persona→Author mapping from settings (NEW)
		if ( ! empty( $payload['persona_id'] ) ) {
			$lang = $payload['lang'] ?? $payload['input']['lang'] ?? 'en';

			// Get author mapped to this language's persona (centralized fallback chain)
			$author_id = $this->options->get_author_for_language( $lang );

			if ( $author_id && is_numeric( $author_id ) ) {
				$user = get_user_by( 'id', (int) $author_id );
				if ( $user ) {
					$this->logger->debug(
						'Using persona→author mapping',
						array(
							'lang'        => $lang,
							'persona_id'  => $payload['persona_id'],
							'author_id'   => $author_id,
							'author_name' => $user->display_name,
						)
					);
					return $user->ID;
				}
			}
		}

		// Priority 4: User who created the calendar entry
		if ( ! empty( $payload['created_by'] ) && is_numeric( $payload['created_by'] ) ) {
			$user = get_user_by( 'id', (int) $payload['created_by'] );
			if ( $user ) {
				$this->logger->debug(
					'Using entry creator as author',
					array(
						'author_id'   => $user->ID,
						'author_name' => $user->display_name,
					)
				);
				return $user->ID;
			}
		}

		// Final fallback
		return $content_settings['fallback_author'];
	}

	public function determine_language( array $payload ) {
		if ( ! $this->options->is_wpml_active() ) {
			return null;
		}

		// Check for manual language override first
		if ( isset( $payload['wpml_language_override'] ) && ! empty( $payload['wpml_language_override'] ) ) {
			$this->logger->info(
				'Using manual language override',
				array(
					'override_language' => $payload['wpml_language_override'],
					'original_lang'     => $payload['lang'] ?? 'unknown',
				)
			);
			return $payload['wpml_language_override'];
		}

		// Use WPML Manager for smart detection
		$wpml_manager = $this->get_wpml_manager();
		if ( $wpml_manager ) {
			$input_data = array(
				'lang'    => $payload['lang'] ?? $payload['input']['lang'] ?? '',
				'country' => $payload['country'] ?? $payload['input']['country'] ?? '',
			);

			return $wpml_manager->determine_wpml_language( $input_data );
		}

		return null;
	}

	private function parse_gmdate( $date_string ) {
		$timestamp = strtotime( $date_string );
		return $timestamp ? gmdate( 'Y-m-d H:i:s', $timestamp ) : null;
	}

	private function store_wisewand_metadata( $post_id, array $payload ) {
		// Store external ID for future updates
		update_post_meta( $post_id, '_wisewand_external_id', sanitize_text_field( $payload['external_id'] ) );

		// Store original input data (without sensitive info)
		$input_data = $payload['input'] ?? array();
		unset( $input_data['api_key'], $input_data['secret'] );
		update_post_meta( $post_id, '_wisewand_input_data', $input_data );

		// Store output data for SEO processing
		$output_data = $payload['output'] ?? array();
		if ( ! empty( $output_data ) ) {
			update_post_meta( $post_id, '_wisewand_output_data', $output_data );
		}

		// Store processing timestamp
		update_post_meta( $post_id, '_wisewand_processed_at', current_time( 'mysql' ) );

		// Store import method for tracking
		update_post_meta( $post_id, '_wisewand_import_method', 'api_import' );

		// Store article version if available
		if ( isset( $payload['version'] ) ) {
			update_post_meta( $post_id, '_wisewand_article_version', $payload['version'] );
		}

		// Store schema/JSON-LD if present
		if ( isset( $payload['schema'] ) || isset( $payload['json_ld'] ) ) {
			$schema = $payload['schema'] ?? $payload['json_ld'];
			update_post_meta( $post_id, '_wisewand_schema_json', wp_json_encode( $schema ) );
		}

		// Store Wisewand article URL/slug for reference
		if ( isset( $payload['output']['url'] ) ) {
			update_post_meta( $post_id, '_wisewand_original_url', sanitize_text_field( $payload['output']['url'] ) );
		}
	}

	private function process_taxonomies( $post_id, array $payload ) {
		return $this->taxonomy_processor->process_taxonomies( $post_id, $payload );
	}

	private function process_category_terms( $categories, $auto_create ) {
		return $this->taxonomy_processor->process_category_terms( $categories, $auto_create );
	}

	private function process_tag_terms( $tags, $auto_create ) {
		return $this->taxonomy_processor->process_tag_terms( $tags, $auto_create );
	}

	private function process_custom_taxonomy_terms( $terms, $taxonomy, $auto_create ) {
		return $this->taxonomy_processor->process_custom_taxonomy_terms( $terms, $taxonomy, $auto_create );
	}

	private function process_media( $post_id, array $payload ) {
		$warnings = array();

		// Process featured image (AI metadata generated in Import_Enhancer, not here)
		// This is the fallback path for direct API imports without Import_Enhancer
		if ( isset( $payload['featured_image_url'] ) || isset( $payload['output']['cover_image'] ) ) {
			$image_url = $payload['featured_image_url'] ?? $payload['output']['cover_image'];
			if ( $image_url ) {
				// No AI metadata here - that's handled by Import_Enhancer for full imports
				$result = $this->media_manager->download_featured_image( $image_url, $post_id, array() );
				if ( is_wp_error( $result ) ) {
					$warnings[] = 'Featured image download failed: ' . $result->get_error_message();
				}
			}
		}

		// Process gallery images (AI metadata generated in Import_Enhancer, not here)
		$gallery_urls = array();
		if ( isset( $payload['cover_images'] ) && is_array( $payload['cover_images'] ) ) {
			$gallery_urls = array_merge( $gallery_urls, $payload['cover_images'] );
		}
		if ( isset( $payload['output']['cover_images'] ) && is_array( $payload['output']['cover_images'] ) ) {
			$gallery_urls = array_merge( $gallery_urls, $payload['output']['cover_images'] );
		}

		if ( ! empty( $gallery_urls ) ) {
			// No AI metadata here - that's handled by Import_Enhancer for full imports
			$gallery_result = $this->media_manager->download_gallery_images( $gallery_urls, $post_id, array() );
			foreach ( $gallery_result['errors'] as $error ) {
				$warnings[] = 'Gallery image download failed: ' . $error['error'];
			}
		}

		// Process audio
		if ( isset( $payload['audio'] ) || isset( $payload['output']['audio'] ) ) {
			$audio_url = $payload['audio'] ?? $payload['output']['audio'];
			if ( $audio_url ) {
				$result = $this->media_manager->download_audio( $audio_url, $post_id );
				if ( is_wp_error( $result ) ) {
					$error_msg = $result->get_error_message();
					$warnings[] = 'Audio download failed: ' . $error_msg;

					// Log error for debugging
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log(
							sprintf(
								'Wisewand: Audio download failed for post %d - URL: %s - Error: %s',
								$post_id,
								$audio_url,
								$error_msg
							)
						);
					}
				} else {
					// Audio downloaded — player insertion is handled by Import_Enhancer
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log(
							sprintf(
								'Wisewand: Audio downloaded successfully for post %d, attachment %d',
								$post_id,
								$result['attachment_id']
							)
						);
					}
				}
			}
		}

		// Process inline images and audio in content
		$post = get_post( $post_id );
		if ( $post ) {
			$updated_content = $post->post_content;

			// Process inline images with AI metadata (if enabled and context available)
			$output          = $payload['output'] ?? array();
			$updated_content = $this->media_manager->process_inline_images(
				$updated_content,
				$post_id,
				$post->post_title,
				$updated_content,
				$output
			);

			// Process inline audio
			$updated_content = $this->media_manager->process_inline_audio( $updated_content, $post_id );

			// Update post content if any changes were made
			if ( $updated_content !== $post->post_content ) {
				// Preserve audio tags during wp_update_post (KSES strips them in CRON context)
				add_filter( 'wp_kses_allowed_html', array( 'Wisewand\\Utils\\Content_Cleaner', 'allow_audio_kses' ), 10, 2 );

				wp_update_post(
					array(
						'ID'           => $post_id,
						'post_content' => $updated_content,
					)
				);

				remove_filter( 'wp_kses_allowed_html', array( 'Wisewand\\Utils\\Content_Cleaner', 'allow_audio_kses' ), 10 );

				$this->logger->info(
					'Updated post content with processed media',
					array(
						'post_id'               => $post_id,
						'content_length_before' => strlen( $post->post_content ),
						'content_length_after'  => strlen( $updated_content ),
					)
				);
			}
		}

		return $warnings;
	}

	private function process_custom_fields( $post_id, array $payload ) {
		$whitelist = $this->options->get_custom_fields_whitelist();

		// Process general custom fields
		if ( isset( $payload['custom_fields'] ) && is_array( $payload['custom_fields'] ) ) {
			foreach ( $payload['custom_fields'] as $key => $value ) {
				if ( in_array( $key, $whitelist, true ) ) {
					update_post_meta( $post_id, sanitize_key( $key ), $this->sanitize_meta_value( $value ) );
				}
			}
		}


		return true;
	}


	private function process_seo_data( $post_id, array $payload ) {
		// This will be handled by the SEO_Manager via save_post hook
		// Store in meta for hook processing
		$seo_data = array();

		if ( isset( $payload['output']['meta_title'] ) ) {
			$seo_data['title'] = $payload['output']['meta_title'];
		}

		if ( isset( $payload['output']['meta_description'] ) ) {
			$seo_data['description'] = $payload['output']['meta_description'];
		}

		if ( isset( $payload['seo'] ) ) {
			$seo_data = array_merge( $seo_data, $payload['seo'] );
		}

		if ( ! empty( $seo_data ) ) {
			update_post_meta( $post_id, '_wisewand_seo_data', $seo_data );

			// Trigger save_post hook again so SEO_Manager can process the data
			// The data wasn't available during the initial wp_insert_post/wp_update_post call
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Using WordPress core hook, not creating it
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress core save_post action hook, cannot be prefixed
			do_action( 'save_post', $post_id, get_post( $post_id ), false );

			// Safety net: if SEO_Manager hook did not fire (CRON context, hook timing),
			// call SEO_Manager directly. This is idempotent — if the hook already
			// consumed _wisewand_seo_data, process_post_seo_data() returns early.
			$remaining = get_post_meta( $post_id, '_wisewand_seo_data', true );
			if ( ! empty( $remaining ) ) {
				if ( WP_DEBUG ) {
					error_log( 'Wisewand: SEO_Manager save_post hook did not fire for post ' . $post_id . ', calling directly as fallback' );
				}
				$seo_manager = \Wisewand\Core\Plugin::instance()->get_seo_manager();
				if ( $seo_manager ) {
					$seo_manager->process_post_seo_data( $post_id );
				}
			}
		}

		return true;
	}

	private function process_language( $post_id, array $payload ) {
		if ( ! $this->options->is_wpml_active() ) {
			return null;
		}

		$language = $this->determine_language( $payload );
		if ( ! $language ) {
			return null;
		}

		// Use WPML Manager for proper language handling
		$wpml_manager = $this->get_wpml_manager();
		if ( $wpml_manager ) {
			$wpml_manager->set_post_language( $post_id, $language );

			// Handle translation linking if specified
			if ( isset( $payload['translate_of_external_id'] ) ) {
				$original_post = $wpml_manager->find_post_by_external_id( $payload['translate_of_external_id'] );
				if ( $original_post ) {
					$wpml_manager->link_as_translation( $post_id, $original_post->ID, $language );
					$this->logger->info(
						'Linked post as translation',
						array(
							'post_id'          => $post_id,
							'original_post_id' => $original_post->ID,
							'language'         => $language,
						)
					);
				}
			}
		}

		return $language;
	}

	private function get_wpml_manager() {
		$plugin = \Wisewand\Core\Plugin::instance();
		return $plugin->get_wpml_manager();
	}

	private function sanitize_meta_value( $value ) {
		if ( is_array( $value ) ) {
			return array_map( array( $this, 'sanitize_meta_value' ), $value );
		}

		if ( is_string( $value ) ) {
			return sanitize_textarea_field( $value );
		}

		return $value;
	}

	private function fetch_from_wisewand_api( $article_id ) {
		return $this->pull_content_handler->fetch_from_wisewand_api( $article_id );
	}

	private function extract_output_data( $api_data, $output_index ) {
		return $this->pull_content_handler->extract_output_data( $api_data, $output_index );
	}

	private function get_queue_item( $article_id ) {
		return $this->pull_content_handler->get_queue_item( $article_id );
	}

	private function schedule_pull_retry( $article_id, $output_index = 0, $attempt = 1 ) {
		$this->pull_content_handler->schedule_pull_retry( $article_id, $output_index, $attempt );
	}

	private function update_queue_item( $article_id, $status, $error_message = null, $post_id = null ) {
		$this->pull_content_handler->update_queue_item( $article_id, $status, $error_message, $post_id );
	}

	private function increment_queue_attempts( $article_id, $error_message = null ) {
		$this->pull_content_handler->increment_queue_attempts( $article_id, $error_message );
	}

	private function schedule_next_retry( $article_id ) {
		$this->pull_content_handler->schedule_next_retry( $article_id );
	}

	private function get_next_retry_delay( $attempt ) {
		return $this->pull_content_handler->get_next_retry_delay( $attempt );
	}

	private function calculate_retry_after( $queue_item ) {
		return $this->pull_content_handler->calculate_retry_after( $queue_item );
	}

	private function determine_target_post_type( $payload ) {
		return $this->category_creator->determine_target_post_type( $payload );
	}

	private function find_existing_content_for_upgmdate( $payload ) {
		return $this->category_creator->find_existing_content_for_upgmdate( $payload );
	}

	private function handle_category_creation( $payload ) {
		return $this->category_creator->handle_category_creation( $payload );
	}

	private function get_taxonomy_for_post_type( $post_type ) {
		return $this->category_creator->get_taxonomy_for_post_type( $post_type );
	}

	private function update_existing_category( $payload, $taxonomy, $category_name, $category_description ) {
		return $this->category_creator->update_existing_category( $payload, $taxonomy, $category_name, $category_description );
	}

	private function parse_existing_category_id( $category_selection ) {
		return $this->category_creator->parse_existing_category_id( $category_selection );
	}

	private function process_language_for_taxonomy( $term_id, $payload ) {
		return $this->taxonomy_processor->process_language_for_taxonomy( $term_id, $payload );
	}

	private function prepare_category_description( $content, $payload ) {
		return $this->category_creator->prepare_category_description( $content, $payload );
	}
}
