<?php
/**
 * LuxStrat International — Universal AI Content Generator
 *
 * One engine for ALL languages. Pass $lang + $lang_config to every method.
 *
 * Isolation per language:
 *   Option:    dba_luxstrat_{lang}_settings
 *   Meta:      _dba_luxstrat_{lang}_*
 *   Rotation:  dba_luxstrat_{lang}_last_theme
 *
 * @package DamalionBlogAI
 * @subpackage LuxStratIntl
 */

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

class DBA_LuxStrat_Intl_Generator {

    /* ══════════════════════════════════════════════════════════════
     *  DALL-E PROMPT POOL
     *  One prompt is selected randomly per generation.
     *  All render at 950 × 600 px landscape, ≤ 90 KB web-optimised.
     * ══════════════════════════════════════════════════════════════ */
    private static $dalle_prompts = array(
        'modern_building' => 'Ultra-realistic architectural rendering of a modern commercial office building located in a prime urban business district. The building features a sleek glass curtain wall façade with reflective blue-tinted glazing, exposed steel structural elements, and clean geometric lines. 8 to 12 floors, ground-level retail spaces with large transparent storefront windows, professional signage zones, and a main entrance with automatic sliding glass doors. A unique, striking blue sky dominates the background — deep gradient sapphire tones transitioning into lighter azure near the horizon, with subtle high-altitude cirrus clouds creating texture and visual depth. Crisp natural daylight, soft dynamic shadows, realistic light reflections on glass surfaces, brushed steel detailing, and finely textured concrete elements. Surroundings include paved sidewalks, minimal contemporary landscaping with small sculpted trees, a few parked modern vehicles, and pedestrians in business attire. Shot from a slightly low-angle perspective to emphasize scale and verticality. 4K-quality photorealistic rendering in professional architectural photography style.',
        'tech_startup' => 'Ultra-realistic professional photography of a tech startup founder working in a bright open-plan office. Modern glass walls, collaborative workspace, large screens displaying analytics dashboards, and a minimalist standing desk setup. The entrepreneur is casually dressed (smart casual), focused yet energized, typing on a laptop with wireless headphones nearby. Clean Scandinavian-inspired design, natural daylight flooding through large windows, subtle indoor plants, soft shadows, vibrant yet professional color tones (light blues, white, muted wood textures). Crisp focus, shallow depth of field, authentic candid moment, high-end business photography style.',
        'pe_executive' => 'Ultra-realistic portrait of a private equity executive in a premium corporate office. Floor-to-ceiling windows overlooking a financial district skyline. Elegant desk with leather accessories, financial reports neatly arranged, dual monitors with performance charts. The executive wears a tailored business suit, confident expression, composed posture. Natural daylight with soft reflections on glass surfaces, refined color palette (deep navy, charcoal, warm beige accents). High-detail textures, sharp tailoring fabric, luxury corporate aesthetic, professional architectural photography lighting.',
        'creative_entrepreneur' => 'Ultra-realistic image of a creative entrepreneur working in a colorful modern studio. Bright workspace with mood boards, design sketches, a tablet and stylus, modern art pieces, and natural light streaming through large industrial-style windows. Joyful, energetic atmosphere with vibrant accent colors (turquoise, coral, mustard), soft shadows, authentic candid expression. Contemporary interior with polished concrete floors and minimalist furniture. Crisp focus, professional lifestyle photography style.',
        'fintech_founder' => 'Ultra-realistic professional image of a female fintech founder working in a sleek modern office with glass partitions and subtle blue LED accents. Laptop displaying financial software interface, smartphone and tablet nearby. Confident, natural smile, focused expression. Bright natural daylight combined with soft ambient lighting. Clean geometric office design, brushed steel and glass textures, calm corporate color scheme (light gray, blue tones, white). Professional editorial photography style.',
        'family_business' => 'Ultra-realistic image of a family business CEO in a refined yet welcoming executive office. Warm wood finishes, framed certificates on the wall, tasteful indoor plants, large windows with natural daylight. The entrepreneur reviews documents while seated at a polished desk. Balanced lighting, soft shadows, warm and modern color grading (natural wood tones, soft beige, subtle green accents). Authentic, approachable leadership atmosphere. Professional business photography style with high texture detail.',
        'real_estate' => 'Ultra-realistic professional photography of a real estate investor working in a bright modern office overlooking a city skyline with high-rise buildings visible through floor-to-ceiling windows. The entrepreneur is reviewing architectural plans and property financial reports on a sleek desk, with a tablet displaying building renderings. Modern interior design with glass, brushed steel, and neutral tones (light gray, blue, soft beige). Natural daylight creates soft dynamic shadows and reflections on glass surfaces. Confident, focused posture, refined business attire. Professional architectural photography style, crisp textures, high detail.',
        'biotech_founder' => 'Ultra-realistic image of a biotech entrepreneur working in a modern laboratory-office hybrid space. Bright, clean environment with glass partitions, subtle lab equipment in the background, and a laptop displaying scientific data visualizations. The founder wears smart business attire with a lab coat casually placed nearby. Natural daylight combined with soft white ambient lighting. Color palette: white, soft green, light blue accents for a clinical yet optimistic atmosphere. Shallow depth of field, sharp focus, realistic skin tones, high-end editorial photography style.',
        'hospitality_owner' => 'Ultra-realistic professional photo of a hospitality entrepreneur working in a stylish hotel lounge office area. Elegant modern interior design with warm lighting, marble surfaces, indoor plants, and refined decorative accents. The entrepreneur reviews expansion plans on a laptop while seated at a contemporary table. Joyful, confident expression. Warm yet modern color palette (champagne tones, soft gold accents, deep navy). Natural daylight blended with soft interior lighting. Professional lifestyle photography with crisp textures and subtle depth of field.',
        'renewable_energy' => 'Ultra-realistic image of a renewable energy founder working in a bright eco-conscious office with sustainable design elements. Large windows with sunlight streaming in, indoor plants, natural wood surfaces, and subtle solar panel imagery visible on screens. Laptop displaying energy performance dashboards and sustainability metrics. Calm, focused expression. Fresh modern palette (white, soft green, light wood, sky blue accents). Clean geometric interior, natural light reflections, soft dynamic shadows, professional corporate photography style.',
        'asset_management' => 'Ultra-realistic professional portrait of an asset management executive working in a premium financial office. Large panoramic windows overlooking a financial district skyline. Dual monitors displaying portfolio allocation charts and market analytics. The executive is seated at a minimalist desk with refined leather accessories. Sophisticated color scheme (charcoal gray, deep blue, silver accents). Crisp daylight reflections on glass surfaces, high-detail textures, sharp focus, corporate editorial photography style.',
    );

    /* ──────────────────────────────────────────────────
     *  PROGRESS TRACKING (via WP transients)
     *  Allows the admin JS to poll real-time status.
     * ────────────────────────────────────────────────── */

    /**
     * Update the progress transient for a given language.
     *
     * @param string $lang    Language code.
     * @param string $phase   Current phase: 'content', 'validate', 'seo', 'dalle', 'done', 'error'.
     * @param int    $percent Percent complete (0-100).
     * @param string $detail  Human-readable status detail.
     */
    public static function update_progress( $lang, $phase, $percent, $detail = '' ) {
        $data = array(
            'phase'   => $phase,
            'percent' => min( 100, max( 0, (int) $percent ) ),
            'detail'  => $detail,
            'time'    => current_time( 'H:i:s' ),
        );
        set_transient( 'dba_luxstrat_' . $lang . '_progress', $data, 300 ); // 5 min TTL
    }

    /**
     * Get the current progress for a language (called via AJAX poll).
     *
     * @param  string $lang Language code.
     * @return array|false  Progress data or false if none.
     */
    public static function get_progress( $lang ) {
        return get_transient( 'dba_luxstrat_' . $lang . '_progress' );
    }

    /**
     * Clear progress after completion.
     *
     * @param string $lang Language code.
     */
    public static function clear_progress( $lang ) {
        delete_transient( 'dba_luxstrat_' . $lang . '_progress' );
    }

    /**
     * Get the DALL-E prompt pool (for admin display).
     *
     * @return array Prompt pool keyed by slug => prompt text.
     */
    public static function get_dalle_prompt_pool() {
        return self::$dalle_prompts;
    }

    /**
     * Public wrapper to generate a featured image for an existing post.
     * Used by the enrich/repair admin action.
     *
     * @param int    $post_id
     * @param string $title
     * @param array  $theme_config
     * @param array  $settings
     * @param string $lang
     */
    public static function generate_featured_image_for_post( $post_id, $title, $theme_config, $settings, $lang = '' ) {
        self::generate_featured_image( $post_id, $title, $theme_config, $settings, $lang );
    }

    /**
     * Main entry point — generates 1 post for the given language.
     *
     * @param  string $lang        Language code (e.g. 'fr', 'de', 'es').
     * @param  array  $lang_config Full language config array from languages/{lang}.php.
     * @param  bool   $force       Skip daily/weekly dedup (manual run).
     * @return array|WP_Error      Result summary or error.
     */
    public static function run( $lang, $lang_config, $force = false ) {
        $log = 'LuxStrat[' . $lang . ']';

        try {
            $settings = get_option( 'dba_luxstrat_' . $lang . '_settings', array() );

            if ( empty( $settings['enabled'] ) ) {
                error_log( $log . ': Module disabled. Skipping.' );
                self::update_progress( $lang, 'error', 0, 'Module disabled.' );
                return new WP_Error( 'disabled', 'LuxStrat ' . $lang . ' module is disabled.' );
            }

            // ── Guard: only 1 post per day ──
            if ( ! $force && DBA_LuxStrat_Intl_Dedupe::posted_today( $lang ) ) {
                error_log( $log . ': Already posted today. Skipping.' );
                self::update_progress( $lang, 'error', 0, 'Already posted today.' );
                return new WP_Error( 'already_posted', 'Already posted today for ' . $lang );
            }

            // ── Pick next theme ──
            self::update_progress( $lang, 'content', 5, 'Selecting theme...' );

            $theme_slug = self::get_next_theme( $lang, $lang_config, $force );
            if ( ! $theme_slug ) {
                error_log( $log . ': No available theme.' );
                self::update_progress( $lang, 'error', 5, 'No theme available.' );
                return new WP_Error( 'no_theme', 'No theme available for ' . $lang );
            }

            $themes       = ! empty( $lang_config['themes'] ) ? $lang_config['themes'] : array();
            $theme_config = isset( $themes[ $theme_slug ] ) ? $themes[ $theme_slug ] : null;
            if ( ! $theme_config ) {
                self::update_progress( $lang, 'error', 5, 'Invalid theme: ' . $theme_slug );
                return new WP_Error( 'invalid_theme', 'Theme "' . $theme_slug . '" not found in ' . $lang . ' config.' );
            }

            error_log( $log . ': Generating — theme "' . $theme_slug . '" (' . $theme_config['short_label'] . ')' );

            // ══════════════════════════════════════════════════
            //  PHASE 1: CONTENT GENERATION
            // ══════════════════════════════════════════════════
            self::update_progress( $lang, 'content', 10, 'Generating article: ' . $theme_config['short_label'] . '...' );

            $result = self::generate_article( $lang, $lang_config, $theme_slug, $theme_config, $settings );
            if ( is_wp_error( $result ) ) {
                self::update_progress( $lang, 'error', 15, 'Generation failed: ' . $result->get_error_message() );
                return $result;
            }

            self::update_progress( $lang, 'content', 30, 'Article generated. Validating...' );

            // ══════════════════════════════════════════════════
            //  PHASE 2: VALIDATION + STYLE CHECK (SEO)
            // ══════════════════════════════════════════════════
            self::update_progress( $lang, 'seo', 35, 'Validating content quality...' );

            $validation = DBA_LuxStrat_Intl_Validator::validate( $lang, $lang_config, $result );
            if ( ! $validation['valid'] ) {
                error_log( $log . ': Validation failed — ' . implode( ' | ', $validation['errors'] ) );
                self::update_progress( $lang, 'seo', 38, 'Validation issues found. Retrying...' );
                // Retry once
                $result = self::generate_article( $lang, $lang_config, $theme_slug, $theme_config, $settings, $validation['errors'] );
                if ( is_wp_error( $result ) ) {
                    self::update_progress( $lang, 'error', 40, 'Retry failed: ' . $result->get_error_message() );
                    return $result;
                }
                $validation = DBA_LuxStrat_Intl_Validator::validate( $lang, $lang_config, $result );
                if ( ! $validation['valid'] ) {
                    self::update_progress( $lang, 'error', 40, 'Validation failed: ' . implode( ' | ', $validation['errors'] ) );
                    return new WP_Error( 'validation_failed', implode( ' | ', $validation['errors'] ) );
                }
            }

            if ( ! empty( $validation['warnings'] ) ) {
                error_log( $log . ' warnings: ' . implode( ' | ', $validation['warnings'] ) );
            }

            self::update_progress( $lang, 'seo', 45, 'Running style checks (passive voice, transitions)...' );

            // ── Style check ──
            $style_errors = DBA_LuxStrat_Intl_Validator::validate_style( $lang, $lang_config, $result );
            if ( ! empty( $style_errors ) ) {
                error_log( $log . ': Style check failed — ' . implode( ' | ', $style_errors ) );
                self::update_progress( $lang, 'seo', 48, 'Style issues found. Regenerating...' );
                $result = self::generate_article( $lang, $lang_config, $theme_slug, $theme_config, $settings, $style_errors );
                if ( is_wp_error( $result ) ) {
                    self::update_progress( $lang, 'error', 50, 'Style retry failed.' );
                    return $result;
                }
                $validation = DBA_LuxStrat_Intl_Validator::validate( $lang, $lang_config, $result );
                if ( ! $validation['valid'] ) {
                    self::update_progress( $lang, 'error', 50, 'Validation failed after style fix.' );
                    return new WP_Error( 'validation_failed', implode( ' | ', $validation['errors'] ) );
                }
            }

            self::update_progress( $lang, 'seo', 55, 'Adding Yoast/RankMath SEO meta...' );

            // ══════════════════════════════════════════════════
            //  PHASE 3: CREATE POST (with SEO meta)
            // ══════════════════════════════════════════════════
            self::update_progress( $lang, 'seo', 60, 'Creating WordPress post + SEO meta + categories...' );

            $post_id = self::create_post( $lang, $lang_config, $result, $theme_slug, $theme_config, $settings );
            if ( is_wp_error( $post_id ) ) {
                self::update_progress( $lang, 'error', 65, 'Post creation failed: ' . $post_id->get_error_message() );
                return $post_id;
            }

            self::update_progress( $lang, 'seo', 70, 'Post #' . $post_id . ' created. SEO meta applied.' );

            // ══════════════════════════════════════════════════
            //  PHASE 4: DALL-E FEATURED IMAGE
            // ══════════════════════════════════════════════════
            self::update_progress( $lang, 'dalle', 72, 'Selecting DALL-E prompt from pool...' );

            self::generate_featured_image( $post_id, $result['title'], $theme_config, $settings, $lang );

            // ── GUARD: No image = no post. Trash it. ──
            if ( ! has_post_thumbnail( $post_id ) ) {
                wp_trash_post( $post_id );
                error_log( $log . ': DALL-E failed — post #' . $post_id . ' TRASHED (no featured image).' );
                self::update_progress( $lang, 'error', 90, 'DALL-E failed — post #' . $post_id . ' trashed. No post without featured image.' );
                return new WP_Error( 'no_image', 'DALL-E failed — post trashed. No post will be published without a featured image.' );
            }

            self::update_progress( $lang, 'dalle', 95, 'Featured image set for post #' . $post_id );

            // ── KAIZEN: store original content for diff tracking ──
            if ( class_exists( 'DBA_LuxStrat_Intl_Kaizen' ) ) {
                DBA_LuxStrat_Intl_Kaizen::store_original( $lang, $post_id, $result['title'], $result['content'], $result['excerpt'] ?? '' );
            }

            // ── ANTI-ORPHAN: authority links + related block + backlinks (same lang) ──
            $ao_result = array();
            if ( class_exists( 'DBA_LuxStrat_Intl_Anti_Orphan' ) ) {
                $ao_result = DBA_LuxStrat_Intl_Anti_Orphan::on_post_created( $lang, $post_id, $theme_slug );
                error_log( $log . ': Anti-Orphan: auth=' . ( $ao_result['auth_links'] ?? 0 ) . ' related=' . ( $ao_result['related'] ?? 0 ) . ' backlinks=' . ( $ao_result['backlinks'] ?? 0 ) );
            }

            // ── Mark used + advance rotation ──
            DBA_LuxStrat_Intl_Dedupe::mark_used_today( $lang, $theme_slug );
            update_option( 'dba_luxstrat_' . $lang . '_last_theme', $theme_slug, false );

            $word_count = str_word_count( strip_tags( $result['content'] ) );

            self::update_progress( $lang, 'done', 100, 'Post #' . $post_id . ' — "' . $result['title'] . '" (' . $word_count . ' words)' );
            error_log( $log . ': Success — post #' . $post_id . ' "' . $result['title'] . '"' );

            return array(
                'post_id'      => $post_id,
                'title'        => $result['title'],
                'theme'        => $theme_slug,
                'words'        => $word_count,
                'kaizen'       => class_exists( 'DBA_LuxStrat_Intl_Kaizen' ) ? 'stored' : 'off',
                'anti_orphan'  => $ao_result,
            );

        } catch ( \Throwable $e ) {
            $msg = $log . ' fatal: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
            error_log( $msg );
            self::update_progress( $lang, 'error', 0, 'Fatal: ' . $e->getMessage() );
            return new WP_Error( 'fatal_error', $msg );
        }
    }

    /* ──────────────────────────────────────────────────
     *  ARTICLE GENERATION
     * ────────────────────────────────────────────────── */

    private static function generate_article( $lang, $lang_config, $theme_slug, $theme_config, $settings, $feedback = array() ) {

        $dba_settings = get_option( 'dba_settings', array() );
        $api_key      = ! empty( $dba_settings['openai_api_key'] ) ? $dba_settings['openai_api_key'] : '';

        if ( empty( $api_key ) ) {
            return new WP_Error( 'no_api_key', 'OpenAI API key not configured.' );
        }

        $model    = ! empty( $dba_settings['openai_model'] ) ? $dba_settings['openai_model'] : 'gpt-4o-mini';
        $word_min = ! empty( $settings['word_count_min'] ) ? (int) $settings['word_count_min'] : 1000;
        $word_max = ! empty( $settings['word_count_max'] ) ? (int) $settings['word_count_max'] : 1800;

        // CTA: settings override → language defaults
        $cta_defaults = ! empty( $lang_config['cta_defaults'] ) ? $lang_config['cta_defaults'] : array();
        $cta_text     = ! empty( $settings['cta_text'] )      ? $settings['cta_text']      : ( $cta_defaults['text'] ?? '' );
        $cta_link     = ! empty( $settings['cta_link_url'] )   ? $settings['cta_link_url']   : ( $cta_defaults['link_url'] ?? 'https://www.damalion.com/lets-connect/' );
        $cta_anchor   = ! empty( $settings['cta_link_text'] )  ? $settings['cta_link_text']  : ( $cta_defaults['link_text'] ?? 'Contact us.' );

        // Prompt fragments from config
        $pf = ! empty( $lang_config['prompt_fragments'] ) ? $lang_config['prompt_fragments'] : array();

        // Recent titles
        $recent_titles = self::get_recent_titles( $lang, $theme_slug, 20 );
        $avoid_block   = '';
        if ( ! empty( $recent_titles ) ) {
            $avoid_block = "\n\n" . ( $pf['avoid_titles'] ?? 'Do NOT reuse these recent titles:' ) . "\n- " . implode( "\n- ", $recent_titles );
        }

        // Pillar links
        $pillar_block = '';
        if ( ! empty( $theme_config['pillar_urls'] ) ) {
            $pillar_block = "\n\n" . ( $pf['pillar_intro'] ?? 'Include at least one link to:' ) . ' ' . implode( ', ', $theme_config['pillar_urls'] );
        }

        // ── Link Sanitizer: approved internal links for prompt ──
        $approved_links_block = '';
        if ( class_exists( 'DBA_Link_Sanitizer' ) ) {
            $approved_links_block = DBA_Link_Sanitizer::get_prompt_block( $lang );
        }

        // H2 blueprint
        $h2_block = '';
        if ( ! empty( $theme_config['h2_blueprint'] ) ) {
            $h2_block = "\n\n" . ( $pf['h2_intro'] ?? 'H2 HEADING STRUCTURE:' ) . "\n";
            foreach ( $theme_config['h2_blueprint'] as $i => $h2 ) {
                $h2_block .= ( $i + 1 ) . '. ' . $h2 . "\n";
            }
            $h2_block .= ( $pf['h2_footer'] ?? 'Use H3 sub-headings as needed. First H2 must contain the primary keyword.' );
        }

        // Feedback
        $feedback_block = '';
        if ( ! empty( $feedback ) ) {
            $feedback_block = "\n\n⚠️ " . ( $pf['feedback_intro'] ?? 'Fix these issues from the previous attempt:' ) . "\n- " . implode( "\n- ", $feedback );
        }

        // ── Learn from last 5 published posts (as corrected by editor) ──
        $corrections_block = '';
        if ( class_exists( 'DBA_LuxStrat_Intl_Corrections' ) ) {
            $corrections_block = DBA_LuxStrat_Intl_Corrections::build_learning_prompt( $lang, $theme_slug );
        }

        // System prompt from config + shared style rules
        $system_prompt = ( $lang_config['system_prompt'] ?? 'You are a Luxembourg corporate law specialist writing for Damalion.' )
            . "\n\n"
            . ( $lang_config['style_rules_prompt'] ?? '' )
            . "\n\n"
            . 'Output ONLY valid JSON — no markdown fences, no preamble, no commentary.';

        $keywords_str = implode( ', ', $theme_config['keywords'] ?? array() );
        $intro        = $pf['intro'] ?? 'Write a professional article about:';
        $lang_rule    = $pf['language_rule'] ?? '';
        $json_note    = $pf['json_lang_note'] ?? '';

        $user_prompt = <<<PROMPT
{$intro} {$theme_config['label']}

Topic description: {$theme_config['description']}

Target keywords (use naturally): {$keywords_str}

Requirements:
- Word count: MINIMUM {$word_min} words, target {$word_max} words. Articles under {$word_min} words will be REJECTED.
- Structure: Use H2 and H3 subheadings. At least 4 H2 sections. First H2 must contain the primary keyword.
- Tone: Authoritative, professional, no fluff
- Audience: Institutional investors, fund managers, tax advisors, family offices
- Include practical structuring insights
- Include at least one Luxembourg legal reference
- ACTIVE VOICE: At least 90% of sentences must use active voice.
- SHORT SENTENCES: At least 75% of sentences must be under 20 words.
- TRANSITION WORDS: At least 30% of sentences must contain a transition word.
- {$lang_rule}
- End with a call-to-action: "{$cta_text} <a href=\"{$cta_link}\" target=\"_blank\" rel=\"noopener\">{$cta_anchor}</a>"
{$h2_block}
{$pillar_block}
{$approved_links_block}
{$avoid_block}
{$feedback_block}
{$corrections_block}

Return ONLY a JSON object with these keys:
{
  "title": "SEO-optimised article title {$json_note} (50-80 chars)",
  "content": "Full HTML article body with <h2>, <h3>, <p>, <ul>/<li>, <a> tags. No <h1>.",
  "excerpt": "Compelling excerpt {$json_note} (120-135 chars STRICTLY)",
  "meta_description": "SEO meta description {$json_note} (120-135 chars STRICTLY)",
  "focus_keyword": "Primary 2-4 word keyword phrase {$json_note}",
  "image_alt": "SEO image alt text {$json_note} (60-100 chars)"
}
PROMPT;

        // ── SEO Sniper: inject competitive intelligence ──
        // Priority: live transient (Sniper-triggered) → stored analysis (every cron run)
        if ( class_exists( 'DBA_SEO_Sniper_Router' ) ) {
            $sniper_brief = DBA_SEO_Sniper_Router::get_brief_for_prompt( $theme_slug, 'luxstrat-intl-' . $lang );
            if ( $sniper_brief ) {
                $user_prompt .= "\n\n" . $sniper_brief;
            }
        }

        // ── TIS: inject strategic content gap priorities ──
        if ( class_exists( 'DBA_TIS_Recommender' ) ) {
            $tis_context = DBA_TIS_Recommender::get_generator_context( 'luxstrat-intl-' . $lang );
            if ( $tis_context ) {
                $system_prompt .= "\n\n" . $tis_context;
            }
        }

        // ── KAIZEN: inject diff-based learning context (per language) ──
        if ( class_exists( 'DBA_LuxStrat_Intl_Kaizen' ) ) {
            $kaizen_ctx = DBA_LuxStrat_Intl_Kaizen::get_learning_context( $lang, $theme_slug );
            if ( $kaizen_ctx ) {
                $system_prompt .= $kaizen_ctx;
            }
        }

        // ── API call ──
        $body = array(
            'model'       => $model,
            'messages'    => array(
                array( 'role' => 'system', 'content' => $system_prompt ),
                array( 'role' => 'user',   'content' => $user_prompt ),
            ),
            'temperature' => 0.7,
            'max_tokens'  => 8192,
        );

        $api_response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
            'timeout' => 120,
            'headers' => array(
                'Content-Type'  => 'application/json',
                'Authorization' => 'Bearer ' . $api_key,
            ),
            'body' => wp_json_encode( $body ),
        ) );

        if ( is_wp_error( $api_response ) ) {
            return new WP_Error( 'api_error', 'OpenAI API failed: ' . $api_response->get_error_message() );
        }

        $http_code = wp_remote_retrieve_response_code( $api_response );
        $raw_body  = wp_remote_retrieve_body( $api_response );

        if ( $http_code !== 200 ) {
            $err = json_decode( $raw_body, true );
            return new WP_Error( 'api_http_error', 'OpenAI HTTP ' . $http_code . ': ' . ( $err['error']['message'] ?? $raw_body ) );
        }

        $resp = json_decode( $raw_body, true );
        if ( empty( $resp['choices'][0]['message']['content'] ) ) {
            return new WP_Error( 'empty_response', 'OpenAI returned empty.' );
        }

        $cleaned = $resp['choices'][0]['message']['content'];
        $cleaned = preg_replace( '/^```(?:json)?\s*/i', '', $cleaned );
        $cleaned = preg_replace( '/\s*```$/', '', $cleaned );
        $parsed  = json_decode( trim( $cleaned ), true );

        if ( json_last_error() !== JSON_ERROR_NONE ) {
            return new WP_Error( 'json_error', 'JSON parse failed: ' . json_last_error_msg() );
        }

        foreach ( array( 'title', 'content' ) as $f ) {
            if ( empty( $parsed[ $f ] ) ) {
                return new WP_Error( 'missing_field', 'Missing: ' . $f );
            }
        }

        $parsed['theme'] = $theme_slug;

        // ── Link Sanitizer: strip/replace hallucinated damalion.com links ──
        if ( class_exists( 'DBA_Link_Sanitizer' ) && ! empty( $parsed['content'] ) ) {
            $parsed['content'] = DBA_Link_Sanitizer::sanitize( $parsed['content'], $lang );
        }

        return $parsed;
    }

    /* ──────────────────────────────────────────────────
     *  POST CREATION
     * ────────────────────────────────────────────────── */

    private static function create_post( $lang, $lang_config, $data, $theme_slug, $theme_config, $settings ) {
        $prefix      = DBA_LuxStrat_Intl_Dedupe::meta_prefix( $lang );
        $post_status = ! empty( $settings['post_status'] ) ? $settings['post_status'] : 'publish';
        $author_id   = ! empty( $settings['author_id'] ) ? (int) $settings['author_id'] : 1;

        // ══════════════════════════════════════════════════════════════
        //  WPML: Switch language BEFORE resolve_categories() AND
        //  wp_insert_post(). This is CRITICAL — without switching first:
        //  1) get_term_by() in resolve_categories returns English term IDs
        //  2) wp_insert_post() assigns the post to English
        // ══════════════════════════════════════════════════════════════
        $original_lang = null;
        $switched      = false;
        if ( function_exists( 'icl_get_languages' ) ) {
            global $sitepress;
            if ( $sitepress ) {
                $original_lang = $sitepress->get_current_language();
                $sitepress->switch_lang( $lang );
                $switched = true;
                error_log( 'LuxStrat[' . $lang . ']: WPML switched from "' . $original_lang . '" to "' . $lang . '" before categories + post creation' );
            }
        }

        // ── Categories: theme slugs + extra from admin ──
        // Must run AFTER WPML switch so get_term_by() resolves FR slugs
        $category_ids = self::resolve_categories( $lang, $theme_config, $settings );

        $post_data = array(
            'post_title'   => sanitize_text_field( $data['title'] ),
            'post_content' => wp_kses_post( $data['content'] ),
            'post_excerpt' => ! empty( $data['excerpt'] ) ? sanitize_text_field( $data['excerpt'] ) : '',
            'post_status'  => $post_status,
            'post_author'  => $author_id,
            'post_type'    => 'post',
            'meta_input'   => array(
                $prefix . 'auto_created' => '1',
                $prefix . 'theme'        => $theme_slug,
                $prefix . 'theme_label'  => $theme_config['short_label'],
                $prefix . 'generated_at' => current_time( 'mysql' ),
                $prefix . 'word_count'   => str_word_count( strip_tags( $data['content'] ) ),
                $prefix . 'model'        => self::get_model(),
                $prefix . 'language'     => $lang,
            ),
        );

        // Yoast / RankMath
        if ( ! empty( $data['meta_description'] ) ) {
            $desc = sanitize_text_field( $data['meta_description'] );
            if ( mb_strlen( $desc ) > 135 ) $desc = mb_substr( $desc, 0, 132 ) . '...';
            $post_data['meta_input']['_yoast_wpseo_metadesc']  = $desc;
            $post_data['meta_input']['rank_math_description']   = $desc;
        }
        if ( ! empty( $data['focus_keyword'] ) ) {
            $post_data['meta_input']['_yoast_wpseo_focuskw']    = sanitize_text_field( $data['focus_keyword'] );
            $post_data['meta_input']['rank_math_focus_keyword']  = sanitize_text_field( $data['focus_keyword'] );
        }
        if ( ! empty( $data['image_alt'] ) ) {
            $post_data['meta_input'][ $prefix . 'image_alt' ] = sanitize_text_field( $data['image_alt'] );
        }

        if ( ! empty( $category_ids ) ) {
            $post_data['post_category'] = $category_ids;
        }

        $post_id = wp_insert_post( $post_data, true );

        // ══════════════════════════════════════════════════════════════
        //  WPML: Force-set language AFTER creation (belt + suspenders)
        //  Also set trid to null so WPML creates a new translation group.
        // ══════════════════════════════════════════════════════════════
        if ( ! is_wp_error( $post_id ) ) {
            if ( function_exists( 'wpml_set_element_language_details' ) ) {
                $set_result = wpml_set_element_language_details( array(
                    'element_id'           => $post_id,
                    'element_type'         => 'post_post',
                    'trid'                 => null,
                    'language_code'        => $lang,
                    'source_language_code' => null,
                ) );
                error_log( 'LuxStrat[' . $lang . ']: WPML language set for post #' . $post_id . ' — result: ' . ( $set_result ? 'OK' : 'FAIL' ) );
            }

            // Also set via the icl_translations direct approach as fallback
            if ( has_filter( 'wpml_element_language_code' ) ) {
                $verify = apply_filters( 'wpml_element_language_code', null, array(
                    'element_id'   => $post_id,
                    'element_type' => 'post',
                ) );
                if ( $verify !== $lang ) {
                    error_log( 'LuxStrat[' . $lang . ']: WARNING — WPML reports post #' . $post_id . ' as "' . $verify . '" instead of "' . $lang . '"' );
                } else {
                    error_log( 'LuxStrat[' . $lang . ']: VERIFIED — post #' . $post_id . ' is "' . $verify . '"' );
                }
            }
        }

        // ══════════════════════════════════════════════════════════════
        //  WPML: Restore original language context
        // ══════════════════════════════════════════════════════════════
        if ( $switched && isset( $sitepress ) && $sitepress ) {
            $sitepress->switch_lang( $original_lang ?: 'en' );
        }

        if ( is_wp_error( $post_id ) ) return $post_id;

        // Auto-linker
        if ( class_exists( 'DBA_Auto_Linker' ) ) {
            try {
                $linker = DBA_Auto_Linker::get_instance();
                $linked = $linker->process( $data['content'] );
                if ( $linked !== $data['content'] ) {
                    wp_update_post( array( 'ID' => $post_id, 'post_content' => $linked ) );
                }
            } catch ( \Throwable $e ) {
                error_log( 'LuxStrat[' . $lang . ']: Auto-linker error: ' . $e->getMessage() );
            }
        }

        // ── TIS: track directive progress ──
        if ( class_exists( 'DBA_TIS_Recommender' ) ) {
            DBA_TIS_Recommender::auto_track_progress( 'luxstrat-intl-' . $lang, $data['title'] ?? '' );
        }

        return $post_id;
    }

    /**
     * Resolve categories: theme category_slugs + extra ticked categories from admin.
     */
    private static function resolve_categories( $lang, $theme_config, $settings ) {
        $resolved = array();

        // Theme-specific category slugs
        if ( ! empty( $theme_config['category_slugs'] ) && is_array( $theme_config['category_slugs'] ) ) {
            foreach ( $theme_config['category_slugs'] as $cat_slug ) {
                $cat_slug = trim( $cat_slug );
                if ( empty( $cat_slug ) ) continue;
                $term = get_term_by( 'slug', $cat_slug, 'category' );
                if ( $term && ! is_wp_error( $term ) ) {
                    $resolved[] = (int) $term->term_id;
                } else {
                    error_log( 'LuxStrat[' . $lang . ']: Category slug "' . $cat_slug . '" not found. Skipping.' );
                }
            }
        }

        // Extra categories ticked in admin
        if ( ! empty( $settings['extra_category_ids'] ) && is_array( $settings['extra_category_ids'] ) ) {
            foreach ( $settings['extra_category_ids'] as $extra_id ) {
                $extra_id = (int) $extra_id;
                if ( $extra_id > 0 ) $resolved[] = $extra_id;
            }
        }

        $resolved = array_unique( $resolved );

        // Fallback
        if ( empty( $resolved ) ) {
            $fallback = ! empty( $settings['category_id'] ) ? (int) $settings['category_id'] : 0;
            return $fallback > 0 ? array( $fallback ) : array();
        }

        return $resolved;
    }

    /* ──────────────────────────────────────────────────
     *  FEATURED IMAGE
     * ────────────────────────────────────────────────── */

    /**
     * Generate featured image using DALL-E prompt pool.
     * Randomly selects one of 11 prompt variants.
     * Landscape orientation (1792x1024 → ~950×600 display).
     *
     * @param int    $post_id
     * @param string $title
     * @param array  $theme_config
     * @param array  $settings
     * @param string $lang Language code for progress tracking.
     */
    private static function generate_featured_image( $post_id, $title, $theme_config, $settings, $lang = '' ) {
        // ── Select random prompt from pool ──
        $pool      = self::$dalle_prompts;
        $pool_keys = array_keys( $pool );
        $chosen_key = $pool_keys[ array_rand( $pool_keys ) ];
        $dalle_prompt = $pool[ $chosen_key ];

        // Readable label for logging / progress
        $prompt_label = ucwords( str_replace( '_', ' ', $chosen_key ) );

        $dba_settings = get_option( 'dba_settings', array() );
        // Force landscape orientation for DALL-E 3
        $image_size   = '1792x1024';

        if ( $lang ) {
            self::update_progress( $lang, 'dalle', 75, 'DALL-E prompt: ' . $prompt_label );
        }

        error_log( 'LuxStrat[' . $lang . ']: DALL-E generating — prompt "' . $chosen_key . '" (' . $prompt_label . ') — size ' . $image_size );

        try {
            $media_id = null;

            if ( $lang ) {
                self::update_progress( $lang, 'dalle', 78, 'Calling DALL-E 3 API — generating ' . $prompt_label . '...' );
            }

            if ( class_exists( 'DBA_Image_Generator' ) ) {
                try {
                    $media_id = DBA_Image_Generator::get_instance()->generate_and_attach( $dalle_prompt, $post_id, $title, $image_size );
                    if ( is_wp_error( $media_id ) ) $media_id = null;
                } catch ( \Throwable $img_e ) {
                    error_log( 'LuxStrat[' . $lang . ']: DBA_Image_Generator failed: ' . $img_e->getMessage() . ' — falling back to direct API call.' );
                    $media_id = null;
                }
            }

            if ( ! $media_id ) {
                if ( $lang ) {
                    self::update_progress( $lang, 'dalle', 82, 'DALL-E 3 rendering image (may take 15-30s)...' );
                }
                $media_id = self::generate_image_direct( $dalle_prompt, $post_id, $title, $image_size, $dba_settings );
            }

            if ( ! $media_id || is_wp_error( $media_id ) ) {
                if ( $lang ) {
                    self::update_progress( $lang, 'dalle', 90, 'DALL-E image failed — post saved without featured image.' );
                }
                error_log( 'LuxStrat[' . $lang . ']: DALL-E image generation failed for post #' . $post_id );
                return;
            }

            if ( $lang ) {
                self::update_progress( $lang, 'dalle', 88, 'Image received. Setting as featured image...' );
            }

            set_post_thumbnail( $post_id, $media_id );

            $prefix = $lang ? DBA_LuxStrat_Intl_Dedupe::meta_prefix( $lang ) : '_dba_luxstrat_fr_';

            update_post_meta( $post_id, $prefix . 'media_id', $media_id );
            update_post_meta( $post_id, $prefix . 'dalle_prompt_key', $chosen_key );

            $alt = get_post_meta( $post_id, $prefix . 'image_alt', true );
            if ( empty( $alt ) ) {
                $primary = ! empty( $theme_config['keywords'][0] ) ? $theme_config['keywords'][0] : $theme_config['short_label'];
                $alt     = $primary . ' — ' . $title;
                if ( mb_strlen( $alt ) > 125 ) $alt = mb_substr( $alt, 0, 122 ) . '...';
            }
            update_post_meta( $media_id, '_wp_attachment_image_alt', sanitize_text_field( $alt ) );
            wp_update_post( array( 'ID' => $media_id, 'post_title' => sanitize_text_field( $title ) ) );

            if ( $lang ) {
                self::update_progress( $lang, 'dalle', 93, 'Featured image set: ' . $prompt_label . ' (landscape 950×600)' );
            }
            error_log( 'LuxStrat[' . $lang . ']: DALL-E success — prompt "' . $chosen_key . '" → media #' . $media_id );

        } catch ( \Throwable $e ) {
            error_log( 'LuxStrat: Image error for post #' . $post_id . ': ' . $e->getMessage() );
            if ( $lang ) {
                self::update_progress( $lang, 'dalle', 90, 'Image error: ' . $e->getMessage() );
            }
        }
    }

    private static function generate_image_direct( $prompt, $post_id, $title, $size, $dba_settings ) {
        $api_key = ! empty( $dba_settings['openai_api_key'] ) ? $dba_settings['openai_api_key'] : '';
        if ( empty( $api_key ) ) return null;

        $resp = wp_remote_post( 'https://api.openai.com/v1/images/generations', array(
            'timeout' => 120,
            'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key ),
            'body'    => wp_json_encode( array( 'model' => 'dall-e-3', 'prompt' => $prompt, 'n' => 1, 'size' => $size, 'quality' => 'standard', 'response_format' => 'url' ) ),
        ) );

        if ( is_wp_error( $resp ) || wp_remote_retrieve_response_code( $resp ) !== 200 ) return null;

        $data = json_decode( wp_remote_retrieve_body( $resp ), true );
        if ( empty( $data['data'][0]['url'] ) ) return null;

        if ( ! function_exists( 'media_sideload_image' ) ) {
            require_once ABSPATH . 'wp-admin/includes/media.php';
            require_once ABSPATH . 'wp-admin/includes/file.php';
            require_once ABSPATH . 'wp-admin/includes/image.php';
        }

        $tmp = download_url( $data['data'][0]['url'] );
        if ( is_wp_error( $tmp ) ) return null;

        $media_id = media_handle_sideload( array( 'name' => sanitize_file_name( 'luxstrat-' . sanitize_title( $title ) . '.png' ), 'tmp_name' => $tmp ), $post_id, $title );
        if ( is_wp_error( $media_id ) ) { @unlink( $tmp ); return null; }

        return $media_id;
    }

    /* ──────────────────────────────────────────────────
     *  THEME ROTATION
     * ────────────────────────────────────────────────── */

    private static function get_next_theme( $lang, $lang_config, $force = false ) {
        $settings      = get_option( 'dba_luxstrat_' . $lang . '_settings', array() );
        $all_slugs     = array_keys( $lang_config['themes'] ?? array() );
        $active_themes = ! empty( $settings['active_themes'] ) ? $settings['active_themes'] : $all_slugs;
        $last_theme    = get_option( 'dba_luxstrat_' . $lang . '_last_theme', '' );

        if ( empty( $active_themes ) ) return false;

        $last_index = array_search( $last_theme, $active_themes, true );
        $next_index = ( $last_index === false ) ? 0 : ( ( $last_index + 1 ) % count( $active_themes ) );
        $candidate  = $active_themes[ $next_index ];

        if ( $force ) return $candidate;

        if ( DBA_LuxStrat_Intl_Dedupe::theme_posted_this_week( $lang, $candidate ) ) {
            for ( $i = 1; $i < count( $active_themes ); $i++ ) {
                $try = $active_themes[ ( $next_index + $i ) % count( $active_themes ) ];
                if ( ! DBA_LuxStrat_Intl_Dedupe::theme_posted_this_week( $lang, $try ) ) return $try;
            }
            return false;
        }

        return $candidate;
    }

    private static function get_recent_titles( $lang, $theme_slug, $limit = 20 ) {
        $prefix = DBA_LuxStrat_Intl_Dedupe::meta_prefix( $lang );
        $query  = new WP_Query( array(
            'post_type'      => 'post',
            'post_status'    => array( 'publish', 'draft', 'pending', 'private' ),
            'posts_per_page' => $limit,
            'orderby'        => 'date',
            'order'          => 'DESC',
            'meta_query'     => array(
                'relation' => 'AND',
                array( 'key' => $prefix . 'auto_created', 'compare' => 'EXISTS' ),
                array( 'key' => $prefix . 'theme', 'value' => $theme_slug ),
            ),
            'no_found_rows' => true,
        ) );

        $titles = array();
        if ( $query->have_posts() ) {
            foreach ( $query->posts as $post ) $titles[] = $post->post_title;
        }
        return $titles;
    }

    private static function get_model() {
        $s = get_option( 'dba_settings', array() );
        return ! empty( $s['openai_model'] ) ? $s['openai_model'] : 'gpt-4o-mini';
    }
}
