<?php
/**
 * KineticCopy Calendar Push Receiver
 *
 * Receives calendar entry briefs from KineticLaunch via webhook,
 * creates a WordPress draft post with all metadata stored as post_meta,
 * and optionally auto-generates content using the Claude API.
 *
 * Workflow:
 *   KineticLaunch → POST /wp-json/kineticcopy/v1/calendar-push
 *     (BrandContextExport + CalendarEntry)
 *     ↓
 *   Validates webhook secret (x-webhook-secret header)
 *     ↓
 *   Creates WP draft post with all _kc_* post_meta
 *     ↓
 *   If auto-generate is ON: generates content, updates post_content
 *     ↓
 *   Callbacks KineticLaunch with status (draft_pending or draft_ready) + wp_post_id
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Claude_AI_Calendar_Push_Receiver {

    /**
     * WP Option key for the global webhook secret
     */
    const OPTION_SECRET = 'kineticcopy_calendar_push_secret';

    /**
     * WP Option key prefix for per-product webhook secrets.
     * Full key: OPTION_SECRET_PREFIX . sanitize_key( $copy_product_key )
     * Allows different KineticLaunch products sharing one WP install to use
     * different secrets — set via Settings > KineticCopy > Calendar Push.
     */
    const OPTION_SECRET_PREFIX = 'kineticcopy_calendar_push_secret_product_';

    /**
     * Register REST API routes and hooks
     * Called from main plugin init
     */
    public static function init() {
        add_action( 'rest_api_init', [ __CLASS__, 'register_routes' ] );
    }

    /**
     * Register the POST endpoints
     */
    public static function register_routes() {
        // Main calendar-push endpoint
        register_rest_route( 'kineticcopy/v1', '/calendar-push', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'handle_calendar_push' ],
            'permission_callback' => '__return_true', // Auth via webhook secret header
        ] );

        // Test endpoint for validating webhook connection
        register_rest_route( 'kineticcopy/v1', '/calendar-push/test', [
            'methods'             => 'POST',
            'callback'            => [ __CLASS__, 'handle_calendar_push_test' ],
            'permission_callback' => '__return_true', // Auth via webhook secret header
        ] );
    }

    /**
     * Test webhook endpoint - validates that KineticLaunch can reach us
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public static function handle_calendar_push_test( $request ) {
        $auth_error = self::validate_webhook_secret( $request );
        if ( is_wp_error( $auth_error ) ) {
            return new WP_REST_Response( [
                'success' => false,
                'error'   => $auth_error->get_error_message(),
            ], 401 );
        }

        $webhook_url = rest_url( 'kineticcopy/v1/calendar-push' );
        $secret_set  = (bool) get_option( self::OPTION_SECRET );

        return new WP_REST_Response( [
            'success'     => true,
            'webhook_url' => $webhook_url,
            'secret_set'  => $secret_set,
            'message'     => 'Webhook connection valid. Ready to receive calendar entries.',
        ], 200 );
    }

    /**
     * Main calendar-push handler
     * Creates a WP draft post with all KineticLaunch metadata stored as post_meta.
     * Optionally auto-generates content if the setting is enabled.
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response
     */
    public static function handle_calendar_push( $request ) {
        // ── 0. License gate ─────────────────────────────────────────────
        if ( ! Claude_AI_Copywriter_License_Manager::has_feature( 'calendar' ) ) {
            return new WP_REST_Response( [
                'success' => false,
                'error'   => 'Calendar sync requires a Studio or higher KineticCopy license.',
            ], 403 );
        }

        // ── 1. Parse body first (needed to extract copy_product_key before auth) ──
        $payload = json_decode( $request->get_body(), true );
        if ( ! is_array( $payload ) ) {
            return new WP_REST_Response( [
                'success' => false,
                'error'   => 'Invalid JSON payload',
            ], 400 );
        }

        // Extract copy_product_key early — it determines which secret to validate against.
        // Null means "default" (current single-product behaviour, fully backward-compatible).
        $copy_product_key = isset( $payload['copy_product_key'] ) && is_string( $payload['copy_product_key'] )
            ? sanitize_key( $payload['copy_product_key'] )
            : null;

        // ── 2. Validate webhook secret (per-product or global) ─────────
        $auth_error = self::validate_webhook_secret( $request, $copy_product_key );
        if ( is_wp_error( $auth_error ) ) {
            return new WP_REST_Response( [
                'success' => false,
                'error'   => $auth_error->get_error_message(),
            ], 401 );
        }

        // ── 3. Validate payload structure ──────────────────────────────
        $validation = self::validate_payload( $payload );
        if ( is_wp_error( $validation ) ) {
            return new WP_REST_Response( [
                'success' => false,
                'error'   => $validation->get_error_message(),
            ], 400 );
        }

        // ── 4. Extract all data from payload ───────────────────────────
        $brand_ctx      = $payload;
        $calendar_entry = $payload['calendarEntry'] ?? [];

        // $copy_product_key already extracted above (may be null)

        $entry_id            = sanitize_text_field( $calendar_entry['entry_id']            ?? '' );
        $title               = sanitize_text_field( $calendar_entry['title']               ?? '' );
        $content_body        = sanitize_textarea_field( wp_unslash( $calendar_entry['content_body'] ?? '' ) );
        $content_type        = sanitize_text_field( $calendar_entry['content_type']        ?? 'blog_post' );
        $callback_url        = esc_url_raw( $calendar_entry['callback_url']                ?? '' );
        $target_word_count   = absint( $calendar_entry['target_word_count']                ?? 1500 );
        $content_instructions = sanitize_textarea_field( wp_unslash( $calendar_entry['content_instructions'] ?? '' ) );
        $keywords_primary    = sanitize_text_field( $calendar_entry['keywords_primary']    ?? '' );
        $keywords_secondary  = is_array( $calendar_entry['keywords_secondary'] ?? null ) ? $calendar_entry['keywords_secondary'] : [];
        $seo_keywords        = is_array( $calendar_entry['seo_keywords']        ?? null ) ? $calendar_entry['seo_keywords']        : [];
        $hashtags            = is_array( $calendar_entry['hashtags']            ?? null ) ? $calendar_entry['hashtags']            : [];
        $image_prompt        = sanitize_textarea_field( wp_unslash( $calendar_entry['image_prompt']    ?? '' ) );
        $image_style         = sanitize_text_field( $calendar_entry['image_style']         ?? '' );
        $tone_override       = sanitize_text_field( $calendar_entry['tone_override']       ?? '' );
        $strategy_phase      = sanitize_text_field( $calendar_entry['strategy_phase']      ?? '' );
        $strategy_objective  = sanitize_textarea_field( wp_unslash( $calendar_entry['strategy_objective'] ?? '' ) );
        $campaign_context    = is_array( $calendar_entry['campaign_context']    ?? null ) ? $calendar_entry['campaign_context'] : null;

        if ( ! $entry_id ) {
            return new WP_REST_Response( [
                'success' => false,
                'error'   => 'Missing entry_id in calendar entry',
            ], 400 );
        }

        if ( $callback_url && self::is_callback_url_self_referential( $callback_url ) ) {
            return new WP_REST_Response( [
                'success' => false,
                'error'   => 'Callback URL cannot point back to this site',
            ], 400 );
        }

        // ── 5. Determine post date from campaign or entry ──────────────
        $post_date = null;
        if ( ! empty( $campaign_context['start_date'] ) ) {
            $ts = strtotime( $campaign_context['start_date'] );
            if ( $ts ) {
                $post_date = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $ts ) );
            }
        }

        // ── 6. Create WP draft post ────────────────────────────────────
        $post_data = [
            'post_title'   => $title ?: __( 'KineticLaunch Draft', 'claude-ai-copywriter' ),
            'post_status'  => 'draft',
            'post_author'  => self::get_site_owner_user_id(),
            'post_type'    => 'post',
            'post_content' => '', // Filled below (placeholder or generated)
        ];

        if ( $post_date ) {
            $post_data['post_date'] = $post_date;
        }

        $post_id = wp_insert_post( $post_data, true );

        if ( is_wp_error( $post_id ) ) {
            error_log( '[KineticCopy calendar-push] wp_insert_post failed: ' . $post_id->get_error_message() );
            return new WP_REST_Response( [
                'success' => false,
                'error'   => 'Failed to create draft post: ' . $post_id->get_error_message(),
            ], 500 );
        }

        // ── 7. Store all metadata as post_meta ────────────────────────
        update_post_meta( $post_id, '_kc_entry_id',            $entry_id );
        update_post_meta( $post_id, '_kc_prompt',              $content_body );
        update_post_meta( $post_id, '_kc_content_instructions', $content_instructions );
        update_post_meta( $post_id, '_kc_content_type',        $content_type );
        update_post_meta( $post_id, '_kc_target_words',        $target_word_count );
        update_post_meta( $post_id, '_kc_keywords_primary',    $keywords_primary );
        update_post_meta( $post_id, '_kc_keywords_secondary',  wp_json_encode( $keywords_secondary ) );
        update_post_meta( $post_id, '_kc_seo_keywords',        wp_json_encode( $seo_keywords ) );
        update_post_meta( $post_id, '_kc_hashtags',            wp_json_encode( $hashtags ) );
        update_post_meta( $post_id, '_kc_campaign',            wp_json_encode( $campaign_context ) );
        update_post_meta( $post_id, '_kc_brand_context',       wp_json_encode( $brand_ctx ) );
        update_post_meta( $post_id, '_kc_image_prompt',        $image_prompt );
        update_post_meta( $post_id, '_kc_image_style',         $image_style );
        update_post_meta( $post_id, '_kc_tone_override',       $tone_override );
        update_post_meta( $post_id, '_kc_strategy_phase',      $strategy_phase );
        update_post_meta( $post_id, '_kc_strategy_objective',  $strategy_objective );
        update_post_meta( $post_id, '_kc_source',              'kineticlaunch' );
        update_post_meta( $post_id, '_kc_callback_url',        $callback_url );
        update_post_meta( $post_id, '_kc_generated',           false );
        // Multi-product routing: tag the post with which Launch product created it.
        // Null (not stored) means the default single-product setup.
        if ( $copy_product_key ) {
            update_post_meta( $post_id, '_kc_product_key', $copy_product_key );
        }

        // ── 7b. Upsert campaign into kc_campaigns option ──────────────
        // So this campaign appears in the Content Calendar filter bar
        if ( ! empty( $campaign_context['name'] ) ) {
            $camp_name   = sanitize_text_field( $campaign_context['name'] );
            $camp_color  = sanitize_hex_color( $campaign_context['color'] ?? '#7c3aed' ) ?: '#7c3aed';
            $camp_goal   = sanitize_text_field( $campaign_context['goal']       ?? '' );
            $camp_start  = sanitize_text_field( $campaign_context['start_date'] ?? '' );
            $camp_end    = sanitize_text_field( $campaign_context['end_date']   ?? '' );

            $campaigns  = get_option( 'kc_campaigns', [] );
            $found_idx  = null;

            foreach ( $campaigns as $i => $camp ) {
                if ( strtolower( $camp['name'] ?? '' ) === strtolower( $camp_name ) ) {
                    $found_idx = $i;
                    break;
                }
            }

            $camp_record = [
                'id'         => $found_idx !== null ? $campaigns[ $found_idx ]['id'] : sanitize_key( 'kl_' . uniqid() ),
                'name'       => $camp_name,
                'color'      => $camp_color,
                'goal'       => $camp_goal,
                'start_date' => $camp_start,
                'end_date'   => $camp_end,
                'source'     => 'kineticlaunch',
            ];

            if ( $found_idx !== null ) {
                $campaigns[ $found_idx ] = $camp_record;
            } else {
                $campaigns[] = $camp_record;
            }

            update_option( 'kc_campaigns', $campaigns );
        }

        // Store primary SEO keyword as Yoast/RankMath focus keyword if available
        if ( $keywords_primary ) {
            update_post_meta( $post_id, '_yoast_wpseo_focuskw', $keywords_primary );
            update_post_meta( $post_id, 'rank_math_focus_keyword', $keywords_primary );
        }

        // ── 8. Auto-generate or placeholder ───────────────────────────
        $settings      = get_option( 'claude_ai_copywriter_settings', [] );
        $auto_generate = ! empty( $settings['kc_auto_generate_on_push'] );
        $auto_publish  = ! empty( $settings['kc_auto_publish'] );

        if ( $auto_generate ) {
            $generation = self::generate_content_from_calendar_entry( $brand_ctx, $calendar_entry );

            if ( ! is_wp_error( $generation ) ) {
                $generated_content = $generation['content'] ?? '';
                $word_count        = str_word_count( wp_strip_all_tags( $generated_content ) );

                // Determine final post status
                $final_status = 'draft';
                if ( $auto_publish && $post_date && strtotime( $post_date ) > time() ) {
                    $final_status = 'future'; // WP "Scheduled"
                }

                wp_update_post( [
                    'ID'           => $post_id,
                    'post_content' => $generated_content,
                    'post_status'  => $final_status,
                ] );

                update_post_meta( $post_id, '_kc_generated',   true );
                update_post_meta( $post_id, '_kc_word_count',  $word_count );
                update_post_meta( $post_id, '_kc_model_used',  $generation['model'] ?? '' );

                // Callback: draft_ready
                if ( $callback_url ) {
                    self::post_callback( $callback_url, [
                        'entry_id'   => $entry_id,
                        'status'     => 'draft_ready',
                        'wp_post_id' => $post_id,
                        'word_count' => $word_count,
                    ] );
                }

                return new WP_REST_Response( [
                    'success'        => true,
                    'entry_id'       => $entry_id,
                    'wp_post_id'     => $post_id,
                    'edit_url'       => get_edit_post_link( $post_id, 'raw' ),
                    'status'         => $final_status,
                    'auto_generated' => true,
                    'word_count'     => $word_count,
                    'message'        => 'Draft post created and content generated.',
                ], 200 );
            }

            // Generation failed - still have the draft, just no content yet
            error_log( '[KineticCopy calendar-push] Auto-generation failed: ' . $generation->get_error_message() );
        }

        // No auto-generate (or generation failed): set placeholder content
        wp_update_post( [
            'ID'           => $post_id,
            'post_content' => '<!-- kineticcopy:pending --><p><em>' . esc_html__( 'This post was created from your KineticLaunch content calendar. Open the KineticCopy Brief panel in the post editor to generate the full content from your brief.', 'claude-ai-copywriter' ) . '</em></p>',
        ] );

        // Callback: draft_pending
        if ( $callback_url ) {
            self::post_callback( $callback_url, [
                'entry_id'   => $entry_id,
                'status'     => 'draft_pending',
                'wp_post_id' => $post_id,
            ] );
        }

        return new WP_REST_Response( [
            'success'        => true,
            'entry_id'       => $entry_id,
            'wp_post_id'     => $post_id,
            'edit_url'       => get_edit_post_link( $post_id, 'raw' ),
            'status'         => 'draft',
            'auto_generated' => false,
            'message'        => 'Draft post created. Open in editor to generate content.',
        ], 200 );
    }

    /**
     * Validate webhook secret from x-webhook-secret header.
     *
     * Secret resolution order (most-specific wins):
     *   1. Per-product secret  (if $copy_product_key is set and a matching option exists)
     *   2. Global secret       (OPTION_SECRET — the existing single-product secret)
     *
     * This means a site with one product needs no config change. A site receiving
     * pushes from multiple Launch products can set per-product secrets in Settings.
     *
     * @param WP_REST_Request $request
     * @param string|null     $copy_product_key  sanitize_key()'d value from payload, or null
     * @return true|WP_Error
     */
    private static function validate_webhook_secret( $request, $copy_product_key = null ) {
        $provided_secret = sanitize_text_field( $request->get_header( 'x-webhook-secret' ) );

        if ( empty( $provided_secret ) ) {
            return new WP_Error( 'missing_secret', 'Missing x-webhook-secret header' );
        }

        // 1. Try per-product secret first (only when a key was supplied)
        if ( $copy_product_key ) {
            $product_secret = get_option( self::OPTION_SECRET_PREFIX . $copy_product_key, '' );
            if ( ! empty( $product_secret ) ) {
                if ( ! hash_equals( $product_secret, $provided_secret ) ) {
                    return new WP_Error( 'invalid_secret', 'Invalid webhook secret for product' );
                }
                return true;
            }
        }

        // 2. Fall back to global secret
        $stored_secret = get_option( self::OPTION_SECRET, '' );
        if ( empty( $stored_secret ) ) {
            return new WP_Error( 'no_secret_configured', 'Calendar push secret not configured in this site' );
        }

        if ( ! hash_equals( $stored_secret, $provided_secret ) ) {
            return new WP_Error( 'invalid_secret', 'Invalid webhook secret' );
        }

        return true;
    }

    /**
     * Validate CalendarCopyPayload structure
     */
    private static function validate_payload( $payload ) {
        $required_brand_fields = [ 'projectId', 'projectName', 'targetAudience', 'voice' ];
        foreach ( $required_brand_fields as $field ) {
            if ( empty( $payload[ $field ] ) ) {
                return new WP_Error( 'missing_field', "Missing required field: {$field}" );
            }
        }

        if ( empty( $payload['calendarEntry'] ) || ! is_array( $payload['calendarEntry'] ) ) {
            return new WP_Error( 'missing_calendar_entry', 'Missing calendarEntry object' );
        }

        $entry = $payload['calendarEntry'];
        $required_entry_fields = [ 'entry_id', 'title', 'content_body', 'content_type' ];
        foreach ( $required_entry_fields as $field ) {
            if ( empty( $entry[ $field ] ) ) {
                return new WP_Error( 'missing_entry_field', "Missing calendarEntry.{$field}" );
            }
        }

        return true;
    }

    /**
     * Check if callback URL points back to this site (prevent infinite loops)
     */
    private static function is_callback_url_self_referential( $callback_url ) {
        $callback_host = wp_parse_url( $callback_url, PHP_URL_HOST );
        $home_host     = wp_parse_url( home_url(), PHP_URL_HOST );

        return strcasecmp( $callback_host, $home_host ) === 0;
    }

    /**
     * Generate content from calendar entry using Claude API.
     * Public so the meta box AJAX handler can call it directly.
     *
     * @param array $brand_ctx    BrandContextExport
     * @param array $calendar_entry CalendarEntry
     * @return array|WP_Error { content, usage, model } on success
     */
    public static function generate_content_from_calendar_entry( $brand_ctx, $calendar_entry ) {
        $admin_user_id = self::get_site_owner_user_id();
        if ( ! $admin_user_id ) {
            return new WP_Error( 'no_admin_user', 'Could not find site administrator' );
        }

        try {
            $claude = new Claude_AI_Copywriter_Claude_API( $admin_user_id );
        } catch ( Exception $e ) {
            return new WP_Error( 'api_error', 'Failed to initialize Claude API: ' . $e->getMessage() );
        }

        $content_type  = sanitize_text_field( $calendar_entry['content_type'] ?? 'blog_post' );
        $content_type  = self::map_content_type( $content_type );
        $system_prompt = self::build_system_prompt( $brand_ctx, $calendar_entry );
        $user_prompt   = self::build_user_prompt( $brand_ctx, $calendar_entry );

        // Override system prompt via direct API call to preserve our custom system prompt
        $admin_user_id_int = (int) $admin_user_id;
        $claude_instance   = new Claude_AI_Copywriter_Claude_API( $admin_user_id_int );

        $type_config  = $claude_instance->get_content_type_config( $content_type );
        $model        = $type_config['model']      ?? 'claude-sonnet-4-6';
        $max_tokens   = $type_config['max_tokens'] ?? 4096;

        $generation = $claude_instance->make_request( 'messages', [
            'model'       => $model,
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'system'      => $system_prompt,
            'messages'    => [
                [ 'role' => 'user', 'content' => $user_prompt ],
            ],
        ] );

        if ( is_wp_error( $generation ) ) {
            return $generation;
        }

        $content = '';
        if ( isset( $generation['content'][0]['text'] ) ) {
            $content = $generation['content'][0]['text'];
        }

        return [
            'content' => $content,
            'usage'   => $generation['usage'] ?? [],
            'model'   => $generation['model'] ?? $model,
        ];
    }

    /**
     * Build system prompt from brand context, including tone override if set
     *
     * @param array $brand_ctx
     * @param array $calendar_entry
     * @return string
     */
    private static function build_system_prompt( $brand_ctx, $calendar_entry = [] ) {
        $project_name = sanitize_text_field( $brand_ctx['projectName'] ?? 'the brand' );
        $voice        = $brand_ctx['voice'] ?? [];
        $target_aud   = sanitize_text_field( $brand_ctx['targetAudience'] ?? '' );
        $words_avoid  = isset( $voice['wordsToAvoid'] ) ? implode( ', ', array_slice( (array) $voice['wordsToAvoid'], 0, 10 ) ) : '';

        // Tone override: per-entry tone takes precedence over brand default
        $tone_override = sanitize_text_field( $calendar_entry['tone_override'] ?? '' );
        $tone_desc = $tone_override ?: sanitize_text_field( $voice['toneDescription'] ?? '' );
        $voice_rules = sanitize_text_field( $voice['rules'] ?? '' );

        $parts = [
            "You are a professional copywriter for {$project_name}.",
        ];

        if ( $tone_desc ) {
            $parts[] = "Write in this voice and tone: {$tone_desc}.";
        }

        if ( $voice_rules ) {
            $parts[] = $voice_rules;
        }

        if ( $words_avoid ) {
            $parts[] = "Avoid these words and phrases: {$words_avoid}.";
        }

        if ( $target_aud ) {
            $parts[] = "Write for this audience: {$target_aud}.";
        }

        $parts[] = "Every claim must serve the reader. Use clear headings and structure. Write for humans, optimized for search intent.";

        return implode( ' ', $parts );
    }

    /**
     * Build user prompt from calendar entry + brand context.
     * Uses the rich CalendarCopyPayload fields including content_instructions,
     * keywords_primary/secondary, hashtags, and strategy_objective.
     *
     * @param array $brand_ctx
     * @param array $calendar_entry
     * @return string
     */
    private static function build_user_prompt( $brand_ctx, $calendar_entry ) {
        $parts = [];

        // Content type instruction
        $content_type     = sanitize_text_field( $calendar_entry['content_type'] ?? 'blog_post' );
        $type_instruction = self::get_content_type_instruction( $content_type );
        if ( $type_instruction ) {
            $parts[] = $type_instruction . "\n";
        }

        // Channel/type-specific writing rules from KineticLaunch
        $content_instructions = sanitize_textarea_field( wp_unslash( $calendar_entry['content_instructions'] ?? '' ) );
        if ( $content_instructions ) {
            $parts[] = "Additional content rules:\n{$content_instructions}";
        }

        // Title and brief
        $title = sanitize_text_field( $calendar_entry['title'] ?? '' );
        $brief = sanitize_textarea_field( wp_unslash( $calendar_entry['content_body'] ?? '' ) );
        $parts[] = "Title: {$title}";
        $parts[] = "Brief/Outline:\n{$brief}";

        // Strategy context
        $strategy_objective = sanitize_text_field( $calendar_entry['strategy_objective'] ?? '' );
        if ( $strategy_objective ) {
            $parts[] = "Strategic objective for this piece: {$strategy_objective}";
        }

        // Brand context
        if ( isset( $brand_ctx['positioning'] ) && is_array( $brand_ctx['positioning'] ) ) {
            $uvp = sanitize_text_field( $brand_ctx['positioning']['uniqueValueProposition'] ?? '' );
            if ( $uvp ) {
                $parts[] = "Brand UVP: {$uvp}";
            }
            $differentiators = array_slice( (array) ( $brand_ctx['positioning']['differentiators'] ?? [] ), 0, 5 );
            if ( ! empty( $differentiators ) ) {
                $parts[] = "Key differentiators: " . implode( ', ', array_map( 'sanitize_text_field', $differentiators ) );
            }
        } elseif ( ! empty( $brand_ctx['positioning'] ) && is_string( $brand_ctx['positioning'] ) ) {
            $parts[] = "Brand positioning:\n" . sanitize_text_field( $brand_ctx['positioning'] );
        }

        // SEO keywords - use primary/secondary if available, fall back to legacy seo_keywords
        $keywords_primary   = sanitize_text_field( $calendar_entry['keywords_primary']   ?? '' );
        $keywords_secondary = is_array( $calendar_entry['keywords_secondary'] ?? null ) ? $calendar_entry['keywords_secondary'] : [];
        $seo_keywords       = is_array( $calendar_entry['seo_keywords']        ?? null ) ? $calendar_entry['seo_keywords'] : [];

        if ( $keywords_primary ) {
            $parts[] = "Primary SEO keyword (use naturally in title, first paragraph, and throughout): {$keywords_primary}";
        }

        if ( ! empty( $keywords_secondary ) ) {
            $secondary_text = implode( ', ', array_slice( array_map( 'sanitize_text_field', $keywords_secondary ), 0, 6 ) );
            $parts[] = "Secondary keywords to weave in naturally: {$secondary_text}";
        } elseif ( ! empty( $seo_keywords ) ) {
            $keywords_text = implode( ', ', array_slice( array_map( 'sanitize_text_field', $seo_keywords ), 0, 8 ) );
            $parts[] = "SEO keywords to include naturally: {$keywords_text}";
        }

        // Hashtags
        $hashtags = is_array( $calendar_entry['hashtags'] ?? null ) ? $calendar_entry['hashtags'] : [];
        if ( ! empty( $hashtags ) ) {
            $tags_text = implode( ' ', array_slice( array_map( 'sanitize_text_field', $hashtags ), 0, 10 ) );
            $parts[] = "Suggested hashtags (include where appropriate): {$tags_text}";
        }

        // Target word count
        $target_word_count = (int) ( $calendar_entry['target_word_count'] ?? 1500 );
        if ( $target_word_count > 0 ) {
            $parts[] = "Target length: approximately {$target_word_count} words.";
        }

        return "Write the full content now:\n\n" . implode( "\n\n", array_filter( $parts ) );
    }

    /**
     * Map calendar content_type to KineticCopy content_type
     */
    private static function map_content_type( $content_type ) {
        $mapping = [
            'blog_post'     => 'blog_post_medium',
            'article'       => 'blog_post_long',
            'newsletter'    => 'newsletter',
            'substack_post' => 'substack_post',
            'email'         => 'email_body',
            'social_post'   => 'ad_copy',
            'case_study'    => 'blog_post_long',
            'guide'         => 'blog_post_long',
        ];

        return $mapping[ $content_type ] ?? 'blog_post_medium';
    }

    /**
     * Get content-type-specific writing instruction
     */
    private static function get_content_type_instruction( $content_type ) {
        $instructions = [
            'blog_post'     => 'Write an engaging blog post with clear H2/H3 headings, strong opening hook, well-developed body sections, and a compelling conclusion with a call-to-action.',
            'article'       => 'Write a comprehensive, in-depth article (1500+ words) with multiple sections, clear headings, supporting evidence, examples, and actionable takeaways.',
            'newsletter'    => 'Write an engaging newsletter piece with a conversational tone, clear sections, and a strong opening that hooks the reader immediately.',
            'substack_post' => 'Write a Substack-style post: personal, direct, conversational. Start with a hook. Include examples or stories. End with a reflection or insight.',
            'email'         => 'Write an email body: short paragraphs, scannable, one main idea, clear CTA. Assume limited time and attention.',
            'social_post'   => 'Write social media copy: hook in the first sentence, benefit-driven, shareable. Include clear CTA.',
            'case_study'    => 'Write a case study: situation, challenge, solution, result. Include specifics and make it compelling.',
            'guide'         => 'Write a step-by-step guide: clear intro, numbered steps or sections, actionable advice, examples, and summary.',
        ];

        return $instructions[ $content_type ] ?? '';
    }

    /**
     * POST the callback to KineticLaunch with the status update.
     * Public so the meta box AJAX handler can call it.
     *
     * @param string $callback_url
     * @param array  $data { entry_id, status, [wp_post_id], [word_count], [error] }
     * @return true|WP_Error
     */
    public static function post_callback( $callback_url, $data ) {
        $webhook_secret = get_option( self::OPTION_SECRET, '' );

        $response = wp_remote_post( $callback_url, [
            'timeout'   => 30,
            'blocking'  => true,
            'sslverify' => true,
            'headers'   => [
                'Content-Type'     => 'application/json',
                'x-webhook-secret' => $webhook_secret,
                'User-Agent'       => 'KineticCopy/' . CLAUDE_AI_COPYWRITER_VERSION . ' WordPress/' . get_bloginfo( 'version' ),
            ],
            'body'      => wp_json_encode( $data ),
        ] );

        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code = wp_remote_retrieve_response_code( $response );
        if ( $code < 200 || $code >= 300 ) {
            $body = wp_remote_retrieve_body( $response );
            return new WP_Error( 'callback_failed', "Callback returned HTTP {$code}: {$body}" );
        }

        return true;
    }

    /**
     * Get the site's primary administrator user ID
     */
    private static function get_site_owner_user_id() {
        $admins = get_users( [
            'role'   => 'administrator',
            'number' => 1,
            'fields' => [ 'ID' ],
        ] );

        return ! empty( $admins ) ? (int) $admins[0]->ID : 1;
    }
}
