<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /includes/class-post-actions.php
// Description: AJAX handlers for post approval, rejection, content editing, image upload, Unsplash, manual compose, bio generation, and panel image generation - NUCLEAR+++
// Last Edited: 2026-04-10
// ==================================================

namespace AutoMarketerAI;

// No direct access
defined('ABSPATH') || exit;

final class PostActions
{
    // Allowed MIME types for image uploads
    private const ALLOWED_MIME_TYPES = [
        'image/jpeg',
        'image/png',
        'image/gif',
        'image/webp',
    ];

    // Max upload size in bytes (8 MB)
    private const MAX_UPLOAD_BYTES = 8388608;

    public static function init(): void
    {
        add_action('wp_ajax_amai_fb_post_action',            [self::class, 'handlePostAction']);
        add_action('wp_ajax_amai_fb_post_save',              [self::class, 'handlePostSave']);
        add_action('wp_ajax_amai_fb_post_regenerate',        [self::class, 'handlePostRegenerate']);
        add_action('wp_ajax_amai_fb_post_now',               [self::class, 'handlePostNow']);
        add_action('wp_ajax_amai_fb_image_upload',           [self::class, 'handleImageUpload']);
        add_action('wp_ajax_amai_fb_unsplash_update',        [self::class, 'handleUnsplashUpdate']);
        add_action('wp_ajax_amai_autopilot_nudge_dismiss',   [self::class, 'handleAutopilotNudgeDismiss']);
        add_action('wp_ajax_amai_generate_fb_bio',           [self::class, 'handleGenerateFbBio']);
        add_action('wp_ajax_amai_save_fb_bio',              [self::class, 'handleSaveFbBio']);
        add_action('wp_ajax_amai_manual_unsplash_search',    [self::class, 'handleManualUnsplashSearch']);
        add_action('wp_ajax_amai_manual_post_now',           [self::class, 'handleManualPostNow']);
        add_action('wp_ajax_amai_manual_image_upload',       [self::class, 'handleManualImageUpload']);
        add_action('wp_ajax_amai_manual_unsplash_track',     [self::class, 'handleManualUnsplashTrack']);
        add_action('wp_ajax_amai_generate_post_seed',        [self::class, 'handleGeneratePostSeed']);
        add_action('wp_ajax_amai_panel_unsplash_search',     [self::class, 'handlePanelUnsplashSearch']);
        add_action('wp_ajax_amai_panel_generate_image',      [self::class, 'handlePanelGenerateImage']);
        add_action('wp_ajax_amai_compose_generate_image', [self::class, 'handleComposeGenerateImage']);
        add_action('wp_ajax_amai_panel_suggest_image_prompt', [self::class, 'handlePanelSuggestImagePrompt']);
        add_action('wp_ajax_amai_regenerate_week',            [self::class, 'handleRegenerateWeek']);
    }

    // --------------------------------------------------
    // Approve or reject a post
    // POST: post_id, action (approve|reject), _wpnonce
    // --------------------------------------------------
    public static function handlePostAction(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_post_action')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id = (int) ($_POST['post_id'] ?? 0);
        $action  = sanitize_key((string) ($_POST['post_action'] ?? ''));
        $reason  = sanitize_text_field((string) ($_POST['rejection_reason'] ?? ''));

        if ($post_id <= 0 || !in_array($action, ['approve', 'reject', 'draft'], true)) {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        global $wpdb;

        $post = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT id, status FROM `wp_amai_scheduled_posts` WHERE id = %d LIMIT 1',
                $post_id
            ),
            ARRAY_A
        );

        if ($post === null) {
            wp_send_json_error(['message' => 'Post not found.'], 404);
            return;
        }

        $new_status  = match($action) {
            'approve' => 'approved',
            'reject'  => 'rejected',
            default   => 'draft',
        };
        $reviewed_at = gmdate('Y-m-d H:i:s');

        if ($action === 'reject' && $reason !== '') {
            $updated = $wpdb->query(
                $wpdb->prepare(
                    'UPDATE `wp_amai_scheduled_posts` SET status = %s, reviewed_at = %s, failure_reason = %s WHERE id = %d',
                    $new_status,
                    $reviewed_at,
                    $reason,
                    $post_id
                )
            );
        } else {
            $updated = $wpdb->query(
                $wpdb->prepare(
                    'UPDATE `wp_amai_scheduled_posts` SET status = %s, reviewed_at = %s WHERE id = %d',
                    $new_status,
                    $reviewed_at,
                    $post_id
                )
            );
        }

        if ($updated === false) {
            wp_send_json_error(['message' => 'Failed to update post. Please try again.'], 500);
            return;
        }

        // On approval, generate and inject outbound links into post content
        if ($action === 'approve') {
            $approved_post = $wpdb->get_row(
                $wpdb->prepare(
                    'SELECT post_content, post_type FROM `wp_amai_scheduled_posts` WHERE id = %d LIMIT 1',
                    $post_id
                ),
                ARRAY_A
            );
            if ($approved_post !== null && trim((string) ($approved_post['post_content'] ?? '')) !== '') {
                $focus = sanitize_text_field((string) ($approved_post['post_type'] ?? ''));
                OutboundLinks::generateForPost(
                    (string) $approved_post['post_content'],
                    $post_id,
                    $focus
                );
            }
        }

        wp_send_json_success([
            'post_id'    => $post_id,
            'new_status' => $new_status,
        ]);
    }

    // --------------------------------------------------
    // Save edited post content + optional media fields
    // POST: post_id, post_content, post_image_url (opt),
    //       feeling (opt), _wpnonce
    // --------------------------------------------------
    public static function handlePostSave(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_post_save')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id                 = (int) ($_POST['post_id'] ?? 0);
        $post_content            = wp_unslash(sanitize_textarea_field((string) ($_POST['post_content'] ?? '')));
        $post_image_url          = esc_url_raw(wp_unslash((string) ($_POST['post_image_url'] ?? '')));
        $feeling                 = sanitize_text_field(wp_unslash((string) ($_POST['feeling'] ?? '')));
        $unsplash_attachment_id  = (int) ($_POST['unsplash_attachment_id'] ?? 0);

        if ($post_id <= 0 || $post_content === '') {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        // Validate feeling against known values if provided
        if ($feeling !== '' && !in_array($feeling, self::validFeelings(), true)) {
            wp_send_json_error(['message' => 'Invalid feeling value.'], 400);
            return;
        }

        global $wpdb;

        $post = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT id FROM `wp_amai_scheduled_posts` WHERE id = %d LIMIT 1',
                $post_id
            ),
            ARRAY_A
        );

        if ($post === null) {
            wp_send_json_error(['message' => 'Post not found.'], 404);
            return;
        }

        $location_place_id = sanitize_text_field((string) ($_POST['location_place_id'] ?? ''));
        $location_name     = sanitize_text_field((string) ($_POST['location_name'] ?? ''));
        $updated = $wpdb->query(
            $wpdb->prepare(
                'UPDATE `wp_amai_scheduled_posts`
                 SET post_content           = %s,
                     status                 = %s,
                     post_image_url         = NULLIF(%s, \'\'),
                     location_place_id      = NULLIF(%s, \'\'),
                     location_name          = NULLIF(%s, \'\'),
                     feeling                = NULLIF(%s, \'\'),
                     unsplash_attachment_id = NULLIF(%d, 0)
                 WHERE id = %d',
                $post_content,
                'draft',
                $post_image_url,
                $location_place_id,
                $location_name,
                $feeling,
                $unsplash_attachment_id,
                $post_id
            )
        );

        if ($updated === false) {
            wp_send_json_error(['message' => 'Failed to save post. Please try again.'], 500);
            return;
        }

        wp_send_json_success([
            'post_id'                => $post_id,
            'post_content'           => $post_content,
            'post_image_url'         => $post_image_url,
            'feeling'                => $feeling,
            'unsplash_attachment_id' => $unsplash_attachment_id,
        ]);
    }

    // --------------------------------------------------
    // Upload an image to WP media library and return
    // the local URL to be stored on the post.
    // POST: post_id, _wpnonce — file in $_FILES['image']
    // --------------------------------------------------
    public static function handleImageUpload(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_image_upload')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        if (empty($_FILES['image']) || (int) ($_FILES['image']['error'] ?? 1) !== UPLOAD_ERR_OK) {
            wp_send_json_error(['message' => 'No file received or upload error.'], 400);
            return;
        }

        $file = $_FILES['image'];

        if ((int) $file['size'] > self::MAX_UPLOAD_BYTES) {
            wp_send_json_error(['message' => 'Image must be under 8 MB.'], 400);
            return;
        }

        $finfo    = new \finfo(FILEINFO_MIME_TYPE);
        $detected = $finfo->file((string) $file['tmp_name']);

        if (!in_array($detected, self::ALLOWED_MIME_TYPES, true)) {
            wp_send_json_error(['message' => 'Only JPEG, PNG, GIF, and WebP images are allowed.'], 400);
            return;
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';

        $attachment_id = media_handle_upload('image', 0);

        if (is_wp_error($attachment_id)) {
            wp_send_json_error(['message' => 'Upload failed: ' . $attachment_id->get_error_message()], 500);
            return;
        }

        $url = wp_get_attachment_url($attachment_id);

        if ($url === false) {
            wp_send_json_error(['message' => 'Could not get URL for uploaded image.'], 500);
            return;
        }

        wp_send_json_success([
            'attachment_id' => $attachment_id,
            'url'           => $url,
        ]);
    }

    // --------------------------------------------------
    // Search Facebook Places via Graph API.
    // Proxied server-side so the page token stays off the client.
    // GET (via POST): q (search query), _wpnonce
    // --------------------------------------------------
    public static function handleLocationSearch(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_location_search')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $query = sanitize_text_field((string) ($_POST['q'] ?? ''));
        if (strlen($query) < 2) {
            wp_send_json_error(['message' => 'Query too short.'], 400);
            return;
        }

        $token = FacebookAuth::getPageToken();
        if ($token === null) {
            wp_send_json_error(['message' => 'No active Facebook connection.'], 400);
            return;
        }

        $url = add_query_arg([
            'type'         => 'place',
            'q'            => rawurlencode($query),
            'fields'       => 'id,name,location',
            'limit'        => '8',
            'access_token' => $token,
        ], 'https://graph.facebook.com/v19.0/search');

        $response = wp_remote_get($url, ['timeout' => 10]);

        if (is_wp_error($response)) {
            wp_send_json_error(['message' => 'Location search failed. Try again.'], 502);
            return;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $raw  = wp_remote_retrieve_body($response);
        $json = json_decode($raw, true);

        if ($code !== 200 || !is_array($json) || !isset($json['data'])) {
            wp_send_json_error(['message' => 'No results found.'], 502);
            return;
        }

        $places = [];
        foreach ($json['data'] as $place) {
            if (empty($place['id']) || empty($place['name'])) {
                continue;
            }
            $loc    = $place['location'] ?? [];
            $parts  = array_filter([
                $loc['city']    ?? '',
                $loc['state']   ?? '',
                $loc['country'] ?? '',
            ]);
            $places[] = [
                'id'      => (string) $place['id'],
                'name'    => (string) $place['name'],
                'address' => implode(', ', $parts),
            ];
        }

        wp_send_json_success(['places' => $places]);
    }

    // --------------------------------------------------
    // Regenerate a single post via Core API
    // POST: post_id, post_type (optional), _wpnonce
    // --------------------------------------------------
    public static function handlePostRegenerate(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_post_regenerate')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id   = (int) ($_POST['post_id'] ?? 0);
        $post_type = sanitize_key((string) ($_POST['post_type'] ?? ''));

        $valid_types = ['educational', 'product', 'feel_good', 'behind_scenes', 'engagement'];
        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }
        if ($post_type !== '' && !in_array($post_type, $valid_types, true)) {
            wp_send_json_error(['message' => 'Invalid post type.'], 400);
            return;
        }

        global $wpdb;

        $post = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT id, post_type, scheduled_date, facebook_page_id, generation_batch
                 FROM `wp_amai_scheduled_posts` WHERE id = %d LIMIT 1',
                $post_id
            ),
            ARRAY_A
        );

        if ($post === null) {
            wp_send_json_error(['message' => 'Post not found.'], 404);
            return;
        }

        $target_type = $post_type !== '' ? $post_type : (string) $post['post_type'];

        $fb_connection = \AutoMarketerAI\FacebookAuth::getConnection();
        if ($fb_connection === null) {
            wp_send_json_error(['message' => 'No active Facebook connection.'], 400);
            return;
        }

        $business_profile = new \AutoMarketerAI\BusinessProfile();
        $profile_data     = $business_profile->getOrCreate();

        $profile_data['page_name']        = (string) ($fb_connection['page_name'] ?? '');
        $profile_data['facebook_page_id'] = (string) ($fb_connection['page_id']   ?? '');

        $core_url  = rtrim((string) AMAI_CORE_URL, '/') . '/api/content/regenerate-post';
        $tenant    = amai_tenant_id();
        $timestamp = (string) time();
        $nonce_hex = bin2hex(random_bytes(16));
        $body      = wp_json_encode([
            'post_id'          => $post_id,
            'post_type'        => $target_type,
            'scheduled_date'   => $post['scheduled_date'],
            'generation_batch' => $post['generation_batch'],
            'business_profile' => $profile_data,
        ]);

        $secret    = (string) get_option('amai_tenant_hmac_secret', '');
        $key_id    = 'WP_PROXY';
        $body_b64  = base64_encode(hash('sha256', (string) $body, true));
        $canonical = implode('|', ['POST', '/api/content/regenerate-post', $timestamp, $nonce_hex, $tenant, $key_id, $body_b64]);
        $signature = rtrim(base64_encode(hash_hmac('sha256', $canonical, hex2bin($secret), true)), "\n");

        $response = wp_remote_post($core_url, [
            'timeout' => 45,
            'headers' => [
                'Content-Type'       => 'application/json',
                'X-Amai-Tenant'      => $tenant,
                'X-Amai-Key-Id'      => $key_id,
                'X-Amai-Timestamp'   => $timestamp,
                'X-Amai-Nonce'       => $nonce_hex,
                'X-Amai-Signature'   => $signature,
                'X-Amai-Body-SHA256' => $body_b64,
            ],
            'body' => $body,
        ]);

        if (is_wp_error($response)) {
            wp_send_json_error(['message' => 'Could not reach content engine. Please try again.'], 502);
            return;
        }

        $code          = (int) wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);
        $decoded       = json_decode($response_body, true);

        if ($code !== 200 || empty($decoded['post_content'])) {
            wp_send_json_error(['message' => 'Content engine returned an error. Please try again.'], 502);
            return;
        }

        $new_content = wp_unslash(sanitize_textarea_field((string) $decoded['post_content']));

        $wpdb->query(
            $wpdb->prepare(
                'UPDATE `wp_amai_scheduled_posts`
                 SET post_content = %s, post_type = %s, status = %s, failure_reason = NULL
                 WHERE id = %d',
                $new_content,
                $target_type,
                'draft',
                $post_id
            )
        );

        wp_send_json_success([
            'post_id'      => $post_id,
            'post_type'    => $target_type,
            'post_content' => $new_content,
        ]);
    }

    // --------------------------------------------------
    // Manually post a failed post to Facebook right now.
    // Only permitted for failed posts in the current week.
    // POST: post_id, _wpnonce
    // --------------------------------------------------
    public static function handlePostNow(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_post_now')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        $result = PostingEngine::postNow($post_id);

        if (!$result['ok']) {
            wp_send_json_error(['message' => $result['message'] ?? 'Something went wrong. Please try again.'], 422);
            return;
        }

        wp_send_json_success(['post_id' => $post_id]);
    }

    // --------------------------------------------------
    // Update unsplash_attachment_id only — never touches status or content.
    // POST: post_id, unsplash_attachment_id, _wpnonce (amai_post_save nonce)
    // --------------------------------------------------
    public static function handleUnsplashUpdate(): void
    {
        check_ajax_referer('amai_post_save', '_wpnonce');

        $post_id                = (int) ($_POST['post_id'] ?? 0);
        $unsplash_attachment_id = (int) ($_POST['unsplash_attachment_id'] ?? 0);

        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        global $wpdb;

        $post = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT id FROM `' . $wpdb->prefix . 'amai_scheduled_posts` WHERE id = %d LIMIT 1',
                $post_id
            ),
            ARRAY_A
        );

        if ($post === null) {
            wp_send_json_error(['message' => 'Post not found.'], 404);
            return;
        }

        $updated = $wpdb->query(
            $wpdb->prepare(
                'UPDATE `' . $wpdb->prefix . 'amai_scheduled_posts`
                 SET unsplash_attachment_id = NULLIF(%d, 0)
                 WHERE id = %d',
                $unsplash_attachment_id,
                $post_id
            )
        );

        if ($updated === false) {
            wp_send_json_error(['message' => 'Failed to update photo.'], 500);
            return;
        }

        wp_send_json_success(['unsplash_attachment_id' => $unsplash_attachment_id]);
    }

    // --------------------------------------------------
    // Panel edit mode — search Unsplash by keywords extracted
    // from the current post content. Returns up to 6 results.
    // POST: post_id, _wpnonce (amai_post_save nonce)
    // --------------------------------------------------
    public static function handlePanelUnsplashSearch(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_post_save')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id  = (int) ($_POST['post_id'] ?? 0);
        $keywords = sanitize_text_field(wp_unslash((string) ($_POST['keywords'] ?? '')));

        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        // If caller supplied keywords use them; otherwise pull from stored post content
        if ($keywords === '') {
            global $wpdb;
            $content = (string) $wpdb->get_var(
                $wpdb->prepare(
                    'SELECT post_content FROM `' . $wpdb->prefix . 'amai_scheduled_posts` WHERE id = %d LIMIT 1',
                    $post_id
                )
            );
            $keywords = Unsplash::extractKeywordsAi($content);
        }

        if ($keywords === '') {
            wp_send_json_error(['message' => 'Could not extract keywords from post content.'], 400);
            return;
        }

        $results = Unsplash::searchMultiple($keywords, 6);

        if (empty($results)) {
            wp_send_json_error(['message' => 'No images found. Try different keywords.'], 404);
            return;
        }

        wp_send_json_success([
            'keywords' => $keywords,
            'photos'   => $results,
        ]);
    }

    // --------------------------------------------------
    // Panel edit mode — generate an image via the active
    // image provider (e.g. DALL-E 3), download to WP media
    // library, return URL for preview.
    // POST: post_id, prompt, _wpnonce (amai_post_save nonce)
    // --------------------------------------------------
    public static function handlePanelGenerateImage(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_post_save')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id = (int) ($_POST['post_id'] ?? 0);
        $prompt  = sanitize_textarea_field(wp_unslash((string) ($_POST['prompt'] ?? '')));

        if ($post_id <= 0 || trim($prompt) === '') {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        // resolveForPost() handles provider resolution, key lookup, and generation internally.
        // Returns a local WP attachment URL on success, null on failure.
        $local_url = ImageProvider::resolveForPost($prompt);

        if ($local_url === null) {
            wp_send_json_error(['message' => 'Image generation failed. No active image provider, missing API key, or generation error.'], 502);
            return;
        }

        wp_send_json_success(['url' => $local_url]);
    }

    // --------------------------------------------------
    // Manual compose -- generate image with active AI image provider.
    // POST: prompt, _wpnonce (amai_manual_post)
    // Returns: { url: string } on success.
    public static function handleComposeGenerateImage(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }
        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_manual_post')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }
        $prompt = sanitize_textarea_field(wp_unslash((string) ($_POST['prompt'] ?? '')));
        if (trim($prompt) === '') {
            wp_send_json_error(['message' => 'Prompt is required.'], 400);
            return;
        }
        $local_url = ImageProvider::resolveForPost($prompt);
        if ($local_url === null) {
            wp_send_json_error(['message' => 'Image generation failed. Check that an image provider is active and configured.'], 502);
            return;
        }
        wp_send_json_success(['url' => $local_url]);
    }

    // Manual compose — search Unsplash and return multiple
    // results for user selection. No download tracking here —
    // tracking fires via amai_manual_unsplash_track on accept.
    // POST: post_content, _wpnonce (amai_manual_post)
    // --------------------------------------------------
    public static function handleManualUnsplashSearch(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_manual_post')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $direct_keyword = sanitize_text_field((string) ($_POST['keyword'] ?? ''));
        $post_content = wp_unslash(sanitize_textarea_field((string) ($_POST['post_content'] ?? '')));
        if ($direct_keyword === '' && trim($post_content) === '') {
            wp_send_json_error(['message' => 'Post content is required.'], 400);
            return;
        }

        $keywords = $direct_keyword !== '' ? $direct_keyword : Unsplash::extractKeywordsAi($post_content);
        if ($keywords === '') {
            wp_send_json_error(['message' => 'Could not extract keywords from post content.'], 400);
            return;
        }

        $results = Unsplash::searchMultiple($keywords, 6);

        if (empty($results)) {
            wp_send_json_error(['message' => 'No images found. Try different keywords.'], 404);
            return;
        }

        wp_send_json_success([
            'keywords' => $keywords,
            'photos'   => $results,
        ]);
    }

    // --------------------------------------------------
    // Manual compose — trigger Unsplash download tracking
    // when user selects a photo. Required by Unsplash guidelines.
    // POST: download_location, _wpnonce (amai_manual_post)
    // --------------------------------------------------
    public static function handleManualUnsplashTrack(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_manual_post')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $download_location = esc_url_raw(wp_unslash((string) ($_POST['download_location'] ?? '')));
        if ($download_location === '') {
            wp_send_json_error(['message' => 'Missing download location.'], 400);
            return;
        }

        Unsplash::trackDownload($download_location);
        wp_send_json_success();
    }

    // --------------------------------------------------
    // Manual compose — upload image to media library.
    // No post_id required — this is a pre-post upload.
    // POST: _wpnonce (amai_manual_post) — file in $_FILES['image']
    // --------------------------------------------------
    public static function handleManualImageUpload(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_manual_post')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        if (empty($_FILES['image']) || (int) ($_FILES['image']['error'] ?? 1) !== UPLOAD_ERR_OK) {
            wp_send_json_error(['message' => 'No file received or upload error.'], 400);
            return;
        }

        $file = $_FILES['image'];

        if ((int) $file['size'] > self::MAX_UPLOAD_BYTES) {
            wp_send_json_error(['message' => 'Image must be under 8 MB.'], 400);
            return;
        }

        $finfo    = new \finfo(FILEINFO_MIME_TYPE);
        $detected = $finfo->file((string) $file['tmp_name']);

        if (!in_array($detected, self::ALLOWED_MIME_TYPES, true)) {
            wp_send_json_error(['message' => 'Only JPEG, PNG, GIF, and WebP images are allowed.'], 400);
            return;
        }

        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';
        require_once ABSPATH . 'wp-admin/includes/media.php';

        $attachment_id = media_handle_upload('image', 0);

        if (is_wp_error($attachment_id)) {
            wp_send_json_error(['message' => 'Upload failed: ' . $attachment_id->get_error_message()], 500);
            return;
        }

        $url = wp_get_attachment_url($attachment_id);
        if ($url === false) {
            wp_send_json_error(['message' => 'Could not get URL for uploaded image.'], 500);
            return;
        }

        wp_send_json_success([
            'attachment_id' => $attachment_id,
            'url'           => $url,
        ]);
    }

    // --------------------------------------------------
    // Manual compose — post now.
    // Inserts a history row with batch "manual", publishes immediately.
    // POST: post_content, feeling (opt), attachment_id (opt),
    //       image_url (opt), _wpnonce (amai_manual_post)
    // --------------------------------------------------
    public static function handleManualPostNow(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_manual_post')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_content  = wp_unslash(sanitize_textarea_field((string) ($_POST['post_content'] ?? '')));
        $feeling       = sanitize_text_field(wp_unslash((string) ($_POST['feeling'] ?? '')));
        $attachment_id = (int) ($_POST['attachment_id'] ?? 0);
        $image_url     = esc_url_raw(wp_unslash((string) ($_POST['image_url'] ?? '')));
        $post_type     = sanitize_key((string) ($_POST['post_type'] ?? 'other'));
        $channel       = sanitize_key((string) ($_POST['channel'] ?? 'facebook'));
        if (!in_array($channel, ['facebook', 'instagram', 'x', 'tiktok'], true)) { $channel = 'facebook'; }

        $valid_post_types = ['educational', 'product', 'feel_good', 'behind_scenes', 'engagement', 'other'];
        if (!in_array($post_type, $valid_post_types, true)) {
            $post_type = 'other';
        }

        if (trim($post_content) === '') {
            wp_send_json_error(['message' => 'Post content is required.'], 400);
            return;
        }

        if ($feeling !== '' && !in_array($feeling, self::validFeelings(), true)) {
            wp_send_json_error(['message' => 'Invalid feeling value.'], 400);
            return;
        }

        $result = PostingEngine::postManual($post_content, $feeling, $attachment_id, $image_url, $post_type, $channel);

        if (!$result['ok']) {
            wp_send_json_error(['message' => $result['message'] ?? 'Something went wrong. Please try again.'], 422);
            return;
        }

        wp_send_json_success(['post_id' => $result['post_id'] ?? 0]);
    }

    // --------------------------------------------------
    // AJAX — generate a single AI post seed for manual compose.
    // No post_id — generates and returns content only, no DB write.
    // POST: post_type, _wpnonce (amai_manual_post)
    // --------------------------------------------------
    public static function handleGeneratePostSeed(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }
        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_manual_post')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }
        $post_type = sanitize_key((string) ($_POST['post_type'] ?? 'educational'));
        $valid_types = ['educational', 'product', 'feel_good', 'behind_scenes', 'engagement', 'other'];
        if (!in_array($post_type, $valid_types, true)) {
            $post_type = 'educational';
        }
        $channel = sanitize_key((string) ($_POST['channel'] ?? 'facebook'));
        if (!in_array($channel, ['facebook', 'instagram', 'x', 'tiktok'], true)) {
            $channel = 'facebook';
        }
        $business_profile = new \AutoMarketerAI\BusinessProfile();
        $profile          = $business_profile->getOrCreate();
        $fb_connection    = \AutoMarketerAI\FacebookAuth::getConnection();
        $core_url  = rtrim((string) AMAI_CORE_URL, '/') . '/api/content/generate-seed';
        $tenant    = amai_tenant_id();
        $timestamp = (string) time();
        $nonce_hex = bin2hex(random_bytes(16));
        $tone_pool     = ['warm_personal', 'professional_informative', 'witty_light'];
        $tone_index    = (int) gmdate('G') % 3;
        $rotated_tone  = $tone_pool[$tone_index];
        $body      = wp_json_encode([
            'post_type'        => $post_type,
            'channel'          => $channel,
            'business_profile' => [
                'page_name'           => (string) ($fb_connection['page_name'] ?? ($profile['page_name'] ?? '')),
                'business_category'   => (string) ($profile['business_category'] ?? ''),
                'business_tone'       => $rotated_tone,
                'customer_descriptor' => (string) ($profile['customer_descriptor'] ?? ''),
                'differentiator'      => (string) ($profile['differentiator'] ?? ''),
                'values_and_causes'   => (string) ($profile['values_and_causes'] ?? ''),
            ],
        ]);
        $secret    = (string) get_option('amai_tenant_hmac_secret', '');
        $key_id    = 'WP_PROXY';
        $body_b64  = base64_encode(hash('sha256', (string) $body, true));
        $canonical = implode('|', ['POST', '/api/content/generate-seed', $timestamp, $nonce_hex, $tenant, $key_id, $body_b64]);
        $signature = rtrim(base64_encode(hash_hmac('sha256', $canonical, hex2bin($secret), true)), "\n");
        $response  = wp_remote_post($core_url, [
            'timeout' => 45,
            'headers' => [
                'Content-Type'       => 'application/json',
                'X-Amai-Tenant'      => $tenant,
                'X-Amai-Key-Id'      => $key_id,
                'X-Amai-Timestamp'   => $timestamp,
                'X-Amai-Nonce'       => $nonce_hex,
                'X-Amai-Signature'   => $signature,
                'X-Amai-Body-SHA256' => $body_b64,
            ],
            'body' => $body,
        ]);
        if (is_wp_error($response)) {
            wp_send_json_error(['message' => 'Could not reach content engine. Please try again.'], 502);
            return;
        }
        $code    = (int) wp_remote_retrieve_response_code($response);
        $decoded = json_decode(wp_remote_retrieve_body($response), true);
        if ($code !== 200 || empty($decoded['post_content'])) {
            wp_send_json_error(['message' => 'AI generation failed. Please try again.'], 502);
            return;
        }
        $post_content_clean = sanitize_textarea_field((string) $decoded['post_content']);
        $image_keyword = \AutoMarketerAI\Unsplash::extractKeywordsAi($post_content_clean);
        wp_send_json_success(['post_content' => $post_content_clean, 'image_keyword' => $image_keyword]);
    }

    // --------------------------------------------------
    // Valid feeling values — matches Facebook Graph API
    // feeling param strings for the practical business subset.
    // --------------------------------------------------
    public static function validFeelings(): array
    {
        return [
            'HAPPY',
            'EXCITED',
            'GRATEFUL',
            'BLESSED',
            'MOTIVATED',
            'PROUD',
            'THANKFUL',
            'INSPIRED',
            'HOPEFUL',
            'DETERMINED',
            'OPTIMISTIC',
            'CONFIDENT',
            'LOVED',
            'ACCOMPLISHED',
            'ENERGIZED',
            'FOCUSED',
            'JOYFUL',
            'CELEBRATED',
            'AMBITIOUS',
            'REFRESHED',
            'FANTASTIC',
            'WONDERFUL',
            'POSITIVE',
            'STRONG',
            'THRILLED',
        ];
    }

    // --------------------------------------------------
    // AJAX — dismiss autopilot nudge
    // Records the highest approved post id at time of dismissal.
    // Drops threshold to 10 for all subsequent nudges.
    // POST: _wpnonce (amai_autopilot_nudge_dismiss)
    // --------------------------------------------------
    public static function handleAutopilotNudgeDismiss(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }
        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_autopilot_nudge_dismiss')) {
            wp_send_json_error(['message' => 'Invalid nonce.'], 403);
            return;
        }
        global $wpdb;
        $latest_id = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT MAX(id) FROM `{$wpdb->prefix}amai_scheduled_posts`
                 WHERE tenant_id = %s AND status = 'approved'",
                'default'
            )
        );
        update_user_meta(get_current_user_id(), 'amai_autopilot_nudge_dismissed_at', $latest_id);
        update_option('amai_autopilot_nudge_threshold', 10, false);
        wp_send_json_success();
    }

    // --------------------------------------------------
    // AJAX — generate Facebook page bio via Core AI
    // Pulls website_url from business profile, proxies to Core /api/content/generate-bio.
    // POST: _wpnonce (amai_generate_fb_bio), website_url (optional override)

    // --------------------------------------------------
    // Manual bio save.
    // Saves a user-edited bio text to the business profile.
    // POST: _wpnonce (amai_save_fb_bio), bio_text
    // --------------------------------------------------
    public static function handleSaveFbBio(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }
        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_save_fb_bio')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }
        $bio_text  = sanitize_textarea_field(trim((string) ($_POST['bio_text'] ?? '')));
        $profile   = new \AutoMarketerAI\BusinessProfile();
        $profile->update(['facebook_bio' => $bio_text]);
        wp_send_json_success(['bio' => $bio_text]);
    }
    // --------------------------------------------------
    public static function handleGenerateFbBio(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }
        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_generate_fb_bio')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }
        $website_url           = esc_url_raw(trim((string) ($_POST['website_url'] ?? '')));
        $business_name_override = sanitize_text_field(trim((string) ($_POST['business_name_override'] ?? '')));
        $business_profile = new \AutoMarketerAI\BusinessProfile();
        $profile          = $business_profile->getOrCreate();
        if ($website_url === '') {
            $website_url = esc_url_raw((string) ($profile['website_url'] ?? ''));
        }
        $fb_connection = \AutoMarketerAI\FacebookAuth::getConnection();
        $resolved_name = $business_name_override !== ''
            ? $business_name_override
            : (string) ($fb_connection['page_name'] ?? ($profile['page_name'] ?? ''));
        $core_url  = rtrim((string) AMAI_CORE_URL, '/') . '/api/content/generate-bio';
        $tenant    = amai_tenant_id();
        $timestamp = (string) time();
        $nonce_hex = bin2hex(random_bytes(16));
        $body      = wp_json_encode([
            'website_url'       => $website_url,
            'business_name'     => $resolved_name,
            'business_category' => (string) ($profile['business_category'] ?? ''),
            'business_tone'     => (string) ($profile['business_tone'] ?? 'warm_personal'),
        ]);
        $secret    = (string) get_option('amai_tenant_hmac_secret', '');
        $key_id    = 'WP_PROXY';
        $body_b64  = base64_encode(hash('sha256', (string) $body, true));
        $canonical = implode('|', ['POST', '/api/content/generate-bio', $timestamp, $nonce_hex, $tenant, $key_id, $body_b64]);
        $signature = rtrim(base64_encode(hash_hmac('sha256', $canonical, hex2bin($secret), true)), "\n");
        $response  = wp_remote_post($core_url, [
            'timeout' => 45,
            'headers' => [
                'Content-Type'       => 'application/json',
                'X-Amai-Tenant'      => $tenant,
                'X-Amai-Key-Id'      => $key_id,
                'X-Amai-Timestamp'   => $timestamp,
                'X-Amai-Nonce'       => $nonce_hex,
                'X-Amai-Signature'   => $signature,
                'X-Amai-Body-SHA256' => $body_b64,
            ],
            'body' => $body,
        ]);
        if (is_wp_error($response)) {
            wp_send_json_error(['message' => 'Could not reach content engine. Please try again.'], 502);
            return;
        }
        $code    = (int) wp_remote_retrieve_response_code($response);
        $decoded = json_decode(wp_remote_retrieve_body($response), true);
        if ($code !== 200 || empty($decoded['bio'])) {
            wp_send_json_error(['message' => 'Bio generation failed. Please try again.'], 502);
            return;
        }
        $clean_bio = sanitize_textarea_field((string) $decoded['bio']);
        $business_profile->update(['facebook_bio' => $clean_bio]);
        wp_send_json_success(['bio' => $clean_bio]);
    }

    // --------------------------------------------------
    // Panel AI image prompt suggestion.
    // Generates a concrete image description from post content
    // using the active AI provider. Used to prefill the AI
    // image textarea so the user does not have to write from scratch.
    // POST: post_id, _wpnonce (amai_post_save)
    // --------------------------------------------------
    public static function handlePanelSuggestImagePrompt(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }

        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_post_save')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }

        $post_id = (int) ($_POST['post_id'] ?? 0);
        if ($post_id <= 0) {
            wp_send_json_error(['message' => 'Invalid request.'], 400);
            return;
        }

        global $wpdb;
        $content = (string) $wpdb->get_var(
            $wpdb->prepare(
                'SELECT post_content FROM `' . $wpdb->prefix . 'amai_scheduled_posts` WHERE id = %d LIMIT 1',
                $post_id
            )
        );

        if (trim($content) === '') {
            wp_send_json_error(['message' => 'Post content not found.'], 404);
            return;
        }

        // Resolve the active ai-* provider
        $active      = (array) get_option('amai_active_modules', []);
        $provider_id = null;
        foreach ($active as $module_id) {
            if (is_string($module_id) && str_starts_with($module_id, 'ai-')) {
                $provider_id = $module_id;
                break;
            }
        }

        if ($provider_id === null) {
            wp_send_json_error(['message' => 'No AI provider active.'], 422);
            return;
        }

        $module_file = AMAI_PLUGIN_DIR . '/modules/' . $provider_id . '.php';
        if (!is_file($module_file)) {
            wp_send_json_error(['message' => 'Provider module not found.'], 422);
            return;
        }

        require_once $module_file;

        $basename  = substr($provider_id, 3);
        $classname = 'AutoMarketerAI\\Modules\\' . ucfirst($basename);

        if (!class_exists($classname)) {
            wp_send_json_error(['message' => 'Provider class not found.'], 422);
            return;
        }

        $api_key = KeyVault::decrypt((string) get_option('amai_api_key_' . $provider_id, ''));
        if ($api_key === '') {
            wp_send_json_error(['message' => 'Provider API key not configured.'], 422);
            return;
        }

        $model = (string) get_option('amai_model_' . $provider_id, '');
        /** @var \AutoMarketerAI\ModuleInterface $provider */
        $provider = new $classname();
        if ($model === '') {
            $model = $provider->get_default_model();
        }

        $meta_prompt = 'You are a stock photo art director. Given the following social media post, '
            . 'write a single concrete image description (under 20 words) that a stock photo '
            . 'AI generator could use to create a relevant, visually appealing image. '
            . 'Be specific and visual - describe lighting, setting, subject. '
            . 'Do not use abstract business jargon. Reply with only the description, nothing else.'
            . "\n\nPost: " . $content;

        $suggestion = $provider->generate_raw($meta_prompt, $api_key, $model);

        if ($suggestion === '') {
            wp_send_json_error(['message' => 'Could not generate a suggestion. Please type your own.'], 502);
            return;
        }

        wp_send_json_success(['prompt' => sanitize_textarea_field($suggestion)]);
    }

    // --------------------------------------------------
    // Regenerate this week's posts for the channel that failed.
    // POST: channel, _wpnonce
    // --------------------------------------------------
    public static function handleRegenerateWeek(): void
    {
        if (!current_user_can('manage_automarketer_ai')) {
            wp_send_json_error(['message' => 'Unauthorized.'], 403);
            return;
        }
        $nonce = sanitize_text_field((string) ($_POST['_wpnonce'] ?? ''));
        if (!wp_verify_nonce($nonce, 'amai_regenerate_week')) {
            wp_send_json_error(['message' => 'Invalid security token.'], 403);
            return;
        }
        $channel = sanitize_key((string) ($_POST['channel'] ?? 'facebook'));
        $allowed  = ['facebook', 'instagram', 'tiktok', 'x', 'google'];
        if (!in_array($channel, $allowed, true)) {
            wp_send_json_error(['message' => 'Invalid channel.'], 400);
            return;
        }
        match ($channel) {
            'instagram' => ContentEngine::runInstagram(),
            'tiktok'    => ContentEngine::runTikTok(),
            'x'         => ContentEngine::runX(),
            'google'    => ContentEngine::runGoogle(),
            default     => ContentEngine::run(),
        };
        // Clear the failure flag regardless -- if it fails again the flag will be re-set by ContentEngine
        delete_option('amai_generation_failed');
        wp_send_json_success(['message' => 'Regeneration complete.']);
    }
}