<?php
/**
 * Admin logic and settings handlers for the plugin.
 *
 * This file contains all administrative functionality for the plugin,
 * including registration of settings pages, rendering admin UI sections,
 * saving plugin options, handling product suggestion controls, processing
 * content-generation configurations, and managing API log actions.
 *
 * Responsibilities:
 * - Outputs the General Settings page (API key, defaults, behaviors).
 * - Handles Product Suggestion settings (template, filters, visibility).
 * - Handles Content Generation settings (languages, prompts, automation).
 * - Registers and renders the API Logs screen for review and debugging.
 * - Provides AJAX/REST handlers for log retrieval and filtering.
 * - Validates and sanitizes all admin-submitted data.
 *
 * @package Magecomp AI Description Generator for WooCommerce
 */

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

require_once dirname( __DIR__ ) . '/vendor/autoload.php';

use Yethee\Tiktoken\EncoderProvider;
use Yethee\Tiktoken\Encoder;

/**
 * Get available language options for plugin.
 * This function returns an associative array of language codes and
 * their corresponding display names, which can be used in settings
 * dropdowns or other UI elements.
 *
 * @since 1.0.0
 * @return array Language code => Language name.
 */
function descgpt_get_language_options() {
	return array(
		array(
			'id'   => 'Afrikaans',
			'name' => 'Afrikaans',
		),
		array(
			'id'   => 'Albanian',
			'name' => 'Albanian',
		),
		array(
			'id'   => 'Arabic',
			'name' => 'Arabic',
		),
		array(
			'id'   => 'Amharic',
			'name' => 'Amharic',
		),
		array(
			'id'   => 'Armenian',
			'name' => 'Armenian',
		),
		array(
			'id'   => 'Azerbaijani',
			'name' => 'Azerbaijani',
		),
		array(
			'id'   => 'Basque',
			'name' => 'Basque',
		),
		array(
			'id'   => 'Belarusian',
			'name' => 'Belarusian',
		),
		array(
			'id'   => 'Bengali',
			'name' => 'Bengali',
		),
		array(
			'id'   => 'Bosnian',
			'name' => 'Bosnian',
		),
		array(
			'id'   => 'Bulgarian',
			'name' => 'Bulgarian',
		),
		array(
			'id'   => 'Catalan',
			'name' => 'Catalan',
		),
		array(
			'id'   => 'Cebuano',
			'name' => 'Cebuano',
		),
		array(
			'id'   => 'Chichewa',
			'name' => 'Chichewa',
		),
		array(
			'id'   => 'Chinese-Simplified',
			'name' => 'Chinese (Simplified)',
		),
		array(
			'id'   => 'Chinese-Traditional',
			'name' => 'Chinese (Traditional)',
		),
		array(
			'id'   => 'Croatian',
			'name' => 'Croatian',
		),
		array(
			'id'   => 'Czech',
			'name' => 'Czech',
		),
		array(
			'id'   => 'Danish',
			'name' => 'Danish',
		),
		array(
			'id'   => 'Dutch',
			'name' => 'Dutch',
		),
		array(
			'id'   => 'English',
			'name' => 'English',
		),
		array(
			'id'   => 'Esperanto',
			'name' => 'Esperanto',
		),
		array(
			'id'   => 'Estonian',
			'name' => 'Estonian',
		),
		array(
			'id'   => 'Filipino',
			'name' => 'Filipino',
		),
		array(
			'id'   => 'Faroese',
			'name' => 'Faroese',
		),
		array(
			'id'   => 'Finnish',
			'name' => 'Finnish',
		),
		array(
			'id'   => 'French',
			'name' => 'French',
		),
		array(
			'id'   => 'Galician',
			'name' => 'Galician',
		),
		array(
			'id'   => 'Georgian',
			'name' => 'Georgian',
		),
		array(
			'id'   => 'German',
			'name' => 'German',
		),
		array(
			'id'   => 'Greek',
			'name' => 'Greek',
		),
		array(
			'id'   => 'Gujarati',
			'name' => 'Gujarati',
		),
		array(
			'id'   => 'Haitian',
			'name' => 'Haitian',
		),
		array(
			'id'   => 'Hausa',
			'name' => 'Hausa',
		),
		array(
			'id'   => 'Hebrew',
			'name' => 'Hebrew',
		),
		array(
			'id'   => 'Hindi',
			'name' => 'Hindi',
		),
		array(
			'id'   => 'Hmong',
			'name' => 'Hmong',
		),
		array(
			'id'   => 'Hungarian',
			'name' => 'Hungarian',
		),
		array(
			'id'   => 'Icelandic',
			'name' => 'Icelandic',
		),
		array(
			'id'   => 'Igbo',
			'name' => 'Igbo',
		),
		array(
			'id'   => 'Indonesian',
			'name' => 'Indonesian',
		),
		array(
			'id'   => 'Irish',
			'name' => 'Irish',
		),
		array(
			'id'   => 'Italian',
			'name' => 'Italian',
		),
		array(
			'id'   => 'Japanese',
			'name' => 'Japanese',
		),
		array(
			'id'   => 'Javanese',
			'name' => 'Javanese',
		),
		array(
			'id'   => 'Kannada',
			'name' => 'Kannada',
		),
		array(
			'id'   => 'Khmer',
			'name' => 'Khmer',
		),
		array(
			'id'   => 'Korean',
			'name' => 'Korean',
		),
		array(
			'id'   => 'Kazakh',
			'name' => 'Kazakh',
		),
		array(
			'id'   => 'Lao',
			'name' => 'Lao',
		),
		array(
			'id'   => 'Latin',
			'name' => 'Latin',
		),
		array(
			'id'   => 'Latvian',
			'name' => 'Latvian',
		),
		array(
			'id'   => 'Lithuanian',
			'name' => 'Lithuanian',
		),
		array(
			'id'   => 'Macedonian',
			'name' => 'Macedonian',
		),
		array(
			'id'   => 'Malay',
			'name' => 'Malay',
		),
		array(
			'id'   => 'Malagasy',
			'name' => 'Malagasy',
		),
		array(
			'id'   => 'Maltese',
			'name' => 'Maltese',
		),
		array(
			'id'   => 'Maori',
			'name' => 'Maori',
		),
		array(
			'id'   => 'Marathi',
			'name' => 'Marathi',
		),
		array(
			'id'   => 'Malayalam',
			'name' => 'Malayalam',
		),
		array(
			'id'   => 'Mongolian',
			'name' => 'Mongolian',
		),
		array(
			'id'   => 'Norwegian',
			'name' => 'Norwegian',
		),
		array(
			'id'   => 'Nepali',
			'name' => 'Nepali',
		),
		array(
			'id'   => 'Persian',
			'name' => 'Persian',
		),
		array(
			'id'   => 'Polish',
			'name' => 'Polish',
		),
		array(
			'id'   => 'Portuguese',
			'name' => 'Portuguese',
		),
		array(
			'id'   => 'Punjabi',
			'name' => 'Punjabi',
		),
		array(
			'id'   => 'Romanian',
			'name' => 'Romanian',
		),
		array(
			'id'   => 'Russian',
			'name' => 'Russian',
		),
		array(
			'id'   => 'Serbian',
			'name' => 'Serbian',
		),
		array(
			'id'   => 'Slovak',
			'name' => 'Slovak',
		),
		array(
			'id'   => 'Slovenian',
			'name' => 'Slovenian',
		),
		array(
			'id'   => 'Sesotho',
			'name' => 'Sesotho',
		),
		array(
			'id'   => 'Somali',
			'name' => 'Somali',
		),
		array(
			'id'   => 'Sinhalese',
			'name' => 'Sinhalese',
		),
		array(
			'id'   => 'Spanish',
			'name' => 'Spanish',
		),
		array(
			'id'   => 'Swahili',
			'name' => 'Swahili',
		),
		array(
			'id'   => 'Sundanese',
			'name' => 'Sundanese',
		),
		array(
			'id'   => 'Swedish',
			'name' => 'Swedish',
		),
		array(
			'id'   => 'Tajik',
			'name' => 'Tajik',
		),
		array(
			'id'   => 'Tamil',
			'name' => 'Tamil',
		),
		array(
			'id'   => 'Telugu',
			'name' => 'Telugu',
		),
		array(
			'id'   => 'Thai',
			'name' => 'Thai',
		),
		array(
			'id'   => 'Turkish',
			'name' => 'Turkish',
		),
		array(
			'id'   => 'Ukrainian',
			'name' => 'Ukrainian',
		),
		array(
			'id'   => 'Urdu',
			'name' => 'Urdu',
		),
		array(
			'id'   => 'Uzbek',
			'name' => 'Uzbek',
		),
		array(
			'id'   => 'Vietnamese',
			'name' => 'Vietnamese',
		),
		array(
			'id'   => 'Welsh',
			'name' => 'Welsh',
		),
		array(
			'id'   => 'Xhosa',
			'name' => 'Xhosa',
		),
		array(
			'id'   => 'Yiddish',
			'name' => 'Yiddish',
		),
		array(
			'id'   => 'Yoruba',
			'name' => 'Yoruba',
		),
		array(
			'id'   => 'Zulu',
			'name' => 'Zulu',
		),
	);
}

/**
 * Get available AI model options for the Description Generator plugin.
 * This function returns an array of models supported by the plugin,
 * each containing an 'id' for the API and a human-readable 'name'
 * for displaying in settings or dropdowns.
 *
 * @since 1.0.0
 * @return array List of AI models, each with 'id' and 'name' keys.
 */
function descgpt_get_model_options() {
	return array(
		array(
			'id'   => 'gpt-4o-mini',
			'name' => 'GPT-4o Mini (Recommended)',
		),
		array(
			'id'   => 'gpt-4.1-mini',
			'name' => 'GPT-4.1 Mini',
		),
		array(
			'id'   => 'o3-mini',
			'name' => 'O3 Mini',
		),
		array(
			'id'   => 'gpt-4o',
			'name' => 'GPT-4o',
		),
		array(
			'id'   => 'gpt-4.1',
			'name' => 'GPT-4.1',
		),
		array(
			'id'   => 'o3',
			'name' => 'O3',
		),
		array(
			'id'   => 'o4-mini',
			'name' => 'O4 Mini',
		),
	);
}

/**
 * Default content generation prompts.
 */
define(
	'MCADGW_DESC_GEN_CHTGPT_PROMPT_PRODUCT',
	'You are an expert e-commerce content writer and SEO specialist.

Generate content for **product** named **[product name]** in **[language]**.

--- STRICT FORMAT ---

### 1. PRODUCT ONLY
Short Description: 

Full Description: 

[END_OF_PRODUCT_CONTENT]

--- STOP ---
STOP IMMEDIATELY after [END_OF_PRODUCT_CONTENT].
DO NOT write "ENDOFPRODUCTCONTENT" or any variation.

--- LENGTH ---
Write approximately [limit_value] [limit_type] in total.
- Short: 20–30% of total
- Full: 70–80% of total
- Use vivid, descriptive language.

--- END ---
Output ONLY the content up to [END_OF_PRODUCT_CONTENT].'
);

// For CATEGORIES, TAGS, BRANDS taxonomies.
define(
	'DESCGPT_PROMPT_TAXONOMY',
	'You are an expert e-commerce content writer and SEO specialist.

Generate a compelling, SEO-optimized description for the **[type]** named **[product name]** in **[language]**.

Write approximately [limit_value] [limit_type].

Rules:
- Write in natural, engaging paragraphs.
- Do NOT use headers, numbered lists, bullet points, or labels like "Short Description:", "Full Description:", etc.
- Do NOT use markdown, code blocks, or delimiters like [END_OF_PRODUCT_CONTENT].
- Do NOT add any explanations or extra text.
- Output ONLY the plain text description — nothing else.

Just write the description naturally.'
);

/**
 * Sanitize a select field input by validating against allowed options.
 *
 * @since 1.0.0
 *
 * @param string $input The input value to sanitize.
 * @param string $field The field name.
 * @return string Sanitized value if valid; otherwise, an empty string.
 */
function descgpt_sanitize_select( $input, $field ) {
	$allowed = array();

	if ( 'descgpt_model' === $field ) {
		$allowed = array_column( descgpt_get_model_options(), 'id' );
	} elseif ( 'descgpt_language' === $field ) {
		$allowed = array_column( descgpt_get_language_options(), 'id' );
	}

	return in_array( $input, $allowed, true ) ? $input : '';
}

/**
 * Handle saving of general settings via admin-post.
 */
function descgpt_handle_settings_save() {
	if ( 'POST' !== sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) {
		wp_die( esc_html__( 'Invalid request method.', 'magecomp-ai-description-generator-for-woocommerce' ), 400 );
	}

	if ( ! current_user_can( 'manage_options' ) ) {
		wp_die( esc_html__( 'Unauthorized.', 'magecomp-ai-description-generator-for-woocommerce' ), 403 );
	}

	check_admin_referer( 'descgpt_save_settings' );

	$updated = array();

	// === 1. API Key ===
	$api_key = isset( $_POST['descgpt_api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['descgpt_api_key'] ) ) : '';

	if ( update_option( 'descgpt_api_key', $api_key, true ) ) {
		$updated[] = 'api_key';
	}

	// === 2. Model ===
	$model_input = isset( $_POST['descgpt_model'] ) ? sanitize_text_field( wp_unslash( $_POST['descgpt_model'] ) ) : '';
	$model_final = descgpt_sanitize_select( $model_input, 'descgpt_model' );
	if ( empty( $model_final ) ) {
		$model_final = 'gpt-4o-mini';
	}

	if ( update_option( 'descgpt_model', $model_final, true ) ) {
		$updated[] = 'model';
	}

	// === 3. Temperature ===
	$temp = max( 0.0, min( 1.0, isset( $_POST['descgpt_temperature'] ) ? floatval( wp_unslash( $_POST['descgpt_temperature'] ) ) : 0.5 ) );

	$current_temp = floatval( get_option( 'descgpt_temperature', 0.5 ) );

	if ( abs( $current_temp - $temp ) > 0.0001 ) {
		if ( update_option( 'descgpt_temperature', $temp, true ) ) {
			$updated[] = 'temperature';
		}
	}

	// === 4. Language ===
	$language_input = isset( $_POST['descgpt_language'] ) ? sanitize_text_field( wp_unslash( $_POST['descgpt_language'] ) ) : '';
	$language_final = descgpt_sanitize_select( $language_input, 'descgpt_language' );
	if ( empty( $language_final ) ) {
		$language_final = 'English';
	}

	if ( update_option( 'descgpt_language', $language_final, true ) ) {
		$updated[] = 'language';
	}

	if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
		// === 5. Recommendation Prompt ===

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$rec_prompt = isset( $_POST['descgpt_recommendation_prompt'] ) ? wp_kses_post( trim( wp_unslash( $_POST['descgpt_recommendation_prompt'] ) ) ) : MCADGW_DESC_GEN_CHTGPT_DEFAULT_RECOMMENDATION_PROMPT;

		$current_prompt = get_option( 'descgpt_recommendation_prompt' );
		if ( $rec_prompt !== $current_prompt ) {
			update_option( 'descgpt_recommendation_prompt', $rec_prompt, true );
			$updated[] = 'recommendation_prompt';
		}
	}

	// === Success Message ===
	if ( ! empty( $updated ) ) {
		add_settings_error(
			'descgpt_settings',
			'settings_updated',
			esc_html__( 'Settings saved successfully!', 'magecomp-ai-description-generator-for-woocommerce' ),
			'success'
		);
	}

	set_transient( 'settings_errors', get_settings_errors(), 30 );

	$redirect_url = add_query_arg(
		array(
			'page'             => 'descgpt-general-settings',
			'settings-updated' => '1',
		),
		admin_url( 'admin.php' )
	);

	wp_safe_redirect( esc_url_raw( $redirect_url ) );
	exit;
}
add_action( 'admin_post_descgpt_save_settings', 'descgpt_handle_settings_save' );

/**
 * Retrieve all general plugin settings.
 *
 * Returns an array of sanitized general settings including API key, model,
 * temperature, language, prompts, and available models and languages.
 *
 * @since 1.0.0
 *
 * @return array Associative array of plugin general settings.
 */
function descgpt_get_general_settings() {
	$settings = array(
		'api_key'     => sanitize_text_field( get_option( 'descgpt_api_key', '' ) ),
		'model'       => sanitize_text_field( get_option( 'descgpt_model', 'gpt-4o-mini' ) ),
		'temperature' => floatval( get_option( 'descgpt_temperature', 0.5 ) ),
		'language'    => sanitize_text_field( get_option( 'descgpt_language', 'English' ) ),
		'languages'   => descgpt_get_language_options(),
		'models'      => descgpt_get_model_options(),
	);

	if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
		$settings['recommendation_prompt'] = wp_kses_post( get_option( 'descgpt_recommendation_prompt', '' ) );
	}

	return $settings;
}

/**
 * Verify the provided OpenAI API key via AJAX.
 * This function checks the submitted API key by making a request to the OpenAI
 * models endpoint. Returns a JSON response indicating success or failure.
 * Access is restricted via a nonce and only available to users with proper capabilities.
 *
 * @since 1.0.0
 * @return void Sends JSON response with success or error message.
 */
function descgpt_verify_api_key() {
	check_ajax_referer( 'descgpt_verify_key_nonce', 'security' );

	$api_key = isset( $_POST['api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['api_key'] ) ) : '';

	if ( empty( $api_key ) ) {
		wp_send_json_error( array( 'message' => 'Please enter an API key.' ) );
	}

	$response = wp_remote_get(
		'https://api.openai.com/v1/models',
		array(
			'headers' => array(
				'Authorization' => 'Bearer ' . $api_key,
			),
			'timeout' => 15,
		)
	);

	if ( is_wp_error( $response ) ) {
		wp_send_json_error( array( 'message' => 'Request failed. Please try again.' ) );
	}

	$code = wp_remote_retrieve_response_code( $response );
	$body = wp_remote_retrieve_body( $response );

	if ( 200 !== $code ) {
		wp_send_json_error( array( 'message' => 'Invalid API key or unable to fetch models.' ) );
	}

	$data = json_decode( $body, true );
	if ( empty( $data['data'] ) ) {
		wp_send_json_error( array( 'message' => 'No models returned.' ) );
	}

	// Extract model IDs.
	$models = array_map(
		function ( $item ) {
			return $item['id'] ?? '';
		},
		$data['data']
	);

	wp_send_json_success(
		array(
			'message' => 'API key verified successfully!',
			'models'  => $models,
		)
	);
}
add_action( 'wp_ajax_descgpt_verify_api_key', 'descgpt_verify_api_key' );

// After register_setting calls.
add_action(
	'updated_option',
	function ( $option ) {
		if ( strpos( $option, 'descgpt_' ) === 0 ) {
		}
	},
	10,
	1
);

add_action(
	'added_option',
	function ( $option ) {
		if ( strpos( $option, 'descgpt_' ) === 0 ) {
		}
	},
	10,
	1
);

/**
 * Sanitize a sensitive text input.
 * Trims the input and applies sanitize_text_field.
 *
 * @since 1.0.0
 * @param string $input Input value to sanitize.
 * @return string Sanitized text.
 */
function descgpt_sanitize_sensitive( $input ) {
	return sanitize_text_field( trim( $input ) );
}

/**
 * Sanitize a temperature value for AI generation.
 * Ensures the value is a float between 0.0 and 1.0, rounded to 1 decimal place.
 *
 * @since 1.0.0
 * @param float|int $value Temperature value to sanitize.
 * @return float Sanitized temperature.
 */
function descgpt_sanitize_temperature( $value ) {
	$value = floatval( $value );

	if ( $value < 0.0 ) {
		$value = 0.0;
	} elseif ( $value > 1.0 ) {
		$value = 1.0;
	}

	return round( $value, 1 );
}

/**
 * AJAX handler to fetch available AI models.
 * Checks user capability, verifies nonce, validates the API key,
 * and returns a list of AI models for the plugin settings.
 * Responds with JSON success containing the models array, or JSON error messages.
 *
 * @since 1.0.0
 * @return void Outputs JSON response and terminates.
 */
function descgpt_get_models_ajax() {
	check_ajax_referer( 'descgpt_nonce', 'nonce' );

	if ( ! current_user_can( 'manage_options' ) ) {
		wp_send_json_error( array( 'message' => 'Unauthorized.' ) );
	}

	$api_key = ! empty( $_POST['api_key'] )
		? sanitize_text_field( wp_unslash( $_POST['api_key'] ) )
		: get_option( 'descgpt_api_key', '' );

	if ( empty( $api_key ) ) {
		wp_send_json_error( array( 'message' => 'No API key.' ) );
	}

	$response = wp_remote_get(
		'https://api.openai.com/v1/models',
		array(
			'headers' => array( 'Authorization' => 'Bearer ' . $api_key ),
			'timeout' => 20,
		)
	);

	if ( is_wp_error( $response ) ) {
		wp_send_json_error( array( 'message' => 'Connection failed.' ) );
	}

	$code = wp_remote_retrieve_response_code( $response );
	$body = wp_remote_retrieve_body( $response );

	if ( 200 !== $code ) {
		$data = json_decode( $body, true );
		$msg  = $data['error']['message'] ?? 'Invalid API key';
		wp_send_json_error( array( 'message' => $msg ) );
	}

	$data = json_decode( $body, true );

	$allowed_models = descgpt_get_model_options();
	$available_ids  = array_column( $data['data'], 'id' );
	$final_models   = array();

	foreach ( $allowed_models as $model ) {
		if ( in_array( $model['id'], $available_ids, true ) ) {
			$final_models[] = $model;
		}
	}

	if ( empty( $final_models ) ) {
		$final_models = $allowed_models;
	}

	wp_send_json_success( $final_models );
}
add_action( 'wp_ajax_descgpt_get_models', 'descgpt_get_models_ajax' );

/**
 * Sanitize a checkbox input.
 * Ensures the value is either 1 (checked) or 0 (unchecked).
 *
 * @since 1.0.0
 * @param mixed $input The input value.
 * @return int 1 if checked, 0 otherwise.
 */
function descgpt_sanitize_checkbox( $input ) {
	return ( 1 === $input || '1' === $input ) ? 1 : 0;
}


if ( ! function_exists( 'descgpt_call_openai_api' ) ) {
	/**
	 * Calls the OpenAI API and caches the result.
	 *
	 * @param array  $settings Plugin settings.
	 * @param string $full_prompt Full prompt for API.
	 * @param string $cache_key Cache key for transient.
	 * @param int    $order_id Order ID for logging.
	 * @param string $functionality Functionality (e.g., 'suggestions', 'content').
	 * @param string $product_name Product name, if applicable.
	 * @return array|bool Suggestions array or false on failure.
	 */
	function descgpt_call_openai_api( $settings, $full_prompt, $cache_key, $order_id = 0, $functionality = 'suggestions', $product_name = '' ) {
		$endpoint = 'https://api.openai.com/v1/chat/completions';

		$headers = array(
			'Authorization' => 'Bearer ' . sanitize_text_field( $settings['api_key'] ),
			'Content-Type'  => 'application/json',
		);

		$body_array = array(
			'model'       => sanitize_text_field( $settings['model'] ),
			'messages'    => array(
				array(
					'role'    => 'user',
					'content' => $full_prompt,
				),
			),
			'max_tokens'  => 500,
			'temperature' => max( 0.0, min( 1.0, floatval( $settings['temperature'] ) ) ),
		);

		if ( str_starts_with( $settings['model'], 'gpt-3' ) ) {
			$body_array['max_tokens'] = 300;
		}

		$body = wp_json_encode( $body_array );

		$start_time    = microtime( true );
		$response      = wp_remote_post(
			$endpoint,
			array(
				'headers'   => $headers,
				'body'      => $body,
				'timeout'   => 30,
				'sslverify' => true,
			)
		);
		$response_time = round( microtime( true ) - $start_time, 3 );

		$status      = wp_remote_retrieve_response_code( $response );
		$tokens_used = 0;
		$result      = '';

		if ( is_wp_error( $response ) ) {
			$result = $response->get_error_message();

			if ( function_exists( 'descgpt_log_api_call' ) ) {
				descgpt_log_api_call( $endpoint, 'error', $status, $response_time, $tokens_used, $functionality, $order_id, $product_name, $full_prompt, $result, $settings['model'] );
			}

			return false;
		}

		$response_body = wp_remote_retrieve_body( $response );
		$data          = json_decode( $response_body, true );

		if ( isset( $data['usage']['total_tokens'] ) ) {
			$tokens_used = $data['usage']['total_tokens'];
		}

		if ( isset( $data['choices'][0]['message']['content'] ) ) {
			$suggestions_json = $data['choices'][0]['message']['content'];
			$suggestions_json = preg_replace( '/^```json\n|\n```$/s', '', $suggestions_json );
			$suggestions_json = trim( $suggestions_json );
			$suggestions      = json_decode( $suggestions_json, true );

			if ( json_last_error() === JSON_ERROR_NONE && is_array( $suggestions ) && ! empty( $suggestions ) ) {
				$product_names = array();

				if ( array_is_list( $suggestions ) ) {
					$product_names = $suggestions;
				} else {
					foreach ( $suggestions as $sugg ) {
						if ( isset( $sugg['name'] ) ) {
							$product_names[] = $sugg['name'];
						} elseif ( isset( $sugg['product'] ) ) {
							$product_names[] = $sugg['product'];
						} elseif ( is_string( $sugg ) ) {
							$product_names[] = $sugg;
						}
					}
				}

				$result = ! empty( $product_names ) ? implode( ', ', $product_names ) : 'No suggestions';

				if ( function_exists( 'descgpt_log_api_call' ) ) {
					descgpt_log_api_call( $endpoint, 'success', $status, $response_time, $tokens_used, $functionality, $order_id, $product_name, $full_prompt, $result, $settings['model'] );
				}

				return array(
					'suggestions' => $suggestions,
					'usage'       => $data['usage'] ?? array(),
				);
			} else {
				$result = 'JSON Parse Error: ' . json_last_error_msg();
			}
		} else {
			$result = 'No content in response';
		}

		if ( function_exists( 'descgpt_log_api_call' ) ) {
			descgpt_log_api_call( $endpoint, 'error', $status, $response_time, $tokens_used, $functionality, $order_id, $product_name, $full_prompt, $result, $settings['model'] );
		}

		return false;
	}
}

if ( ! function_exists( 'descgpt_get_settings' ) ) {
	/**
	 * Retrieves and sanitizes plugin settings.
	 *
	 * @return array Sanitized settings or false if critical settings are missing.
	 */
	function descgpt_get_settings() {
		$settings = array(
			'api_key'     => get_option( 'descgpt_api_key', '' ),
			'model'       => sanitize_text_field( get_option( 'descgpt_model', 'gpt-4o-mini' ) ),
			'temperature' => (float) get_option( 'descgpt_temperature', 0.5 ),
			'language'    => sanitize_text_field( get_option( 'descgpt_language', 'English' ) ),
		);

		return apply_filters( 'descgpt_settings', $settings );
	}
}

// PRODUCT CONTENT GENERATION LOGIC.
// AJAX for fetching items.
add_action(
	'wp_ajax_descgpt_fetch_items',
	function () {
		check_ajax_referer( 'descgpt_description_nonce', 'nonce' );

		$type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';

		$items = array();

		switch ( $type ) {
			case 'product':
				$posts = get_posts(
					array(
						'post_type'      => 'product',
						'posts_per_page' => -1,
						'orderby'        => 'title',
						'order'          => 'ASC',
					)
				);
				foreach ( $posts as $p ) {
					$items[] = array(
						'id'   => absint( $p->ID ),
						'name' => wp_kses_post( $p->post_title ),
					);
				}
				break;

			case 'category':
				$terms = get_terms(
					array(
						'taxonomy'   => 'product_cat',
						'hide_empty' => false,
					)
				);
				foreach ( $terms as $t ) {
					$items[] = array(
						'id'   => absint( $t->term_id ),
						'name' => wp_kses_post( $t->name ),
					);
				}
				break;

			case 'tag':
				$terms = get_terms(
					array(
						'taxonomy'   => 'product_tag',
						'hide_empty' => false,
					)
				);
				foreach ( $terms as $t ) {
					$items[] = array(
						'id'   => absint( $t->term_id ),
						'name' => wp_kses_post( $t->name ),
					);
				}
				break;

			case 'brand':
				$terms = get_terms(
					array(
						'taxonomy'   => 'product_brand',
						'hide_empty' => false,
					)
				);
				foreach ( $terms as $t ) {
					$items[] = array(
						'id'   => absint( $t->term_id ),
						'name' => wp_kses_post( $t->name ),
					);
				}
				break;
		}
		wp_send_json_success( $items );
	}
);

/**
 * Parse AI-generated content into structured sections.
 *
 * This function splits the input content by lines and detects sections
 * using a "Header: value" format. Subsequent lines without headers are
 * appended to the last detected section. Section keys are normalized
 * to lowercase with underscores instead of spaces.
 *
 * @since 1.0.0
 * @param string $content Raw AI-generated content.
 * @return array Parsed associative array of sections.
 */
function descgpt_parse_generated_content( $content ) {
	$parsed          = array();
	$lines           = explode( "\n", $content );
	$current_section = '';

	foreach ( $lines as $line ) {
		$line = trim( $line );
		if ( empty( $line ) ) {
			continue;
		}

		if ( preg_match( '/^([a-zA-Z ]+): ?(.*)$/i', $line, $matches ) ) {
			$current_section            = strtolower( str_replace( ' ', '_', trim( $matches[1] ) ) );
			$parsed[ $current_section ] = trim( $matches[2] ?? '' );
		} elseif ( $current_section ) {
			$parsed[ $current_section ] .= "\n" . $line;
		}
	}

	// Trim all values.
	foreach ( $parsed as $key => &$value ) {
		$value = trim( $value );
	}

	return $parsed;
}

// AJAX for last generated.
add_action(
	'wp_ajax_descgpt_get_last_generated',
	function () {
		check_ajax_referer( 'descgpt_description_nonce', 'nonce' );

		$type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
		$id   = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0;

		if ( ! $id ) {
			wp_send_json_error( 'Invalid ID' );
		}

		$timestamp = '';

		// Map frontend type values to backend taxonomy slugs.
		$taxonomy_map = array(
			'product'  => 'product',
			'category' => 'product_cat',
			'tag'      => 'product_tag',
			'brand'    => 'product_brand',
		);

		$mapped_type = $taxonomy_map[ $type ] ?? '';

		if ( 'product' === $mapped_type ) {
			$timestamp = get_post_meta( $id, '_descgpt_last_generated', true );
		} elseif ( in_array( $mapped_type, array( 'product_cat', 'product_tag', 'product_brand' ), true ) ) {
			$timestamp = get_term_meta( $id, '_descgpt_last_generated', true );
		} else {
			wp_send_json_error( 'Invalid type: ' . $type );
		}

		wp_send_json_success(
			array(
				'timestamp' => $timestamp ? gmdate( 'Y-m-d H:i:s', (int) $timestamp ) : 'Never',
			)
		);
	}
);

add_action(
	'wp_ajax_descgpt_check_safety',
	function () {
		check_ajax_referer( 'descgpt_description_nonce', 'nonce' );

		$content = isset( $_POST['content'] ) ? wp_kses_post( wp_unslash( $_POST['content'] ) ) : '';
		$warning = '';

		if ( substr_count( $content, 'lorem ipsum' ) > 0 ) {
			$warning .= 'Generic placeholder detected. ';
		}
		if ( preg_match_all( '/(\b\w+\b)\s+\1/i', $content ) > 3 ) {
			$warning .= 'Repetitive phrases detected. ';
		}
		if ( strlen( $content ) < 100 ) {
			$warning .= 'Content too short for SEO. ';
		}

		if ( $warning ) {
			wp_send_json_success( array( 'warning' => $warning ) );
		} else {
			wp_send_json_success( array( 'warning' => '' ) );
		}
	}
);

// For Save to Item button.
add_action(
	'wp_ajax_descgpt_save_to_item',
	function () {
		check_ajax_referer( 'descgpt_description_nonce', 'nonce' );

		$type     = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
		$ids      = isset( $_POST['ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['ids'] ) ) : array();
		$content  = isset( $_POST['content'] ) ? wp_kses_post( wp_unslash( $_POST['content'] ) ) : '';
		$language = isset( $_POST['language'] ) ? sanitize_text_field( wp_unslash( $_POST['language'] ) ) : 'English';
		$enable   = isset( $_POST['enable'] ) ? absint( wp_unslash( $_POST['enable'] ) ) : 0;
		$force    = isset( $_POST['force'] ) ? absint( wp_unslash( $_POST['force'] ) ) : 0;

		if ( empty( $type ) || empty( $ids ) || empty( $content ) ) {
			wp_send_json_error( 'Invalid input: type, IDs, or content missing.' );
		}

		$success_count = 0;
		$errors        = array();

		foreach ( $ids as $id ) {
			$name = '';

			if ( 'product' === $type ) {
				$post = get_post( $id );
				if ( ! $post ) {
					$errors[] = "ID $id - Product not found.";
					continue;
				}
				$name   = $post->post_title;
				$parsed = array();

				// Try English headers first.
				preg_match( '/Short Description:\s*(.*?)(?=\n\nFull Description:|$)/s', $content, $short_matches );
				preg_match( '/Full Description:\s(.*)$/s', $content, $full_matches );
				$parsed['short_description'] = trim( $short_matches[1] ?? '' );
				$parsed['full_description']  = trim( $full_matches[1] ?? '' );

				// If headers not in English, try translated headers.
				if ( 'English' !== $language && ( empty( $parsed['short_description'] ) || empty( $parsed['full_description'] ) ) ) {
					$translated_headers = array(
						'Gujarati' => array(
							'Short Description' => 'ટૂંકું વર્ણન',
							'Full Description'  => 'પૂર્ણ વર્ણન',
						),
					);
					$short_header       = $translated_headers[ $language ]['Short Description'] ?? 'Short Description';
					$full_header        = $translated_headers[ $language ]['Full Description'] ?? 'Full Description';
					preg_match( "/$short_header:\s(.*?) (?=\n\n$full_header:|$)/s", $content, $short_matches );
					preg_match( "/$full_header:\s(.*)$/s", $content, $full_matches );
					$parsed['short_description'] = trim( $short_matches[1] ?? $parsed['short_description'] );
					$parsed['full_description']  = trim( $full_matches[1] ?? $parsed['full_description'] );
				}

				// Fallback if still empty.
				if ( empty( $parsed['short_description'] ) && empty( $parsed['full_description'] ) ) {
					$normalized_content          = str_replace( array( "\r\n", "\r" ), "\n", $content );
					$parts                       = explode( "\n\n", $normalized_content, 2 );
					$parsed['short_description'] = trim( preg_replace( '/^(Short Description|ટૂંકું વર્ણન):\s*/i', '', $parts[0] ?? '' ) );
					$parsed['full_description']  = trim( preg_replace( '/^(Full Description|પૂર્ણ વર્ણન):\s*/i', '', $parts[1] ?? '' ) );
				}

				if ( empty( $parsed['short_description'] ) && ! empty( $parsed['full_description'] ) ) {
					$parsed['short_description'] = substr( $parsed['full_description'], 0, 100 ) . '...';
				} elseif ( ! empty( $parsed['short_description'] ) && empty( $parsed['full_description'] ) ) {
					$parsed['full_description'] = $parsed['short_description'];
				}

				// Update WooCommerce product fields.
				$update_args = array(
					'ID'           => $id,
					'post_excerpt' => $parsed['short_description'],
					'post_content' => $parsed['full_description'],
				);
				$result      = wp_update_post( $update_args );

				if ( is_wp_error( $result ) ) {
					$errors[] = "ID $id ($name) - Failed to update: " . $result->get_error_message();
				} else {
					update_post_meta( $id, '_descgpt_last_generated', time() );
					update_post_meta( $id, '_descgpt_language', $language );
					$success_count++;
				}
			} else {
				$taxonomy = ( 'category' === $type ? 'product_cat' : ( 'tag' === $type ? 'product_tag' : 'product_brand' ) );
				if ( ! taxonomy_exists( $taxonomy ) ) {
					$errors[] = "ID $id - Taxonomy $taxonomy does not exist.";
					continue;
				}

				$term = get_term( $id, $taxonomy );
				if ( is_wp_error( $term ) || ! $term ) {
					$errors[] = "ID $id - Term not found.";
					continue;
				}

				$name     = $term->name;
				$desc_key = match ( $type ) {
					'category' => 'Category Description',
					'tag' => 'Tag Descriptions',
					'brand' => 'Brand Description',
					default => 'Description'
				};
				preg_match( "/$desc_key:\s(.*)$/s", $content, $matches );
				$clean_content = trim( $matches[1] ?? $content );

				$clean_content = preg_replace( '/^' . preg_quote( str_replace( ' ', '', $desc_key ), '/' ) . ':/i', '', $clean_content );
				$clean_content = preg_replace( '/([.,;!?])([^\s])/', '$1 $2', $clean_content );
				$clean_content = preg_replace( '/^[A-Za-z ]+:/m', '', $clean_content );
				$clean_content = trim( $clean_content );

				$result = wp_update_term( $id, $taxonomy, array( 'description' => $clean_content ) );

				if ( is_wp_error( $result ) ) {
					$errors[] = "ID $id ($name) - Failed to update: " . $result->get_error_message();
				} else {
					update_term_meta( $id, '_descgpt_last_generated', time() );
					update_term_meta( $id, '_descgpt_language', $language );
					$success_count++;
				}
			}
		}

		if ( $success_count > 0 ) {
			wp_send_json_success(
				array(
					'message' => "Saved content for $success_count item(s).",
					'errors'  => $errors,
				)
			);
		} else {
			wp_send_json_error( implode( ', ', $errors ) );
		}
	}
);

// Actual content generation.
add_action(
	'wp_ajax_descgpt_generate_description',
	function () {
		check_ajax_referer( 'descgpt_description_nonce', 'nonce' );

		$type        = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
		$ids         = isset( $_POST['ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['ids'] ) ) : array();
		$limit_type  = isset( $_POST['limit_type'] ) ? sanitize_text_field( wp_unslash( $_POST['limit_type'] ) ) : 'words';
		$limit_value = isset( $_POST['limit_value'] ) ? absint( wp_unslash( $_POST['limit_value'] ) ) : 150;
		$bulk        = isset( $_POST['bulk'] ) && sanitize_text_field( wp_unslash( $_POST['bulk'] ) ) === '1';
		$regenerate  = isset( $_POST['regenerate'] ) ? intval( wp_unslash( $_POST['regenerate'] ) ) : 0;

		// === VALIDATION ===
		if ( empty( $type ) ) {
			wp_send_json_error( 'Invalid content type.' );
		}
		if ( $bulk && 'product' === $type ) {
			wp_send_json_error( 'Bulk generation is only supported for taxonomies.' );
		}
		if ( $limit_value < 10 || $limit_value > 2000 ) {
			wp_send_json_error( 'Limit must be between 10 and 2000.' );
		}
		if ( ! $bulk && empty( $ids ) ) {
			wp_send_json_error( 'No items selected.' );
		}
		if ( ! $bulk && 'product' === $type && count( $ids ) > 1 ) {
			wp_send_json_error( 'Bulk generation is only supported for taxonomies.' );
		}

		$settings = descgpt_get_settings();
		if ( ! $settings || empty( $settings['api_key'] ) ) {
			wp_send_json_error( 'API key not configured.' );
		}

		$selected_language = $settings['language'] ?? 'English';
		$model             = $settings['model'] ?? 'gpt-4o-mini';

		if ( 'product' === $type ) {
			$base_prompt = defined( 'MCADGW_DESC_GEN_CHTGPT_PROMPT_PRODUCT' ) ? MCADGW_DESC_GEN_CHTGPT_PROMPT_PRODUCT : '';
		} else {
			$base_prompt = defined( 'DESCGPT_PROMPT_TAXONOMY' ) ? DESCGPT_PROMPT_TAXONOMY : '';
		}

		// === BULK TAXONOMY ===
		if ( $bulk ) {
			$taxonomy = match ( $type ) {
				'category' => 'product_cat',
				'tag'      => 'product_tag',
				'brand'    => 'product_brand',
				default    => '',
			};
			if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) {
				wp_send_json_error( "Taxonomy $taxonomy does not exist." );
			}
			$terms = get_terms(
				array(
					'taxonomy'   => $taxonomy,
					'hide_empty' => false,
				)
			);
			if ( is_wp_error( $terms ) ) {
				wp_send_json_error( 'Failed to fetch terms.' );
			}
			$ids = wp_list_pluck( $terms, 'term_id' );
			if ( empty( $ids ) ) {
				wp_send_json_success(
					array(
						'summary' => array( 'No items found' ),
						'content' => array(),
					)
				);
			}
		}

		$results = array();
		$summary = array();

		foreach ( $ids as $id ) {
			// === SKIP IF ALREADY GENERATED (bulk only) ===
			if ( $bulk && ! $regenerate ) {
				$has_content = false;
				if ( 'product' === $type ) {
					$has_content = get_post_meta( $id, '_descgpt_last_generated', true ) && ! empty( trim( get_post_field( 'post_content', $id ) ) );
				} else {
					$term        = get_term( $id );
					$desc        = $term->description ?? '';
					$has_content = get_term_meta( $id, '_descgpt_last_generated', true ) && ! empty( trim( $desc ) );
					if ( ! $has_content && get_term_meta( $id, '_descgpt_last_generated', true ) ) {
						delete_term_meta( $id, '_descgpt_last_generated' );
					}
				}

				if ( $has_content ) {
					$name      = ( 'product' === $type ) ? ( get_post( $id )->post_title ?? 'Unknown' ) : ( get_term( $id )->name ?? 'Unknown' );
					$summary[] = "Skipped ID $id ($name) - content already exists";
					continue;
				}
			}

			// === GET NAME ===
			$name = '';
			if ( 'product' === $type ) {
				$post = get_post( $id );
				$name = $post ? $post->post_title : '';
			} else {
				$term = get_term( $id );
				$name = $term ? $term->name : '';
			}

			if ( empty( $name ) ) {
				$summary[] = "Skipped ID $id - item not found or name empty";
				continue;
			}

			// === BUILD PROMPT ===
			$type_label = match ( $type ) {
				'product'  => 'product',
				'category' => 'product category',
				'tag'      => 'product tag',
				'brand'    => 'brand',
				default    => 'item',
			};

			$replacements = array(
				'[product name]' => $name,
				'[language]'     => $selected_language,
				'[type]'         => $type_label,
				'[limit_value]'  => $limit_value,
				'[limit_type]'   => ( 'tokens' === $limit_type ? 'tokens' : 'words' ),
			);

			$full_prompt       = str_replace( array_keys( $replacements ), array_values( $replacements ), $base_prompt );
			$prompt_for_tokens = $full_prompt;

			if ( $regenerate ) {
				$full_prompt .= "\n\nIMPORTANT: This is a REGENERATION request. ";
				$full_prompt .= 'Generate a completely fresh and different version with new tone, style, and wording. ';
				$full_prompt .= "BUT strictly respect the original length limit of approximately {$limit_value} {$limit_type}. ";
				$full_prompt .= "Do NOT write more just because it's a new version. Stay within the limit.";
			}

			if ( 'product' !== $type ) {
				$full_prompt .= "\n\nCRITICAL RULES (MUST FOLLOW EXACTLY):\n";
				$full_prompt .= "- Output ONLY plain text description\n";
				$full_prompt .= "- No headers, no lists, no markdown\n";
				$full_prompt .= "- Approximately {$limit_value} {$limit_type} — do not exceed\n";
				$full_prompt .= '- Especially on regeneration: stay within limit even if being creative';
			}

			// === SYSTEM PROMPT ===
			$system_prompt = 'You are a precise SEO-friendly content generator. ';
			if ( 'product' === $type ) {
				$system_prompt .= 'For products, output in the exact structured format with numbered sections. ';
			} else {
				$system_prompt .= 'For categories, tags, and brands, output ONLY plain text — no headers, no numbering, no labels. ';
			}
			$system_prompt .= 'Do NOT add explanations, markdown, or extra text. Use natural language only.';

			// === TOKEN CALCULATION ===
			$max_tokens = 800;
			try {
				$encoder_provider = new EncoderProvider();
				$encoder          = match ( $model ) {
					'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-4.1' => $encoder_provider->getForModel( 'gpt-4o' ),
					'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-3.5', 'gpt-3' => $encoder_provider->getForModel( 'gpt-3.5-turbo' ),
					default => $encoder_provider->get( 'cl100k_base' ),
				};

				$prompt_tokens = count( $encoder->encode( $system_prompt ) ) + count( $encoder->encode( $prompt_for_tokens ) );

				if ( 'tokens' === $limit_type ) {
					$desired    = max( 1, (int) $limit_value );
					$buffer     = max( 20, ceil( $desired * 0.1 ) );
					$available  = 4096 - $prompt_tokens;
					$max_tokens = min( $desired + $buffer, $available );
					$max_tokens = max( $desired, $max_tokens );
					$max_tokens = min( 2048, $max_tokens );
				} else {
					$target_tokens = ceil( $limit_value * 1.2 );
					$buffer        = 'product' === $type ? 1.2 : 1.1;
					if ( 'English' !== $selected_language ) {
						$buffer *= 1.2;
					}
					$max_tokens = ceil( $target_tokens * $buffer );
					$max_tokens = min( 2048, max( 256, $max_tokens ) );
				}
			} catch ( \Exception $e ) {
				$max_tokens = 'tokens' === $limit_type ? $limit_value : 800;
			}

			// === API CALL ===
			$endpoint = 'https://api.openai.com/v1/chat/completions';
			$headers  = array(
				'Authorization' => 'Bearer ' . $settings['api_key'],
				'Content-Type'  => 'application/json',
			);

			$body_array = array(
				'model'       => $model,
				'messages'    => array(
					array(
						'role'    => 'system',
						'content' => $system_prompt,
					),
					array(
						'role'    => 'user',
						'content' => $full_prompt,
					),
				),
				'max_tokens'  => $max_tokens,
				'temperature' => max( 0, min( 1, floatval( $settings['temperature'] ?? 0.5 ) + ( $regenerate ? 0.1 : 0 ) ) ),
			);

			$body = wp_json_encode( $body_array );

			$max_retries   = 2;
			$retry_count   = 0;
			$content       = '';
			$tokens_used   = 0;
			$response_time = 0;

			// Prepare functionality label once per item.
			$display_type = match ( $type ) {
				'category' => 'Product Category',
				'tag'      => 'Product Tag',
				'brand'    => 'Product Brand',
				default    => 'Product',
			};
			$functionality = "Content Generation - $display_type";

			while ( $retry_count <= $max_retries ) {
				$call_start = microtime( true );

				$response = wp_remote_post(
					$endpoint,
					array(
						'headers'   => $headers,
						'body'      => $body,
						'timeout'   => 30,
						'sslverify' => true,
					)
				);

				$call_end      = microtime( true );
				$response_time = round( $call_end - $call_start, 3 );

				// === 1. WP REMOTE ERROR ===
				if ( is_wp_error( $response ) ) {
					$error_message = $response->get_error_message();
					$summary[]     = "Failed for ID $id ($name) - Request error: $error_message";

					if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
						descgpt_log_api_call(
							$endpoint,
							'error',
							0,
							$response_time,
							0,
							$functionality,
							$id,
							$name,
							$full_prompt,
							$error_message,
							$model
						);
					}

					break;
				}

				$response_code = wp_remote_retrieve_response_code( $response );
				$response_body = wp_remote_retrieve_body( $response );
				$data          = json_decode( $response_body, true );

				// === 2. INVALID JSON ===
				if ( null === $data ) {
					$retry_count++;
					if ( $retry_count > $max_retries ) {
						$error_msg = 'Invalid JSON response';
						$summary[] = "Failed for ID $id ($name) - Invalid JSON after retries";

						if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
							descgpt_log_api_call(
								$endpoint,
								'error',
								500,
								$response_time,
								0,
								$functionality,
								$id,
								$name,
								$full_prompt,
								$error_msg,
								$model
							);
						}
					} elseif ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
							descgpt_log_api_call(
								$endpoint,
								'retry',
								$response_code,
								$response_time,
								0,
								$functionality,
								$id,
								$name,
								$full_prompt,
								'Invalid JSON, retrying...',
								$model
							);
					}
					sleep( 1 );
					continue;
				}

				// === 3. HTTP ERROR (4xx/5xx) ===
				if ( 200 !== $response_code ) {
					$error_message = $data['error']['message'] ?? 'Unknown error';
					$summary[]     = "Failed for ID $id ($name) - HTTP $response_code: $error_message";

					if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
						descgpt_log_api_call(
							$endpoint,
							'error',
							$response_code,
							$response_time,
							0,
							$functionality,
							$id,
							$name,
							$full_prompt,
							$error_message,
							$model
						);
					}
					break;
				}

				// === 4. SUCCESS: CONTENT RECEIVED ===
				if ( ! empty( $data['choices'][0]['message']['content'] ) ) {
					$content = trim( $data['choices'][0]['message']['content'] );

					// === Token usage ===
					$prompt_tokens     = ( isset( $data['usage']['prompt_tokens'] ) && is_numeric( $data['usage']['prompt_tokens'] ) ) ? absint( $data['usage']['prompt_tokens'] ) : 0;
					$completion_tokens = ( isset( $data['usage']['completion_tokens'] ) && is_numeric( $data['usage']['completion_tokens'] ) ) ? absint( $data['usage']['completion_tokens'] ) : 0;
					$total_tokens      = ( isset( $data['usage']['total_tokens'] ) && is_numeric( $data['usage']['total_tokens'] ) ) ? absint( $data['usage']['total_tokens'] ) : ( $prompt_tokens + $completion_tokens );
					$total_tokens      = max( $total_tokens, $prompt_tokens + $completion_tokens );

					$tokens_used = $total_tokens;

					if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
						descgpt_log_api_call(
							$endpoint,
							'success',
							200,
							$response_time,
							$tokens_used,
							$functionality,
							$id,
							$name,
							$full_prompt,
							$content,
							$model
						);
					}

					$summary[] = "Generated for ID $id ($name)";
					break;
				}

				// === 5. NO CONTENT → RETRY ===
				$retry_count++;
				if ( $retry_count <= $max_retries ) {

					if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
						descgpt_log_api_call(
							$endpoint,
							'retry',
							$response_code,
							$response_time,
							0,
							$functionality,
							$id,
							$name,
							$full_prompt,
							'Empty content in response, retrying...',
							$model
						);
					}
					sleep( 1 );
				}
			}

			// Final fallback if still no content after retries.
			if ( empty( $content ) ) {
				$summary[] = "Failed for ID $id ($name) - No content after " . ( $max_retries + 1 ) . ' attempts';

				// Log final failure.
				if ( defined( 'MCADGW_DESC_GEN_CHTGPT_PRO' ) ) {
					descgpt_log_api_call(
						$endpoint,
						'error',
						0,
						$response_time,
						0,
						$functionality,
						$id,
						$name,
						$full_prompt,
						'No content generated after max retries',
						$model
					);
				}
				continue;
			}

			if ( empty( $content ) ) {
				continue;
			}

			// === CLEANUP & SAVE ===
			$content = preg_replace( '/[*_`#~%$@${}|<>]+/', '', $content );
			$content = trim( preg_replace( '/\s+/', ' ', $content ) );

			$parsed_content = array();
			if ( 'product' === $type ) {
				$parsed_content = descgpt_parse_product_content( $content, $selected_language );
			} else {
				$clean = $content;
				$clean = preg_replace( '/\[END_OF_PRODUCT_CONTENT\].*/is', '', $clean );
				$clean = preg_replace( '/<<END_OF_PRODUCT_CONTENT>>.*/is', '', $clean );
				$clean = preg_replace( '/ENDOFPRODUCTCONTENT.*/i', '', $clean );
				$clean = preg_replace( '/^[\d\)\s]+.*?:/m', '', $clean );
				$clean = preg_replace( '/^\s*[-*•]\s*/m', '', $clean );
				$clean = trim( preg_replace( '/\s+/', ' ', $clean ) );

				$key = match ( $type ) {
					'category' => 'category_description',
					'tag'      => 'tag_description',
					'brand'    => 'brand_description',
					default    => 'description'
				};
				$parsed_content[ $key ] = $clean;
			}

			if ( 'product' === $type ) {
				update_post_meta( $id, '_descgpt_last_generated', time() );
			} else {
				update_term_meta( $id, '_descgpt_last_generated', time() );
			}

			$results[] = array(
				'id'             => $id,
				'name'           => $name,
				'content'        => $content,
				'parsed_content' => $parsed_content,
				'tokens_used'    => $tokens_used ?? 0,
				'output_tokens'  => $completion_tokens,
				'response_time'  => $response_time,
				'model'          => $model,
			);
		}

		wp_send_json_success(
			array(
				'summary' => $summary,
				'content' => $results,
			)
		);
	}
);

/**
 * Parse product content into short and full descriptions with fallback.
 *
 * This function attempts to extract structured "Short Description" and
 * "Full Description" from a raw product content string. It supports
 * multiple delimiter patterns and provides a fallback when standard
 * headers are missing.
 *
 * Steps:
 * 1. Cut content at any recognized delimiter variations.
 * 2. Remove trailing junk or markers.
 * 3. Extract Short Description if available.
 * 4. Extract Full Description if available.
 * 5. Fallback: split by first blank line or use a truncated version for short description.
 * 6. Final cleanup: remove trailing brackets, bullet characters, and normalize whitespace.
 *
 * @since 1.0.0
 *
 * @param string $raw_content Raw product content to parse.
 * @param string $language    Language of the content (e.g., 'English', 'Gujarati').
 *
 * @return array {
 *     Parsed product descriptions.
 *
 *     @type string $short_description Short version of the product description.
 *     @type string $full_description  Full version of the product description.
 * }
 */
function descgpt_parse_product_content( $raw_content, $language ) {
	$parsed = array(
		'short_description' => '',
		'full_description'  => '',
	);

	// === STEP 1: CUT AT ANY DELIMITER VARIATION ===
	$delimiter_patterns = array(
		'/\[END_OF_PRODUCT_CONTENT\]/i',
		'/<<END_OF_PRODUCT_CONTENT>>/i',
		'/ENDOFPRODUCTCONTENT/i',
		'/END_OF_PRODUCT_CONTENT/i',
	);

	$cut = $raw_content;
	foreach ( $delimiter_patterns as $pattern ) {
		if ( preg_match( $pattern, $raw_content ) ) {
			$cut = preg_split( $pattern, $raw_content, 2 )[0];
			break;
		}
	}

	// === STEP 2: Remove any trailing junk (safety) ===
	$cut = preg_replace( '/\s*(ENDOFPRODUCTCONTENT|END OF PRODUCT CONTENT).*$/i', '', $cut );

	// === STEP 3: Extract Short Description ===
	if ( preg_match( '/Short Description:\s*([\s\S]*?)(?=Full Description:|$)/i', $cut, $m ) ) {
		$parsed['short_description'] = trim( $m[1] );
	}

	// === STEP 4: Extract Full Description ===
	if ( preg_match( '/Full Description:\s*([\s\S]*)/i', $cut, $m ) ) {
		$parsed['full_description'] = trim( $m[1] );
	}

	// === STEP 5: Fallback — split by first blank line or paragraph ===
	if ( empty( $parsed['short_description'] ) && empty( $parsed['full_description'] ) ) {
		$parts = preg_split( '/\n\s*\n/', $cut, 2 );
		if ( count( $parts ) >= 2 ) {
			$parsed['short_description'] = trim( $parts[0] );
			$parsed['full_description']  = trim( $parts[1] );
		} else {
			$parsed['full_description']  = trim( $cut );
			$parsed['short_description'] = mb_substr( $cut, 0, 160 ) . '...';
		}
	}

	// === STEP 6: Final cleanup ===
	foreach ( $parsed as $key => $value ) {
		$parsed[ $key ] = preg_replace( '/\s*\[\s*$/', '', $value );
		$parsed[ $key ] = preg_replace( '/^\s*[-*•]\s*/m', '', $parsed[ $key ] );
		$parsed[ $key ] = trim( preg_replace( '/\s+/', ' ', $parsed[ $key ] ) );
	}

	return $parsed;
}

/**
 * Parse product content into short and full descriptions (legacy fallback).
 * This function extracts "Short Description" and "Full Description" from
 * a given product content string. It supports multiple languages and
 * falls back to splitting the content if the standard headers are missing.
 *
 * If only one description is found, it uses it for both short and full
 * descriptions, with a truncation for the short version if necessary.
 *
 * @since 1.0.0
 *
 * @param string $content  The raw product content to parse.
 * @param string $language The language of the content (e.g., 'English', 'Gujarati').
 *
 * @return array {
 *     Parsed descriptions.
 *
 *     @type string $short_description Short version of the product description.
 *     @type string $full_description  Full version of the product description.
 * }
 */
function descgpt_parse_product_content_legacy( $content, $language ) {
	$short   = '';
	$full    = '';
	$headers = array( 'Short Description', 'Full Description' );

	preg_match( '/Short Description:\s*(.*?)(?=\n\nFull Description:|$)/s', $content, $m1 );
	preg_match( '/Full Description:\s(.*)$/s', $content, $m2 );
	$short = trim( $m1[1] ?? '' );
	$full  = trim( $m2[1] ?? '' );

	if ( 'English' !== $language && ( empty( $short ) || empty( $full ) ) ) {
		$map = array(
			'Gujarati' => array(
				'Short Description' => 'ટૂંકું વર્ણન',
				'Full Description'  => 'પૂર્ણ વર્ણન',
			),
		);
		$h   = $map[ $language ] ?? $headers;
		preg_match( "/{$h[0]}:\s*(.*?)(?=\n\n{$h[1]}:|$)/s", $content, $m1 );
		preg_match( "/{$h[1]}:\s(.*)$/s", $content, $m2 );
		$short = trim( $m1[1] ?? $short );
		$full  = trim( $m2[1] ?? $full );
	}

	if ( empty( $short ) && empty( $full ) ) {
		$lines = array_filter( array_map( 'trim', explode( "\n", $content ) ) );
		if ( count( $lines ) === 1 ) {
			$full = $lines[0];
		} else {
			$short = $lines[0] ?? '';
			$full  = implode( "\n\n", array_slice( $lines, 1 ) );
		}
	}

	if ( empty( $short ) && ! empty( $full ) ) {
		$short = substr( $full, 0, 100 ) . '...';
	}
	if ( empty( $full ) && ! empty( $short ) ) {
		$full = $short;
	}

	return array(
		'short_description' => $short,
		'full_description'  => $full,
	);
}

// Save generated content.
add_action(
	'wp_ajax_descgpt_save_generated_content',
	function () {
		check_ajax_referer( 'descgpt_description_nonce', 'nonce' );

		$type    = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
		$id      = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;
		$content = isset( $_POST['content'] ) ? wp_kses_post( wp_unslash( $_POST['content'] ) ) : '';
		$enable  = isset( $_POST['enable'] ) ? 1 : 0;

		if ( 'product' === $type ) {
			update_post_meta( $id, '_descgpt_generated_full', $content );
			update_post_meta( $id, '_descgpt_enable_ai', $enable );
		} else {
			$taxonomy = 'brand' === $type ? 'product_brand' : ( 'category' === $type ? 'product_cat' : 'product_tag' );
			update_term_meta( $id, '_descgpt_generated_desc', $content );
			update_term_meta( $id, '_descgpt_enable_ai', $enable );
		}

		wp_send_json_success();
	}
);
