<?php
/**
 * Gutenberg / Classic Translation Engine
 * SECTION 17 MOVED FROM reeid-translate.php
 *
 * Contains:
 * - Translation mini-batch logic
 * - Reinjection of translated text
 * - Preserved attribute JSON encoding
 * - Serialization helpers
 */
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}


/*==============================================================================
 SECTION 17 : TEXT EXTRACTOR + REINJECTOR (Gutenberg/Classic only)
 - Extract visible text lines from HTML
 - Translate line by line via OpenAI 
 - Reinject preserving block structure
 - Protect inline formatting (<strong>, <em>, <i>, <u>, <mark>, bold spans…)
 - Wrap RTL output (ar/he/fa/ur)
==============================================================================*/
/*==============================================================================
  17.1 — CANONICAL OPENAI SINGLE-STRING TRANSLATOR (WP-HTTP ONLY)
==============================================================================*/
if ( ! function_exists( 'reeid_openai_translate_single' ) ) {

    /**
     * Translate a single string using OpenAI
     * - System prompt is built ONLY via reeid_get_combined_prompt()
     * - Custom prompt is consumed verbatim
     * - Deterministic PHP-level preserve rules
     */
    function reeid_openai_translate_single(
        string $text,
        string $target_lang,
        string $tone = 'Neutral',
        string $prompt = '',
        bool $return_wp_error = false
    ) {

        if ( $text === '' ) {
            return $text;
        }

        $api_key = (string) get_option( 'reeid_openai_api_key', '' );

        if ( $api_key === '' ) {
            $err = new WP_Error(
                'no_api_key',
                __( 'OpenAI API key not configured.', 'reeid-translate' )
            );
            return $return_wp_error ? $err : $text;
        }

        /* ------------------------------------------------------------
         * HARD RULE: deterministic phrase preservation
         * Extract quoted strings and bypass translation if exact match
         * ------------------------------------------------------------ */
        if ( is_string( $prompt ) && $prompt !== '' ) {
            if ( preg_match_all( '/"([^"]+)"/u', $prompt, $m ) ) {
                foreach ( $m[1] as $keep ) {
                    if ( trim( $text ) === trim( $keep ) ) {
                        return $text; // DO NOT translate
                    }
                }
            }
        }

        /*
         * IMPORTANT
         * ----------
         * $prompt is already the FULL instruction block
         * (global + post + UI override).
         * We DO NOT modify it.
         */
        $system_prompt = function_exists( 'reeid_get_combined_prompt' )
            ? reeid_get_combined_prompt( 0, $target_lang, (string) $prompt )
            : (string) $prompt;
            // If formatting placeholders are present, instruct model to translate inside them.
// Otherwise bold/italic segments can be mistakenly preserved unchanged.
if ( strpos( $text, '[[PHO:' ) !== false || strpos( $text, '[[PHC:' ) !== false ) {
    $system_prompt .= "\n\n"
        . "FORMATTING MARKERS RULE:\n"
        . "- You may see markers like [[PHO:123]] and [[PHC:123]].\n"
        . "- Keep these markers EXACTLY unchanged.\n"
        . "- Translate the human text BETWEEN the markers normally.\n"
        . "- Do NOT leave the text between markers untranslated.\n";
}
// Enforce strict phrase preservation from quoted strings in the instruction prompt.
if ( ! empty( $prompt ) ) {

    if ( preg_match_all( '/"([^"]+)"/', (string) $prompt, $matches ) ) {

        $phrases = array_map( 'trim', $matches[1] );

        if ( ! empty( $phrases ) ) {

            $system_prompt .= "\n\nSTRICT PRESERVATION RULES:\n";

            foreach ( $phrases as $phrase ) {
                $system_prompt .= "- The exact phrase: \"" . $phrase . "\" MUST remain unchanged in the output.\n";
            }

            $system_prompt .= "- These phrases must NOT be translated, partially translated, or modified.\n";
        }
    }
}


        if ( function_exists( 'reeid_debug_log' ) ) {
            reeid_debug_log( 'S17/SYSTEM_PROMPT', [
                'len'     => strlen( $system_prompt ),
                'preview' => mb_substr( $system_prompt, 0, 200, 'UTF-8' ),
            ] );
        }

       $protected_map = [];

if ( ! empty( $prompt ) && preg_match_all('/"([^"]+)"/', (string) $prompt, $matches ) ) {
    foreach ( $matches[1] as $i => $phrase ) {
        $token = "__REEID_PROTECTED_" . $i . "__";
        $protected_map[ $token ] = $phrase;
        $text = str_replace( $phrase, $token, $text );
    }
}

$out = function_exists( 'reeid_translate_html_with_openai' )
    ? reeid_translate_html_with_openai(
        $text,
        'auto',
        $target_lang,
        'gutenberg',
        $tone,
        $system_prompt
    )
    : $text;

if ( $out === '' || $out === $text ) {
    return $text;
}

$out = preg_replace( '/^```.*?\n|\n```$/s', '', $out );

if ( ! empty( $protected_map ) ) {
    foreach ( $protected_map as $token => $original ) {
        $out = str_replace( $token, $original, $out );
    }
}

return trim( $out );
    }
}


/*==============================================================================
  17.2 — INLINE FORMATTING PLACEHOLDER SYSTEM
==============================================================================*/
if (! function_exists('reeid_inline_placehold_formatting')) {
    function reeid_inline_placehold_formatting(string $html, array &$ph): string
    {
        /* Normalize bold/italic spans into strong/em */
        $norm = preg_replace_callback(
            '#<span\b([^>]*)>(.*?)</span>#is',
            function ($m) {
                $attrs = strtolower($m[1]);
                $body  = $m[2];
                $bold  = preg_match('#font-weight\s*:\s*(bold|[6-9]00)#', $attrs);
                $ital  = preg_match('#font-style\s*:\s*italic#', $attrs);

                if ($bold && !$ital) return "<strong>$body</strong>";
                if ($ital && !$bold) return "<em>$body</em>";
                if ($bold && $ital)  return "<strong><em>$body</em></strong>";
                return "<span{$m[1]}>$body</span>";
            },
            $html
        );

        $ph = [];
        $id = 0;

        $tags = [
            ['<strong>', '</strong>'],
            ['<b>', '</b>'],
            ['<em>', '</em>'],
            ['<i>', '</i>'],
            ['<u>', '</u>'],
            ['<mark>', '</mark>'],
        ];

        $out = $norm;
        foreach ($tags as [$open, $close]) {
            $pattern = '#' . preg_quote($open, '#') . '(.*?)' . preg_quote($close, '#') . '#is';
            $out = preg_replace_callback($pattern, function ($m) use (&$ph, &$id, $open, $close) {
                $id++;
                $ph[$id] = ['open' => $open, 'close' => $close];
                return "[[PHO:$id]]" . $m[1] . "[[PHC:$id]]";
            }, $out);
        }

        return $out;
    }
}

if (! function_exists('reeid_inline_restore_formatting')) {
    function reeid_inline_restore_formatting(string $html, array $ph): string
    {
        foreach ($ph as $id => $p) {
            $html = str_replace("[[PHC:$id]]", $p['close'], $html);
        }
        foreach ($ph as $id => $p) {
            $html = str_replace("[[PHO:$id]]", $p['open'], $html);
        }
        return $html;
    }
}

/*==============================================================================
  17.3 — EXTRACT TEXT LINES (with currency guard)
==============================================================================*/
if ( ! function_exists( 'reeid_extract_text_lines' ) ) {
    function reeid_extract_text_lines( string $html ): array {
        $lines = [];

        if ( preg_match_all( '/>(.*?)</us', $html, $m ) ) {
            foreach ( $m[1] as $txt ) {
                $has_html = ( strpos( $txt, '<' ) !== false );

                if ( preg_match( '/^([\x{00A0}\s]*)(.*?)([\x{00A0}\s]*)$/u', $txt, $mm ) ) {
                    $left  = $mm[1];
                    $core  = $mm[2];
                    $right = $mm[3];
                } else {
                    $left  = '';
                    $core  = $txt;
                    $right = '';
                }

                // What we actually feed to the model:
                $core_for_model = $has_html ? $txt : $core;

                // -----------------------------------------------------------------
                // CURRENCY GUARD:
                // Skip pure price tokens like "$0", "$39", "€59", "¥1000", etc.
                // Pattern: optional currency symbol, number with optional decimals,
                // optional trailing currency symbol. We do NOT send these to OpenAI.
                // -----------------------------------------------------------------
                $pure = trim( $core_for_model );
                if ( $pure !== '' && preg_match( '/^\p{Sc}?\s*[0-9]+(?:[.,][0-9]+)?\s*\p{Sc}?$/u', $pure ) ) {
                    continue;
                }

                if ( $core_for_model !== '' ) {
                    $lines[] = [
                        'full'     => $txt,
                        'left'     => $left,
                        'core'     => $core_for_model,
                        'right'    => $right,
                        'has_html' => $has_html,
                    ];
                }
            }
        }

        if ( function_exists( 'reeid_debug_log' ) ) {
            reeid_debug_log( 'S17/EXTRACTED', [ 'count' => count( $lines ) ] );
        }

        return $lines;
    }
}



/*==============================================================================
  TRANSLATE LINES (GUTENBERG / CLASSIC SAFE)
  - Respects metabox tone override
  - Falls back to plugin default tone
  - Repo-safe sanitization
==============================================================================*/

if ( ! function_exists( 'reeid_translate_lines' ) ) {

    /**
     * Translate extracted text lines.
     *
     * @param array  $lines        Extracted text lines.
     * @param string $target_lang  Target language code.
     * @param string $tone         Tone override (from metabox / AJAX).
     * @param string $prompt       Additional translation instructions.
     * @return array
     */
    function reeid_translate_lines(
        array $lines,
        string $target_lang,
        string $tone = '',
        string $prompt = ''
    ): array {

        // Tone already resolved upstream; DO NOT re-resolve here.
        $tone = is_string( $tone ) && $tone !== '' ? $tone : 'Neutral';

        $map = array();

        foreach ( $lines as $i => $row ) {
            $core = is_array( $row ) && isset( $row['core'] )
                ? (string) $row['core']
                : (string) $row;

            $map[ (string) $i ] = $core;
        }

        $chunks = function_exists( 'reeid_chunk_translation_map' )
            ? reeid_chunk_translation_map( $map, 4000 )
            : array( $map );

        $translated_map = array();

        foreach ( $chunks as $chunk ) {

            $has_content = false;

            foreach ( $chunk as $v ) {
                if ( trim( (string) $v ) !== '' ) {
                    $has_content = true;
                    break;
                }
            }

            if ( ! $has_content ) {
                foreach ( $chunk as $ck => $core_in ) {
                    $translated_map[ $ck ] = (string) $core_in;
                }
                continue;
            }

            $batch_prompt =
                "Task: Translate all JSON values to {$target_lang}. Keep keys EXACTLY unchanged.\n" .
                "Translate ONLY the values. Do not add/remove keys or change JSON.\n" .
                "Return STRICT JSON only (no comments, no markdown).";

            $json_in = wp_json_encode(
                $chunk,
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
            );

           $json_prompt = trim( $batch_prompt . "\n\n" . (string) $prompt );

$json_out = function_exists( 'reeid_translate_html_with_openai' )
    ? reeid_translate_html_with_openai(
        $json_in,
        'auto',
        $target_lang,
        'gutenberg',
        $tone,
        $json_prompt
    )
    : '';

            $decoded = null;
            $matched = '';
            $clean   = '';
            $s       = array();

            if ( is_string( $json_out ) && preg_match( '/(\{.*\}|\[.*\])/s', $json_out, $m ) ) {
                $matched = (string) $m[0];

                $clean = function_exists( 'reeid_json_sanitize_controls_v2' )
                    ? reeid_json_sanitize_controls_v2( $matched, $s )
                    : $matched;

                $decoded = function_exists( 'reeid_safe_json_decode' )
                    ? reeid_safe_json_decode( $clean )
                    : json_decode( $clean, true );
            }

            if ( is_array( $decoded ) ) {
                $decoded_is_list = array_keys( $decoded ) === range( 0, count( $decoded ) - 1 );

                if ( $decoded_is_list && count( $decoded ) === count( $chunk ) ) {
                    $chunk_values = array_values( $decoded );
                    $pos = 0;

                    foreach ( $chunk as $ck => $core_in ) {
                        $translated_map[ $ck ] = isset( $chunk_values[ $pos ] ) && is_string( $chunk_values[ $pos ] )
                            ? (string) $chunk_values[ $pos ]
                            : (string) $core_in;
                        $pos++;
                    }
                } else {
                    foreach ( $chunk as $ck => $core_in ) {
                        $translated_map[ $ck ] = array_key_exists( $ck, $decoded ) && is_string( $decoded[ $ck ] )
                            ? (string) $decoded[ $ck ]
                            : (string) $core_in;
                    }
                }
            } else {
                foreach ( $chunk as $ck => $core_in ) {
                    $translated_map[ $ck ] = function_exists( 'reeid_openai_translate_single' )
                        ? (string) reeid_openai_translate_single(
                            (string) $core_in,
                            $target_lang,
                            $tone,
                            $prompt
                        )
                        : (string) $core_in;
                }
            }
        }

        $out = array();

        foreach ( $lines as $i => $row ) {
            $key      = (string) $i;
            $original = is_array( $row ) && isset( $row['core'] )
                ? (string) $row['core']
                : (string) $row;

            $out[ $i ] = array_key_exists( $key, $translated_map ) && $translated_map[ $key ] !== ''
                ? (string) $translated_map[ $key ]
                : $original;
        }

        return $out;
    }
}
/*==============================================================================
  17.5 — REINJECT TRANSLATED TEXT (PATCHED FOR CURRENCY BUG)
==============================================================================*/
if ( ! function_exists( 'reeid_reinject_lines' ) ) {
    function reeid_reinject_lines( string $html, array $en, array $tr, string $target_lang ): string {

        // Clean model output and normalize text
        $clean = function ( string $s ): string {
            $s = trim( $s );
            $s = preg_replace( '/^```.*?\n|\n```$/s', '', $s );
            $maybe = @json_decode( '"' . str_replace( '"', '\"', $s ) . '"' );
            return $maybe !== null ? $maybe : $s;
        };

        foreach ( $en as $i => $row ) {

            if ( ! isset( $tr[ $i ] ) ) {
                continue;
            }

            $translated = $clean( $tr[ $i ] );

            // ----------------------------------------------------------
            // PATCH: Prevent accidental wrapping: >$0< → >$0< (no change)
            // Remove any stray angle brackets injected by model.
            // ----------------------------------------------------------
            $translated = preg_replace( '/^>(.*?)<$/u', '$1', $translated );

            // Also remove any model-added leading/trailing brackets
            $translated = ltrim( $translated, '<>' );
            $translated = rtrim( $translated, '<>' );

            if ( $row['has_html'] ) {
                $new_full = $translated;
            } else {
                $new_full = $row['left'] . $translated . $row['right'];
            }

            // ----------------------------------------------------------
            // SAFE REPLACE: replace TEXT ONLY between > ... <
            // ----------------------------------------------------------
            $needle = preg_quote( $row['full'], '/' );
            $pattern = '/>' . $needle . '</u';

            $html = preg_replace(
                $pattern,
                '>' . $new_full . '<',
                $html,
                1
            );
        }

        // RTL wrapping
        $rtl = [ 'ar', 'he', 'fa', 'ur' ];
        if ( in_array( strtolower( $target_lang ), $rtl, true ) ) {
            $html = '<div dir="rtl" lang="' . esc_attr( $target_lang ) . '">' . $html . '</div>';
        }

        return $html;
    }
}


/*==============================================================================
  17.6 — MAIN GUTENBERG/CLASSIC EXTRACT→TRANSLATE→REINJECT
==============================================================================*/
if (! function_exists('reeid_gutenberg_classic_translate_via_extractor')) {
    function reeid_gutenberg_classic_translate_via_extractor(
        string $html,
        string $target_lang,
        string $tone = 'Neutral',
        string $prompt = ''
    ): string {

    // IMPORTANT: Prompt is already fully built upstream (system prompt).
$merged_prompt = is_string($prompt) ? trim($prompt) : '';


        /* Placeholder inline formatting */
        $ph = [];
        $html2 = reeid_inline_placehold_formatting($html, $ph);

        /* Extract */
        $en = reeid_extract_text_lines($html2);
        if (empty($en)) {
            return reeid_inline_restore_formatting($html2, $ph);
        }

        /* Translate */
        $tr = reeid_translate_lines($en, $target_lang, $tone, $merged_prompt);

        /* Reinject */
        $out = reeid_reinject_lines($html2, $en, $tr, $target_lang);

        /* Restore formatting placeholders */
        return reeid_inline_restore_formatting($out, $ph);
    }
}