<?php
if (!defined('ABSPATH')) {
	exit;
}

/**
 * Handles the admin area functionality.
 */
require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-api-gemini.php';

class Podcastmaker_Admin
{
	/**
	 * Get the active prompt speaker filter.
	 *
	 * @return string
	 */
	private function get_prompt_speaker_filter()
	{
		$filter = isset($_REQUEST['speaker_filter']) ? sanitize_text_field(wp_unslash($_REQUEST['speaker_filter'])) : 'single'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return $filter === 'multi' ? 'multi' : 'single';
	}

	/**
	 * Build the prompt list redirect URL while preserving the current filter.
	 *
	 * @param string $speaker_filter The active prompt filter.
	 * @param array  $extra_args Additional query arguments.
	 * @return string
	 */
	private function get_prompt_list_redirect_url($speaker_filter, $extra_args = array())
	{
		$args = array_merge(
			array(
				'page' => 'Podcastmaker',
				'tab' => 'prompts',
				'speaker_filter' => $speaker_filter === 'multi' ? 'multi' : 'single',
			),
			$extra_args
		);

		return add_query_arg($args, admin_url('admin.php'));
	}

	/**
	 * Normalize a prompt language context value.
	 *
	 * @param string $language The requested language.
	 * @return string
	 */
	private function normalize_prompt_language($language)
	{
		$allowed_languages = array('ja', 'en', 'zh-cn', 'zh-tw', 'ko');
		return in_array($language, $allowed_languages, true) ? $language : 'en';
	}

	/**
	 * Get the active smart tone speaker filter.
	 *
	 * @return string
	 */
	private function get_smart_tone_speaker_filter()
	{
		$filter = isset($_REQUEST['speaker_filter']) ? sanitize_text_field(wp_unslash($_REQUEST['speaker_filter'])) : 'single'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return $filter === 'multi' ? 'multi' : 'single';
	}

	/**
	 * Build the smart tone list redirect URL while preserving the current filter.
	 *
	 * @param string $speaker_filter The active smart tone filter.
	 * @param array  $extra_args Additional query arguments.
	 * @return string
	 */
	private function get_smart_tone_list_redirect_url($speaker_filter, $extra_args = array())
	{
		$args = array_merge(
			array(
				'page' => 'Podcastmaker',
				'tab' => 'smart_tone',
				'speaker_filter' => $speaker_filter === 'multi' ? 'multi' : 'single',
			),
			$extra_args
		);

		return add_query_arg($args, admin_url('admin.php'));
	}


	/**
	 * Initialize the class.
	 */
	public function __construct()
	{
		add_action('admin_menu', array($this, 'add_admin_menu'));
		add_action('admin_init', array($this, 'register_settings'));
		add_action('admin_init', array($this, 'maybe_refresh_license_state_on_settings_page'));
		add_action('admin_init', array($this, 'handle_prompt_sets_actions'));
		add_action('admin_init', array($this, 'handle_smart_tone_actions'));
		add_action('admin_post_podcastmaker_recheck_license', array($this, 'handle_license_recheck'));
		add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_styles'));
		add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
		add_action('admin_enqueue_scripts', array($this, 'enqueue_bulk_voice_assets'));
		add_action('wp_ajax_podcastmaker_test_vertex_connection', array($this, 'ajax_test_vertex_connection'));
		add_action('wp_ajax_podcastmaker_check_vertex_availability', array($this, 'ajax_check_vertex_availability'));
		add_action('wp_ajax_podcastmaker_refresh_gemini_models', array($this, 'ajax_refresh_gemini_models'));
	}

	/**
	 * Refresh the license state when the General settings page is opened and a recheck is due.
	 *
	 * @return void
	 */
	public function maybe_refresh_license_state_on_settings_page()
	{
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : 'general';

		if (!in_array($page, array('Podcastmaker', 'Podcastmaker-settings'), true) || $tab !== 'general') {
			return;
		}

		if (!class_exists('Podcastmaker_License')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-license.php';
		}

		Podcastmaker_License::maybe_refresh_due_license_state();
	}

	/**
	 * Handle manual license recheck requests.
	 *
	 * @return void
	 */
	public function handle_license_recheck()
	{
		check_admin_referer('podcastmaker_recheck_license', 'podcastmaker_recheck_nonce');

		if (!current_user_can('manage_options')) {
			wp_die(esc_html__('Insufficient permissions.', 'quikvox-ai'));
		}

		if (!class_exists('Podcastmaker_License')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-license.php';
		}

		$license_key = get_option('podcastmaker_license_key', '');
		$result = Podcastmaker_License::verify_license($license_key, true);
		$state = Podcastmaker_License::get_license_state();
		$message = !empty($state['message']) ? (string) $state['message'] : __('License recheck completed.', 'quikvox-ai');
		$result_flag = isset($state['status']) && $state['status'] === 'active' ? 'success' : 'error';

		$redirect_url = add_query_arg(
			array(
				'page' => 'Podcastmaker',
				'tab' => 'general',
				'license_recheck_result' => $result_flag,
				'license_recheck_message' => $message,
			),
			admin_url('admin.php')
		);

		wp_safe_redirect($redirect_url);
		exit;
	}

	public function enqueue_admin_styles($hook)
	{
		// Only load on our plugin pages
		if (stripos($hook, 'podcastmaker') !== false) {
			wp_enqueue_style('podcastmaker-admin-css', plugin_dir_url(dirname(__FILE__)) . 'assets/css/admin.css', array(), '1.0.0');
		}
	}

	/**
	 * Enqueue admin scripts for the settings screen.
	 *
	 * @param string $hook Current admin hook suffix.
	 * @return void
	 */
	public function enqueue_admin_scripts($hook)
	{
		if (stripos($hook, 'podcastmaker') === false) {
			return;
		}

		wp_enqueue_script(
			'podcastmaker-admin',
			plugin_dir_url(dirname(__FILE__)) . 'assets/js/admin.js',
			array(),
			PODCASTMAKER_VERSION,
			true
		);

		wp_localize_script(
			'podcastmaker-admin',
			'PodcastmakerAdminData',
			array(
				'ajaxUrl' => admin_url('admin-ajax.php'),
				'adminNonce' => wp_create_nonce('podcastmaker_admin_nonce'),
				'strings' => array(
					'testingConnection' => __('Testing authentication...', 'quikvox-ai'),
					'connectionFailed' => __('Authentication Failed.', 'quikvox-ai'),
					'connectionSuccess' => __('Authentication successful.', 'quikvox-ai'),
					'checkingAvailability' => __('Checking availability...', 'quikvox-ai'),
					'availabilityFailed' => __('Availability check failed.', 'quikvox-ai'),
					'availabilitySuccess' => __('Availability confirmed.', 'quikvox-ai'),
					'connectionSuccessPrefix' => __('✓', 'quikvox-ai'),
					'connectionErrorPrefix' => __('✗ Error:', 'quikvox-ai'),
					'availabilitySuccessPrefix' => __('✓', 'quikvox-ai'),
					'availabilityErrorPrefix' => __('✗ Error:', 'quikvox-ai'),
					'refreshingModels' => __('Updating models...', 'quikvox-ai'),
					'modelsRefreshFailedPrefix' => __('Model update failed:', 'quikvox-ai'),
					'modelsRefreshFailed' => __('Model update failed.', 'quikvox-ai'),
					'modelsRefreshSuccess' => __('Model list updated.', 'quikvox-ai'),
				),
			)
		);
	}

	/**
	 * Return the model names that should not be shown in the Google AI Studio list.
	 *
	 * @return array<int, string>
	 */
	public static function get_gemini_model_exclusion_list()
	{
		return array(
			'gemini-exp-1206',
			'gemini-2.0-flash',
			'gemini-2.0-flash-001',
			'gemini-2.0-flash-lite',
			'gemini-2.0-flash-lite-001',
			'gemini-flash-latest',
			'gemini-flash-lite-latest',
			'gemini-pro-latest',
			'nano-banana-pro-preview',
			'gemini-robotics-er-1.5-preview',
			'gemini-2.5-computer-use-preview-10-2025',
			'deep-research-pro-preview-12-2025',
			'deep-research-max-preview-04-2026',
			'deep-research-preview-04-2026',
			'gemini-3-pro-image-preview',
			'gemini-2.5-flash-image',
			'gemini-3.1-flash-image-preview',
		);
	}

	/**
	 * Determine whether a model name is a TTS model.
	 *
	 * @param string $model_name The normalized model name.
	 * @return bool
	 */
	private function is_gemini_tts_model_name($model_name)
	{
		return strpos($model_name, 'tts') !== false;
	}

	/**
	 * Normalize a model name returned by the Gemini models list.
	 *
	 * @param mixed $model The raw model entry.
	 * @return string
	 */
	private function normalize_gemini_model_name($model)
	{
		$normalized_model = is_string($model) ? trim($model) : '';
		if ($normalized_model === '') {
			return '';
		}

		$normalized_model = str_replace('models/', '', $normalized_model);
		return sanitize_text_field($normalized_model);
	}

	/**
	 * Filter the Gemini model list with the local exclusion list.
	 *
	 * @param array<int, mixed> $models Raw model entries.
	 * @return array<int, string>
	 */
	private function filter_gemini_model_list($models)
	{
		$excluded_models = array_fill_keys(self::get_gemini_model_exclusion_list(), true);
		$filtered_models = array();

		foreach ((array) $models as $model) {
			$normalized_model = $this->normalize_gemini_model_name($model);
			if ($normalized_model === '') {
				continue;
			}

			if (isset($excluded_models[$normalized_model])) {
				continue;
			}

			if ($this->is_gemini_tts_model_name($normalized_model)) {
				continue;
			}

			if (!in_array($normalized_model, $filtered_models, true)) {
				$filtered_models[] = $normalized_model;
			}
		}

		return $filtered_models;
	}

	/**
	 * Refresh the cached Gemini model list for the Google AI Studio tab.
	 *
	 * @return void
	 */
	public function ajax_refresh_gemini_models()
	{
		check_ajax_referer('podcastmaker_admin_nonce', 'nonce');
		if (!current_user_can('manage_options')) {
			wp_send_json_error(array(
				'message' => __('Insufficient permissions.', 'quikvox-ai'),
			));
		}

		$api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : '';
		if ($api_key === '') {
			$api_key = get_option('Podcastmaker_gemini_api_key', '');
		}
		if (empty($api_key)) {
			wp_send_json_error(array(
				'message' => __('Script Generation API Key is missing.', 'quikvox-ai'),
			));
		}

		if (!class_exists('Podcastmaker_API_Gemini')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-api-gemini.php';
		}

		$gemini_api = new Podcastmaker_API_Gemini($api_key);
		$api_result = $gemini_api->get_models();

		if (is_wp_error($api_result)) {
			wp_send_json_error(array(
				'message' => $api_result->get_error_message(),
			));
		}

		$filtered_models = $this->filter_gemini_model_list($api_result);
		if (empty($filtered_models)) {
			wp_send_json_error(array(
				'message' => __('No usable models were returned.', 'quikvox-ai'),
			));
		}

		$cache_payload = array(
			'models' => $filtered_models,
			'updated_at' => time(),
		);
		update_option('Podcastmaker_gemini_model_list_cache', $cache_payload, false);

		wp_send_json_success(array(
			'message' => __('Model list updated.', 'quikvox-ai'),
			'count' => count($filtered_models),
			'updated_at' => $cache_payload['updated_at'],
		));
	}

	/**
	 * Register plugin settings.
	 */
	public function register_settings()
	{
		// General Settings Group
		register_setting('Podcastmaker_settings_group', 'podcastmaker_license_key', array(
			'type' => 'string',
			'sanitize_callback' => array('Podcastmaker_License', 'sanitize_license_key'),
			'default' => '',
		));
		register_setting('Podcastmaker_settings_group', 'podcastmaker_api_provider', array(
			'type' => 'string',
			'sanitize_callback' => array($this, 'sanitize_api_provider'),
			'default' => 'google_ai_studio',
		));

		// Gemini Settings Group
		register_setting('Podcastmaker_gemini_settings', 'Podcastmaker_gemini_api_key', array(
			'type' => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'default' => '',
		));
		register_setting('Podcastmaker_gemini_settings', 'Podcastmaker_gemini_tts_api_key', array(
			'type' => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'default' => '',
		));
		register_setting('Podcastmaker_gemini_settings', 'Podcastmaker_gemini_tts_model', array(
			'type' => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'default' => 'gemini-2.5-flash-preview-tts',
		));
		register_setting('Podcastmaker_gemini_settings', 'Podcastmaker_gemini_model', array(
			'type' => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'default' => 'gemini-2.5-flash',
		));
		register_setting('Podcastmaker_gemini_settings', 'Podcastmaker_gemini_available_models', array(
			'type' => 'array',
			'sanitize_callback' => function ($value) {
			return is_array($value) ? array_map('sanitize_text_field', $value) : array();
		},
			'default' => array('gemini-3.1-pro-preview', 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite'),
		));
		register_setting('Podcastmaker_gemini_settings', 'Podcastmaker_gemini_available_tts_models', array(
			'type' => 'array',
			'sanitize_callback' => function ($value) {
			return is_array($value) ? array_map('sanitize_text_field', $value) : array();
		},
			'default' => array('gemini-2.5-flash-preview-tts', 'gemini-2.5-pro-preview-tts'),
		));

		// Vertex Settings Group
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_location', array(
			'type' => 'string',
			'sanitize_callback' => array($this, 'sanitize_vertex_location'),
			'default' => 'global',
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_auth_json', array(
			'type' => 'string',
			'sanitize_callback' => array($this, 'sanitize_vertex_auth_json'),
			'default' => '',
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_model', array(
			'type' => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'default' => 'gemini-2.5-flash',
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_available_models', array(
			'type' => 'array',
			'sanitize_callback' => function ($value) {
			return is_array($value) ? array_map('sanitize_text_field', $value) : array();
		},
			'default' => array('gemini-2.5-flash', 'gemini-3.1-pro-preview', 'gemini-2.5-pro', 'gemini-2.5-flash-lite'),
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_tts_model', array(
			'type' => 'string',
			'sanitize_callback' => 'sanitize_text_field',
			'default' => 'gemini-2.5-flash-tts',
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_available_tts_models', array(
			'type' => 'array',
			'sanitize_callback' => function ($value) {
			return is_array($value) ? array_map('sanitize_text_field', $value) : array();
		},
			'default' => array('gemini-3.1-flash-tts-preview', 'gemini-2.5-flash-tts', 'gemini-2.5-pro-tts', 'gemini-2.5-flash-lite-preview-tts'),
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_model_regions', array(
			'type' => 'array',
			'sanitize_callback' => array($this, 'sanitize_vertex_model_regions'),
			'default' => array(
				'gemini-3.1-pro-preview' => 'global',
				'gemini-2.5-pro' => 'global',
				'gemini-2.5-flash' => 'global',
				'gemini-2.5-flash-lite' => 'global',
			),
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_tts_model_regions', array(
			'type' => 'array',
			'sanitize_callback' => array($this, 'sanitize_vertex_model_regions'),
			'default' => array(
				'gemini-3.1-flash-tts-preview' => 'global',
				'gemini-2.5-flash-tts' => 'global',
				'gemini-2.5-pro-tts' => 'global',
				'gemini-2.5-flash-lite-preview-tts' => 'global',
			),
		));
		register_setting('Podcastmaker_vertex_settings', 'podcastmaker_vertex_tts_location', array(
			'type' => 'string',
			'sanitize_callback' => array($this, 'sanitize_vertex_tts_location'),
			'default' => 'global',
		));

		// Smart Tone Settings Group
		register_setting('Podcastmaker_smart_tone_settings', 'Podcastmaker_smart_tone_config', array(
			'type' => 'array',
			'sanitize_callback' => array($this, 'sanitize_smart_tone_config'),
			'default' => array(),
		));
	}

	/**
	 * Sanitize Vertex AI Service Account JSON.
	 * Validates it is a valid JSON string containing required fields.
	 */
	public function sanitize_vertex_auth_json($value)
	{
		if (empty($value)) {
			return '';
		}

		$decoded = json_decode($value, true);
		if (json_last_error() !== JSON_ERROR_NONE) {
			add_settings_error(
				'podcastmaker_vertex_auth_json',
				'invalid_json',
				esc_html__('Service Account JSON is not valid JSON.', 'quikvox-ai'),
				'error'
			);
			return get_option('podcastmaker_vertex_auth_json', '');
		}

		$required_fields = array('type', 'project_id', 'private_key', 'client_email');
		foreach ($required_fields as $field) {
			if (empty($decoded[$field])) {
				add_settings_error(
					'podcastmaker_vertex_auth_json',
					'missing_field',
					esc_html__('Service Account JSON is missing required fields.', 'quikvox-ai'),
					'error'
				);
				return get_option('podcastmaker_vertex_auth_json', '');
			}
		}

		return $value;
	}

	/**
	 * Sanitize the selected API provider.
	 *
	 * @param mixed $value Raw provider value.
	 * @return string
	 */
	public function sanitize_api_provider($value)
	{
		$allowed_providers = array('google_ai_studio', 'vertex_ai');
		$provider = sanitize_key((string) $value);
		return in_array($provider, $allowed_providers, true) ? $provider : 'google_ai_studio';
	}

	/**
	 * Sanitize the selected Vertex AI region.
	 *
	 * @param mixed $value Raw region value.
	 * @return string
	 */
	public function sanitize_vertex_location($value)
	{
		$allowed_locations = array(
			'global',
			'us-central1',
			'us-east1',
			'us-east4',
			'us-east5',
			'us-south1',
			'us-west1',
			'us-west4',
			'northamerica-northeast1',
			'europe-central2',
			'europe-north1',
			'europe-southwest1',
			'europe-west1',
			'europe-west4',
			'asia-northeast1',
		);

		$location = sanitize_key((string) $value);
		return in_array($location, $allowed_locations, true) ? $location : 'global';
	}

	/**
	 * Sanitize the selected Vertex AI TTS region.
	 *
	 * @param mixed $value Raw region value.
	 * @return string
	 */
	public function sanitize_vertex_tts_location($value)
	{
		return $this->sanitize_vertex_location($value);
	}

	/**
	 * Get the allowed locations for each Vertex model.
	 *
	 * @return array<string, array<string, array<int, string>>>
	 */
	private function get_vertex_model_location_allowlist()
	{
		return array(
			'text' => array(
				'gemini-3.1-pro-preview' => array('global'),
				'gemini-2.5-pro' => array('global', 'us-central1', 'us-east1', 'us-east4', 'us-east5', 'us-south1', 'us-west1', 'us-west4', 'europe-central2', 'europe-north1', 'europe-southwest1', 'europe-west1', 'europe-west4'),
				'gemini-2.5-flash' => array('global', 'us-central1', 'us-east1', 'us-east4', 'us-east5', 'us-south1', 'us-west1', 'us-west4', 'europe-central2', 'europe-north1', 'europe-southwest1', 'europe-west1', 'europe-west4'),
				'gemini-2.5-flash-lite' => array('global', 'us-central1', 'us-east1', 'us-east4', 'us-east5', 'us-south1', 'us-west1', 'us-west4', 'europe-central2', 'europe-north1', 'europe-southwest1', 'europe-west1', 'europe-west4'),
			),
			'tts' => array(
				'gemini-3.1-flash-tts-preview' => array('global'),
				'gemini-2.5-flash-tts' => array('global', 'us-central1', 'us-east1', 'us-east4', 'us-east5', 'us-south1', 'us-west1', 'us-west4', 'europe-central2', 'europe-north1', 'europe-southwest1', 'europe-west1', 'europe-west4', 'northamerica-northeast1'),
				'gemini-2.5-pro-tts' => array('global', 'us-central1', 'us-east1', 'us-east4', 'us-east5', 'us-south1', 'us-west1', 'us-west4', 'europe-central2', 'europe-north1', 'europe-southwest1', 'europe-west1', 'europe-west4'),
				'gemini-2.5-flash-lite-preview-tts' => array('global', 'us-central1', 'us-east1', 'us-east4', 'us-east5', 'us-south1', 'us-west1', 'us-west4', 'europe-central2', 'europe-north1', 'europe-southwest1', 'europe-west1', 'europe-west4', 'northamerica-northeast1'),
			),
		);
	}

	/**
	 * Determine whether a model is allowed to use a given location.
	 *
	 * @param string $kind Model family.
	 * @param string $model_id Model identifier.
	 * @param string $location Location identifier.
	 * @return bool
	 */
	private function is_vertex_model_location_allowed($kind, $model_id, $location)
	{
		$allowlist = $this->get_vertex_model_location_allowlist();
		$kind_key = sanitize_key((string) $kind);
		$model_key = sanitize_text_field((string) $model_id);
		$location_key = sanitize_key((string) $location);

		return isset($allowlist[$kind_key][$model_key]) && in_array($location_key, $allowlist[$kind_key][$model_key], true);
	}

	/**
	 * Sanitize the selected Vertex AI model region map.
	 *
	 * @param mixed $value Raw model region map.
	 * @return array
	 */
	public function sanitize_vertex_model_regions($value)
	{
		$allowed_locations = array(
			'global',
			'us-central1',
			'us-east1',
			'us-east4',
			'us-east5',
			'us-south1',
			'us-west1',
			'us-west4',
			'northamerica-northeast1',
			'europe-central2',
			'europe-north1',
			'europe-southwest1',
			'europe-west1',
			'europe-west4',
			'asia-northeast1',
		);

		$model_allowlist = $this->get_vertex_model_location_allowlist();

		$sanitized = array();
		foreach ($model_allowlist as $kind_locations) {
			foreach ($kind_locations as $model_id => $allowed_model_locations) {
				$sanitized[$model_id] = in_array('global', $allowed_model_locations, true) ? 'global' : (string) reset($allowed_model_locations);
			}
		}

		if (!is_array($value)) {
			return $sanitized;
		}

		foreach ($value as $model_id => $locations) {
			$clean_model_id = sanitize_text_field((string) $model_id);
			if ($clean_model_id === '') {
				continue;
			}

			if (is_array($locations)) {
				$locations = reset($locations);
			}

			$clean_location = sanitize_key((string) $locations);
			$allowed_model_locations = array();
			if (isset($model_allowlist['text'][$clean_model_id])) {
				$allowed_model_locations = $model_allowlist['text'][$clean_model_id];
			}
			elseif (isset($model_allowlist['tts'][$clean_model_id])) {
				$allowed_model_locations = $model_allowlist['tts'][$clean_model_id];
			}

			if (in_array($clean_location, $allowed_locations, true) && !empty($allowed_model_locations) && in_array($clean_location, $allowed_model_locations, true)) {
				$sanitized[$clean_model_id] = $clean_location;
				continue;
			}

			if (!empty($allowed_model_locations)) {
				$sanitized[$clean_model_id] = in_array('global', $allowed_model_locations, true) ? 'global' : reset($allowed_model_locations);
			}
		}

		return $sanitized;
	}

	/**
	 * Sanitize Smart Tone configuration.
	 */
	public function sanitize_smart_tone_config($value)
	{
		if (!class_exists('Podcastmaker_Smart_Tone')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-smart-tone.php';
		}

		if (!is_array($value)) {
			return Podcastmaker_Smart_Tone::get_defaults();
		}

		$defaults = Podcastmaker_Smart_Tone::get_defaults();
		$sanitized = $defaults;
		foreach ($defaults as $key => $default_value) {
			if (!array_key_exists($key, $value)) {
				continue;
			}

			if ($key === 'default_single_smart_tone_style_id' || $key === 'default_multi_smart_tone_style_id') {
				$sanitized[$key] = sanitize_key($value[$key]);
				continue;
			}

			$sanitized[$key] = sanitize_textarea_field($value[$key]);
		}
		return $sanitized;
	}

	/**
	 * Register the admin menu.
	 */
	public function add_admin_menu()
	{
		add_menu_page(
			__('QuikVox AI', 'quikvox-ai'),
			__('QuikVox AI', 'quikvox-ai'),
			'edit_posts',
			'Podcastmaker',
			array($this, 'render_admin_page'),
			'dashicons-format-audio',
			76
		);

		add_options_page(
			__('QuikVox AI Settings', 'quikvox-ai'),
			__('QuikVox AI Settings', 'quikvox-ai'),
			'manage_options',
			'Podcastmaker',
			array($this, 'render_admin_page')
		);

		add_submenu_page(
			'Podcastmaker',
			__('一括音声作成', 'quikvox-ai'),
			__('一括音声作成', 'quikvox-ai'),
			'manage_options',
			'podcastmaker-bulk-voice',
			array($this, 'render_bulk_voice_page')
		);
	}

	/**
	 * Render the admin page by loading the template.
	 */
	public function render_admin_page()
	{
		if (!current_user_can('manage_options')) {
			wp_safe_redirect(admin_url('admin.php?page=podcastmaker_audio_analytics'));
			exit;
		}
		require_once plugin_dir_path(dirname(__FILE__)) . 'admin/settings-page.php';
		podcastmaker_render_settings_page();
	}

	/**
	 * Render the bulk voice creation page.
	 */
	public function render_bulk_voice_page()
	{
		require_once plugin_dir_path(dirname(__FILE__)) . 'admin/bulk-voice-page.php';
		podcastmaker_render_bulk_voice_page();
	}

	/**
	 * Enqueue CSS and JS assets for the bulk voice creation page.
	 *
	 * @param string $hook Current admin page hook suffix.
	 */
	public function enqueue_bulk_voice_assets($hook)
	{
		if (strpos($hook, 'podcastmaker-bulk-voice') === false) {
			return;
		}
		$version = defined('PODCASTMAKER_VERSION') ? PODCASTMAKER_VERSION : '1.0.0';
		wp_enqueue_style(
			'podcastmaker-bulk-voice-admin',
			plugin_dir_url(dirname(__FILE__)) . 'assets/css/bulk-voice-admin.css',
			array(),
			$version
		);
		wp_enqueue_script(
			'podcastmaker-bulk-voice-admin',
			plugin_dir_url(dirname(__FILE__)) . 'assets/js/bulk-voice-admin.js',
			array(),
			$version,
			true
		);
	}

	/**
	 * Handle Prompt Sets POST actions (save, delete, duplicate).
	 * Called on admin_init hook to ensure it runs before any output.
	 */
	public function handle_prompt_sets_actions()
	{
		// Only process on Podcastmaker admin pages
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
		if ($page !== 'Podcastmaker' && $page !== 'Podcastmaker-settings' && $page !== 'Podcastmaker_settings') {
			return;
		}

		// Only process on prompts tab
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$active_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'general';
		if ($active_tab !== 'prompts') {
			return;
		}

		// Load Prompt Sets class
		if (!class_exists('Podcastmaker_Prompt_Sets')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-prompt-sets.php';
		}
		$prompt_manager = new Podcastmaker_Prompt_Sets();
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$edit_id = isset($_REQUEST['id']) ? sanitize_text_field(wp_unslash($_REQUEST['id'])) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$base_language = isset($_REQUEST['base_language']) ? sanitize_text_field(wp_unslash($_REQUEST['base_language'])) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$base_speaker_type = isset($_REQUEST['base_speaker_type']) ? sanitize_text_field(wp_unslash($_REQUEST['base_speaker_type'])) : 'single';
		$base_speaker_type = $base_speaker_type === 'multi' ? 'multi' : 'single';
		if ($base_language === '') {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$base_language = isset($_REQUEST['prompt_lang']) ? sanitize_text_field(wp_unslash($_REQUEST['prompt_lang'])) : 'en';
		}
		$base_language = $this->normalize_prompt_language($base_language);
		$speaker_filter = $base_speaker_type;

		if (isset($_POST['save_base_prompt']) && check_admin_referer('Podcastmaker_save_base_prompt_nonce')) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('You do not have sufficient permissions to save base prompts.', 'quikvox-ai'));
			}

			$language = isset($_POST['base_prompt_language']) ? sanitize_text_field(wp_unslash($_POST['base_prompt_language'])) : 'en';
			$speaker_type = isset($_POST['base_prompt_speaker_type']) ? sanitize_text_field(wp_unslash($_POST['base_prompt_speaker_type'])) : 'single';
			$content = isset($_POST['base_prompt_content']) ? sanitize_textarea_field(wp_unslash($_POST['base_prompt_content'])) : '';
			$current_base_prompt = $prompt_manager->get_base_prompt_record($language, $speaker_type);
			$name = isset($current_base_prompt['name']) ? (string) $current_base_prompt['name'] : 'Base Prompt';

			if (mb_strlen($content) > 5000) {
				wp_die(esc_html__('Invalid or too long base prompt content.', 'quikvox-ai'));
			}

			$prompt_manager->save_base_prompt($language, $speaker_type, $name, $content);
			wp_safe_redirect($this->get_prompt_list_redirect_url($speaker_filter, array(
				'base_language' => $this->normalize_prompt_language($language),
				'base_speaker_type' => $speaker_type === 'multi' ? 'multi' : 'single',
				'base_prompt_saved' => '1',
			)));
			exit;
		}

		// Handle Save
		if (isset($_POST['save_prompt_set']) && check_admin_referer('Podcastmaker_save_prompt_nonce')) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('You do not have sufficient permissions to save prompt sets.', 'quikvox-ai'));
			}
			$name = isset($_POST['prompt_name']) ? sanitize_text_field(wp_unslash($_POST['prompt_name'])) : '';
			$content = isset($_POST['prompt_content']) ? sanitize_textarea_field(wp_unslash($_POST['prompt_content'])) : '';

			if (empty($content) || mb_strlen($content) > 5000) {
				wp_die(esc_html__('Invalid or too long prompt content.', 'quikvox-ai'));
			}

			$lang = isset($_POST['prompt_lang']) ? sanitize_text_field(wp_unslash($_POST['prompt_lang'])) : 'en';
			$id = isset($_POST['prompt_id']) ? sanitize_text_field(wp_unslash($_POST['prompt_id'])) : '';
			$speaker_type = isset($_POST['speaker_type']) ? sanitize_text_field(wp_unslash($_POST['speaker_type'])) : 'single';
			if ($id !== '') {
				$existing_prompt_set = $prompt_manager->get($id);
				if (is_array($existing_prompt_set) && isset($existing_prompt_set['source_type']) && $existing_prompt_set['source_type'] === 'builtin') {
					wp_die(esc_html__('Built-in prompt sets cannot be edited. Duplicate it first to create a custom prompt set.', 'quikvox-ai'));
				}
			}
			$context_language = isset($_POST['base_language']) ? sanitize_text_field(wp_unslash($_POST['base_language'])) : $lang;
			$context_language = $this->normalize_prompt_language($context_language);
			$context_speaker_type = isset($_POST['base_speaker_type']) ? sanitize_text_field(wp_unslash($_POST['base_speaker_type'])) : $speaker_type;
			$context_speaker_type = $context_speaker_type === 'multi' ? 'multi' : 'single';

			$prompt_manager->save($id, $name, $content, $lang, $speaker_type);
			wp_safe_redirect($this->get_prompt_list_redirect_url($context_speaker_type, array(
				'base_language' => $context_language,
				'base_speaker_type' => $context_speaker_type,
			)));
			exit;
		}

		// Handle Delete (POST + Nonce + Capability)
		if (isset($_POST['delete_prompt_set']) && !empty($edit_id) && check_admin_referer('podcastmaker_delete_prompt_' . $edit_id)) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('You do not have sufficient permissions to delete prompt sets.', 'quikvox-ai'));
			}
			$existing_prompt_set = $prompt_manager->get($edit_id);
			if (is_array($existing_prompt_set) && isset($existing_prompt_set['source_type']) && $existing_prompt_set['source_type'] === 'builtin') {
				wp_die(esc_html__('Built-in prompt sets cannot be deleted.', 'quikvox-ai'));
			}
			$prompt_manager->delete($edit_id);
			wp_safe_redirect($this->get_prompt_list_redirect_url($speaker_filter, array(
				'base_language' => $base_language,
				'base_speaker_type' => $base_speaker_type,
			)));
			exit;
		}

		// Handle Duplication (POST + Nonce + Capability)
		if (isset($_POST['duplicate_prompt_set']) && !empty($edit_id) && check_admin_referer('podcastmaker_duplicate_prompt_' . $edit_id)) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('You do not have sufficient permissions to duplicate prompt sets.', 'quikvox-ai'));
			}
			$prompt_manager->duplicate($edit_id);
			wp_safe_redirect($this->get_prompt_list_redirect_url($speaker_filter, array(
				'base_language' => $base_language,
				'base_speaker_type' => $base_speaker_type,
			)));
			exit;
		}

		if (isset($_POST['set_default_prompt_set']) && !empty($edit_id) && check_admin_referer('podcastmaker_set_default_prompt_' . $edit_id)) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('You do not have sufficient permissions to update prompt defaults.', 'quikvox-ai'));
			}

			$default_speaker_type = isset($_POST['default_speaker_type']) ? sanitize_text_field(wp_unslash($_POST['default_speaker_type'])) : 'single';
			$prompt_manager->set_default_prompt_set_id($default_speaker_type, $edit_id);
			wp_safe_redirect($this->get_prompt_list_redirect_url($speaker_filter, array(
				'base_language' => $base_language,
				'base_speaker_type' => $base_speaker_type,
				'default_updated' => '1',
			)));
			exit;
		}
	}

	/**
	 * Handle Smart Tone POST actions.
	 */
	public function handle_smart_tone_actions()
	{
		// Only process on Podcastmaker admin pages
		$page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
		if ($page !== 'Podcastmaker' && $page !== 'Podcastmaker-settings' && $page !== 'Podcastmaker_settings') {
			return;
		}

		// Only process on smart_tone tab
		$active_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'general';
		if ($active_tab !== 'smart_tone') {
			return;
		}

		if (!class_exists('Podcastmaker_Smart_Tone')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-smart-tone.php';
		}

		$edit_id = isset($_REQUEST['id']) ? sanitize_text_field(wp_unslash($_REQUEST['id'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$speaker_filter = $this->get_smart_tone_speaker_filter();

		if (isset($_POST['save_smart_tone_style']) && check_admin_referer('podcastmaker_save_smart_tone_style_nonce')) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('Insufficient permissions.', 'quikvox-ai'));
			}

			$style_id = isset($_POST['style_id']) ? sanitize_text_field(wp_unslash($_POST['style_id'])) : '';
			if ($style_id !== '') {
				$existing_style = Podcastmaker_Smart_Tone::get_style($style_id);
				if (is_array($existing_style) && !empty($existing_style['is_system'])) {
					wp_die(esc_html__('Built-in Smart Tone styles cannot be edited directly. Duplicate it first.', 'quikvox-ai'));
				}
			}
			$name = isset($_POST['style_name']) ? sanitize_text_field(wp_unslash($_POST['style_name'])) : '';
			$speaker_type = isset($_POST['speaker_type']) ? sanitize_text_field(wp_unslash($_POST['speaker_type'])) : 'single';
			$description = isset($_POST['style_description']) ? sanitize_text_field(wp_unslash($_POST['style_description'])) : '';
			$directive = isset($_POST['style_directive']) ? sanitize_textarea_field(wp_unslash($_POST['style_directive'])) : '';
			$voice_direction_template = isset($_POST['voice_direction_template']) ? sanitize_textarea_field(wp_unslash($_POST['voice_direction_template'])) : '';

			if ($name === '' || $directive === '') {
				wp_die(esc_html__('Style title and directive are required.', 'quikvox-ai'));
			}

			Podcastmaker_Smart_Tone::save_style($style_id, $name, $speaker_type, $description, $directive, $voice_direction_template);
			wp_safe_redirect($this->get_smart_tone_list_redirect_url($speaker_type, array('saved_style' => '1')));
			exit;
		}

		if (isset($_POST['duplicate_smart_tone_style']) && !empty($edit_id) && check_admin_referer('podcastmaker_duplicate_smart_tone_' . $edit_id)) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('Insufficient permissions.', 'quikvox-ai'));
			}

			Podcastmaker_Smart_Tone::duplicate_style($edit_id);
			wp_safe_redirect($this->get_smart_tone_list_redirect_url($speaker_filter, array('duplicated_style' => '1')));
			exit;
		}

		if (isset($_POST['delete_smart_tone_style']) && !empty($edit_id) && check_admin_referer('podcastmaker_delete_smart_tone_' . $edit_id)) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('Insufficient permissions.', 'quikvox-ai'));
			}

			$existing_style = Podcastmaker_Smart_Tone::get_style($edit_id);
			if (is_array($existing_style) && !empty($existing_style['is_system'])) {
				wp_die(esc_html__('Built-in Smart Tone styles cannot be deleted.', 'quikvox-ai'));
			}

			Podcastmaker_Smart_Tone::delete_style($edit_id);
			wp_safe_redirect($this->get_smart_tone_list_redirect_url($speaker_filter, array('deleted_style' => '1')));
			exit;
		}

		if (isset($_POST['set_default_smart_tone_style']) && !empty($edit_id) && check_admin_referer('podcastmaker_set_default_smart_tone_' . $edit_id)) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('Insufficient permissions.', 'quikvox-ai'));
			}

			$style_type = isset($_POST['default_speaker_type']) ? sanitize_text_field(wp_unslash($_POST['default_speaker_type'])) : 'single';
			Podcastmaker_Smart_Tone::set_default_style_id($style_type, $edit_id);
			wp_safe_redirect($this->get_smart_tone_list_redirect_url($style_type, array('default_updated' => '1')));
			exit;
		}

		// Handle Restore Defaults
		if (isset($_POST['restore_smart_tone_defaults']) && check_admin_referer('Podcastmaker_smart_tone_nonce')) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('Insufficient permissions.', 'quikvox-ai'));
			}

			Podcastmaker_Smart_Tone::reset();
			wp_safe_redirect($this->get_smart_tone_list_redirect_url($speaker_filter, array('restored' => '1')));
			exit;
		}

		// Handle Save
		if (isset($_POST['save_smart_tone_settings']) && check_admin_referer('Podcastmaker_smart_tone_nonce')) {
			if (!current_user_can('manage_options')) {
				wp_die(esc_html__('Insufficient permissions.', 'quikvox-ai'));
			}

			$config = isset($_POST['smart_tone_config']) ? (array) wp_unslash($_POST['smart_tone_config']) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via sanitize_smart_tone_config() below.

			// Sanitize is handled by register_setting, but we're doing manual save here for better control/tabs
			$sanitized = $this->sanitize_smart_tone_config($config);
			Podcastmaker_Smart_Tone::save($sanitized);

			wp_safe_redirect(admin_url('admin.php?page=Podcastmaker&tab=smart_tone&saved=1'));
			exit;
		}
	}

	/**
	 * AJAX handler to test Vertex AI connection.
	 */
	public function ajax_test_vertex_connection()
	{
		check_ajax_referer('podcastmaker_admin_nonce', 'nonce');
		if (!current_user_can('manage_options')) {
			wp_send_json_error(array('message' => esc_html__('Insufficient permissions.', 'quikvox-ai')));
		}

		$auth_json = isset($_POST['auth_json']) ? sanitize_textarea_field(wp_unslash($_POST['auth_json'])) : '';
		if (empty($auth_json)) {
			wp_send_json_error(array('message' => esc_html__('Service Account JSON is empty.', 'quikvox-ai')));
		}

		$decoded = json_decode($auth_json, true);
		if (!is_array($decoded)) {
			wp_send_json_error(array(
				'message' => esc_html__('Vertex AI: Service Account JSON could not be parsed.', 'quikvox-ai'),
				'code' => 'invalid_json',
				'details' => function_exists('json_last_error_msg') ? json_last_error_msg() : esc_html__('Unknown JSON error.', 'quikvox-ai'),
			));
		}

		$required_fields = array('type', 'project_id', 'private_key', 'client_email');
		$missing_fields = array();
		foreach ($required_fields as $required_field) {
			if (empty($decoded[$required_field])) {
				$missing_fields[] = $required_field;
			}
		}

		if (!empty($missing_fields)) {
			wp_send_json_error(array(
				'message' => esc_html__('Vertex AI: Service Account JSON is missing required fields.', 'quikvox-ai'),
				'code' => 'missing_fields',
				'details' => implode(', ', $missing_fields),
			));
		}

		if (!class_exists('Podcastmaker_API_Vertex')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-api-vertex.php';
		}

		$api = new Podcastmaker_API_Vertex($auth_json, 'global');
		$token = $api->get_access_token();

		if (is_wp_error($token)) {
			$error_data = $token->get_error_data();
			wp_send_json_error(array(
				'message' => $token->get_error_message(),
				'code' => $token->get_error_code(),
				'details' => is_array($error_data) && isset($error_data['details']) ? $error_data['details'] : '',
			));
		}

		wp_send_json_success(array(
			'message' => esc_html__('Authentication successful! Access token generated.', 'quikvox-ai'),
		));
	}

	/**
	 * AJAX handler: Check whether all enabled Vertex models and locations are usable.
	 */
	public function ajax_check_vertex_availability()
	{
		check_ajax_referer('podcastmaker_admin_nonce', 'nonce');
		if (!current_user_can('manage_options')) {
			wp_send_json_error(array('message' => esc_html__('Insufficient permissions.', 'quikvox-ai')));
		}

		$auth_json = isset($_POST['auth_json']) ? sanitize_textarea_field(wp_unslash($_POST['auth_json'])) : '';
		$kind = isset($_POST['kind']) ? sanitize_key(wp_unslash($_POST['kind'])) : 'text';
		$targets_raw = isset($_POST['targets']) ? sanitize_textarea_field(wp_unslash($_POST['targets'])) : '';
		$targets = array();

		if (empty($auth_json)) {
			wp_send_json_error(array('message' => esc_html__('Service Account JSON is empty.', 'quikvox-ai')));
		}

		if (!in_array($kind, array('text', 'tts'), true)) {
			wp_send_json_error(array(
				'message' => esc_html__('Invalid model kind.', 'quikvox-ai'),
				'code' => 'invalid_kind',
			));
		}

		if (!empty($targets_raw)) {
			$decoded_targets = json_decode($targets_raw, true);
			if (is_array($decoded_targets)) {
				$targets = $decoded_targets;
			}
		}

		if (empty($targets)) {
			$legacy_model = isset($_POST['model']) ? sanitize_text_field(wp_unslash($_POST['model'])) : '';
			$legacy_location = isset($_POST['location']) ? sanitize_key(wp_unslash($_POST['location'])) : '';

			if (!empty($legacy_model) && !empty($legacy_location)) {
				$targets[] = array(
					'model' => $legacy_model,
					'location' => $legacy_location,
				);
			}
		}

		if (empty($targets) || !is_array($targets)) {
			wp_send_json_error(array(
				'message' => esc_html__('Availability check failed.', 'quikvox-ai'),
				'code' => 'missing_selection',
				'details' => 'No enabled models were selected.',
			));
		}

		if (!class_exists('Podcastmaker_API_Vertex')) {
			require_once plugin_dir_path(__FILE__) . 'class-podcastmaker-api-vertex.php';
		}

		$failures = array();
		$successes = array();

		foreach ($targets as $target) {
			$model = isset($target['model']) ? sanitize_text_field($target['model']) : '';
			$location = isset($target['location']) ? sanitize_key($target['location']) : '';

			if (empty($model) || empty($location)) {
				$failures[] = sprintf('- %1$s: %2$s', $model, __('Please select both a model and a location.', 'quikvox-ai'));
				continue;
			}

			if (!$this->is_vertex_model_location_allowed($kind, $model, $location)) {
				/* translators: %1$s is the selected location. */
				$failures[] = sprintf('- %1$s @ %2$s: %3$s', $model, $location, sprintf(__('The selected model is not available at %1$s.', 'quikvox-ai'), $location));
				continue;
			}

			$api = new Podcastmaker_API_Vertex($auth_json, $location, $location);
			$result = $api->check_model_availability($kind, $model);

			if (is_wp_error($result)) {
				$error_data = $result->get_error_data();
				$failure_message = $result->get_error_message();
				if (is_array($error_data) && isset($error_data['details']) && !empty($error_data['details'])) {
					$failure_message .= ' ' . $error_data['details'];
				}
				$failures[] = sprintf('- %1$s @ %2$s: %3$s', $model, $location, $failure_message);
				continue;
			}

			$successes[] = sprintf('- %1$s @ %2$s', $model, $location);
		}

		if (!empty($failures)) {
			wp_send_json_error(array(
				'message' => esc_html__('Availability check failed.', 'quikvox-ai'),
				'code' => 'availability_failed',
				'details' => implode(' | ', $failures),
			));
		}

		wp_send_json_success(array(
			'message' => esc_html__('Availability confirmed.', 'quikvox-ai'),
			'details' => implode("\n", $successes),
			'kind' => $kind,
			'checked_count' => count($successes),
		));
	}
}
