<?php

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;

class TRP_IN_Deepl_Machine_Translator extends TRP_Machine_Translator {
    /**
     * Send request to Google Translation API
     *
     * @param string $source_language       Translate from language
     * @param string $language_code         Translate to language
     * @param array $strings_array          Array of string to translate
     *
     * @return array|WP_Error               Response
     */
    public function send_request( $source_language, $language_code, $strings_array, $formality = "default" ){
        /* build our translation request */

        /*
         * Adds a filter so the user can add their glossary IDs to the requests
         */

        // array input from users
        $glossary_language_pairs_user_input = apply_filters( 'trp_add_deepl_glossaries_ids', array() );

        if ( ! empty( $glossary_language_pairs_user_input ) ) {

            $source_language_without_region = strtolower( strtok( $source_language, '_' ) );
            $target_language_without_region = strtolower( strtok( $language_code, '_' ) );

            //retrieve the inputed glossary id for the current source-target langs pairs
            $glossary_id = $this->sort_the_user_input_data_for_glossary( $glossary_language_pairs_user_input, $source_language, $language_code );

            if ( $glossary_id ) {

                $translation_request = '&source_lang=' . $source_language_without_region;
                $translation_request .= '&target_lang=' . $target_language_without_region;
                $translation_request .= '&split_sentences=1';
                $translation_request .= '&glossary_id=' . $glossary_id;

                foreach ( $strings_array as $new_string ) {
                    $translation_request .= '&text=' . rawurlencode( html_entity_decode( $new_string, ENT_QUOTES ) );
                }
            }

        }

        if ( !isset( $translation_request ) ) {
            $translation_request = '&source_lang=' . $source_language;
            $translation_request .= '&target_lang=' . $language_code;
            $translation_request .= '&split_sentences=1';
            if ( $formality != "default" ) {
                $translation_request .= '&formality=' . $formality;
            }
            foreach ( $strings_array as $new_string ) {
                $translation_request .= '&text=' . rawurlencode( html_entity_decode( $new_string, ENT_QUOTES ) );
            }
        }
/*
 * IMPORTANT: As of March 2025, DeepL is no longer allowing
 * authentification of a request to any endpoint by sending an API key in a query parameter.
 * Instead, send your API key in an HTTP header named "Authorization"

   Other changes:  GET requests or query parameters will not be allowed anymore, "/translate" will accept
   only POST requests with data included in the request body

   EX:
   POST /v2/translate HTTP/2
   Host: api.deepl.com
   Authorization: DeepL-Auth-Key [yourAuthKey]

   SOURCE: https://developers.deepl.com/docs/resources/breaking-changes-change-notices/march-2025-deprecating-get-requests-to-translate-and-authenticating-with-auth_key
 */
        $request_headers = [
            'Referer' => $this->get_referer(),
            'Authorization' => "DeepL-Auth-Key {$this->get_api_key()}"
        ];

        /* Due to url length restrictions we need so send a POST request faked as a GET request and send the strings in the body of the request and not in the URL */
        $response = wp_remote_post( "{$this->get_api_url()}/translate", array(
                'method'    => 'POST',
                'timeout'   => 45,
                'headers'   => $request_headers,
                'body'      => $translation_request,
            )
        );

        if ( isset( $glossary_id ) ) {
            do_action( 'trp_is_deepl_glossary_id_valid', $response );
        }

        return $response;
    }

    /**
     * Returns an array with the API provided translations of the $new_strings array.
     *
     * @param array $new_strings            array with the strings that need translation. The keys are the node number in the DOM so we need to preserve the m
     * @param string $target_language_code  wp language code of the language that we will be translating to. Not equal to the google language code
     * @param string $source_language_code  wp language code of the language that we will be translating from. Not equal to the google language code
     * @return array                        array with the translation strings and the preserved keys or an empty array if something went wrong
     */
    public function translate_array( $new_strings, $target_language_code, $source_language_code = null ){

        if ( $source_language_code == null )
            $source_language_code = $this->settings['default-language'];

        if( empty( $new_strings ) || !$this->verify_request_parameters( $target_language_code, $source_language_code ) )
            return [];

        $translated_strings = [];

        $source_language = apply_filters( 'trp_deepl_source_language', $this->machine_translation_codes[$source_language_code], $source_language_code, $target_language_code );
        $target_language = apply_filters( 'trp_deepl_target_language', $this->machine_translation_codes[$target_language_code], $source_language_code, $target_language_code );

        $formality = $this->get_request_formality_for_language($target_language_code);

        // split the strings array in 50 string parts;
        $new_strings_chunks = array_chunk( $new_strings, 50, true );

        foreach( $new_strings_chunks as $new_strings_chunk ){

            // Skip request if rate limited recently
            if ( get_transient( 'trp_deepl_translation_throttle' ) ) {
                continue;
            }

            $response = $this->send_request( $source_language, $target_language, $new_strings_chunk, $formality );

            // this runs only if "Log machine translation queries." is set to Yes.
            $this->machine_translator_logger->log([
                'strings'     => serialize( $new_strings_chunk),
                'response'    => serialize( $response ),
                'lang_source' => $source_language,
                'lang_target' => $target_language,
            ]);

            if ( is_array( $response ) && ! is_wp_error( $response ) && isset( $response['response'] ) &&
                isset( $response['response']['code']) && $response['response']['code'] == 200 ) {

                $this->machine_translator_logger->count_towards_quota( $new_strings_chunk );

                $translation_response = json_decode( $response['body'] );

                /* if we have strings build the translation strings array and make sure we keep the original keys from $new_string */
                $translations = ( empty( $translation_response->translations ) )? array() : $translation_response->translations;
                $i            = 0;

                foreach( $new_strings_chunk as $key => $old_string ){

                    if( isset( $translations[$i] ) && ! empty( $translations[$i]->text ) ) {
                        $translated_strings[ $key ] = $translations[ $i ]->text;
                    }else{
                        /*  In some cases when API doesn't have a translation for a particular string,
                        translation is returned empty instead of same string. Setting original string as translation
                        prevents TP from keep trying to submit same string for translation endlessly.  */
                        $translated_strings[ $key ] = $old_string;
                    }

                    $i++;

                }

                if( $this->machine_translator_logger->quota_exceeded() )
                    break;

            }

            if ( is_array( $response ) && ! is_wp_error( $response ) && isset( $response['response'] ) &&
                isset( $response['response']['code']) && $response['response']['code'] == 429 ) {
                // Rate limit hit: prevent further calls for 10 seconds
                $throttle_duration = apply_filters( 'trp_deepl_throttle_duration', 10 );
                set_transient( 'trp_deepl_translation_throttle', true, $throttle_duration );
                break;
            }

        }

        return $translated_strings;
    }

    public function get_formality_setting_for_language($target_language_code){

        $formality = "default";

        if(isset($this->settings["translation-languages-formality-parameter"][ $target_language_code ])) {
            if ( $this->settings["translation-languages-formality-parameter"][ $target_language_code ] == 'informal'){
                $formality = "less";
            }else{
                if($this->settings["translation-languages-formality-parameter"][ $target_language_code ] == 'formal'){
                    $formality = "more";
                }
            }
        }

        return $formality;
    }

    public function get_languages_that_support_formality(){

        $data = get_option('trp_db_stored_data', array() );

        if(!isset($data['trp_mt_supported_languages'][$this->settings['trp_machine_translation_settings']['translation-engine']]['formality-supported-languages'])) {
            $this->check_languages_availability($this->settings['translation-languages'], true);
            $data = get_option('trp_db_stored_data', array());
        }

        $formality_supported_languages = isset( $data['trp_mt_supported_languages'][$this->settings['trp_machine_translation_settings']['translation-engine']]['formality-supported-languages'] ) ?
            $data['trp_mt_supported_languages'][$this->settings['trp_machine_translation_settings']['translation-engine']]['formality-supported-languages'] : [];

        return $formality_supported_languages;
    }

    public function get_request_formality_for_language($target_language_code){

        $formality = $this->get_formality_setting_for_language($target_language_code);
        $formality_supported_languages = $this->get_languages_that_support_formality();

        if(isset($formality_supported_languages[$target_language_code]) && $formality_supported_languages[$target_language_code] == "true"){
            return $formality;
        }else{
            return 'default';
        }
    }

    public function check_formality() {

        $formality_supported_languages = [];
        $language_iso_codes = [];

        $request_url = "{$this->get_api_url()}/languages";

        $request_headers = [
            'Referer' => $this->get_referer(),
            'Authorization' => "DeepL-Auth-Key {$this->get_api_key()}"
        ];

        $deepl_response = wp_remote_post( $request_url, array(
                'method'  => 'POST',
                'body' => 'type=target',
                'timeout' => 45,
                'headers' => $request_headers,
            )
        );

        // Check for WP_Error
        if (is_wp_error($deepl_response)) {
            error_log('DeepL API request failed: ' . $deepl_response->get_error_message());
            return;
        }

        // Ensure response is an array and contains the response code
        if (!is_array($deepl_response) || !isset($deepl_response['response']['code'])) {
            error_log('Invalid response from DeepL API');
            return;
        }

        $deepl_response_code = $deepl_response['response']['code'];

        if ($deepl_response_code !== 200) {
            error_log('DeepL API request returned unexpected status code: ' . $deepl_response_code);
            return;
        }

        $all_languages         = $this->trp_languages->get_wp_languages();
        $supported_languages   = json_decode( wp_remote_retrieve_body( $deepl_response ) );
        $portuguese_variations = [
            (object) [
                'language'           => 'pt',
                'name'               => 'Portuguese',
                'supports_formality' =>  true
            ]
        ];

        $supported_languages = array_merge( $supported_languages, $portuguese_variations );

        foreach ( $all_languages as $language ){
            $language_iso_codes[ $language['language'] ] = reset( $language['iso'] );
        }

        // In some cases, the ISO codes provided by DeepL do not accurately match with our list - therefore, we match them manually
        $exceptions_map = [
            'EN-GB' => 'en_GB',
            'EN-US' => 'en_US'
        ];

        foreach ( $supported_languages as $supported_language ){
            if ( array_key_exists( $supported_language->language, $exceptions_map ) ){
                $formality_supported_languages[ $exceptions_map[$supported_language->language] ] = $supported_language->supports_formality ? 'true' : 'false';                continue;
            }

            $matched_languages = array_keys( $language_iso_codes, strtolower( $supported_language->language ) );

            if ($matched_languages) {
                foreach ($matched_languages as $matched_language) {
                    $formality_supported_languages[$matched_language] = $supported_language->supports_formality ? 'true' : 'false';
                }
            }
        }

        return apply_filters( 'trp_deepl_formality_languages', $formality_supported_languages );
    }

    /**
     * Send a test request to verify if the functionality is working
     */
    public function test_request(){

        return $this->send_request( 'en', 'es', [ 'Where are you from ?' ], 'less' );

    }

    public function get_api_key(){

        return isset( $this->settings['trp_machine_translation_settings'], $this->settings['trp_machine_translation_settings']['deepl-api-key'] ) ? $this->settings['trp_machine_translation_settings']['deepl-api-key'] : false;

    }


    public function get_supported_languages(){
        if ( $this->get_api_key() ) {

            $request_headers = [
                'Referer' => $this->get_referer(),
                'Authorization' => "DeepL-Auth-Key {$this->get_api_key()}"
            ];

            $response = wp_remote_post( "{$this->get_api_url()}/languages", array(
                    'method'  => 'POST',
                    'timeout' => 45,
                    'headers' => $request_headers,
                )
            );

            if ( is_array( $response ) && !is_wp_error( $response ) && isset( $response['response'] ) &&
                isset( $response['response']['code'] ) && $response['response']['code'] == 200 ) {
                $data                = json_decode( $response['body'] );
                $supported_languages = array();
                foreach ( $data as $data_entry ) {
                    $supported_languages[] = strtolower( $data_entry->language );
                }
                return apply_filters( 'trp_deepl_supported_languages', $supported_languages );
            }
        }

        return array();
    }

    public function get_engine_specific_language_codes($languages){

        $iso_translation_codes = $this->trp_languages->get_iso_codes($languages);
        $engine_specific_languages = array();
        foreach( $languages as $language ) {
            /* All combinations of source and target languages are supported.
            Target language code can be country specific. Source language code is not. So the source language code is used here.
            */
            $engine_specific_languages[] = apply_filters( 'trp_deepl_source_language', $iso_translation_codes[ $language ], $language, null );
        }
        return $engine_specific_languages;
    }

    public function get_api_url(){
       if( isset( $this->settings['trp_machine_translation_settings']['deepl-api-type'] ) && $this->settings['trp_machine_translation_settings']['deepl-api-type'] == 'free' )
           return 'https://api-free.deepl.com/v2';

       return 'https://api.deepl.com/v2';
    }

    public function check_api_key_validity() {

        $machine_translator = $this;
        $translation_engine = $this->settings['trp_machine_translation_settings']['translation-engine'];
        $api_key            = $machine_translator->get_api_key();

        $is_error       = false;
        $return_message = '';

        if ( 'deepl' === $translation_engine && $this->settings['trp_machine_translation_settings']['machine-translation'] === 'yes') {

            if ( isset( $this->correct_api_key ) && $this->correct_api_key != null ) {
                return $this->correct_api_key;
            }

            if ( empty( $api_key ) ) {
                $is_error       = true;
                $return_message = __( 'Please enter your DeepL API key.', 'translatepress-multilingual' );
            } else {
                // Perform test.
                $is_error = false;
                $response = $machine_translator->test_request();
                $code     = wp_remote_retrieve_response_code( $response );
                if ( 200 !== $code ) {

                    $translate_response = TRP_IN_DeepL::deepl_response_codes( $code );

                    $is_error       = true;
                    $return_message = $translate_response['message'];
                }
            }
            $this->correct_api_key = array(
                'message' => $return_message,
                'error'   => $is_error,
            );
        }

        return array(
            'message' => $return_message,
            'error'   => $is_error,
        );
    }

    /*
     * Sort the inputed array in order to get the correct glossary_id necessary
     */
    public function sort_the_user_input_data_for_glossary( $array_of_values, $current_source_lang, $current_target_lang ){

        if ( is_array( $array_of_values ) ){
            foreach ( $array_of_values as $source_language => $target_gloss_id_pairs ){
                if ( strtolower( $source_language ) == $current_source_lang ){
                    foreach ( $target_gloss_id_pairs as $target_language => $glossary_id ){
                        if ( strtolower( $target_language ) == $current_target_lang ){

                            return $glossary_id;

                        }
                    }
                }
            }
        }

        return false;
    }

}
