<?php

declare(strict_types=1);
if (!defined('WPINC')) {
    die();
}
/**
 * Class CPEPAI_Api_Service
 * Handles the REST API endpoints registration.
 */
class CPEPAI_Api_Service
{
    /**
     * The options service instance.
     *
     * @var CPEPAI_Options_Service
     */
    private CPEPAI_Options_Service $options_service;

    /**
     * The database service instance.
     *
     * @var CPEPAI_Database_Service
     */
    private CPEPAI_Database_Service $database_service;

    /**
     * The posts service instance.
     *
     * @var CPEPAI_Posts_Service
     */
    private CPEPAI_Posts_Service $posts_service;

    /**
     * The content plan helper instance.
     *
     * @var CPEPAI_Content_Plan_Helper
     */
    private CPEPAI_Content_Plan_Helper $content_plan_helper;

    /**
     * The backend service instance.
     *
     * @var CPEPAI_Backend_Service
     */
    private CPEPAI_Backend_Service $backend_service;

    /**
     * The logging service instance.
     *
     * @var CPEPAI_Logging_Service|null
     */
    private ?CPEPAI_Logging_Service $logging_service = null;

    /**
     * Get the logging service instance.
     *
     * @return CPEPAI_Logging_Service
     */
    private function get_logging_service(): CPEPAI_Logging_Service
    {
        if ($this->logging_service === null) {
            $this->logging_service = new CPEPAI_Logging_Service($this->database_service);
        }
        return $this->logging_service;
    }

    /**
     * Constructor.
     *
     * @param CPEPAI_Options_Service $options_service The options service instance.
     * @param CPEPAI_Database_Service $database_service The database service instance.
     * @param CPEPAI_Posts_Service $posts_service The posts service instance.
     */
    public function __construct(
        CPEPAI_Options_Service $options_service,
        CPEPAI_Database_Service $database_service,
        CPEPAI_Posts_Service $posts_service
    ) {
        $this->options_service = $options_service;
        $this->database_service = $database_service;
        $this->posts_service = $posts_service;
        $this->content_plan_helper = new CPEPAI_Content_Plan_Helper($posts_service, $database_service);
        $this->backend_service = new CPEPAI_Backend_Service($database_service);
    }
    /**
     * Register the service.
     *
     * @return void
     */
    public function register(): void
    {
        add_action('rest_api_init', [$this, 'register_routes']);
        // Ensure text domain is loaded for REST API requests
        add_action('rest_api_init', [$this, 'load_textdomain_for_rest_api']);
    }

    /**
     * Load plugin text domain for REST API requests.
     * This ensures translations are available when REST API endpoints are called.
     *
     * @param string|\WP_REST_Server|null $locale Optional locale to use. If not provided, uses get_locale().
     *                                             Can also be called as a hook callback (receives WP_REST_Server).
     * @return bool True if switch_to_locale() was called (caller should restore_previous_locale() when done).
     */
    public function load_textdomain_for_rest_api($locale = null): bool
    {
        $switched = false;

        // Handle being called as a hook callback (receives WP_REST_Server object)
        if ($locale instanceof \WP_REST_Server) {
            $locale = null;
        }

        // Ensure locale is either null or a string
        if ($locale !== null && !is_string($locale)) {
            $locale = null;
        }

        $current_locale = $locale !== null ? $locale : get_locale();

        // Switch locale if provided and different from current locale
        if ($locale !== null && function_exists('switch_to_locale')) {
            $current_wp_locale = get_locale();
            if ($locale !== $current_wp_locale) {
                switch_to_locale($locale);
                $switched = true;
            }
        }

        $text_domain = 'cplanteam-ai-content-planner';
        $mo_basename = $text_domain . '-' . $current_locale;

        // Check if text domain is already loaded
        global $l10n;
        $is_already_loaded = isset($l10n[$text_domain]);

        // Try unloading first if already loaded, then reload
        if ($is_already_loaded) {
            unload_textdomain($text_domain);
        }

        // Load translations: WordPress.org uses wp-content/languages/plugins/;
        // plugin ships translations in plugin-dir/languages/
        $mo_file_wp = WP_LANG_DIR . '/plugins/' . $mo_basename . '.mo';
        $mo_file_plugin = CPEPAI_PLUGIN_DIR . 'languages/' . $mo_basename . '.mo';
        if (file_exists($mo_file_wp)) {
            load_textdomain($text_domain, $mo_file_wp, $current_locale);
        } elseif (file_exists($mo_file_plugin)) {
            load_textdomain($text_domain, $mo_file_plugin, $current_locale);
        }

        return $switched;
    }

    /**
     * Register REST API routes.
     *
     * @return void
     */
    public function register_routes(): void
    {
        // Get preset
        register_rest_route('cpepai/v1', '/preset', [
            'methods' => 'GET',
            'callback' => [$this, 'get_preset'],
            'permission_callback' => [$this, 'check_edit_posts_permission']
        ]);

        // Save settings
        register_rest_route('cpepai/v1', '/settings', [
            'methods' => 'POST',
            'callback' => [$this, 'save_settings'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'settings' => [
                    'required' => true,
                    'type' => 'object',
                    'description' => __('Settings data to save', 'cplanteam-ai-content-planner')
                ]
            ]
        ]);

        // Content Plan Items CRUD endpoints
        register_rest_route('cpepai/v1', '/items', [
            'methods' => 'GET',
            'callback' => [$this, 'get_content_plan_items'],
            'permission_callback' => [$this, 'check_edit_posts_permission']
        ]);

        register_rest_route('cpepai/v1', '/items', [
            'methods' => 'POST',
            'callback' => [$this, 'create_content_plan_item'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'title' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field'
                ],
                'structure' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_textarea_field'
                ],
                'keywords' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_textarea_field'
                ],
                'scheduled_on' => [
                    'type' => 'string',
                    'format' => 'date'
                ],
                'lang' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field'
                ]
            ]
        ]);

        // Create multiple content plan items endpoint
        register_rest_route('cpepai/v1', '/items/bulk', [
            'methods' => 'POST',
            'callback' => [$this, 'create_multiple_content_plan_items'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'items' => [
                    'required' => true,
                    'type' => 'array',
                    'description' => __('Array of content plan items to create', 'cplanteam-ai-content-planner'),
                    'validate_callback' => function ($param, $request, $key) {
                        return is_array($param) && !empty($param);
                    },
                    'items' => [
                        'type' => 'object',
                        'properties' => [
                            'title' => [
                                'type' => 'string',
                                'required' => true
                            ],
                            'structure' => [
                                'type' => 'string',
                                'required' => true
                            ],
                            'keywords' => [
                                'type' => 'string'
                            ],
                            'scheduled_on' => [
                                'type' => 'string',
                                'format' => 'date'
                            ],
                            'status' => [
                                'type' => 'string',
                                'default' => 'planned'
                            ],
                            'lang' => [
                                'type' => 'string'
                            ]
                        ]
                    ]
                ]
            ]
        ]);

        register_rest_route('cpepai/v1', '/items/(?P<id>\d+)', [
            'methods' => 'GET',
            'callback' => [$this, 'get_content_plan_item'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'id' => [
                    'type' => 'integer',
                    'required' => true
                ]
            ]
        ]);

        register_rest_route('cpepai/v1', '/items/(?P<id>\d+)', [
            'methods' => 'PUT',
            'callback' => [$this, 'update_content_plan_item'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'id' => [
                    'type' => 'integer',
                    'required' => true
                ],
                'title' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field'
                ],
                'structure' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_textarea_field'
                ],
                'keywords' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_textarea_field'
                ],
                'scheduled_on' => [
                    'type' => 'string',
                    'format' => 'date'
                ],
                'lang' => [
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field'
                ]
            ]
        ]);

        register_rest_route('cpepai/v1', '/items/(?P<id>\d+)', [
            'methods' => 'DELETE',
            'callback' => [$this, 'delete_content_plan_item'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'id' => [
                    'type' => 'integer',
                    'required' => true
                ]
            ]
        ]);

        // Bulk delete content plan items endpoint
        register_rest_route('cpepai/v1', '/items/bulk-delete', [
            'methods' => 'POST',
            'callback' => [$this, 'bulk_delete_content_plan_items'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'ids' => [
                    'required' => true,
                    'type' => 'array',
                    'description' => __('Array of content plan item IDs to delete', 'cplanteam-ai-content-planner'),
                    'validate_callback' => function ($param, $request, $key) {
                        return is_array($param) && !empty($param);
                    },
                    'items' => [
                        'type' => 'integer'
                    ]
                ]
            ]
        ]);

        // Generate content plan endpoint
        register_rest_route('cpepai/v1', '/generate-plan', [
            'methods' => 'POST',
            'callback' => [$this, 'generate_content_plan'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'goals' => [
                    'required' => true,
                    'type' => 'array',
                    'description' => __('Content goals (traffic, leads, authority)', 'cplanteam-ai-content-planner'),
                    'validate_callback' => function ($param, $request, $key) {
                        return is_array($param) && !empty($param);
                    }
                ],
                'publishInterval' => [
                    'required' => true,
                    'type' => 'integer',
                    'minimum' => 1,
                    'maximum' => 365,
                    'description' => __('Publish posts every X days', 'cplanteam-ai-content-planner')
                ],
                'totalPosts' => [
                    'required' => true,
                    'type' => 'integer',
                    'minimum' => 1,
                    'maximum' => 100,
                    'description' => __('Total number of posts to generate', 'cplanteam-ai-content-planner')
                ],
                'language' => [
                    'required' => true,
                    'type' => 'string',
                    'description' => __('Content language', 'cplanteam-ai-content-planner')
                ],
                'customLanguage' => [
                    'type' => 'string',
                    'description' => __('Custom language when language is "other"', 'cplanteam-ai-content-planner')
                ],
                'customRecommendations' => [
                    'type' => 'string',
                    'description' => __(
                        'Custom recommendations for content plan generation',
                        'cplanteam-ai-content-planner'
                    )
                ]
            ]
        ]);

        // Get posts endpoint
        register_rest_route('cpepai/v1', '/posts', [
            'methods' => 'GET',
            'callback' => [$this, 'get_posts'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'fields' => [
                    'type' => 'array',
                    'default' => ['id', 'title'],
                    'description' => __('Fields to return', 'cplanteam-ai-content-planner')
                ],
                'post_types' => [
                    'type' => 'array',
                    'default' => ['post', 'page'],
                    'description' => __('Post types to include', 'cplanteam-ai-content-planner')
                ],
                'search' => [
                    'type' => 'string',
                    'description' => __('Search term', 'cplanteam-ai-content-planner')
                ],
                'limit' => [
                    'type' => 'integer',
                    'default' => -1,
                    'description' => __('Number of posts to return', 'cplanteam-ai-content-planner')
                ],
                'orderby' => [
                    'type' => 'string',
                    'default' => 'date',
                    'description' => __('Order by field', 'cplanteam-ai-content-planner')
                ],
                'order' => [
                    'type' => 'string',
                    'default' => 'DESC',
                    'enum' => ['ASC', 'DESC'],
                    'description' => __('Order direction', 'cplanteam-ai-content-planner')
                ]
            ]
        ]);

        // Generate post from content plan item endpoint
        register_rest_route('cpepai/v1', '/items/(?P<id>\d+)/generate-post', [
            'methods' => 'POST',
            'callback' => [$this, 'generate_post_from_item'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'id' => [
                    'type' => 'integer',
                    'required' => true,
                    'description' => __('Content plan item ID', 'cplanteam-ai-content-planner')
                ],
                'type' => [
                    'type' => 'string',
                    'required' => false,
                    'default' => 'gutenberg',
                    'enum' => ['gutenberg', 'classic'],
                    'description' => __(
                        'Type of post content format (gutenberg or classic)',
                        'cplanteam-ai-content-planner'
                    )
                ]
            ]
        ]);

        // Webhook endpoint to receive data from backend API
        /*
        register_rest_route('cpepai/v1', '/webhook', [
            'methods' => 'POST',
            'callback' => [$this, 'handle_webhook'],
            'permission_callback' => [$this, 'check_webhook_permission'],
        ]);
        */

        // Get tasks endpoint
        register_rest_route('cpepai/v1', '/tasks', [
            'methods' => 'GET',
            'callback' => [$this, 'get_tasks'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'status' => [
                    'type' => 'string',
                    'description' => __('Filter by task status', 'cplanteam-ai-content-planner')
                ],
                'type' => [
                    'type' => 'string',
                    'description' => __('Filter by task type', 'cplanteam-ai-content-planner')
                ],
                'limit' => [
                    'type' => 'integer',
                    'default' => 50,
                    'description' => __('Number of tasks to return', 'cplanteam-ai-content-planner')
                ],
                'offset' => [
                    'type' => 'integer',
                    'default' => 0,
                    'description' => __('Number of tasks to skip', 'cplanteam-ai-content-planner')
                ]
            ]
        ]);

        // Get logs endpoint
        register_rest_route('cpepai/v1', '/logs', [
            'methods' => 'GET',
            'callback' => [$this, 'get_logs'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'level' => [
                    'type' => 'string',
                    'description' => __('Filter by log level', 'cplanteam-ai-content-planner')
                ],
                'limit' => [
                    'type' => 'integer',
                    'default' => 100,
                    'description' => __('Number of logs to return', 'cplanteam-ai-content-planner')
                ],
                'offset' => [
                    'type' => 'integer',
                    'default' => 0,
                    'description' => __('Number of logs to skip', 'cplanteam-ai-content-planner')
                ]
            ]
        ]);

        // Mark task as watched endpoint
        register_rest_route('cpepai/v1', '/tasks/(?P<id>[\d]+)/watch', [
            'methods' => 'POST',
            'callback' => [$this, 'mark_task_as_watched'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'id' => [
                    'type' => 'integer',
                    'required' => true,
                    'description' => __('Task ID', 'cplanteam-ai-content-planner')
                ]
            ]
        ]);

        // Manual process completed task endpoint
        register_rest_route('cpepai/v1', '/tasks/(?P<id>[\d]+)/process', [
            'methods' => 'POST',
            'callback' => [$this, 'process_completed_task'],
            'permission_callback' => [$this, 'check_edit_posts_permission'],
            'args' => [
                'id' => [
                    'type' => 'integer',
                    'required' => true,
                    'description' => __('Task ID', 'cplanteam-ai-content-planner')
                ]
            ]
        ]);

        // Recalculate scheduled dates endpoint
        register_rest_route('cpepai/v1', '/items/recalculate-schedule', [
            'methods' => 'POST',
            'callback' => [$this, 'recalculate_scheduled_dates'],
            'permission_callback' => [$this, 'check_edit_posts_permission']
        ]);
    }

    /**
     * Get preset.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function get_preset(\WP_REST_Request $request): \WP_REST_Response
    {
        $requested_locale = $request->get_param('locale');

        // Determine the locale to use
        // Priority: requested locale > user locale (if explicitly set and not English) > default to en_US
        $locale_to_use = null;

        if (!empty($requested_locale)) {
            $locale_to_use = $requested_locale;
        } else {
            $locale_to_use = function_exists('get_user_locale') ? get_user_locale() : get_locale();
        }

        // Ensure text domain is loaded for translations with the correct locale
        $locale_switched = $this->load_textdomain_for_rest_api($locale_to_use);

        // Get actual settings from options service
        $settings = $this->options_service->get_settings();

        // Get field configurations for dynamic rendering
        $field_configs = $this->options_service->get_field_configurations();

        // Get interface strings
        $strings = $this->get_interface_strings();

        // Fetch usage data from backend API
        $usage_data = null;
        $usage_error = null;
        $backend_configured = $this->backend_service->is_configured();

        if ($backend_configured) {
            $usage_response = $this->backend_service->get_usage_data();

            // Log the response for debugging
            if ($usage_response['success']) {
                // So $usage_response['data'] = ['success' => true, 'data' => usageData]
                if (isset($usage_response['data']['data']) && is_array($usage_response['data']['data'])) {
                    // Extract usage data from nested response structure
                    $usage_data = $usage_response['data']['data'];
                } elseif (isset($usage_response['data']) && is_array($usage_response['data'])) {
                    // Check if data itself is the usage data (has the expected keys)
                    if (
                        isset($usage_response['data']['blocked']) ||
                        isset($usage_response['data']['tokensRemaining']) ||
                        isset($usage_response['data']['renewDate'])
                    ) {
                        $usage_data = $usage_response['data'];
                    } else {
                        $usage_error = 'Usage data format is invalid';
                    }
                } else {
                    $usage_error = 'Usage data is missing from response';
                }
            } else {
                $usage_error = $usage_response['error'] ?? 'Failed to fetch usage data';
            }
        } else {
        }

        // Check if website is new based on post count
        $post_count = wp_count_posts();
        $total_posts = (int) ($post_count->publish ?? 0);
        $is_new_website = $total_posts < 3;
        // Check if user has opted in to Freemius
        $opted = $this->is_user_opted_in();

        // Get WordPress timezone information
        $wp_timezone_string = wp_timezone_string();
        $wp_timezone = wp_timezone();
        $wp_timezone_offset = $wp_timezone->getOffset(new \DateTime('now'));

        // Prepare response data with settings, field configurations, strings, and usage data
        $preset = [
            'settings' => $settings,
            'field_configs' => $field_configs,
            'strings' => $strings,
            'is_new_website' => $is_new_website,
            'backend_configured' => $backend_configured,
            'opted' => $opted,
            'timezone' => [
                'string' => $wp_timezone_string,
                'offset' => $wp_timezone_offset // Offset in seconds
            ]
        ];

        // Include opt-in URL only if user has not opted in
        if (!$opted) {
            $preset['optin_url'] = $this->get_freemius_optin_url();
        }

        // Include usage data if available
        if ($usage_data !== null) {
            $preset['usage'] = $usage_data;
        } else {
            // Include usage error if backend is configured but fetch failed
            if ($backend_configured && $usage_error !== null) {
                $preset['usage_error'] = $usage_error;
            }
        }

        if ($locale_switched && function_exists('restore_previous_locale')) {
            restore_previous_locale();
        }

        return new \WP_REST_Response($preset, 200);
    }

    /**
     * Save settings.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function save_settings(\WP_REST_Request $request): \WP_REST_Response
    {
        // Get validated settings (WordPress has already validated via args)
        $settings = $request->get_param('settings');

        try {
            // Get current settings to detect changes
            $current_settings = $this->options_service->get_settings();
            $current_interval = isset($current_settings['publish_interval_days'])
                ? (int) $current_settings['publish_interval_days']
                : 7;

            // Sanitize the settings using the options service
            $sanitized_settings = $this->options_service->sanitize_settings($settings);

            // Check if publish_interval_days changed
            $new_interval = isset($sanitized_settings['publish_interval_days'])
                ? (int) $sanitized_settings['publish_interval_days']
                : $current_interval;
            $interval_changed = $new_interval !== $current_interval;

            // Update all settings at once
            $success = $this->options_service->update_settings($sanitized_settings);

            if ($success) {
                $response_data = [
                    'message' => __('Settings saved successfully', 'cplanteam-ai-content-planner'),
                    'settings' => $sanitized_settings
                ];

                // If interval changed, check if there are future items that need recalculation
                if ($interval_changed) {
                    $future_items_count = $this->database_service->get_future_items_count();
                    if ($future_items_count > 0) {
                        $response_data['recalculation_needed'] = true;
                        $response_data['future_items_count'] = $future_items_count;
                        $response_data['old_interval'] = $current_interval;
                        $response_data['new_interval'] = $new_interval;
                    }
                }

                return new \WP_REST_Response($response_data, 200);
            } else {
                return new \WP_REST_Response(
                    ['error' => __('Failed to save settings', 'cplanteam-ai-content-planner')],
                    500
                );
            }
        } catch (Exception $e) {
            return new \WP_REST_Response(
                ['error' => __('Error saving settings: ', 'cplanteam-ai-content-planner') . $e->getMessage()],
                500
            );
        }
    }

    /**
     * Get content plan items.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function get_content_plan_items(\WP_REST_Request $request): \WP_REST_Response
    {
        $items = $this->database_service->get_content_plan_items();

        // Validate posts and update items if needed
        foreach ($items as &$item) {
            if (!empty($item['post'])) {
                $post_id = (int) $item['post'];
                $post = get_post($post_id);

                // If post doesn't exist or is in trash, reset post and status
                if (!$post || $post->post_status === 'trash') {
                    $this->database_service->update_content_plan_item((int) $item['id'], [
                        'post' => null,
                        'status' => 'planned'
                    ]);
                    // Update the item in the response
                    $item['post'] = null;
                    $item['status'] = 'planned';
                }
            }
        }
        unset($item); // Unset reference to avoid issues

        return new \WP_REST_Response($items, 200);
    }

    /**
     * Get a single content plan item.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function get_content_plan_item(\WP_REST_Request $request): \WP_REST_Response
    {
        $id = $request->get_param('id');

        $item = $this->database_service->get_content_plan_item($id);

        if (!$item) {
            return new \WP_REST_Response(
                ['error' => __('Content plan item not found', 'cplanteam-ai-content-planner')],
                404
            );
        }

        return new \WP_REST_Response($item, 200);
    }

    /**
     * Create a new content plan item.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function create_content_plan_item(\WP_REST_Request $request): \WP_REST_Response
    {
        // Get validated parameters (WordPress has already validated them via args)
        $data = [
            'title' => $request->get_param('title'),
            'structure' => $request->get_param('structure'),
            'keywords' => $request->get_param('keywords'),
            'status' => $request->get_param('status') ?? 'planned',
            'scheduled_on' => $request->get_param('scheduled_on'),
            'lang' => $request->get_param('lang')
        ];

        $result = $this->database_service->create_content_plan_item($data);

        if ($result instanceof \WP_Error) {
            return new \WP_REST_Response(
                [
                    'error' => $result->get_error_message(),
                    'debug_info' => [
                        'data' => $data,
                        'error_code' => $result->get_error_code(),
                        'error_data' => $result->get_error_data()
                    ]
                ],
                400
            );
        }

        if (!$result || !is_numeric($result)) {
            return new \WP_REST_Response(
                [
                    'error' => __('Failed to create content plan item', 'cplanteam-ai-content-planner'),
                    'debug_info' => [
                        'data' => $data,
                        'result' => $result
                    ]
                ],
                500
            );
        }

        $item = $this->database_service->get_content_plan_item((int) $result);

        return new \WP_REST_Response(
            [
                'success' => true,
                'message' => __('Content plan item created successfully', 'cplanteam-ai-content-planner'),
                'data' => $item
            ],
            201
        );
    }

    /**
     * Create multiple content plan items.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function create_multiple_content_plan_items(\WP_REST_Request $request): \WP_REST_Response
    {
        $items = $request->get_param('items');

        $created_items = [];
        $errors = [];

        foreach ($items as $index => $item_data) {
            $data = [
                'title' => $item_data['title'],
                'structure' => $item_data['structure'],
                'keywords' => $item_data['keywords'] ?? '',
                'status' => $item_data['status'] ?? 'planned',
                'scheduled_on' => $item_data['scheduled_on'] ?? null,
                'lang' => $item_data['lang'] ?? null
            ];

            $result = $this->database_service->create_content_plan_item($data);

            if ($result instanceof \WP_Error) {
                $errors[] = [
                    'index' => $index,
                    'error' => $result->get_error_message(),
                    'data' => $data
                ];
            } elseif ($result && is_numeric($result)) {
                $created_item = $this->database_service->get_content_plan_item((int) $result);
                if ($created_item) {
                    $created_items[] = $created_item;
                }
            } else {
                $errors[] = [
                    'index' => $index,
                    'error' => __('Failed to create content plan item', 'cplanteam-ai-content-planner'),
                    'data' => $data
                ];
            }
        }

        if (!empty($errors)) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'message' => __('Some items failed to create', 'cplanteam-ai-content-planner'),
                    'created_items' => $created_items,
                    'errors' => $errors
                ],
                207 // Multi-status
            );
        }

        $count = count($created_items);
        return new \WP_REST_Response(
            [
                'success' => true,
                'message' =>
                    number_format_i18n($count) .
                    ' ' .
                    _n(
                        'content plan item created successfully',
                        'content plan items created successfully',
                        $count,
                        'cplanteam-ai-content-planner'
                    ),
                'data' => $created_items
            ],
            201
        );
    }

    /**
     * Update an existing content plan item.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function update_content_plan_item(\WP_REST_Request $request): \WP_REST_Response
    {
        $id = $request->get_param('id');

        $data = [];
        if ($request->has_param('title')) {
            $data['title'] = $request->get_param('title');
        }
        if ($request->has_param('structure')) {
            $data['structure'] = $request->get_param('structure');
        }
        if ($request->has_param('keywords')) {
            $data['keywords'] = $request->get_param('keywords');
        }
        if ($request->has_param('status')) {
            $data['status'] = $request->get_param('status');
        }
        if ($request->has_param('scheduled_on')) {
            $data['scheduled_on'] = $request->get_param('scheduled_on');
        }
        if ($request->has_param('lang')) {
            $data['lang'] = $request->get_param('lang');
        }

        $result = $this->database_service->update_content_plan_item($id, $data);

        if (is_wp_error($result)) {
            return new \WP_REST_Response(['error' => $result->get_error_message()], 400);
        }

        $item = $this->database_service->get_content_plan_item($id);

        return new \WP_REST_Response(
            [
                'success' => true,
                'message' => __('Content plan item updated successfully', 'cplanteam-ai-content-planner'),
                'data' => $item
            ],
            200
        );
    }

    /**
     * Delete a content plan item.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function delete_content_plan_item(\WP_REST_Request $request): \WP_REST_Response
    {
        $id = $request->get_param('id');

        $result = $this->database_service->delete_content_plan_item($id);

        if (is_wp_error($result)) {
            return new \WP_REST_Response(['error' => $result->get_error_message()], 400);
        }

        return new \WP_REST_Response(
            [
                'success' => true,
                'message' => __('Content plan item deleted successfully', 'cplanteam-ai-content-planner')
            ],
            200
        );
    }

    /**
     * Bulk delete content plan items.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function bulk_delete_content_plan_items(\WP_REST_Request $request): \WP_REST_Response
    {
        $ids = $request->get_param('ids');

        $deleted_count = 0;
        $errors = [];

        foreach ($ids as $id) {
            $result = $this->database_service->delete_content_plan_item($id);

            if (is_wp_error($result)) {
                $errors[] = [
                    'id' => $id,
                    'error' => $result->get_error_message()
                ];
            } else {
                $deleted_count++;
            }
        }

        if (!empty($errors)) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'message' =>
                        __('Some items failed to delete.', 'cplanteam-ai-content-planner') .
                        ' ' .
                        number_format_i18n($deleted_count) .
                        ' ' .
                        __('deleted,', 'cplanteam-ai-content-planner') .
                        ' ' .
                        number_format_i18n(count($errors)) .
                        ' ' .
                        __('failed.', 'cplanteam-ai-content-planner'),
                    'deleted_count' => $deleted_count,
                    'errors' => $errors
                ],
                207 // Multi-status
            );
        }

        return new \WP_REST_Response(
            [
                'success' => true,
                'message' =>
                    number_format_i18n($deleted_count) .
                    ' ' .
                    _n(
                        'content plan item deleted successfully',
                        'content plan items deleted successfully',
                        $deleted_count,
                        'cplanteam-ai-content-planner'
                    ),
                'deleted_count' => $deleted_count
            ],
            200
        );
    }

    /**
     * Get interface strings for the frontend.
     *
     * @return array
     */
    private function get_interface_strings(): array
    {
        return [
            // App.js strings
            'app' => [
                'loading' => __('Loading...', 'cplanteam-ai-content-planner'),
                'error' => __('Error:', 'cplanteam-ai-content-planner'),
                'generate_content_plan' => __('Generate content plan with AI', 'cplanteam-ai-content-planner')
            ],

            // Content Plan Table strings
            'content_plan_table' => [
                'title' => __('Content Plan', 'cplanteam-ai-content-planner'),
                'description' => __(
                    'Manage your content plans with the table below. Create, edit, and delete content plans as needed.',
                    'cplanteam-ai-content-planner'
                ),
                'content_plan_items' => __('Content Plan Items', 'cplanteam-ai-content-planner'),
                'add_new_item' => __('Schedule new post manually', 'cplanteam-ai-content-planner'),
                'search_placeholder' => __('Search content plan items...', 'cplanteam-ai-content-planner'),
                'loading_items' => __('Loading content plan items...', 'cplanteam-ai-content-planner'),
                'showing' => __('Showing', 'cplanteam-ai-content-planner'),
                'content_plan_items_lower' => __('content plan items', 'cplanteam-ai-content-planner'),
                'total' => __('total', 'cplanteam-ai-content-planner'),
                'edit_item' => __('Edit Content Plan Item', 'cplanteam-ai-content-planner'),
                'add_item' => __('Add New Content Plan Item', 'cplanteam-ai-content-planner'),
                'title_label' => __('Title', 'cplanteam-ai-content-planner'),
                'structure_label' => __('Structure', 'cplanteam-ai-content-planner'),
                'keywords_label' => __('Keywords', 'cplanteam-ai-content-planner'),
                'status_label' => __('Status', 'cplanteam-ai-content-planner'),
                'scheduled_on_label' => __('Scheduled On', 'cplanteam-ai-content-planner'),
                'cancel' => __('Cancel', 'cplanteam-ai-content-planner'),
                'update' => __('Update', 'cplanteam-ai-content-planner'),
                'create' => __('Create', 'cplanteam-ai-content-planner'),
                'title_required' => __('Title is required', 'cplanteam-ai-content-planner'),
                'delete_confirmation' => __(
                    'Are you sure you want to delete this content plan item?',
                    'cplanteam-ai-content-planner'
                ),
                'to' => __('to', 'cplanteam-ai-content-planner'),
                'of' => __('of', 'cplanteam-ai-content-planner'),
                'scheduled_until' => __('scheduled until', 'cplanteam-ai-content-planner'),
                'task_filter_active' => __('Task filter active', 'cplanteam-ai-content-planner'),
                'clear_task_filter' => __('Clear task filter', 'cplanteam-ai-content-planner'),
                'date_filter' => __('Date filter', 'cplanteam-ai-content-planner'),
                'future_posts' => __('Future posts', 'cplanteam-ai-content-planner'),
                'past_posts' => __('Past posts', 'cplanteam-ai-content-planner'),
                'custom' => __('Custom', 'cplanteam-ai-content-planner'),
                'select_date_range' => __('Select date range', 'cplanteam-ai-content-planner'),
                'items_per_page' => __('Items per page', 'cplanteam-ai-content-planner'),
                'first_page' => __('First page', 'cplanteam-ai-content-planner'),
                'previous_page' => __('Previous page', 'cplanteam-ai-content-planner'),
                'page' => __('Page', 'cplanteam-ai-content-planner'),
                'next_page' => __('Next page', 'cplanteam-ai-content-planner'),
                'last_page' => __('Last page', 'cplanteam-ai-content-planner'),
                'item_selected' => __('item selected', 'cplanteam-ai-content-planner'),
                'items_selected' => __('items selected', 'cplanteam-ai-content-planner'),
                'clear_selection' => __('Clear selection', 'cplanteam-ai-content-planner'),
                'delete_x_items' => __('Delete items', 'cplanteam-ai-content-planner'),
                'post_label' => __('Post', 'cplanteam-ai-content-planner'),
                'select_post' => __('Select a post...', 'cplanteam-ai-content-planner'),
                'no_posts_available' => __('No posts available', 'cplanteam-ai-content-planner'),
                'title_placeholder' => __('Enter item title...', 'cplanteam-ai-content-planner'),
                'scheduled_on_placeholder' => __('Select a date', 'cplanteam-ai-content-planner'),
                'delete_item' => __('Delete Item', 'cplanteam-ai-content-planner'),
                'saving' => __('Saving...', 'cplanteam-ai-content-planner'),
                'structure_required' => __('At least one structure item is required', 'cplanteam-ai-content-planner'),
                'scheduled_on_required' => __('Scheduled date is required', 'cplanteam-ai-content-planner')
            ],

            // Table column headers
            'table_columns' => [
                'id' => __('ID', 'cplanteam-ai-content-planner'),
                'title' => __('Title', 'cplanteam-ai-content-planner'),
                'structure' => __('Structure', 'cplanteam-ai-content-planner'),
                'keywords' => __('Keywords', 'cplanteam-ai-content-planner'),
                'status' => __('Status', 'cplanteam-ai-content-planner'),
                'scheduled_on' => __('Scheduled On', 'cplanteam-ai-content-planner'),
                'created' => __('Created', 'cplanteam-ai-content-planner'),
                'actions' => __('Actions', 'cplanteam-ai-content-planner'),
                'post' => __('Post', 'cplanteam-ai-content-planner')
            ],

            // Table cell values
            'table_cells' => [
                'untitled' => __('Untitled', 'cplanteam-ai-content-planner'),
                'no_structure' => __('No structure', 'cplanteam-ai-content-planner'),
                'no_keywords' => __('No keywords', 'cplanteam-ai-content-planner'),
                'not_scheduled' => __('Not scheduled', 'cplanteam-ai-content-planner'),
                'edit' => __('Edit', 'cplanteam-ai-content-planner'),
                'delete' => __('Delete', 'cplanteam-ai-content-planner'),
                'generate' => __('Generate', 'cplanteam-ai-content-planner'),
                'regenerate' => __('Regenerate', 'cplanteam-ai-content-planner'),
                'view_post' => __('View Post', 'cplanteam-ai-content-planner'),
                'no_post' => __('No post', 'cplanteam-ai-content-planner'),
                'not_available' => __('Not available', 'cplanteam-ai-content-planner'),
                'more' => __('more', 'cplanteam-ai-content-planner'),
                'show_less' => __('Show less', 'cplanteam-ai-content-planner')
            ],

            // Settings strings
            'settings' => [
                'title' => __('Settings', 'cplanteam-ai-content-planner'),
                'description' => __('Configure your plugin settings below', 'cplanteam-ai-content-planner'),
                'saving' => __('Saving...', 'cplanteam-ai-content-planner'),
                'reset' => __('Reset', 'cplanteam-ai-content-planner'),
                'no_settings_config' => __('No settings configuration available.', 'cplanteam-ai-content-planner'),
                'save_success' => __('Settings saved successfully', 'cplanteam-ai-content-planner'),
                'save_error' => __('Failed to save settings', 'cplanteam-ai-content-planner'),
                'recalculate_success' => __(
                    'Scheduled dates recalculated successfully',
                    'cplanteam-ai-content-planner'
                ),
                'recalculate_error' => __('Failed to recalculate scheduled dates', 'cplanteam-ai-content-planner')
            ],

            // Dynamic Form Field strings
            'form_fields' => [
                'select_option' => __('Select an option', 'cplanteam-ai-content-planner'),
                'not_set' => __('Not set', 'cplanteam-ai-content-planner'),
                'enabled' => __('Enabled', 'cplanteam-ai-content-planner'),
                'disabled' => __('Disabled', 'cplanteam-ai-content-planner')
            ],

            // Navigation strings
            'navigation' => [
                'content_plan' => __('Content Plan', 'cplanteam-ai-content-planner'),
                'settings' => __('Settings', 'cplanteam-ai-content-planner'),
                'log' => __('Log', 'cplanteam-ai-content-planner')
            ],

            // Log strings
            'log' => [
                'title' => __('Log', 'cplanteam-ai-content-planner'),
                'description' => __('Activity logs will be available here.', 'cplanteam-ai-content-planner'),
                'loading' => __('Loading logs...', 'cplanteam-ai-content-planner'),
                'error_loading' => __('Error loading logs:', 'cplanteam-ai-content-planner'),
                'no_logs' => __('No logs found', 'cplanteam-ai-content-planner'),
                'date' => __('Date', 'cplanteam-ai-content-planner'),
                'level' => __('Level', 'cplanteam-ai-content-planner'),
                'event' => __('Event', 'cplanteam-ai-content-planner'),
                'message' => __('Message', 'cplanteam-ai-content-planner'),
                'task_id' => __('Task ID', 'cplanteam-ai-content-planner'),
                'show_context' => __('Show context', 'cplanteam-ai-content-planner')
            ],

            // Status strings
            'status_planned' => [
                'label' => __('Planned', 'cplanteam-ai-content-planner')
            ],
            'status_draft_ready_ai' => [
                'label' => __('Draft Ready (AI)', 'cplanteam-ai-content-planner')
            ],
            'status_draft_ready_manual' => [
                'label' => __('Draft Ready (Manual)', 'cplanteam-ai-content-planner')
            ],
            'status_published' => [
                'label' => __('Published', 'cplanteam-ai-content-planner')
            ],

            // Common strings
            'common' => [
                'dismiss' => __('Dismiss', 'cplanteam-ai-content-planner'),
                'usage' => __('Usage', 'cplanteam-ai-content-planner'),
                'tokens_remaining' => __('Tokens Remaining', 'cplanteam-ai-content-planner'),
                'renew_date' => __('Renew Date', 'cplanteam-ai-content-planner'),
                'account_blocked' => __('Account is blocked', 'cplanteam-ai-content-planner'),
                'account_status' => __('Account Status', 'cplanteam-ai-content-planner'),
                'blocked' => __('Blocked', 'cplanteam-ai-content-planner'),
                'active' => __('Active', 'cplanteam-ai-content-planner'),
                'usage_load_error_title' => __(
                    '🚀 Houston, we have a connection problem.',
                    'cplanteam-ai-content-planner'
                ),
                'usage_load_error_message' => __(
                    "We can't reach our backend right now.",
                    'cplanteam-ai-content-planner'
                ),
                'usage_load_error_action' => __(
                    'Please try again in a moment or contact support if this keeps happening.',
                    'cplanteam-ai-content-planner'
                )
            ],

            // Generation Modal strings
            'generation_modal' => [
                'title' => __('Generate Content Plan', 'cplanteam-ai-content-planner'),
                'subtitle' => __('Configure your content plan generation settings', 'cplanteam-ai-content-planner'),
                'goals_label' => __('Goals', 'cplanteam-ai-content-planner'),
                'goal_traffic' => __('Traffic', 'cplanteam-ai-content-planner'),
                'goal_leads' => __('Leads', 'cplanteam-ai-content-planner'),
                'goal_authority' => __('Authority', 'cplanteam-ai-content-planner'),
                'total_posts_label' => __('Total number of posts to generate', 'cplanteam-ai-content-planner'),
                'language_label' => __('Language', 'cplanteam-ai-content-planner'),
                'language_placeholder' => __('Select a language', 'cplanteam-ai-content-planner'),
                'cancel' => __('Cancel', 'cplanteam-ai-content-planner'),
                'generate' => __('Generate', 'cplanteam-ai-content-planner'),
                'generating' => __('Generating...', 'cplanteam-ai-content-planner'),
                'error_goals_required' => __('Please select at least one goal', 'cplanteam-ai-content-planner'),
                'error_total_posts' => __('Total posts must be between 1 and 100', 'cplanteam-ai-content-planner'),
                'error_language_required' => __('Please select a language', 'cplanteam-ai-content-planner'),
                'advanced_link' => __('Advanced', 'cplanteam-ai-content-planner'),
                'custom_recommendations_label' => __('Custom Recommendations', 'cplanteam-ai-content-planner'),
                'custom_recommendations_placeholder' => __(
                    'Enter any specific recommendations or requirements for the content plan...',
                    'cplanteam-ai-content-planner'
                ),
                'custom_recommendations_help' => __(
                    'Optional: Provide specific guidance, topics to focus on, or requirements for the generated content plan.',
                    'cplanteam-ai-content-planner'
                ),
                'error_title' => __('Generation Failed', 'cplanteam-ai-content-planner'),
                'connection_error' => __(
                    "🚀 Houston, we have a connection problem. We can't reach our backend right now. Please try again in a moment or contact support if this keeps happening.",
                    'cplanteam-ai-content-planner'
                ),
                'loading_tasks' => __('Loading tasks...', 'cplanteam-ai-content-planner'),
                'error_loading_tasks' => __('Error loading tasks:', 'cplanteam-ai-content-planner'),
                'no_tasks' => __('No tasks found', 'cplanteam-ai-content-planner')
            ],

            // Onboarding Screen strings
            'onboarding' => [
                'hero_title' => __(
                    'Create your first content plan items - completely free',
                    'cplanteam-ai-content-planner'
                ),
                'hero_subtitle' => __(
                    'No API keys or external setup required - everything works out of the box.',
                    'cplanteam-ai-content-planner'
                ),
                'benefit_content_plan' => __(
                    'Automatically generate a multi-month content plan',
                    'cplanteam-ai-content-planner'
                ),
                'benefit_seo_posts' => __('High-quality SEO-optimized posts', 'cplanteam-ai-content-planner'),
                'benefit_editors' => __(
                    'Works with Gutenberg, Elementor, and Classic Editor',
                    'cplanteam-ai-content-planner'
                ),
                'benefit_no_setup' => __('No technical setup needed', 'cplanteam-ai-content-planner'),
                'goals_label' => __('Choose your content strategy focus', 'cplanteam-ai-content-planner'),
                'goal_traffic_description' => __(
                    'Grow your organic reach and page views',
                    'cplanteam-ai-content-planner'
                ),
                'goal_leads_description' => __(
                    'Generate qualified leads and conversions',
                    'cplanteam-ai-content-planner'
                ),
                'goal_authority_description' => __(
                    'Establish expertise and thought leadership',
                    'cplanteam-ai-content-planner'
                ),
                'language_label' => __('Language', 'cplanteam-ai-content-planner'),
                'language_placeholder' => __('Select a language', 'cplanteam-ai-content-planner'),
                'error_goal_required' => __('Please select a goal', 'cplanteam-ai-content-planner'),
                'error_language_required' => __('Please select a language', 'cplanteam-ai-content-planner'),
                'error_terms_required' => __(
                    'You must agree to the Terms of Use and Privacy Policy',
                    'cplanteam-ai-content-planner'
                ),
                'error_submit_failed' => __('Failed to complete onboarding', 'cplanteam-ai-content-planner'),
                'terms_prefix' => __('I agree to the', 'cplanteam-ai-content-planner'),
                'terms_of_use' => __('Terms of Use', 'cplanteam-ai-content-planner'),
                'terms_and' => __('and', 'cplanteam-ai-content-planner'),
                'privacy_policy' => __('Privacy Policy', 'cplanteam-ai-content-planner'),
                'generating' => __('Generating...', 'cplanteam-ai-content-planner'),
                'submit_button' => __('Generate your first post ideas', 'cplanteam-ai-content-planner'),
                'no_credit_card' => __('No credit card required', 'cplanteam-ai-content-planner'),
                'free_plan' => __('Free plan included', 'cplanteam-ai-content-planner'),
                'skip_button' => __('Skip onboarding', 'cplanteam-ai-content-planner')
            ],

            // Generate Post Modal strings
            'generate_post_modal' => [
                'title' => __('Generate Post Content', 'cplanteam-ai-content-planner'),
                'subtitle' => __('Generate AI-powered content for this planned post', 'cplanteam-ai-content-planner'),
                'post_title' => __('Post Title', 'cplanteam-ai-content-planner'),
                'post_structure' => __('Content Structure', 'cplanteam-ai-content-planner'),
                'keywords' => __('Keywords', 'cplanteam-ai-content-planner'),
                'scheduled_on' => __('Scheduled On', 'cplanteam-ai-content-planner'),
                'cancel' => __('Cancel', 'cplanteam-ai-content-planner'),
                'generate' => __('Generate', 'cplanteam-ai-content-planner'),
                'generating' => __('Generating...', 'cplanteam-ai-content-planner'),
                'error_title' => __('Generation Failed', 'cplanteam-ai-content-planner'),
                'connection_error' => __(
                    "🚀 Houston, we have a connection problem. We can't reach our backend right now. Please try again in a moment or contact support if this keeps happening.",
                    'cplanteam-ai-content-planner'
                )
            ],

            // Recalculate Schedule Modal strings
            'recalculate_schedule' => [
                'title' => __('Recalculate Scheduled Dates?', 'cplanteam-ai-content-planner'),
                'message' => __(
                    'You\'ve changed the publish interval from {oldInterval} to {newInterval} days. There are {count} future content plan items that may need their scheduled dates recalculated.',
                    'cplanteam-ai-content-planner'
                ),
                'question' => __(
                    'Would you like to recalculate the scheduled dates for these future items based on the new interval?',
                    'cplanteam-ai-content-planner'
                ),
                'note' => __(
                    'Note: Only future scheduled items will be affected. Past dates will remain unchanged.',
                    'cplanteam-ai-content-planner'
                ),
                'cancel' => __('No, Keep Current Dates', 'cplanteam-ai-content-planner'),
                'confirm' => __('Yes, Recalculate', 'cplanteam-ai-content-planner')
            ],

            // Delete Confirmation Modal strings
            'delete_confirmation' => [
                'title' => __('Delete Content Plan Item', 'cplanteam-ai-content-planner'),
                'message' => __(
                    'Are you sure you want to delete this content plan item?',
                    'cplanteam-ai-content-planner'
                ),
                'structure' => __('Structure:', 'cplanteam-ai-content-planner'),
                'cancel' => __('Cancel', 'cplanteam-ai-content-planner'),
                'confirm' => __('Delete', 'cplanteam-ai-content-planner')
            ],

            // Tasks Panel strings
            'tasks_panel' => [
                'content_plan_generation' => __('Content plan generation task', 'cplanteam-ai-content-planner'),
                'post_generation' => __('Post generation task', 'cplanteam-ai-content-planner'),
                'generic_task' => __('Task', 'cplanteam-ai-content-planner'),
                'filter_items' => __('Show items', 'cplanteam-ai-content-planner'),
                'filter_post' => __('Show generated post', 'cplanteam-ai-content-planner'),
                'auto_publish' => __('Will auto-publish on:', 'cplanteam-ai-content-planner'),
                'loading_error_details' => __('Loading error details...', 'cplanteam-ai-content-planner')
            ],

            // Dynamic List Field strings
            'dynamic_list' => [
                'excerpt_tldr' => __('Excerpt / TL;DR', 'cplanteam-ai-content-planner'),
                'paragraph' => __('Subtopic', 'cplanteam-ai-content-planner'),
                'key_takeaways' => __('Key Takeaways', 'cplanteam-ai-content-planner'),
                'faq' => __('FAQ', 'cplanteam-ai-content-planner'),
                'conclusion_summary' => __('Conclusion / summary', 'cplanteam-ai-content-planner'),
                'add' => __('Add', 'cplanteam-ai-content-planner'),
                'remove' => __('Remove', 'cplanteam-ai-content-planner'),
                'select_item_placeholder' => __('Select an item to add...', 'cplanteam-ai-content-planner'),
                'subtitle_placeholder' => __(
                    'Enter subtitle for this paragraph section...',
                    'cplanteam-ai-content-planner'
                ),
                'no_items' => __(
                    'No items added yet. Choose from the dropdown above to build your structure.',
                    'cplanteam-ai-content-planner'
                )
            ]
        ];
    }

    /**
     * Generate content plan using the backend service.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function generate_content_plan(\WP_REST_Request $request): \WP_REST_Response
    {
        $goals = $request->get_param('goals');
        $total_posts = $request->get_param('totalPosts');
        $language = $request->get_param('language');
        $custom_recommendations = $request->get_param('customRecommendations') ?: '';

        // Validate request parameters
        $errors = [];

        // goals: array, non-empty, first element must map to one of Traffic|Leads|Authority
        if (!is_array($goals) || empty($goals)) {
            $errors['goals'] = 'goals must be a non-empty array';
        } else {
            $first_goal = strtolower(trim((string) $goals[0]));
            $valid_goals_map = ['traffic' => 'Traffic', 'leads' => 'Leads', 'authority' => 'Authority'];
            if (!array_key_exists($first_goal, $valid_goals_map)) {
                $errors['goals'] = 'First goal must be one of: Traffic, Leads, Authority';
            }
        }

        // totalPosts: integer 1..50
        if (!is_numeric($total_posts) || intval($total_posts) != $total_posts) {
            $errors['totalPosts'] = 'totalPosts must be an integer';
        } else {
            $tp = (int) $total_posts;
            if ($tp < 1 || $tp > 10) {
                $errors['totalPosts'] = 'totalPosts must be between 1 and 10';
            }
        }

        // language: string in allowed list
        $allowed_languages = [
            'English',
            'Spanish',
            'French',
            'German',
            'Italian',
            'Portuguese',
            'Dutch',
            'Russian',
            'Chinese (Simplified)',
            'Chinese (Traditional)',
            'Japanese',
            'Korean',
            'Arabic',
            'Hebrew',
            'Turkish',
            'Persian (Farsi)',
            'Urdu',
            'Hindi',
            'Bengali',
            'Punjabi',
            'Marathi',
            'Gujarati',
            'Tamil',
            'Telugu',
            'Kannada',
            'Malayalam',
            'Sinhala',
            'Nepali',
            'Vietnamese',
            'Thai',
            'Indonesian',
            'Malay',
            'Filipino (Tagalog)',
            'Polish',
            'Czech',
            'Slovak',
            'Hungarian',
            'Romanian',
            'Bulgarian',
            'Ukrainian',
            'Serbian',
            'Croatian',
            'Slovenian',
            'Greek',
            'Swedish',
            'Norwegian',
            'Danish',
            'Finnish',
            'Icelandic',
            'Lithuanian',
            'Latvian',
            'Estonian',
            'Albanian',
            'Armenian',
            'Georgian',
            'Afrikaans',
            'Swahili'
        ];
        if (!is_string($language) || trim($language) === '') {
            $errors['language'] = 'language must be a non-empty string';
        } elseif (!in_array($language, $allowed_languages, true)) {
            $errors['language'] = 'language must be one of: ' . implode(', ', $allowed_languages);
        }

        // customRecommendations: optional string length <= 1000
        if ($custom_recommendations !== '' && !is_string($custom_recommendations)) {
            $errors['customRecommendations'] = 'customRecommendations must be a string';
        } elseif (is_string($custom_recommendations) && strlen($custom_recommendations) > 1000) {
            $errors['customRecommendations'] = 'customRecommendations must be 1000 characters or less';
        }

        if (!empty($errors)) {
            return new \WP_REST_Response(
                [
                    'error' => __('Validation failed', 'cplanteam-ai-content-planner'),
                    'validation_errors' => $errors
                ],
                400
            );
        }

        // Check if backend service is configured
        if (!$this->backend_service->is_configured()) {
            return new \WP_REST_Response(
                [
                    'error' => __('Backend service is not properly configured.', 'cplanteam-ai-content-planner'),
                    'validation_errors' => ['backend_service' => 'Backend service not configured']
                ],
                400
            );
        }

        // Gather up to 50 existing WP post titles
        $posts = $this->posts_service->get_posts_by_criteria(['title'], ['post', 'page'], '', 50, 'date', 'DESC');
        $post_titles = [];
        foreach ($posts as $p) {
            if (isset($p['title']) && is_string($p['title']) && trim($p['title']) !== '') {
                $post_titles[] = trim($p['title']);
            }
        }

        // Gather titles of existing content plan items
        $existing_items = $this->database_service->get_content_plan_items();
        $existing_titles = [];
        foreach ($existing_items as $item) {
            if (isset($item['title']) && is_string($item['title']) && trim($item['title']) !== '') {
                $existing_titles[] = trim($item['title']);
            }
        }

        // Map goals array to single goal string expected by backend (Traffic|Leads|Authority)
        $goal = 'Traffic';
        if (is_array($goals) && !empty($goals)) {
            $first = strtolower((string) $goals[0]);
            if ($first === 'leads') {
                $goal = 'Leads';
            } elseif ($first === 'authority') {
                $goal = 'Authority';
            }
        }

        // Prepare variables for backend API (matches contentplan-api schema)
        $site_title = trim((string) get_bloginfo('name'));
        $site_description = trim((string) get_bloginfo('description'));

        // Validate required fields that backend expects
        if (empty($site_title)) {
            $site_title = 'My Website'; // Fallback to prevent validation failure
        }

        if (empty($site_description)) {
            $site_description = 'A WordPress website'; // Fallback to prevent validation failure
        }

        // Helper function to truncate string array to max length
        $truncate_string_array = function (array $items, int $max_length, string $separator = "\n"): string {
            if (empty($items)) {
                return '';
            }

            $result = '';
            $separator_length = strlen($separator);

            foreach ($items as $item) {
                $item_length = strlen($item);
                $new_length = strlen($result) + ($result ? $separator_length : 0) + $item_length;

                if ($new_length > $max_length) {
                    // If adding this item would exceed max, stop
                    break;
                }

                $result .= ($result ? $separator : '') . $item;
            }

            // If result is still empty or exceeds max (edge case), truncate
            if (strlen($result) > $max_length) {
                $result = substr($result, 0, $max_length);
            }

            return $result;
        };

        // Prepare post titles (limit to 50 items, max 2000 chars)
        $post_titles_limited = array_slice($post_titles, 0, 50);
        $up_to_50_post_titles = $truncate_string_array($post_titles_limited, 2000, "\n");

        // Prepare previously generated posts (max 2000 chars)
        // Use most recent items first (they're already sorted by created_at DESC from database)
        $previously_generated_posts = $truncate_string_array($existing_titles, 2000, "\n");

        // Log if truncation occurred
        $original_existing_count = count($existing_titles);
        $original_existing_length = $original_existing_count > 0 ? strlen(implode("\n", $existing_titles)) : 0;
        if ($original_existing_length > 2000) {
            $truncated_count = 0;
            if (!empty($previously_generated_posts)) {
                $newline_count = substr_count($previously_generated_posts, "\n");
                $truncated_count = $newline_count + 1;
            }
        }

        $original_post_titles_length =
            count($post_titles_limited) > 0 ? strlen(implode("\n", $post_titles_limited)) : 0;
        if ($original_post_titles_length > 2000) {
            $truncated_count = 0;
            if (!empty($up_to_50_post_titles)) {
                $newline_count = substr_count($up_to_50_post_titles, "\n");
                $truncated_count = $newline_count + 1;
            }
        }

        // Final validation: ensure both strings are within 2000 character limit
        // This is a safety check to ensure backend validation will pass
        if (strlen($up_to_50_post_titles) > 2000) {
            $up_to_50_post_titles = substr($up_to_50_post_titles, 0, 2000);
        }

        if (strlen($previously_generated_posts) > 2000) {
            $previously_generated_posts = substr($previously_generated_posts, 0, 2000);
        }

        $parameters = [
            'total_posts' => max(1, min(10, (int) $total_posts)),
            'site_title' => $site_title,
            'site_description' => $site_description,
            'goal' => $goal,
            'up_to_50_post_titles' => $up_to_50_post_titles,
            'previously_generated_posts' => $previously_generated_posts,
            'language' => (string) $language
        ];

        // Only include custom_recommendations if it's not empty
        if (!empty(trim($custom_recommendations))) {
            $parameters['custom_recommendations'] = trim((string) $custom_recommendations);
        }

        // Log parameters being sent (without sensitive data)
        // Add webhook URL for callback notification
        $parameters['webhook_url'] = rest_url('cpepai/v1/webhook');

        // Create task record in database before sending request
        $task_record_id = null;
        $temporary_task_id = null;
        // Generate a temporary task_id (UUID) - will be updated if backend returns a different one
        if (function_exists('wp_generate_uuid4')) {
            $temporary_task_id = wp_generate_uuid4();
        }

        if (!empty($temporary_task_id)) {
            $task_data = [
                'task_id' => $temporary_task_id, // Temporary ID, will be updated when backend responds
                'type' => 'content_plan_generation',
                'status' => 'queued',
                'result' => [
                    'input' => [
                        'variables' => $parameters,
                        'webhook_url' => $parameters['webhook_url'] ?? null
                    ]
                ]
            ];

            $task_result = $this->database_service->create_task($task_data);
            if (!is_wp_error($task_result) && is_numeric($task_result)) {
                $task_record_id = $task_result;
            } else {
            }
        }

        // Generate content plan using backend service (backend service resolves credentials internally)
        $backend_response = $this->backend_service->generate_content_plan($parameters);

        // Extract task_id from backend response if available (declare early for use later)
        // Backend service returns the entire decoded JSON body in $backend_response['data']
        // So if backend API returns {"success":true,"data":{"taskId":"..."}},
        // then $backend_response['data'] = ["success"=>true,"data"=>["taskId"=>"..."]]
        $backend_task_id = null;
        if (isset($backend_response['data']) && is_array($backend_response['data'])) {
            // Try nested path first: $backend_response['data']['data']['taskId']
            if (isset($backend_response['data']['data']['taskId'])) {
                $backend_task_id = $backend_response['data']['data']['taskId'];
            }
            // Fallback to direct path: $backend_response['data']['taskId']
            elseif (isset($backend_response['data']['taskId'])) {
                $backend_task_id = $backend_response['data']['taskId'];
            }
        }

        // Log full backend response for debugging
        $response_data = $backend_response['data'] ?? null;
        $error_details = null;
        if (is_array($response_data) && isset($response_data['details'])) {
            $error_details = $response_data['details'];
        }
        // Update task record based on backend response
        if ($task_record_id !== null) {
            $task_update_data = [];

            // Use the already extracted backend_task_id
            if (!empty($backend_task_id)) {
                // Always update task_id if backend returned one (even if same, to ensure consistency)
                $task_update_data['task_id'] = $backend_task_id;
            } else {
            }

            // Update status based on response
            // Only update status if backend returned a taskId (task was accepted)
            // Never mark as 'failed' here - only webhook can mark tasks as failed
            if (!empty($backend_task_id)) {
                // Backend accepted the task and returned taskId - mark as processing
                $task_update_data['status'] = 'processing';
            } else {
                // No taskId returned - keep task as 'queued' (don't change status)
                // The webhook will update the status when processing completes or fails
                // Don't update status - keep it as 'queued' that was set during creation
            }

            // Update the task record
            if (!empty($task_update_data)) {
                $update_result = $this->database_service->update_task($task_record_id, $task_update_data);
                if (is_wp_error($update_result)) {
                } else {
                    $updated_task_id = $backend_task_id ?? $temporary_task_id;
                }
            }
        }

        // Check if backend request was successful
        if (!$backend_response['success']) {
            $error_message = $backend_response['error'] ?? 'Unknown error';
            $response_data = $backend_response['data'] ?? null;
            $validation_details = null;

            // Extract validation details if available
            if (is_array($response_data)) {
                if (isset($response_data['details']) && is_array($response_data['details'])) {
                    $validation_details = $response_data['details'];
                }
                if (isset($response_data['message']) && !empty($response_data['message'])) {
                    $error_message = $response_data['message'];
                }
            }

            // Delete the task record that was created before the backend call
            // since the backend rejected the request, we shouldn't keep the task
            if ($task_record_id !== null) {
                $delete_result = $this->database_service->delete_task($task_record_id);
                if (is_wp_error($delete_result)) {
                } else {
                }
            }

            $error_response = [
                'error' => __('Failed to generate content plan: ', 'cplanteam-ai-content-planner') . $error_message,
                'validation_errors' => ['backend_api' => $error_message],
                'status_code' => $backend_response['status_code'] ?? 500
            ];

            // Include validation details if available
            if (!empty($validation_details)) {
                $error_response['validation_details'] = $validation_details;
            }

            return new \WP_REST_Response($error_response, $backend_response['status_code'] ?? 500);
        }

        // Prepare response data
        $response_data = $backend_response['data'] ?? null;

        // Extract taskId from backend response (use already-extracted backend_task_id which handles nested structure)
        $task_id_for_response = null;
        if (!empty($backend_task_id)) {
            // Use the task_id we extracted and updated in the database
            $task_id_for_response = $backend_task_id;
        } elseif (!empty($temporary_task_id)) {
            // Use temporary task_id if backend didn't return one
            $task_id_for_response = $temporary_task_id;
        }

        // Ensure response includes success flag and taskId if available
        $formatted_response = [
            'success' => true,
            'data' => $response_data
        ];

        // Always include taskId at top level if we have one (from backend or temporary)
        if (!empty($task_id_for_response)) {
            $formatted_response['taskId'] = $task_id_for_response;
        } else {
        }

        return new \WP_REST_Response($formatted_response, 200);
    }

    /**
     * Get posts.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function get_posts(\WP_REST_Request $request): \WP_REST_Response
    {
        // Get validated parameters (WordPress has already validated them via args)
        $fields = $request->get_param('fields') ?: ['id', 'title'];
        $post_types = $request->get_param('post_types') ?: ['post', 'page'];
        $search = $request->get_param('search') ?: '';
        $limit = $request->get_param('limit') ?: -1;
        $orderby = $request->get_param('orderby') ?: 'date';
        $order = $request->get_param('order') ?: 'DESC';

        try {
            // Check if posts service is available
            if (!$this->posts_service) {
                return new \WP_REST_Response(['error' => 'Posts service not available'], 500);
            }

            // Get posts using the posts service
            $posts = $this->posts_service->get_posts_by_criteria(
                $fields,
                $post_types,
                $search,
                $limit,
                $orderby,
                $order
            );

            return new \WP_REST_Response($posts, 200);
        } catch (Exception $e) {
            return new \WP_REST_Response(
                ['error' => __('Error retrieving posts: ', 'cplanteam-ai-content-planner') . $e->getMessage()],
                500
            );
        }
    }

    /**
     * Generate post content from content plan item.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function generate_post_from_item(\WP_REST_Request $request): \WP_REST_Response
    {
        $id = $request->get_param('id');

        // Fetch the content plan item
        $item = $this->database_service->get_content_plan_item($id);

        if (!$item) {
            return new \WP_REST_Response(
                ['error' => __('Content plan item not found', 'cplanteam-ai-content-planner')],
                404
            );
        }

        // Check if backend service is configured
        if (!$this->backend_service->is_configured()) {
            return new \WP_REST_Response(
                [
                    'error' => __('Backend service is not properly configured.', 'cplanteam-ai-content-planner'),
                    'validation_errors' => ['backend_service' => 'Backend service not configured']
                ],
                400
            );
        }

        // Parse the structure and keywords from JSON strings
        $structure = json_decode($item['structure'], true);
        $keywords = json_decode($item['keywords'], true);

        // Validate parsed data
        if (!is_array($structure) || empty($structure)) {
            return new \WP_REST_Response(
                [
                    'error' => __('Invalid or empty post structure', 'cplanteam-ai-content-planner'),
                    'validation_errors' => ['structure' => 'Structure is invalid or empty']
                ],
                400
            );
        }

        // Handle keywords - support both JSON array and comma-separated string formats
        if (!is_array($keywords)) {
            // If json_decode failed, try treating it as a comma-separated string
            if (!empty($item['keywords']) && is_string($item['keywords'])) {
                $keywords = array_map('trim', explode(',', $item['keywords']));
            } else {
                $keywords = []; // Allow empty keywords array
            }
        }

        // Get the language from the item, default to 'English' if not set
        $language = $item['lang'] ?? 'English';

        // Get regeneration instructions from request (optional)
        $regeneration_instructions = $request->get_param('regeneration_instructions');
        if (!empty($regeneration_instructions) && is_string($regeneration_instructions)) {
            $regeneration_instructions = trim($regeneration_instructions);
        } else {
            $regeneration_instructions = null;
        }

        // Prepare parameters for backend API
        $parameters = [
            'title' => $item['title'],
            'keywords' => $keywords,
            'structure' => $structure,
            'language' => $language
        ];

        // Add regeneration instructions if provided
        if (!empty($regeneration_instructions)) {
            $parameters['regeneration_instructions'] = $regeneration_instructions;
        }

        // Add webhook URL for callback notification
        $parameters['webhook_url'] = rest_url('cpepai/v1/webhook');

        // Create task record in database before sending request
        $task_record_id = null;
        $temporary_task_id = null;
        // Generate a temporary task_id (UUID) - will be updated if backend returns a different one
        if (function_exists('wp_generate_uuid4')) {
            $temporary_task_id = wp_generate_uuid4();
        }

        if (!empty($temporary_task_id)) {
            $task_data = [
                'task_id' => $temporary_task_id, // Temporary ID, will be updated when backend responds
                'type' => 'post_generation',
                'status' => 'queued',
                'result' => [
                    'input' => [
                        'variables' => $parameters,
                        'item_id' => $item['id'], // Store item ID for webhook processing
                        'webhook_url' => $parameters['webhook_url'] ?? null
                    ]
                ]
            ];

            $task_result = $this->database_service->create_task($task_data);
            if (!is_wp_error($task_result) && is_numeric($task_result)) {
                $task_record_id = $task_result;
            } else {
            }
        }

        // Generate post using backend service
        $backend_response = $this->backend_service->generate_post($parameters);

        // Extract task_id from backend response if available
        // Backend service returns the entire decoded JSON body in $backend_response['data']
        // So if backend API returns {"success":true,"data":{"taskId":"..."}},
        // then $backend_response['data'] = ["success"=>true,"data"=>["taskId"=>"..."]]
        $backend_task_id = null;
        if (isset($backend_response['data']) && is_array($backend_response['data'])) {
            // Try nested path first: $backend_response['data']['data']['taskId']
            if (isset($backend_response['data']['data']['taskId'])) {
                $backend_task_id = $backend_response['data']['data']['taskId'];
            }
            // Fallback to direct path: $backend_response['data']['taskId']
            elseif (isset($backend_response['data']['taskId'])) {
                $backend_task_id = $backend_response['data']['taskId'];
            } else {
            }
        } else {
        }

        // Log full backend response for debugging
        $response_data = $backend_response['data'] ?? null;
        $error_details = null;
        if (is_array($response_data) && isset($response_data['details'])) {
            $error_details = $response_data['details'];
        }
        // Update task record based on backend response
        if ($task_record_id !== null) {
            $task_update_data = [];

            // Use the already extracted backend_task_id
            if (!empty($backend_task_id)) {
                // Always update task_id if backend returned one (even if same, to ensure consistency)
                $task_update_data['task_id'] = $backend_task_id;
            } else {
            }

            // Update status based on response
            // Only update status if backend returned a taskId (task was accepted)
            // Never mark as 'failed' here - only webhook can mark tasks as failed
            if (!empty($backend_task_id)) {
                // Backend accepted the task and returned taskId - mark as processing
                $task_update_data['status'] = 'processing';
            } else {
                // No taskId returned - keep task as 'queued' (don't change status)
                // The webhook will update the status when processing completes or fails
                // Don't update status - keep it as 'queued' that was set during creation
            }

            // Update the task record
            if (!empty($task_update_data)) {
                $update_result = $this->database_service->update_task($task_record_id, $task_update_data);
                if (is_wp_error($update_result)) {
                } else {
                    $updated_task_id = $backend_task_id ?? $temporary_task_id;
                }
            }
        }

        // Check if backend request was successful
        if (!$backend_response['success']) {
            $error_message = $backend_response['error'] ?? 'Unknown error';
            $response_data = $backend_response['data'] ?? null;
            $validation_details = null;

            // Extract validation details if available
            if (is_array($response_data)) {
                if (isset($response_data['details']) && is_array($response_data['details'])) {
                    $validation_details = $response_data['details'];
                }
                if (isset($response_data['message']) && !empty($response_data['message'])) {
                    $error_message = $response_data['message'];
                }
            }

            // Delete the task record that was created before the backend call
            // since the backend rejected the request, we shouldn't keep the task
            if ($task_record_id !== null) {
                $delete_result = $this->database_service->delete_task($task_record_id);
                if (is_wp_error($delete_result)) {
                } else {
                }
            }

            $error_response = [
                'error' => __('Failed to generate post: ', 'cplanteam-ai-content-planner') . $error_message,
                'validation_errors' => ['backend_api' => $error_message],
                'status_code' => $backend_response['status_code'] ?? 500
            ];

            // Include validation details if available
            if (!empty($validation_details)) {
                $error_response['validation_details'] = $validation_details;
            }

            return new \WP_REST_Response($error_response, $backend_response['status_code'] ?? 500);
        }

        // Prepare response data
        $response_data = $backend_response['data'] ?? null;

        // Extract taskId from backend response (use already-extracted backend_task_id which handles nested structure)
        $task_id_for_response = null;
        if (!empty($backend_task_id)) {
            // Use the task_id we extracted and updated in the database
            $task_id_for_response = $backend_task_id;
        } elseif (!empty($temporary_task_id)) {
            // Use temporary task_id if backend didn't return one
            $task_id_for_response = $temporary_task_id;
        }

        // Ensure response includes success flag and taskId if available
        $formatted_response = [
            'success' => true,
            'message' => __('Post generation task queued successfully', 'cplanteam-ai-content-planner'),
            'data' => $response_data
        ];

        // Always include taskId at top level if we have one (from backend or temporary)
        if (!empty($task_id_for_response)) {
            $formatted_response['taskId'] = $task_id_for_response;
        } else {
        }

        return new \WP_REST_Response($formatted_response, 200);
    }

    /**
     * Get the secret key from Freemius SDK.
     *
     * @return string|false The secret key or false if not available.
     */
    public function get_site_secret_key()
    {
        if (!function_exists('cpepai_fs')) {
            return false;
        }
        $fs = cpepai_fs();
        if (!is_object($fs)) {
            return false;
        }
        // Get storage from Freemius SDK
        if (!method_exists($fs, 'get_storage')) {
            return false;
        }
        // Get the site/install information
        $site = $fs->get_site();
        if (empty($site)) {
            return false;
        }
        // Cover both array and object structures + possible field name variants
        $site_secret_key = null;
        if (is_array($site)) {
            $site_secret_key = $site['secret_key'] ?? ($site['install_secret_key'] ?? null);
        }
        if (is_object($site)) {
            $site_secret_key = $site->secret_key ?? ($site->install_secret_key ?? null);
        }
        return $site_secret_key ?: false;
    }

    /**
     * Get the install ID from Freemius SDK.
     *
     * @return string|false The install ID or false if not available.
     */
    public function get_site_install_id()
    {
        if (!function_exists('cpepai_fs')) {
            return false;
        }
        $fs = cpepai_fs();
        if (!is_object($fs)) {
            return false;
        }

        // Get the install ID using the SDK method
        if (method_exists($fs, 'get_install_id')) {
            return $fs->get_install_id();
        }

        // Fallback: try to get from site data
        $site = $fs->get_site();
        if (empty($site)) {
            return false;
        }

        // Cover both array and object structures
        $install_id = null;
        if (is_array($site)) {
            $install_id = $site['id'] ?? ($site['install_id'] ?? null);
        }
        if (is_object($site)) {
            $install_id = $site->id ?? ($site->install_id ?? null);
        }

        return $install_id ?: false;
    }

    /**
     * Check if user has opted in to Freemius.
     *
     * @return bool True if user is registered and tracking is allowed, false otherwise.
     */
    public function is_user_opted_in(): bool
    {
        if (!function_exists('cpepai_fs')) {
            return false;
        }
        $fs = cpepai_fs();
        if (!is_object($fs) || !method_exists($fs, 'is_registered') || !method_exists($fs, 'is_tracking_allowed')) {
            return false;
        }
        return $fs->is_registered() && $fs->is_tracking_allowed();
    }

    /**
     * Get Freemius opt-in URL.
     *
     * @return string The opt-in URL or admin URL as fallback.
     */
    public function get_freemius_optin_url(): string
    {
        $fs_action = 'cpepai_reconnect';

        $url = add_query_arg(
            [
                'page' => 'cplanteam-ai-content-planner',
                'fs_action' => $fs_action,
                'nonce' => wp_create_nonce($fs_action)
            ],
            admin_url('admin.php')
        );

        return $url;
    }

    /**
     * Check if user has required permissions for content management.
     *
     * @return bool
     */
    public function check_edit_posts_permission(): bool
    {
        return current_user_can('edit_posts');
    }

    /**
     * Check webhook HMAC permission for incoming requests.
     *
     * @param \WP_REST_Request $request The request object.
     * @return bool|\WP_Error True if valid, WP_Error otherwise.
     */
    public function check_webhook_permission(\WP_REST_Request $request)
    {
        // Extract Authorization header
        $auth_header = $request->get_header('authorization');
        if (empty($auth_header)) {
            return new \WP_Error('missing_auth_header', 'Missing Authorization header', ['status' => 401]);
        }

        // Extract HMAC signature from Authorization header (format: "HMAC-SHA256 <signature>")
        $parts = explode(' ', $auth_header);
        if (count($parts) !== 2 || $parts[0] !== 'HMAC-SHA256') {
            return new \WP_Error(
                'invalid_auth_format',
                'Invalid Authorization header format. Expected: HMAC-SHA256 <signature>',
                ['status' => 401]
            );
        }
        $signature = $parts[1];

        // Extract nonce and timestamp headers
        $nonce = $request->get_header('x-nonce');
        $timestamp = $request->get_header('x-timestamp');

        if (empty($nonce) || empty($timestamp)) {
            $missing = [];
            if (empty($nonce)) {
                $missing[] = 'x-nonce';
            }
            if (empty($timestamp)) {
                $missing[] = 'x-timestamp';
            }
            return new \WP_Error('missing_headers', 'Missing required headers: ' . implode(', ', $missing), [
                'status' => 400
            ]);
        }

        // Validate nonce format (64 hex characters)
        if (!preg_match('/^[a-f0-9]{64}$/', $nonce)) {
            return new \WP_Error('invalid_nonce_format', 'Invalid nonce format. Expected 64 hex characters.', [
                'status' => 400
            ]);
        }

        // Validate timestamp (within 5 minutes)
        $request_time = intval($timestamp);
        $current_time = intval(microtime(true) * 1000);
        $time_diff = abs($current_time - $request_time);
        $max_age_ms = 5 * 60 * 1000; // 5 minutes

        if ($time_diff > $max_age_ms) {
            return new \WP_Error('timestamp_expired', 'Request timestamp expired', ['status' => 400]);
        }

        // Get secret key from Freemius
        $secret_key = $this->get_site_secret_key();
        if ($secret_key === false) {
            return new \WP_Error('no_secret_key', 'Unable to retrieve site secret key', ['status' => 500]);
        }

        // Get request body and method
        $method = $request->get_method();

        // Extract path from route - normalize to match TypeScript resolveRestRoute logic
        $path = $request->get_route();
        // Remove leading and trailing slashes
        $path = trim($path, '/');
        // Remove wp-json/ prefix if present
        if (strpos($path, 'wp-json/') === 0) {
            $path = substr($path, strlen('wp-json/'));
        }
        // Remove leading slash again after wp-json removal
        $path = ltrim($path, '/');
        // Default fallback if path is empty
        if (empty($path)) {
            $path = 'cpepai/v1/webhook';
        }

        // Get body data
        $body = $request->get_json_params();
        if (empty($body)) {
            $body = [];
        }

        // Extract installId from request if present
        $install_id = $request->get_param('installId') ?? ($body['installId'] ?? null);

        // Build the payload for HMAC calculation (sorted keys for consistency)
        // Note: Must match TypeScript sortPayloadKeys implementation exactly
        $payload_data = [
            'body' => $body,
            'installId' => $install_id,
            'method' => $method,
            'nonce' => $nonce,
            'path' => $path,
            'timestamp' => $timestamp
        ];
        // Sort keys alphabetically (matches TypeScript Object.keys().sort())
        ksort($payload_data);
        // Use JSON_UNESCAPED_SLASHES to match JavaScript JSON.stringify behavior
        // Also ensure consistent encoding (no pretty print)
        $payload = json_encode($payload_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        // Create the message in the format: timestamp:nonce:payload
        $message = $timestamp . ':' . $nonce . ':' . $payload;

        // Generate HMAC signature
        $expected_signature = hash_hmac('sha256', $message, $secret_key);

        // Debug logging with detailed payload breakdown
        // Constant-time comparison to prevent timing attacks
        if (!hash_equals($expected_signature, $signature)) {
            return new \WP_Error('invalid_hmac', 'Invalid HMAC signature', ['status' => 401]);
        }
        return true;
    }

    /**
     * Check if a string represents "Excerpt / TL;DR" regardless of language.
     *
     * @param string $str The string to check.
     * @return bool True if the string represents excerpt_tldr, false otherwise.
     */
    private function is_excerpt_tldr(string $str): bool
    {
        $trimmed = trim($str);
        if (empty($trimmed)) {
            return false;
        }

        // Exact match
        if ($trimmed === 'excerpt_tldr') {
            return true;
        }

        // Check against WordPress translated string (language-independent)
        $translated_excerpt = __('Excerpt / TL;DR', 'cplanteam-ai-content-planner');
        if (strcasecmp($trimmed, $translated_excerpt) === 0) {
            return true;
        }

        // Check for common patterns (case-insensitive)
        $normalized = strtolower($trimmed);
        $patterns = [
            'excerpt/tldr',
            'excerpt/tl;dr',
            'excerpt/tl/dr',
            'excerpt tldr',
            'excerpt tl;dr',
            'excerpt tl/dr',
            'excerpt/tl dr',
            'excerpt tl dr'
        ];

        foreach ($patterns as $pattern) {
            if ($normalized === $pattern || strpos($normalized, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Process a completed content plan generation task and create items.
     *
     * @param array $task The task array from database.
     * @return void
     */
    private function process_completed_content_plan_task(array &$task): void
    {
        $task_id = $task['id'] ?? 'N/A';
        $task_result = $task['result'] ?? null;

        // Decode result if it's a JSON string
        if (is_string($task_result) && !empty($task_result)) {
            $decoded = json_decode($task_result, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $task_result = $decoded;
            } else {
                return;
            }
        }

        if (!is_array($task_result)) {
            return;
        }

        // Extract plan data from result
        $result_output = $task_result['output'] ?? null;
        if (empty($result_output) || !isset($result_output['plan'])) {
            return;
        }

        $plan_items = $result_output['plan'];
        if (!is_array($plan_items)) {
            return;
        }
        // Extract language from task input if available
        $language = null;
        if (isset($task_result['input']['variables']['language'])) {
            $language = $task_result['input']['variables']['language'];
        }

        // Calculate scheduled dates based on max scheduled date and publish interval
        $latest_scheduled_date = $this->database_service->get_latest_scheduled_date();
        $publish_interval_days = $this->options_service->get_setting('publish_interval_days', 7);

        // Determine the base date for scheduling
        $base_timestamp = null;
        if (!empty($latest_scheduled_date)) {
            $base_timestamp = strtotime($latest_scheduled_date);
            if ($base_timestamp === false) {
            }
        }

        // If no valid base timestamp, use current date/time
        if ($base_timestamp === null || $base_timestamp === false) {
            $base_timestamp = time();
        }

        // Convert AI response items to database format and create them
        $created_items = [];
        $errors = [];

        foreach ($plan_items as $index => $item_data) {
            try {
                // Validate required fields
                if (empty($item_data['post title'])) {
                    $errors[] = [
                        'index' => $index,
                        'error' => 'Missing required field: post title'
                    ];
                    continue;
                }

                // Extract and normalize structure
                $structure_raw = $item_data['Structure of the post'] ?? [];
                $normalized_structure = [];

                if (!is_array($structure_raw) || empty($structure_raw)) {
                    $errors[] = [
                        'index' => $index,
                        'error' => 'Invalid or empty structure'
                    ];
                    continue;
                }

                // Normalize structure items
                foreach ($structure_raw as $structure_item) {
                    if (is_string($structure_item)) {
                        $trimmed = trim($structure_item);
                        if (empty($trimmed)) {
                            continue;
                        }
                        // Check if this is excerpt_tldr (language-independent)
                        if ($this->is_excerpt_tldr($trimmed)) {
                            $normalized_structure[] = 'excerpt_tldr';
                        } elseif (
                            $trimmed === 'conclusion_summary' ||
                            $trimmed === 'key_takeaways' ||
                            $trimmed === 'faq'
                        ) {
                            $normalized_structure[] = $trimmed;
                        } else {
                            $normalized_structure[] = [
                                'type' => 'paragraph',
                                'subtitle' => $trimmed
                            ];
                        }
                    } elseif (is_array($structure_item) && isset($structure_item['type'])) {
                        $normalized_structure[] = $structure_item;
                    }
                }

                if (empty($normalized_structure)) {
                    $errors[] = [
                        'index' => $index,
                        'error' => 'No valid structure items found'
                    ];
                    continue;
                }

                // Extract keywords
                $keywords_raw = $item_data['Keywords.'] ?? [];
                $keywords = '';
                if (is_array($keywords_raw) && !empty($keywords_raw)) {
                    $keywords_array = array_filter(array_map('trim', $keywords_raw), function ($keyword) {
                        return !empty($keyword);
                    });
                    $keywords = implode(', ', $keywords_array);
                } elseif (is_string($keywords_raw) && !empty(trim($keywords_raw))) {
                    $keywords = trim($keywords_raw);
                }

                // Calculate scheduled date
                $item_scheduled_date = null;
                if ($base_timestamp !== null && $base_timestamp !== false) {
                    $days_to_add = ($index + 1) * $publish_interval_days;
                    $item_scheduled_timestamp = strtotime('+' . $days_to_add . ' days', $base_timestamp);
                    if ($item_scheduled_timestamp !== false) {
                        // Set time to 10:00:00 AM in WordPress timezone
                        $date_only = wp_date('Y-m-d', $item_scheduled_timestamp);
                        $item_scheduled_date = $date_only . ' 10:00:00';
                    }
                }

                // Prepare database data
                $data = [
                    'title' => trim($item_data['post title']),
                    'structure' => json_encode($normalized_structure),
                    'keywords' => $keywords,
                    'status' => 'planned',
                    'scheduled_on' => $item_scheduled_date,
                    'lang' => $language ?? null
                ];

                // Create content plan item
                $create_result = $this->database_service->create_content_plan_item($data);

                if ($create_result instanceof \WP_Error) {
                    $errors[] = [
                        'index' => $index,
                        'error' => $create_result->get_error_message()
                    ];
                } elseif ($create_result && is_numeric($create_result)) {
                    $created_item = $this->database_service->get_content_plan_item((int) $create_result);
                    if ($created_item) {
                        $created_items[] = $created_item;
                    }
                }
            } catch (\Exception $e) {
                $errors[] = [
                    'index' => $index,
                    'error' => 'Exception: ' . $e->getMessage()
                ];
            }
        }

        // Update task result to include item IDs
        if (!empty($created_items)) {
            $created_item_ids = array_map(function ($item) {
                return $item['id'];
            }, $created_items);

            if (!is_array($task_result)) {
                $task_result = [];
            }
            if (!isset($task_result['output'])) {
                $task_result['output'] = [];
            }
            $task_result['output']['item_ids'] = $created_item_ids;
            // Update task in database
            $update_result = $this->database_service->update_task($task['id'], [
                'result' => $task_result
            ]);

            if (is_wp_error($update_result)) {
            } else {
            }
        } else {
        }

        if (!empty($errors)) {
        }
    }

    /**
     * Handle webhook callback from backend API.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function handle_webhook(\WP_REST_Request $request): \WP_REST_Response
    {
        try {
            // Get webhook data from request body
            $body = $request->get_json_params();
            if (empty($body)) {
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'error' => 'Empty request body'
                    ],
                    400
                );
            }

            // Log entire payload in a readable format
            // Also log compact version for quick reference
            // Log raw body structure
            // Extract webhook data
            $task_id = $body['taskId'] ?? null;
            $install_id = $body['installId'] ?? null;
            $status = $body['status'] ?? null;

            // Extract result with detailed logging
            // Extract result - if missing or null, use empty array as default
            // This ensures we always have a result object to work with
            $result = isset($body['result']) ? $body['result'] : [];
            // If result is explicitly null, convert to empty array
            if ($result === null) {
                $result = [];
            }

            // Log webhook payload for debugging
            if (empty($task_id) || empty($status)) {
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'error' => 'Missing required fields: taskId or status'
                    ],
                    400
                );
            }
            // Look up task by task_id (this is the primary lookup method after removing correlation_id)
            $task = $this->database_service->get_task_by_task_id($task_id);

            if (!$task) {
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'error' => 'Task not found for taskId: ' . $task_id
                    ],
                    404
                );
            }
            // Update task record with webhook data
            // Get existing task result (but DO NOT preserve input - input should never be stored)
            $existing_result = $task['result'] ?? null;

            // Decode existing result if it's a JSON string
            if (is_string($existing_result) && !empty($existing_result)) {
                $decoded = json_decode($existing_result, true);
                if (json_last_error() === JSON_ERROR_NONE) {
                    $existing_result = $decoded;
                }
            }

            // Extract item_id from existing result before removing input (needed for post generation)
            $item_id_from_existing = null;
            if (is_array($existing_result) && isset($existing_result['input']['item_id'])) {
                $item_id_from_existing = $existing_result['input']['item_id'];
            }

            // Extract language from existing result before removing input (needed for content plan generation)
            $language_from_existing = null;
            if (is_array($existing_result) && isset($existing_result['input']['variables']['language'])) {
                $language_from_existing = $existing_result['input']['variables']['language'];
            }

            // CRITICAL: Remove input from existing result if it exists - input should NEVER be stored
            if (is_array($existing_result) && isset($existing_result['input'])) {
                unset($existing_result['input']);
            }

            // Start with webhook result
            // Important: empty arrays [] are valid results, don't treat them as null
            $merged_result = $result;

            // Log what we received
            // If webhook result is explicitly null (not empty array), try to use existing result
            if ($result === null && $existing_result !== null) {
                $merged_result = $existing_result;
            }

            // For failed tasks: if webhook result is null/empty but we have an existing result with error, preserve it
            // This handles cases where the webhook might arrive before the result is fully populated
            // or where the result is lost in transit
            // Note: empty arrays [] are NOT considered empty here - they're valid results
            if ($status === 'failed') {
                $is_result_empty =
                    $merged_result === null ||
                    (is_array($merged_result) && empty($merged_result) && !isset($merged_result['error']));
                if ($is_result_empty && !empty($existing_result)) {
                    if (is_array($existing_result) && isset($existing_result['error'])) {
                        // Existing result has an error message, preserve it
                        $merged_result = $existing_result;
                    } elseif (is_string($existing_result) && !empty($existing_result)) {
                        // Existing result is a string (legacy format), preserve it
                        $merged_result = $existing_result;
                    }
                }
            }

            // DO NOT merge input from existing result - input should never be stored in WordPress database
            // The webhook payload is already sanitized and should not contain input

            // CRITICAL: Remove input from merged_result if it somehow exists (shouldn't happen since we sanitized existing_result)
            if (is_array($merged_result) && isset($merged_result['input'])) {
                unset($merged_result['input']);
            }

            // Ensure we have a result - if webhook didn't provide one and we don't have existing, use empty array
            // This prevents null from being stored when we should have at least an empty object
            // But preserve empty arrays from webhook as they're valid
            if ($merged_result === null && $existing_result === null) {
                $merged_result = [];
            } elseif (is_array($merged_result) && empty($merged_result)) {
                // Empty array is valid - log but don't change it
            }

            // Final validation: ensure result is never null unless explicitly intended
            // Empty arrays are valid and should be preserved
            if ($merged_result === null) {
                $merged_result = [];
            }

            // CRITICAL: Remove input field if it somehow exists - input should NEVER be stored in WordPress database
            if (is_array($merged_result) && isset($merged_result['input'])) {
                unset($merged_result['input']);
            }

            $task_update_data = [
                'status' => $status === 'completed' ? 'completed' : ($status === 'failed' ? 'failed' : 'processing'),
                'result' => $merged_result
            ];

            // Log full payload that will be stored in database
            // Log what we're updating
            // Always update task_id if webhook provides one and it's different
            // This ensures we can match future webhooks by task_id
            if (!empty($task_id) && $task['task_id'] !== $task_id) {
                $task_update_data['task_id'] = $task_id;
            }

            // Log what we're about to send to update_task
            // Verify task ID exists before updating
            if (empty($task['id'])) {
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'error' => 'Task ID is empty'
                    ],
                    400
                );
            }

            $update_result = $this->database_service->update_task($task['id'], $task_update_data);
            if (is_wp_error($update_result)) {
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'error' => 'Failed to update task: ' . $update_result->get_error_message()
                    ],
                    500
                );
            } else {
                // Verify the status was actually updated in the database
                $verified_task = $this->database_service->get_task($task['id']);
                if ($verified_task) {
                    $actual_status = $verified_task['status'] ?? null;
                    $expected_status = $task_update_data['status'];
                    if ($actual_status !== $expected_status) {
                        // Don't return error here - continue processing but log the issue
                    } else {
                    }
                } else {
                }

                // Log task failures, especially moderation failures
                if ($status === 'failed') {
                    $logging_service = new CPEPAI_Logging_Service($this->database_service);

                    // Extract error information from result
                    $result_data = is_array($merged_result) ? $merged_result : [];
                    $error_message = $result_data['error'] ?? 'Task failed';
                    $moderation_failed = $result_data['moderation_failed'] ?? false;
                    $moderation_blocked = $result_data['moderation_blocked'] ?? false;
                    $failure_reason = $result_data['failure_reason'] ?? null;

                    // Determine if this is a moderation failure
                    $is_moderation_failure =
                        $moderation_failed || $moderation_blocked || $failure_reason === 'moderation';

                    // Determine event type based on task type
                    $event_type = $task['type'] === 'post_generation' ? 'post_generation' : 'content_plan_generation';

                    // Build message - preserve full error message for blocked users
                    if ($is_moderation_failure) {
                        if ($moderation_blocked) {
                            // For blocked users, use the full error message which contains clear blocking information
                            $message = 'ACCOUNT BLOCKED: ' . $error_message;
                        } else {
                            $message = 'Content generation failed: Content moderation check failed';
                        }
                    } else {
                        $message = 'Content generation failed: ' . $error_message;
                    }

                    // Build context
                    $context = [
                        'task_id' => $task_id,
                        'task_type' => $task['type'],
                        'failure_reason' => $failure_reason,
                        'moderation_failed' => $moderation_failed,
                        'moderation_blocked' => $moderation_blocked
                    ];

                    if ($error_message && !$is_moderation_failure) {
                        $context['error'] = $error_message;
                    }

                    // Log the failure
                    $logging_service->write_to_log_table(
                        'error',
                        $event_type,
                        $message,
                        $context,
                        $task_id,
                        null,
                        null,
                        null // No tokens spent for failed tasks
                    );
                }

                // Verify what was actually saved by fetching the task again
                $verify_task = $this->database_service->get_task($task['id']);
                if ($verify_task) {
                    // Log full stored result
                } else {
                }
            }

            // Handle different webhook statuses
            if ($status === 'completed') {
                $task_type = $task['type'] ?? 'content_plan_generation';

                if ($task_type === 'post_generation') {
                    // Handle post generation completion
                    // Extract post content from merged result
                    $result_output = $merged_result['output'] ?? null;
                    if (empty($result_output) || !isset($result_output['postContent'])) {
                        return new \WP_REST_Response(
                            [
                                'success' => false,
                                'error' => 'No post content in result'
                            ],
                            400
                        );
                    }

                    $post_content = $result_output['postContent'];
                    if (!is_array($post_content) || empty($post_content)) {
                        return new \WP_REST_Response(
                            [
                                'success' => false,
                                'error' => 'Invalid post content format'
                            ],
                            400
                        );
                    }

                    // Get the content plan item ID from task result input
                    // We extracted it earlier before removing input for security
                    $item_id = $item_id_from_existing ?? null;

                    if (empty($item_id)) {
                        return new \WP_REST_Response(
                            [
                                'success' => false,
                                'error' => 'Cannot find item ID in task input'
                            ],
                            400
                        );
                    }

                    // Fetch the content plan item
                    $matching_item = $this->database_service->get_content_plan_item($item_id);

                    if (!$matching_item) {
                        return new \WP_REST_Response(
                            [
                                'success' => false,
                                'error' => 'Content plan item not found'
                            ],
                            404
                        );
                    }

                    // Save the post using CPEPAI_Posts_Service
                    // The post_content from backend is an array of post sections (e.g., ['intro' => '<p>...</p>', 'body' => '<p>...</p>'])
                    // The posts service will auto-detect the editor format and convert this to Gutenberg blocks or classic HTML format
                    try {
                        $saved_post = $this->posts_service->save_post_from_ai_response($matching_item, $post_content);

                        if (!$saved_post || is_wp_error($saved_post)) {
                            $error_message = is_wp_error($saved_post)
                                ? $saved_post->get_error_message()
                                : 'Unknown error saving post';
                            return new \WP_REST_Response(
                                [
                                    'success' => false,
                                    'error' => 'Failed to save post: ' . $error_message
                                ],
                                500
                            );
                        }

                        // Update task result to include post information
                        $updated_result = $merged_result;
                        if (!is_array($updated_result)) {
                            $updated_result = [];
                        }
                        if (!isset($updated_result['output'])) {
                            $updated_result['output'] = [];
                        }
                        $updated_result['output']['post_id'] = $saved_post['post_id'];
                        $updated_result['output']['post_url'] = $saved_post['post_url'];
                        $updated_result['output']['post_edit_url'] = $saved_post['post_edit_url'];
                        // Store item_id in output for frontend filtering (input is removed for security)
                        $updated_result['output']['item_id'] = $item_id;

                        // Update task with post information
                        $this->database_service->update_task($task['id'], [
                            'result' => $updated_result
                        ]);

                        // Update content plan item status to 'draft_ready_ai' and store post ID
                        $item_update_data = [
                            'status' => 'draft_ready_ai',
                            'post' => (string) $saved_post['post_id'] // Store post ID
                        ];
                        $item_update_result = $this->database_service->update_content_plan_item(
                            $item_id,
                            $item_update_data
                        );
                        if (is_wp_error($item_update_result)) {
                        } else {
                        }
                        return new \WP_REST_Response(
                            [
                                'success' => true,
                                'message' => 'Post generated and saved successfully',
                                'data' => [
                                    'post_id' => $saved_post['post_id'],
                                    'post_url' => $saved_post['post_url'],
                                    'post_edit_url' => $saved_post['post_edit_url'],
                                    'item_id' => $matching_item['id']
                                ]
                            ],
                            200
                        );
                    } catch (Exception $e) {
                        return new \WP_REST_Response(
                            [
                                'success' => false,
                                'error' => 'Error saving post: ' . $e->getMessage()
                            ],
                            500
                        );
                    }
                } else {
                    // Handle content plan generation (default)
                    // Extract plan data from merged result
                    $result_output = $merged_result['output'] ?? null;
                    if (empty($result_output) || !isset($result_output['plan'])) {
                        return new \WP_REST_Response(
                            [
                                'success' => false,
                                'error' => 'No plan data in result'
                            ],
                            400
                        );
                    }

                    $plan_items = $result_output['plan'];
                    if (!is_array($plan_items)) {
                        return new \WP_REST_Response(
                            [
                                'success' => false,
                                'error' => 'Invalid plan data format'
                            ],
                            400
                        );
                    }
                    // Extract language from task input if available (extracted before input was removed)
                    $language = $language_from_existing ?? null;

                    // Calculate scheduled dates based on max scheduled date and publish interval
                    // 1. Get the latest scheduled_on value from the database
                    $latest_scheduled_date = $this->database_service->get_latest_scheduled_date();

                    // 2. Get publish interval from settings
                    $publish_interval_days = $this->options_service->get_setting('publish_interval_days', 7);

                    // 3. Determine the base date for scheduling
                    // If latest scheduled date exists, use it as base
                    // Otherwise, use current date/time as base
                    $base_timestamp = null;
                    if (!empty($latest_scheduled_date)) {
                        $base_timestamp = strtotime($latest_scheduled_date);
                        if ($base_timestamp !== false) {
                        } else {
                        }
                    }

                    // If no valid base timestamp, use current date/time
                    if ($base_timestamp === null || $base_timestamp === false) {
                        $base_timestamp = time();
                    }

                    // Convert AI response items to database format and create them
                    $created_items = [];
                    $errors = [];

                    foreach ($plan_items as $index => $item_data) {
                        try {
                            // Validate required fields from backend response
                            if (empty($item_data['post title'])) {
                                $errors[] = [
                                    'index' => $index,
                                    'error' => 'Missing required field: post title',
                                    'data' => $item_data
                                ];
                                continue;
                            }

                            // Extract and normalize structure from backend response
                            // Backend sends: "Structure of the post" as array of strings
                            // - Exact strings: "excerpt_tldr", "conclusion_summary"
                            // - Other strings: subtopic titles (e.g., "Key Benefits", "Implementation Steps")
                            $structure_raw = $item_data['Structure of the post'] ?? [];
                            $normalized_structure = [];

                            if (!is_array($structure_raw)) {
                                $errors[] = [
                                    'index' => $index,
                                    'error' => 'Invalid structure format: expected array',
                                    'data' => $item_data
                                ];
                                continue;
                            }

                            if (empty($structure_raw)) {
                                $errors[] = [
                                    'index' => $index,
                                    'error' => 'Structure is empty',
                                    'data' => $item_data
                                ];
                                continue;
                            }

                            // Normalize structure items
                            foreach ($structure_raw as $structure_item) {
                                if (is_string($structure_item)) {
                                    $trimmed = trim($structure_item);
                                    if (empty($trimmed)) {
                                        continue; // Skip empty strings
                                    }

                                    // Check if this is excerpt_tldr (language-independent)
                                    if ($this->is_excerpt_tldr($trimmed)) {
                                        $normalized_structure[] = 'excerpt_tldr';
                                    } elseif (
                                        $trimmed === 'conclusion_summary' ||
                                        $trimmed === 'key_takeaways' ||
                                        $trimmed === 'faq'
                                    ) {
                                        $normalized_structure[] = $trimmed;
                                    } else {
                                        // Treat as subtopic title - wrap in paragraph type with subtitle
                                        $normalized_structure[] = [
                                            'type' => 'paragraph',
                                            'subtitle' => $trimmed
                                        ];
                                    }
                                } elseif (is_array($structure_item)) {
                                    // If already in object format, validate and use it
                                    if (isset($structure_item['type'])) {
                                        $normalized_structure[] = $structure_item;
                                    } else {
                                        // Invalid format, skip
                                    }
                                } else {
                                    // Convert to string as fallback
                                    $normalized_structure[] = [
                                        'type' => 'paragraph',
                                        'subtitle' => (string) $structure_item
                                    ];
                                }
                            }

                            // Validate that we have at least one valid structure item
                            if (empty($normalized_structure)) {
                                $errors[] = [
                                    'index' => $index,
                                    'error' => 'No valid structure items found after normalization',
                                    'data' => $item_data
                                ];
                                continue;
                            }

                            // Extract keywords from backend response
                            // Backend sends: "Keywords." as array of strings
                            $keywords_raw = $item_data['Keywords.'] ?? [];
                            $keywords = '';

                            if (is_array($keywords_raw) && !empty($keywords_raw)) {
                                // Filter out empty values and join with comma
                                $keywords_array = array_filter(array_map('trim', $keywords_raw), function ($keyword) {
                                    return !empty($keyword);
                                });
                                $keywords = implode(', ', $keywords_array);
                            } elseif (is_string($keywords_raw) && !empty(trim($keywords_raw))) {
                                // Fallback: if it's already a string, use it
                                $keywords = trim($keywords_raw);
                            }

                            // Calculate scheduled date for this item
                            // Each item is scheduled (index + 1) * interval days after the base date
                            // Item 0: base + interval, Item 1: base + (interval * 2), etc.
                            $item_scheduled_date = null;
                            if ($base_timestamp !== null && $base_timestamp !== false) {
                                $days_to_add = ($index + 1) * $publish_interval_days;
                                $item_scheduled_timestamp = strtotime('+' . $days_to_add . ' days', $base_timestamp);
                                if ($item_scheduled_timestamp !== false) {
                                    // Set time to 10:00:00 AM in WordPress timezone
                                    $date_only = wp_date('Y-m-d', $item_scheduled_timestamp);
                                    $item_scheduled_date = $date_only . ' 10:00:00';
                                }
                            }

                            // Prepare database data
                            $data = [
                                'title' => trim($item_data['post title']),
                                'structure' => json_encode($normalized_structure),
                                'keywords' => $keywords,
                                'status' => 'planned',
                                'scheduled_on' => $item_scheduled_date,
                                'lang' => $language ?? null
                            ];

                            // Create content plan item
                            $create_result = $this->database_service->create_content_plan_item($data);

                            if ($create_result instanceof \WP_Error) {
                                $errors[] = [
                                    'index' => $index,
                                    'error' => $create_result->get_error_message(),
                                    'data' => $data
                                ];
                            } elseif ($create_result && is_numeric($create_result)) {
                                $created_item = $this->database_service->get_content_plan_item((int) $create_result);
                                if ($created_item) {
                                    $created_items[] = $created_item;
                                }
                            } else {
                                $errors[] = [
                                    'index' => $index,
                                    'error' => 'Failed to create content plan item: invalid result',
                                    'data' => $data
                                ];
                            }
                        } catch (\Exception $e) {
                            $errors[] = [
                                'index' => $index,
                                'error' => 'Exception: ' . $e->getMessage(),
                                'data' => $item_data
                            ];
                        }
                    }

                    // Prepare response based on results
                    $total_items = count($plan_items);
                    $items_created = count($created_items);
                    $items_failed = count($errors);

                    // Extract item IDs from created items
                    $created_item_ids = array_map(function ($item) {
                        return $item['id'];
                    }, $created_items);

                    // Update task result to include item IDs
                    if (!empty($created_item_ids)) {
                        $updated_result = $result;
                        if (!is_array($updated_result)) {
                            $updated_result = [];
                        }
                        if (!isset($updated_result['output'])) {
                            $updated_result['output'] = [];
                        }
                        $updated_result['output']['item_ids'] = $created_item_ids;

                        // Update task with item IDs
                        $this->database_service->update_task($task['id'], [
                            'result' => $updated_result
                        ]);
                    }

                    if (!empty($errors)) {
                    }

                    if ($items_created > 0) {
                    }

                    // Determine response status
                    $response_success = $items_created > 0;
                    $response_message = '';
                    $status_code = 200;

                    if ($items_created === $total_items) {
                        // All items created successfully
                        $response_message = sprintf(
                            'Webhook processed successfully - created %d content plan item(s)',
                            $items_created
                        );
                    } elseif ($items_created > 0) {
                        // Partial success
                        $response_message = sprintf(
                            'Webhook processed with partial success - created %d out of %d content plan item(s)',
                            $items_created,
                            $total_items
                        );
                        $status_code = 207; // Multi-Status
                    } else {
                        // All items failed
                        $response_message = sprintf(
                            'Webhook processed but failed to create any content plan items (%d errors)',
                            $items_failed
                        );
                        $status_code = 500;
                    }

                    $response_data = [
                        'success' => $response_success,
                        'message' => $response_message,
                        'data' => [
                            'items_created' => $items_created,
                            'items_failed' => $items_failed,
                            'total_items' => $total_items
                        ]
                    ];

                    // Include errors in response if any
                    if (!empty($errors)) {
                        $response_data['data']['errors'] = $errors;
                    }

                    return new \WP_REST_Response($response_data, $status_code);
                }
            } elseif ($status === 'failed') {
                // Handle failed task
                $error_message = $result['error'] ?? 'Unknown error';
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'message' => 'Webhook received failure status',
                        'error' => $error_message
                    ],
                    200
                );
            } else {
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'error' => 'Unknown status: ' . $status
                    ],
                    400
                );
            }
        } catch (\Exception $e) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'error' => 'Internal server error: ' . $e->getMessage()
                ],
                500
            );
        }
    }

    /**
     * Get tasks.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function get_tasks(\WP_REST_Request $request): \WP_REST_Response
    {
        // Note: Automatic cleanup of old tasks is disabled.
        // Tasks should only be marked as 'failed' when webhook explicitly reports failure.
        // This prevents false positives where tasks are marked as failed prematurely.
        // If cleanup is needed, it should be done manually or via a separate admin action.

        $status = $request->get_param('status');
        $type = $request->get_param('type');
        $limit = $request->get_param('limit') ?: 50;
        $offset = $request->get_param('offset') ?: 0;

        $args = [
            'limit' => intval($limit),
            'offset' => intval($offset),
            'orderby' => 'created_at',
            'order' => 'DESC'
        ];

        if (!empty($status)) {
            $args['status'] = sanitize_text_field($status);
        }

        if (!empty($type)) {
            $args['type'] = sanitize_text_field($type);
        }

        $tasks = $this->database_service->get_tasks($args);
        // Ensure result is decoded for all tasks and preserve item_id for post generation tasks
        // For tasks processed by webhook: item_id is in output.item_id
        // For tasks not yet processed: item_id is in input.item_id
        foreach ($tasks as &$task) {
            $task_id = $task['id'] ?? 'N/A';
            $task_status = $task['status'] ?? 'N/A';
            $task_type = $task['type'] ?? 'N/A';

            // Log initial state of completed content plan tasks
            if ($task_status === 'completed' && $task_type === 'content_plan_generation') {
                $initial_result = $task['result'] ?? null;
                $initial_has_item_ids = false;
                $initial_item_ids = [];

                if (is_string($initial_result) && !empty($initial_result)) {
                    $decoded = json_decode($initial_result, true);
                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                        $initial_result = $decoded;
                    }
                }

                if (is_array($initial_result)) {
                    $initial_has_item_ids =
                        isset($initial_result['output']['item_ids']) &&
                        is_array($initial_result['output']['item_ids']) &&
                        !empty($initial_result['output']['item_ids']);
                    if ($initial_has_item_ids) {
                        $initial_item_ids = $initial_result['output']['item_ids'];
                    }
                }
            }
            $task_result = $task['result'] ?? null;

            // Decode result if it's a JSON string
            if (is_string($task_result) && !empty($task_result)) {
                $decoded = json_decode($task_result, true);
                if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                    $task['result'] = $decoded;
                    $task_result = $decoded;
                } else {
                    $task['result'] = [];
                    $task_result = [];
                }
            } elseif (!is_array($task_result)) {
                $task['result'] = [];
                $task_result = [];
            }

            // For post generation tasks: ensure item_id is accessible in input for frontend filtering
            // Check output first (webhook stores it there), then fall back to input (for unprocessed tasks)
            if ($task['type'] === 'post_generation') {
                $item_id = null;

                // Check if webhook already stored it in output
                if (isset($task_result['output']['item_id'])) {
                    $item_id = $task_result['output']['item_id'];
                }
                // Fall back to input (for tasks not yet processed by webhook)
                elseif (isset($task_result['input']['item_id'])) {
                    $item_id = $task_result['input']['item_id'];
                }

                // Ensure item_id is accessible in input for frontend (frontend checks input.item_id)
                if ($item_id !== null) {
                    if (!isset($task['result']['input'])) {
                        $task['result']['input'] = [];
                    }
                    $task['result']['input']['item_id'] = $item_id;
                }
            }
        }
        unset($task); // Unset reference

        // Track which tasks were processed during backend sync to prevent duplicate processing
        $processed_task_ids = [];
        // Fetch task state from backend API for each task
        // This ensures we have the most up-to-date status and result from the backend
        // Fetch for tasks with pending status (queued or processing) to optimize performance
        // Also fetch for tasks with task_id to catch any out-of-sync status issues
        if ($this->backend_service->is_configured()) {
            foreach ($tasks as &$task) {
                // Fetch from backend if task has a task_id
                // Always sync pending tasks (queued/processing) for real-time updates
                // Also sync tasks that are not in final states (completed/failed) to catch out-of-sync issues
                // This ensures tasks stuck with incorrect status get updated from backend
                $task_status = $task['status'] ?? '';
                $is_pending = in_array($task_status, ['queued', 'processing'], true);
                $is_final_state = in_array($task_status, ['completed', 'failed'], true);
                $has_task_id = !empty($task['task_id']);

                // Sync if task has task_id AND (is pending OR not in final state)
                // This catches cases where status might be out of sync with backend
                if ($has_task_id && ($is_pending || !$is_final_state)) {
                    // Store original status for comparison
                    $original_status = $task_status;

                    // Preserve item_id before backend overwrites result (for post generation tasks)
                    $preserved_item_id = null;
                    if ($task['type'] === 'post_generation' && is_array($task['result'])) {
                        // Check both output (webhook stored) and input (not yet processed)
                        $preserved_item_id =
                            $task['result']['output']['item_id'] ?? ($task['result']['input']['item_id'] ?? null);
                    }

                    // Preserve item_ids before backend overwrites result (for content plan generation tasks)
                    $preserved_item_ids = null;
                    $task_type_for_preserve = $task['type'] ?? 'content_plan_generation';
                    if ($task_type_for_preserve === 'content_plan_generation' && is_array($task['result'])) {
                        // Check if item_ids exist in output (they're stored there after processing)
                        if (
                            isset($task['result']['output']['item_ids']) &&
                            is_array($task['result']['output']['item_ids']) &&
                            !empty($task['result']['output']['item_ids'])
                        ) {
                            $preserved_item_ids = $task['result']['output']['item_ids'];
                        }
                    }

                    // Preserve language from input before backend overwrites result (for content plan generation tasks)
                    $preserved_language = null;
                    if (
                        $task_type_for_preserve === 'content_plan_generation' &&
                        is_array($task['result']) &&
                        isset($task['result']['input']['variables']['language'])
                    ) {
                        $preserved_language = $task['result']['input']['variables']['language'];
                    }

                    $backend_response = $this->backend_service->get_task_state($task['task_id']);

                    // API returns { success: true, data: { status: "...", moderation: {...}, ... } }
                    // Check both 'data' and 'data.data' for backward compatibility
                    $backend_task_data = null;
                    $backend_moderation_info = null;
                    if ($backend_response['success']) {
                        if (isset($backend_response['data']['data'])) {
                            // Nested structure (legacy)
                            $backend_task_data = $backend_response['data']['data'];
                            $backend_moderation_info = $backend_response['data']['data']['moderation'] ?? null;
                        } elseif (isset($backend_response['data'])) {
                            // Direct structure (current API format)
                            $backend_task_data = $backend_response['data'];
                            $backend_moderation_info = $backend_response['data']['moderation'] ?? null;
                        } else {
                        }
                    } else {
                    }

                    if ($backend_task_data !== null) {
                        // Merge backend state with local task data
                        // Backend data takes precedence for: status, result, timestamps
                        // However, preserve local status if it's more advanced than backend status
                        // Status progression: queued -> processing -> completed/failed
                        $status_changed = false;
                        $backend_status = null;
                        $status_was_updated = false; // Track if status was actually updated in memory
                        if (isset($backend_task_data['status'])) {
                            $backend_status = $backend_task_data['status'];

                            // Define status priority (higher number = more advanced)
                            $status_priority = [
                                'queued' => 1,
                                'processing' => 2,
                                'completed' => 3,
                                'failed' => 3
                            ];

                            $original_priority = $status_priority[$original_status] ?? 0;
                            $backend_priority = $status_priority[$backend_status] ?? 0;

                            // CRITICAL: Always respect 'failed' status from backend
                            // Failed is a final state and indicates a real problem that must be shown
                            $should_update = false;
                            if ($backend_status === 'failed') {
                                // Always update to failed - this is critical for blocked users
                                $should_update = true;
                            } elseif ($backend_priority >= $original_priority) {
                                // Only update status if backend status is same or more advanced
                                // This prevents regression (e.g., processing -> queued)
                                $should_update = true;
                            }

                            if ($should_update) {
                                $status_changed = $backend_status !== $original_status;
                                if ($status_changed) {
                                }
                                $task['status'] = $backend_status;
                                $status_was_updated = true; // Mark that status was updated
                            } else {
                                // Backend status is less advanced - keep local status
                            }
                        }

                        if (isset($backend_task_data['result'])) {
                            $task['result'] = $backend_task_data['result'];

                            // Preserve item_id for post generation tasks after backend overwrites result
                            // Backend result doesn't include input (removed for security), so we restore it
                            // Also check output in case webhook already stored it there
                            if ($preserved_item_id !== null && $task['type'] === 'post_generation') {
                                if (!is_array($task['result'])) {
                                    $task['result'] = [];
                                }
                                // Store in both output (for consistency) and input (for frontend)
                                if (!isset($task['result']['output'])) {
                                    $task['result']['output'] = [];
                                }
                                $task['result']['output']['item_id'] = $preserved_item_id;

                                if (!isset($task['result']['input'])) {
                                    $task['result']['input'] = [];
                                }
                                $task['result']['input']['item_id'] = $preserved_item_id;
                            }

                            // Preserve item_ids for content plan generation tasks after backend overwrites result
                            // Backend result doesn't include item_ids (they're added locally after processing),
                            // so we restore them to prevent duplicate processing on page refresh
                            if ($preserved_item_ids !== null) {
                                $task_type_check = $task['type'] ?? 'content_plan_generation';
                                if ($task_type_check === 'content_plan_generation') {
                                    if (!is_array($task['result'])) {
                                        $task['result'] = [];
                                    }
                                    if (!isset($task['result']['output'])) {
                                        $task['result']['output'] = [];
                                    }
                                    $task['result']['output']['item_ids'] = $preserved_item_ids;
                                }
                            }

                            // Preserve language from input after backend overwrites result
                            // Backend result doesn't include input (removed for security), so we restore it
                            if ($preserved_language !== null) {
                                $task_type_check = $task['type'] ?? 'content_plan_generation';
                                if ($task_type_check === 'content_plan_generation') {
                                    if (!is_array($task['result'])) {
                                        $task['result'] = [];
                                    }
                                    if (!isset($task['result']['input'])) {
                                        $task['result']['input'] = [];
                                    }
                                    if (!isset($task['result']['input']['variables'])) {
                                        $task['result']['input']['variables'] = [];
                                    }
                                    $task['result']['input']['variables']['language'] = $preserved_language;
                                }
                            }

                            // If task just became completed and is content plan generation, process it
                            if ($status_changed && $backend_status === 'completed') {
                                $task_type = $task['type'] ?? 'content_plan_generation';
                                if ($task_type === 'content_plan_generation') {
                                    $task_id_for_log = $task['id'] ?? 'N/A';
                                    // Ensure result is an array before processing
                                    if (!is_array($task['result'])) {
                                        $task['result'] = [];
                                    }

                                    // Check if items were already created to prevent duplicate processing
                                    $task_result = $task['result'];
                                    // Decode result if it's a JSON string
                                    if (is_string($task_result) && !empty($task_result)) {
                                        $decoded = json_decode($task_result, true);
                                        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                                            $task_result = $decoded;
                                        }
                                    }
                                    $has_item_ids =
                                        isset($task_result['output']['item_ids']) &&
                                        is_array($task_result['output']['item_ids']) &&
                                        !empty($task_result['output']['item_ids']);

                                    $item_ids_count = 0;
                                    if ($has_item_ids) {
                                        $item_ids_count = count($task_result['output']['item_ids']);
                                    }
                                    // Only process if items haven't been created yet
                                    if (!$has_item_ids) {
                                        // Preserve the completed status before processing (which may refresh task from DB)
                                        $preserved_completed_status = $task['status'];
                                        $this->process_completed_content_plan_task($task);

                                        // Mark this task as processed to prevent duplicate processing in the second loop
                                        if (isset($task['id'])) {
                                            $processed_task_ids[] = $task['id'];
                                        }

                                        // Refresh task data after processing
                                        $updated_task = $this->database_service->get_task($task['id']);
                                        if ($updated_task) {
                                            $task = $updated_task;
                                            // Restore the completed status after refresh (DB may still have old status)
                                            $task['status'] = $preserved_completed_status;
                                            // Decode result after update
                                            if (is_string($task['result'] ?? null) && !empty($task['result'])) {
                                                $decoded = json_decode($task['result'], true);
                                                if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                                                    $task['result'] = $decoded;
                                                }
                                            }

                                            // Log the updated state
                                            $updated_has_item_ids =
                                                isset($task['result']['output']['item_ids']) &&
                                                is_array($task['result']['output']['item_ids']) &&
                                                !empty($task['result']['output']['item_ids']);
                                            $updated_item_ids_count = $updated_has_item_ids
                                                ? count($task['result']['output']['item_ids'])
                                                : 0;
                                        }
                                    } else {
                                    }
                                }
                            }

                            // If task just became completed and is post generation, process it
                            if ($status_changed && $backend_status === 'completed') {
                                $task_type = $task['type'] ?? 'content_plan_generation';
                                if ($task_type === 'post_generation') {
                                    $task_id_for_log = $task['id'] ?? 'N/A';
                                    // Ensure result is an array before processing
                                    if (!is_array($task['result'])) {
                                        $task['result'] = [];
                                    }

                                    // Check if post was already created to prevent duplicate processing
                                    $task_result = $task['result'];
                                    // Decode result if it's a JSON string
                                    if (is_string($task_result) && !empty($task_result)) {
                                        $decoded = json_decode($task_result, true);
                                        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                                            $task_result = $decoded;
                                        }
                                    }
                                    $has_post_id =
                                        isset($task_result['output']['post_id']) &&
                                        !empty($task_result['output']['post_id']);
                                    // Only process if post hasn't been created yet
                                    if (!$has_post_id) {
                                        // Extract post content from result
                                        $result_output = $task_result['output'] ?? null;
                                        if (empty($result_output) || !isset($result_output['postContent'])) {
                                        } else {
                                            $post_content = $result_output['postContent'];
                                            if (!is_array($post_content) || empty($post_content)) {
                                            } else {
                                                // Get the content plan item ID from task result
                                                // Check output first (webhook stores it there), then fall back to input
                                                $item_id = null;
                                                if (isset($task_result['output']['item_id'])) {
                                                    $item_id = $task_result['output']['item_id'];
                                                } elseif (isset($task_result['input']['item_id'])) {
                                                    $item_id = $task_result['input']['item_id'];
                                                } elseif ($preserved_item_id !== null) {
                                                    $item_id = $preserved_item_id;
                                                }

                                                if (empty($item_id)) {
                                                } else {
                                                    // Fetch the content plan item
                                                    $matching_item = $this->database_service->get_content_plan_item(
                                                        $item_id
                                                    );

                                                    if (!$matching_item) {
                                                    } else {
                                                        // Save the post using CPEPAI_Posts_Service
                                                        try {
                                                            $saved_post = $this->posts_service->save_post_from_ai_response(
                                                                $matching_item,
                                                                $post_content
                                                            );

                                                            if (!$saved_post || is_wp_error($saved_post)) {
                                                                $error_message = is_wp_error($saved_post)
                                                                    ? $saved_post->get_error_message()
                                                                    : 'Unknown error saving post';
                                                            } else {
                                                                // Update task result to include post information
                                                                $updated_result = $task_result;
                                                                if (!is_array($updated_result)) {
                                                                    $updated_result = [];
                                                                }
                                                                if (!isset($updated_result['output'])) {
                                                                    $updated_result['output'] = [];
                                                                }
                                                                $updated_result['output']['post_id'] =
                                                                    $saved_post['post_id'];
                                                                $updated_result['output']['post_url'] =
                                                                    $saved_post['post_url'];
                                                                $updated_result['output']['post_edit_url'] =
                                                                    $saved_post['post_edit_url'];
                                                                // Store item_id in output for frontend filtering
                                                                $updated_result['output']['item_id'] = $item_id;

                                                                // Update task with post information and ensure status is completed
                                                                $this->database_service->update_task($task['id'], [
                                                                    'status' => 'completed',
                                                                    'result' => $updated_result
                                                                ]);

                                                                // Update content plan item status to 'draft_ready_ai' and store post ID
                                                                $item_update_data = [
                                                                    'status' => 'draft_ready_ai',
                                                                    'post' => (string) $saved_post['post_id'] // Store post ID
                                                                ];
                                                                $item_update_result = $this->database_service->update_content_plan_item(
                                                                    $item_id,
                                                                    $item_update_data
                                                                );
                                                                if (is_wp_error($item_update_result)) {
                                                                } else {
                                                                }
                                                                // Preserve the completed status before processing (which may refresh task from DB)
                                                                $preserved_completed_status = $task['status'];

                                                                // Refresh task data after processing
                                                                $updated_task = $this->database_service->get_task(
                                                                    $task['id']
                                                                );
                                                                if ($updated_task) {
                                                                    $task = $updated_task;
                                                                    // Restore the completed status after refresh (DB may still have old status)
                                                                    $task['status'] = $preserved_completed_status;
                                                                    // Decode result after update
                                                                    if (
                                                                        is_string($task['result'] ?? null) &&
                                                                        !empty($task['result'])
                                                                    ) {
                                                                        $decoded = json_decode($task['result'], true);
                                                                        if (
                                                                            json_last_error() === JSON_ERROR_NONE &&
                                                                            is_array($decoded)
                                                                        ) {
                                                                            $task['result'] = $decoded;
                                                                        }
                                                                    }
                                                                }

                                                                // Mark this task as processed to prevent duplicate processing
                                                                if (isset($task['id'])) {
                                                                    $processed_task_ids[] = $task['id'];
                                                                }
                                                            }
                                                        } catch (Exception $e) {
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } else {
                                    }
                                }
                            }
                        }

                        // Update timestamps from backend if available
                        if (isset($backend_task_data['createdAt'])) {
                            $task['created_at'] = $backend_task_data['createdAt'];
                        }
                        if (isset($backend_task_data['updatedAt'])) {
                            $task['updated_at'] = $backend_task_data['updatedAt'];
                        }
                        if (isset($backend_task_data['startedAt'])) {
                            $task['started_at'] = $backend_task_data['startedAt'];
                        }
                        if (isset($backend_task_data['completedAt'])) {
                            $task['completed_at'] = $backend_task_data['completedAt'];
                        }

                        // Update attempts and maxAttempts if available
                        if (isset($backend_task_data['attempts'])) {
                            $task['attempts'] = $backend_task_data['attempts'];
                        }
                        if (isset($backend_task_data['maxAttempts'])) {
                            $task['max_attempts'] = $backend_task_data['maxAttempts'];
                        }

                        // Build update data array for database persistence
                        $task_update_data = [];

                        // Only include fields that were actually updated
                        // Use status_was_updated flag OR check if current status differs from original
                        // This ensures status is included even if task was refreshed from DB during processing
                        if (
                            isset($backend_task_data['status']) &&
                            ($status_was_updated || $task['status'] !== $original_status)
                        ) {
                            $task_update_data['status'] = $task['status'];
                        }

                        if (isset($backend_task_data['result'])) {
                            // Use task['result'] instead of backend_task_data['result'] to preserve
                            // locally-added data like item_ids that aren't in backend response
                            $task_update_data['result'] = $task['result'];
                        }

                        // Update task in database if we have changes to persist
                        if (!empty($task_update_data) && isset($task['id'])) {
                            $update_result = $this->database_service->update_task($task['id'], $task_update_data);

                            if (is_wp_error($update_result)) {
                            } else {
                            }
                        }

                        // Log successful backend fetch for debugging
                    } else {
                        // Backend fetch failed - log but continue with local data
                        $error_msg = $backend_response['error'] ?? 'Unknown error';
                        $status_code = $backend_response['status_code'] ?? 'N/A';
                        $response_data = $backend_response['data'] ?? [];
                        $response_message = is_array($response_data) ? $response_data['message'] ?? '' : '';
                        $response_reason = is_array($response_data) ? $response_data['reason'] ?? '' : '';
                        $raw_body = $backend_response['raw_body'] ?? '';

                        // Log full API response for debugging
                        // CRITICAL: If we get 403 with "Your account has been blocked", user is blocked
                        // The middleware blocks the request before returning task data, so we need to handle this
                        $is_403 = $status_code === 403 || $status_code === '403' || intval($status_code) === 403;
                        $has_blocked_indicator =
                            stripos($error_msg, 'blocked') !== false ||
                            stripos($response_message, 'blocked') !== false ||
                            stripos($response_reason, 'moderation') !== false ||
                            stripos($raw_body, 'blocked') !== false;

                        if ($is_403 && $has_blocked_indicator) {
                            // Update task to failed with moderation_blocked
                            $task['status'] = 'failed';
                            if (!is_array($task['result'])) {
                                $task['result'] = [];
                            }
                            $task['result']['moderation_blocked'] = true;
                            $task['result']['moderation_failed'] = true;
                            $task['result']['moderation_warning'] = false;
                            $task['result']['can_retry'] = false;
                            $task['result']['error'] =
                                'Your account has been suspended due to repeated or severe violations of our content safety policies.\n\nYou are no longer able to generate content. If you believe this is a mistake, please contact support immediately.\n\nTask ID: ' .
                                $task['task_id'];

                            $task['moderation'] = [
                                'blocked' => true,
                                'warning' => false,
                                'failed' => true,
                                'can_retry' => false
                            ];

                            // Update in database immediately
                            if (isset($task['id'])) {
                                $this->database_service->update_task($task['id'], [
                                    'status' => 'failed',
                                    'result' => $task['result']
                                ]);
                            }
                        }
                    }

                    // Extract moderation information from backend or result
                    // Priority: backend moderation info > backend result flags > local result flags
                    // This runs for ALL tasks, not just synced ones
                    $task_result = is_array($task['result']) ? $task['result'] : [];
                    $backend_result = null;
                    if (
                        $backend_task_data !== null &&
                        isset($backend_task_data['result']) &&
                        is_array($backend_task_data['result'])
                    ) {
                        $backend_result = $backend_task_data['result'];
                    }

                    $moderation_blocked = false;
                    $moderation_warning = false;
                    $moderation_failed = false;
                    $can_retry = true;

                    if ($backend_moderation_info !== null && is_array($backend_moderation_info)) {
                        // Use explicit moderation info from backend API
                        $moderation_blocked = $backend_moderation_info['blocked'] ?? false;
                        $moderation_warning = $backend_moderation_info['warning'] ?? false;
                        $moderation_failed = $backend_moderation_info['failed'] ?? false;
                        $can_retry = $backend_moderation_info['canRetry'] ?? true;
                    } elseif ($backend_result !== null && !empty($backend_result)) {
                        // Fallback to backend result flags (from backend database)
                        $moderation_blocked = $backend_result['moderation_blocked'] ?? false;
                        $moderation_warning = $backend_result['moderation_warning'] ?? false;
                        $moderation_failed = $backend_result['moderation_failed'] ?? false;
                        // Blocked users cannot retry, others can
                        $can_retry = !$moderation_blocked;
                    } else {
                        // Last resort: use local result flags
                        $moderation_blocked = $task_result['moderation_blocked'] ?? false;
                        $moderation_warning = $task_result['moderation_warning'] ?? false;
                        $moderation_failed = $task_result['moderation_failed'] ?? false;
                        // Blocked users cannot retry, others can
                        $can_retry = !$moderation_blocked;
                    }

                    // Ensure moderation flags are in result for frontend access
                    if (!is_array($task['result'])) {
                        $task['result'] = [];
                    }
                    $task['result']['moderation_blocked'] = $moderation_blocked;
                    $task['result']['moderation_warning'] = $moderation_warning;
                    $task['result']['moderation_failed'] = $moderation_failed;
                    $task['result']['can_retry'] = $can_retry;

                    // Add moderation info at task level for easy frontend access
                    $task['moderation'] = [
                        'blocked' => $moderation_blocked,
                        'warning' => $moderation_warning,
                        'failed' => $moderation_failed,
                        'can_retry' => $can_retry
                    ];

                    // CRITICAL: If moderation blocked or failed, ensure status is 'failed'
                    // Blocked = no retry, Warning/Failed = can retry
                    if (($moderation_blocked || $moderation_failed) && $task['status'] !== 'failed') {
                        $task['status'] = 'failed';
                        // Update in database immediately
                        if (isset($task['id'])) {
                            $this->database_service->update_task($task['id'], [
                                'status' => 'failed',
                                'result' => $task['result']
                            ]);
                        }
                    }
                }
            }
            unset($task); // Unset reference to avoid issues
        }

        // Extract moderation information for ALL tasks (not just synced ones)
        // This ensures moderation flags are always present, even if sync didn't happen
        // CRITICAL: For processing tasks with task_id, force a backend check if moderation info is missing
        foreach ($tasks as &$task) {
            $task_status = $task['status'] ?? '';
            $has_task_id = !empty($task['task_id']);
            $is_processing = $task_status === 'processing';
            $task_result = is_array($task['result']) ? $task['result'] : [];

            // CRITICAL: Always check processing tasks - they might be blocked but local data is stale
            // Also check if moderation_blocked is false but user might actually be blocked
            $has_moderation_info = isset($task['moderation']);
            $current_moderation_blocked = $has_moderation_info ? $task['moderation']['blocked'] ?? false : false;
            $result_moderation_blocked = $task_result['moderation_blocked'] ?? false;

            // Force sync if: processing status AND (no moderation info OR moderation_blocked is false)
            // This ensures we catch blocked users even if local data shows them as not blocked
            if (
                $is_processing &&
                $has_task_id &&
                $this->backend_service->is_configured() &&
                (!$has_moderation_info || (!$current_moderation_blocked && !$result_moderation_blocked))
            ) {
                // Force backend sync for this task
                $backend_response = $this->backend_service->get_task_state($task['task_id']);
                $status_code = $backend_response['status_code'] ?? null;
                $response_data = $backend_response['data'] ?? [];
                $response_message = is_array($response_data) ? $response_data['message'] ?? '' : '';
                $response_reason = is_array($response_data) ? $response_data['reason'] ?? '' : '';

                // Log full response for debugging
                // CRITICAL: Handle 403 blocked response
                $is_403 = $status_code === 403 || $status_code === '403' || intval($status_code) === 403;
                $has_blocked_indicator =
                    stripos($response_message, 'blocked') !== false ||
                    stripos($response_reason, 'moderation') !== false ||
                    stripos($backend_response['error'] ?? '', 'blocked') !== false ||
                    stripos($backend_response['raw_body'] ?? '', 'blocked') !== false;

                if ($is_403 && $has_blocked_indicator) {
                    $task['status'] = 'failed';
                    if (!is_array($task['result'])) {
                        $task['result'] = [];
                    }
                    $task['result']['moderation_blocked'] = true;
                    $task['result']['moderation_failed'] = true;
                    $task['result']['moderation_warning'] = false;
                    $task['result']['can_retry'] = false;
                    $task['result']['error'] =
                        'Your account has been suspended due to repeated or severe violations of our content safety policies.\n\nYou are no longer able to generate content. If you believe this is a mistake, please contact support immediately.\n\nTask ID: ' .
                        $task['task_id'];

                    $task['moderation'] = [
                        'blocked' => true,
                        'warning' => false,
                        'failed' => true,
                        'can_retry' => false
                    ];

                    // Update database immediately
                    if (isset($task['id'])) {
                        $this->database_service->update_task($task['id'], [
                            'status' => 'failed',
                            'result' => $task['result']
                        ]);
                    }
                } elseif ($backend_response['success']) {
                    $backend_data = $backend_response['data']['data'] ?? ($backend_response['data'] ?? null);
                    if ($backend_data && isset($backend_data['status'])) {
                        // Update status if backend says failed
                        if ($backend_data['status'] === 'failed') {
                            $task['status'] = 'failed';
                        }
                        // Update result with backend result
                        if (isset($backend_data['result'])) {
                            $task['result'] = $backend_data['result'];
                            $task_result = is_array($task['result']) ? $task['result'] : [];
                        }
                        // Extract moderation info from backend
                        $backend_moderation = $backend_data['moderation'] ?? null;
                        if ($backend_moderation) {
                            $moderation_blocked = $backend_moderation['blocked'] ?? false;
                            $moderation_warning = $backend_moderation['warning'] ?? false;
                            $moderation_failed = $backend_moderation['failed'] ?? false;
                            $can_retry = $backend_moderation['canRetry'] ?? true;
                        } else {
                            // Fallback to result flags
                            $moderation_blocked = $task_result['moderation_blocked'] ?? false;
                            $moderation_warning = $task_result['moderation_warning'] ?? false;
                            $moderation_failed = $task_result['moderation_failed'] ?? false;
                            $can_retry = !$moderation_blocked;
                        }

                        // Update task with moderation info
                        if (!is_array($task['result'])) {
                            $task['result'] = [];
                        }
                        $task['result']['moderation_blocked'] = $moderation_blocked;
                        $task['result']['moderation_warning'] = $moderation_warning;
                        $task['result']['moderation_failed'] = $moderation_failed;
                        $task['result']['can_retry'] = $can_retry;
                        $task['moderation'] = [
                            'blocked' => $moderation_blocked,
                            'warning' => $moderation_warning,
                            'failed' => $moderation_failed,
                            'can_retry' => $can_retry
                        ];

                        // Update database if status changed
                        if ($task['status'] === 'failed' && isset($task['id'])) {
                            $this->database_service->update_task($task['id'], [
                                'status' => 'failed',
                                'result' => $task['result']
                            ]);
                        }
                    }
                }
            }

            // Ensure moderation info exists for all tasks
            if (!isset($task['moderation'])) {
                // Extract from result
                $moderation_blocked = $task_result['moderation_blocked'] ?? false;
                $moderation_warning = $task_result['moderation_warning'] ?? false;
                $moderation_failed = $task_result['moderation_failed'] ?? false;
                $can_retry = !$moderation_blocked; // Blocked users cannot retry

                // Ensure moderation flags are in result
                if (!is_array($task['result'])) {
                    $task['result'] = [];
                }
                $task['result']['moderation_blocked'] = $moderation_blocked;
                $task['result']['moderation_warning'] = $moderation_warning;
                $task['result']['moderation_failed'] = $moderation_failed;
                $task['result']['can_retry'] = $can_retry;

                // Add moderation info at task level
                $task['moderation'] = [
                    'blocked' => $moderation_blocked,
                    'warning' => $moderation_warning,
                    'failed' => $moderation_failed,
                    'can_retry' => $can_retry
                ];

                // CRITICAL: If moderation blocked or failed, ensure status is 'failed'
                if (($moderation_blocked || $moderation_failed) && $task['status'] !== 'failed') {
                    $task['status'] = 'failed';
                    // Update in database immediately
                    if (isset($task['id'])) {
                        $this->database_service->update_task($task['id'], [
                            'status' => 'failed',
                            'result' => $task['result']
                        ]);
                    }
                }
            }
        }
        unset($task); // Unset reference

        // Process completed content plan generation tasks that haven't been processed yet
        // Skip tasks that were already processed during backend sync to prevent duplicates
        foreach ($tasks as &$task) {
            $task_status = $task['status'] ?? '';
            $task_type = $task['type'] ?? 'content_plan_generation';
            $task_id = $task['id'] ?? null;

            // Skip if this task was already processed during backend sync
            if ($task_id !== null && in_array($task_id, $processed_task_ids, true)) {
                continue;
            }

            // Only process completed content plan generation tasks
            // Include 'cplanteam-ai-content-planner' for backward compatibility (legacy wrong type from prefix rename)
            if ($task_status === 'completed' && $task_type === 'content_plan_generation') {
                $task_result = $task['result'] ?? null;

                // Decode result if it's a JSON string
                if (is_string($task_result) && !empty($task_result)) {
                    $decoded = json_decode($task_result, true);
                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                        $task_result = $decoded;
                    }
                }

                // Check if items were already created
                $has_item_ids =
                    isset($task_result['output']['item_ids']) &&
                    is_array($task_result['output']['item_ids']) &&
                    !empty($task_result['output']['item_ids']);

                $item_ids_count = 0;
                if ($has_item_ids) {
                    $item_ids_count = count($task_result['output']['item_ids']);
                }
                // Only process if items haven't been created yet
                if (!$has_item_ids) {
                    $this->process_completed_content_plan_task($task);

                    // Refresh task data after processing
                    $updated_task = $this->database_service->get_task($task['id']);
                    if ($updated_task) {
                        $task = $updated_task;
                        // Decode result again after update
                        if (is_string($task['result'] ?? null) && !empty($task['result'])) {
                            $decoded = json_decode($task['result'], true);
                            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                                $task['result'] = $decoded;
                            }
                        }

                        // Log the updated state
                        $updated_has_item_ids =
                            isset($task['result']['output']['item_ids']) &&
                            is_array($task['result']['output']['item_ids']) &&
                            !empty($task['result']['output']['item_ids']);
                        $updated_item_ids_count = $updated_has_item_ids
                            ? count($task['result']['output']['item_ids'])
                            : 0;
                    }
                } else {
                }
            }

            // Process completed post generation tasks that haven't been processed yet
            if ($task_status === 'completed' && $task_type === 'post_generation') {
                $task_result = $task['result'] ?? null;

                // Decode result if it's a JSON string
                if (is_string($task_result) && !empty($task_result)) {
                    $decoded = json_decode($task_result, true);
                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                        $task_result = $decoded;
                    }
                }

                // Check if post was already created
                $has_post_id = isset($task_result['output']['post_id']) && !empty($task_result['output']['post_id']);
                // Only process if post hasn't been created yet
                if (!$has_post_id) {
                    // Extract post content from result
                    $result_output = $task_result['output'] ?? null;
                    if (empty($result_output) || !isset($result_output['postContent'])) {
                    } else {
                        $post_content = $result_output['postContent'];
                        if (!is_array($post_content) || empty($post_content)) {
                        } else {
                            // Get the content plan item ID from task result
                            // Check output first (webhook stores it there), then fall back to input
                            $item_id = null;
                            if (isset($task_result['output']['item_id'])) {
                                $item_id = $task_result['output']['item_id'];
                            } elseif (isset($task_result['input']['item_id'])) {
                                $item_id = $task_result['input']['item_id'];
                            }

                            if (empty($item_id)) {
                            } else {
                                // Fetch the content plan item
                                $matching_item = $this->database_service->get_content_plan_item($item_id);

                                if (!$matching_item) {
                                } else {
                                    // Save the post using CPEPAI_Posts_Service
                                    try {
                                        $saved_post = $this->posts_service->save_post_from_ai_response(
                                            $matching_item,
                                            $post_content
                                        );

                                        if (!$saved_post || is_wp_error($saved_post)) {
                                            $error_message = is_wp_error($saved_post)
                                                ? $saved_post->get_error_message()
                                                : 'Unknown error saving post';
                                        } else {
                                            // Update task result to include post information
                                            $updated_result = $task_result;
                                            if (!is_array($updated_result)) {
                                                $updated_result = [];
                                            }
                                            if (!isset($updated_result['output'])) {
                                                $updated_result['output'] = [];
                                            }
                                            $updated_result['output']['post_id'] = $saved_post['post_id'];
                                            $updated_result['output']['post_url'] = $saved_post['post_url'];
                                            $updated_result['output']['post_edit_url'] = $saved_post['post_edit_url'];
                                            // Store item_id in output for frontend filtering
                                            $updated_result['output']['item_id'] = $item_id;

                                            // Update task with post information and ensure status is completed
                                            $this->database_service->update_task($task['id'], [
                                                'status' => 'completed',
                                                'result' => $updated_result
                                            ]);

                                            // Update content plan item status to 'draft_ready_ai' and store post ID
                                            $item_update_data = [
                                                'status' => 'draft_ready_ai',
                                                'post' => (string) $saved_post['post_id'] // Store post ID
                                            ];
                                            $item_update_result = $this->database_service->update_content_plan_item(
                                                $item_id,
                                                $item_update_data
                                            );
                                            if (is_wp_error($item_update_result)) {
                                            } else {
                                            }
                                            // Refresh task data after processing
                                            $updated_task = $this->database_service->get_task($task['id']);
                                            if ($updated_task) {
                                                $task = $updated_task;
                                                // Decode result after update
                                                if (is_string($task['result'] ?? null) && !empty($task['result'])) {
                                                    $decoded = json_decode($task['result'], true);
                                                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                                                        $task['result'] = $decoded;
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Exception $e) {
                                    }
                                }
                            }
                        }
                    }
                } else {
                }
            }
        }
        unset($task); // Unset reference

        // Log what we're returning for debugging
        foreach ($tasks as $task) {
            if (isset($task['id']) && $task['id'] == 98) {
            }
        }

        // Convert watched field from int to boolean for frontend
        // Also add formatted scheduled dates for post generation tasks
        $tasks = array_map(function ($task) {
            if (isset($task['watched'])) {
                $task['watched'] = (bool) $task['watched'];
            }

            // For completed post generation tasks, add formatted scheduled date from the post
            if ($task['status'] === 'completed' && $task['type'] === 'post_generation' && is_array($task['result'])) {
                $post_id = null;

                // Check if webhook stored post_id in output
                if (isset($task['result']['output']['post_id'])) {
                    $post_id = $task['result']['output']['post_id'];
                }

                if ($post_id) {
                    $post = get_post($post_id);
                    if ($post && !empty($post->post_date)) {
                        // Get post_date from WordPress post and format it directly using mysql2date
                        // mysql2date formats MySQL datetime strings respecting WordPress timezone
                        // Format as: "January 15, 2026 at 10:00 AM"
                        $task['formatted_scheduled_date'] = mysql2date('F j, Y \a\t g:i A', $post->post_date);
                    }
                }
            }

            return $task;
        }, $tasks);

        // Check if there are any tasks with processing or queued status
        // This flag indicates whether the frontend should continue polling
        $has_processing_tasks = false;

        // First check the returned tasks
        foreach ($tasks as $task) {
            if (isset($task['status']) && ($task['status'] === 'processing' || $task['status'] === 'queued')) {
                $has_processing_tasks = true;
                break;
            }
        }

        // FINAL CHECK: Ensure all tasks have moderation information and correct status
        // This catches cases where sync might have missed something
        foreach ($tasks as &$task) {
            $task_result = is_array($task['result']) ? $task['result'] : [];
            $moderation_blocked = $task_result['moderation_blocked'] ?? false;
            $moderation_warning = $task_result['moderation_warning'] ?? false;
            $moderation_failed = $task_result['moderation_failed'] ?? false;

            // Ensure moderation info is always present at task level
            if (!isset($task['moderation'])) {
                $can_retry = !$moderation_blocked; // Blocked users cannot retry
                $task['moderation'] = [
                    'blocked' => $moderation_blocked,
                    'warning' => $moderation_warning,
                    'failed' => $moderation_failed,
                    'can_retry' => $can_retry
                ];
                // Also ensure flags are in result
                if (!is_array($task['result'])) {
                    $task['result'] = [];
                }
                $task['result']['moderation_blocked'] = $moderation_blocked;
                $task['result']['moderation_warning'] = $moderation_warning;
                $task['result']['moderation_failed'] = $moderation_failed;
                $task['result']['can_retry'] = $can_retry;
            }

            // If task has moderation flags but status isn't failed, update it
            if (($moderation_blocked || $moderation_failed) && $task['status'] !== 'failed') {
                $task['status'] = 'failed';
                // Update in database
                if (isset($task['id'])) {
                    $this->database_service->update_task($task['id'], [
                        'status' => 'failed',
                        'result' => $task['result']
                    ]);
                }
            }
        }
        unset($task); // Unset reference

        // If we didn't find any in the returned tasks and we're not filtering by status,
        // or if we're filtering by a different status, check the database
        if (!$has_processing_tasks) {
            // Only check database if we're not already filtering by processing/queued status
            if (empty($args['status']) || !in_array($args['status'], ['processing', 'queued'])) {
                // Check if there are any processing or queued tasks in the database
                // Use limit 1 for efficiency - we only need to know if any exist
                $all_processing_tasks = $this->database_service->get_tasks([
                    'status' => 'processing',
                    'limit' => 1
                ]);
                $all_queued_tasks = $this->database_service->get_tasks([
                    'status' => 'queued',
                    'limit' => 1
                ]);
                $has_processing_tasks = !empty($all_processing_tasks) || !empty($all_queued_tasks);
            }
        }

        return new \WP_REST_Response(
            [
                'success' => true,
                'data' => $tasks,
                'should_poll' => $has_processing_tasks // Flag indicating if polling should continue
            ],
            200
        );
    }

    /**
     * Get logs.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function get_logs(\WP_REST_Request $request): \WP_REST_Response
    {
        $level = $request->get_param('level');
        $limit = $request->get_param('limit') ?: 100;
        $offset = $request->get_param('offset') ?: 0;

        $args = [
            'limit' => intval($limit),
            'offset' => intval($offset),
            'orderby' => 'created_at',
            'order' => 'DESC'
        ];

        if (!empty($level)) {
            $args['level'] = sanitize_text_field($level);
        }

        $logs = $this->database_service->get_logs($args);

        return new \WP_REST_Response(
            [
                'success' => true,
                'data' => $logs
            ],
            200
        );
    }

    /**
     * Manually process a completed task (for tasks that completed while webhook was disabled).
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function process_completed_task(\WP_REST_Request $request): \WP_REST_Response
    {
        $task_id = $request->get_param('id');

        if (empty($task_id)) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'error' => 'Task ID is required'
                ],
                400
            );
        }

        // Get task from database
        $task = $this->database_service->get_task($task_id);

        if (!$task) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'error' => 'Task not found'
                ],
                404
            );
        }

        // Check if task is completed
        if ($task['status'] !== 'completed') {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'error' => 'Task is not completed. Only completed tasks can be processed.'
                ],
                400
            );
        }

        // Check if items were already created
        $task_result = $task['result'] ?? null;
        if (is_string($task_result) && !empty($task_result)) {
            $decoded = json_decode($task_result, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $task_result = $decoded;
            }
        }

        // For content plan generation: check if item_ids already exist
        if ($task['type'] === 'content_plan_generation') {
            if (isset($task_result['output']['item_ids']) && !empty($task_result['output']['item_ids'])) {
                return new \WP_REST_Response(
                    [
                        'success' => true,
                        'message' => 'Task already processed. Items were already created.',
                        'data' => [
                            'item_ids' => $task_result['output']['item_ids']
                        ]
                    ],
                    200
                );
            }
        }

        // For post generation: check if post_id already exists
        if ($task['type'] === 'post_generation') {
            if (isset($task_result['output']['post_id']) && !empty($task_result['output']['post_id'])) {
                return new \WP_REST_Response(
                    [
                        'success' => true,
                        'message' => 'Task already processed. Post was already created.',
                        'data' => [
                            'post_id' => $task_result['output']['post_id']
                        ]
                    ],
                    200
                );
            }
        }

        // Simulate webhook request to process the task
        // Create a mock request with task data in JSON format (as webhook expects)
        $mock_request = new \WP_REST_Request('POST', '/cpepai/v1/webhook');
        $webhook_body = [
            'taskId' => $task['task_id'],
            'status' => 'completed',
            'result' => $task_result
        ];
        // Set JSON body (webhook uses get_json_params())
        $mock_request->set_body(json_encode($webhook_body));
        $mock_request->set_header('Content-Type', 'application/json');

        // Call the webhook handler
        return $this->handle_webhook($mock_request);
    }

    /**
     * Mark task as watched.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function mark_task_as_watched(\WP_REST_Request $request): \WP_REST_Response
    {
        $task_id = $request->get_param('id');

        if (empty($task_id)) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'message' => __('Task ID is required', 'cplanteam-ai-content-planner')
                ],
                400
            );
        }

        $result = $this->database_service->update_task(intval($task_id), ['watched' => 1]);

        if (is_wp_error($result)) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'message' => $result->get_error_message()
                ],
                500
            );
        }

        return new \WP_REST_Response(
            [
                'success' => true,
                'message' => __('Task marked as watched', 'cplanteam-ai-content-planner')
            ],
            200
        );
    }

    /**
     * Recalculate scheduled dates for future content plan items.
     *
     * @param \WP_REST_Request $request The request object.
     * @return \WP_REST_Response
     */
    public function recalculate_scheduled_dates(\WP_REST_Request $request): \WP_REST_Response
    {
        try {
            // Get current publish interval from settings
            $publish_interval_days = $this->options_service->get_setting('publish_interval_days', 7);

            // Recalculate scheduled dates for future items
            $result = $this->database_service->recalculate_future_scheduled_dates($publish_interval_days);

            if (is_wp_error($result)) {
                return new \WP_REST_Response(
                    [
                        'success' => false,
                        'error' => $result->get_error_message()
                    ],
                    500
                );
            }

            $updated_count = $result['updated_count'];
            return new \WP_REST_Response(
                [
                    'success' => true,
                    'message' =>
                        __('Recalculated scheduled dates for', 'cplanteam-ai-content-planner') .
                        ' ' .
                        number_format_i18n($updated_count) .
                        ' ' .
                        _n('future item', 'future items', $updated_count, 'cplanteam-ai-content-planner'),
                    'updated_count' => $result['updated_count']
                ],
                200
            );
        } catch (Exception $e) {
            return new \WP_REST_Response(
                [
                    'success' => false,
                    'error' =>
                        __('Error recalculating scheduled dates: ', 'cplanteam-ai-content-planner') . $e->getMessage()
                ],
                500
            );
        }
    }
}
