<?php
/**
 * Plugin Name: WPStack AI ALT Genius
 * Plugin URI:  https://wpstack.online/
 * Description: Automatically generates alt text, titles, captions, and descriptions for images using AI (OpenRouter, OpenAI, NVIDIA NIM, or MiniMax). Adds generation controls to the Media Library and attachment editor.
 * Version:     1.0.0
 * Author:      WPStack
 * Author URI:  https://wpstack.online
 * Text Domain: wpstack-ai-alt-genius
 * Domain Path: /languages
 * License:     GPLv2 or later
 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

define( 'AIALTG_VERSION', '1.0.0' );
define( 'AIALTG_SLUG', 'wpstack-ai-alt-genius' );
define( 'AIALTG_TEXT_DOMAIN', 'wpstack-ai-alt-genius' );
define( 'AIALTG_PLUGIN_FILE', __FILE__ );
define( 'AIALTG_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'AIALTG_BRAND_WPSTACK', 'https://wpstack.online' );
define( 'AIALTG_BRAND_MIRACUVES', 'https://miracuves.com' );

// Include required classes.
require_once plugin_dir_path( __FILE__ ) . 'includes/class-aialtg-helpers.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-aialtg-settings.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-aialtg-generator.php';

// Conditionally include Cron class if file exists.
$aialtg_cron_file = plugin_dir_path( __FILE__ ) . 'includes/class-aialtg-cron.php';
if ( file_exists( $aialtg_cron_file ) ) {
	require_once $aialtg_cron_file;
}

/**
 * Main Plugin Class
 */
class Aialtg_Image_Descriptor {

	/**
	 * Constructor.
	 */
	public function __construct() {
		// Initialize Settings.
		new Aialtg_Settings();

		// Initialize Cron if available.
		if ( class_exists( 'Aialtg_Cron' ) ) {
			new Aialtg_Cron();
		}

		// Media library columns.
		add_filter( 'manage_media_columns', array( $this, 'add_media_column' ) );
		add_action( 'manage_media_custom_column', array( $this, 'manage_media_column_content' ), 10, 2 );

		// Meta boxes.
		add_action( 'add_meta_boxes', array( $this, 'add_attachment_meta_box' ) );

		// AJAX Handlers.
		add_action( 'wp_ajax_aialtg_generate_meta', array( $this, 'handle_ajax_request' ) );
		add_action( 'wp_ajax_aialtg_reset_progress', array( $this, 'handle_reset_request' ) );
		// New AJAX for fixing JSON errors
		add_action( 'wp_ajax_aialtg_fix_json_errors', array( $this, 'handle_fix_json_errors' ) );
		// New AJAX for retrying failed images
		add_action( 'wp_ajax_aialtg_retry_failed', array( $this, 'handle_retry_failed_request' ) );
		// New AJAX for fetching models
		add_action( 'wp_ajax_aialtg_get_models', array( $this, 'handle_get_models_request' ) );

		// Assets.
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
		add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );

		// Activation & Deactivation (registered at file load — see bottom of file).
	}

	/**
	 * Logic runs on plugin activation.
	 */
	public static function activation_logic() {
		// Reschedule cron after plugin path/slug changes (settings option key is unchanged).
		if ( class_exists( 'Aialtg_Cron' ) ) {
			$options = get_option( 'aialtg_settings' );
			if ( isset( $options['cron_enabled'] ) && '1' === $options['cron_enabled'] ) {
				wp_clear_scheduled_hook( Aialtg_Cron::CRON_HOOK );
				wp_schedule_event( time() + 10, Aialtg_Cron::INTERVAL_NAME, Aialtg_Cron::CRON_HOOK );
			}
		}
	}

	/**
	 * Load plugin translations.
	 */
	public function load_textdomain() {
		load_plugin_textdomain(
			AIALTG_TEXT_DOMAIN,
			false,
			dirname( plugin_basename( __FILE__ ) ) . '/languages'
		);
	}

	/**
	 * Deactivation cleanup.
	 */
	public static function deactivation_cleanup() {
		// We use the constant if available, otherwise fallback string to ensure cleanup.
		$hook = class_exists( 'Aialtg_Cron' ) ? Aialtg_Cron::CRON_HOOK : 'aialtg_cron_process_images';
		wp_clear_scheduled_hook( $hook );
		delete_transient( 'aialtg_stats' );
	}

	// --- Existing UI Logic ---

	/**
	 * Add column to Media Library.
	 *
	 * @param array $columns Existing columns.
	 * @return array New columns.
	 */
	public function add_media_column( $columns ) {
		$columns['aialtg_alt_gen'] = __( 'AI ALT Genius', AIALTG_TEXT_DOMAIN );
		return $columns;
	}

	/**
	 * Render column content.
	 *
	 * @param string $column_name Column name.
	 * @param int    $post_id Post ID.
	 */
	public function manage_media_column_content( $column_name, $post_id ) {
		if ( 'aialtg_alt_gen' !== $column_name ) {
			return;
		}
		if ( ! wp_attachment_is_image( $post_id ) ) {
			echo esc_html__( 'N/A', AIALTG_TEXT_DOMAIN );
			return;
		}

		// Check supported formats logic here.
		// If settings exist, verify against allowed MIME types.
		if ( class_exists( 'Aialtg_Settings' ) && method_exists( 'Aialtg_Settings', 'get_allowed_mimes' ) ) {
			$allowed_mimes = Aialtg_Settings::get_allowed_mimes();
			$current_mime  = get_post_mime_type( $post_id );

			// If allowed_mimes is strict array and current isn't in it.
			if ( is_array( $allowed_mimes ) && ! in_array( $current_mime, $allowed_mimes, true ) ) {
				echo esc_html__( 'N/A', AIALTG_TEXT_DOMAIN );
				return;
			}
		}

		$has_alt = get_post_meta( $post_id, '_wp_attachment_image_alt', true );
		$status_class = $has_alt ? 'button-secondary' : 'button-primary';
		$button_text  = $has_alt ? __( 'Regenerate', AIALTG_TEXT_DOMAIN ) : __( 'Generate Info', AIALTG_TEXT_DOMAIN );

		?>
		<div class="aialtg-descriptor-wrap">
			<button type="button"
					class="button <?php echo esc_attr( $status_class ); ?> aialtg-generate-btn"
					data-id="<?php echo esc_attr( $post_id ); ?>"
					data-nonce="<?php echo esc_attr( wp_create_nonce( 'aialtg_gen_nonce_' . $post_id ) ); ?>">
				<?php echo esc_html( $button_text ); ?>
			</button>
			<span class="spinner aialtg-spinner"></span>
			<div class="aialtg-message"></div>
		</div>
		<?php
	}

	/**
	 * Add meta box to Edit Media screen.
	 */
	public function add_attachment_meta_box() {
		add_meta_box(
			'aialtg_meta_box',
			__( 'WPStack AI ALT Genius', AIALTG_TEXT_DOMAIN ),
			array( $this, 'render_meta_box' ),
			'attachment',
			'side',
			'high'
		);
	}

	/**
	 * Render meta box content.
	 *
	 * @param WP_Post $post Post object.
	 */
	public function render_meta_box( $post ) {
		if ( ! wp_attachment_is_image( $post->ID ) ) {
			echo '<p>' . esc_html__( 'This is not an image.', AIALTG_TEXT_DOMAIN ) . '</p>';
			return;
		}

		$has_alt = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
		$btn_text = $has_alt ? __( 'Regenerate', AIALTG_TEXT_DOMAIN ) : __( 'Generate Alt Text', AIALTG_TEXT_DOMAIN );
		$btn_class = $has_alt ? 'button-secondary' : 'button-primary';
		?>
		<div class="aialtg-meta-box-wrap">
			<button type="button"
				class="button <?php echo esc_attr( $btn_class ); ?> aialtg-generate-btn"
				data-id="<?php echo esc_attr( $post->ID ); ?>"
				data-nonce="<?php echo esc_attr( wp_create_nonce( 'aialtg_gen_nonce_' . $post->ID ) ); ?>">
			<?php echo esc_html( $btn_text ); ?>
			</button>
			<span class="spinner aialtg-spinner" style="float: none; margin-top: 4px;"></span>
			<div class="aialtg-message" style="margin-top: 8px;"></div>

			<hr style="margin: 15px 0;">

			<?php
			$error = get_post_meta( $post->ID, '_aialtg_error_log', true );
			if ( ! empty( $error ) ) {
				echo '<div style="color: #d63638; margin-bottom: 10px;">';
				echo '<strong>' . esc_html__( 'Last Error:', AIALTG_TEXT_DOMAIN ) . '</strong><br>';
				echo esc_html( $error );
				echo '</div>';
			}

			$date   = get_post_meta( $post->ID, '_aialtg_gen_date', true );
			$source = get_post_meta( $post->ID, '_aialtg_gen_source', true );

			if ( empty( $date ) && empty( $error ) ) {
				echo '<p class="description">' . esc_html__( 'No generation info yet.', AIALTG_TEXT_DOMAIN ) . '</p>';
			} elseif ( ! empty( $date ) ) {
				echo '<p><strong>' . esc_html__( 'Last Generated:', AIALTG_TEXT_DOMAIN ) . '</strong><br>' . esc_html( $date ) . '</p>';
				echo '<p><strong>' . esc_html__( 'Source:', AIALTG_TEXT_DOMAIN ) . '</strong> ' . esc_html( ucfirst( $source ) ) . '</p>';
			}
			?>
		</div>
		<?php
	}

	/**
	 * AJAX Handler for Generation.
	 */
	public function handle_ajax_request() {
		$post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
		if ( ! $post_id ) {
			wp_send_json_error( array( 'message' => __( 'Invalid ID', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! isset( $_POST['nonce'] ) ) {
			wp_send_json_error( array( 'message' => __( 'Nonce missing', AIALTG_TEXT_DOMAIN ) ) );
		}

		// Fix: Unslash before sanitizing.
		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'aialtg_gen_nonce_' . $post_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! current_user_can( 'upload_files' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', AIALTG_TEXT_DOMAIN ) ) );
		}

		// Use the Generator Class.
		$generator = new Aialtg_Generator();
		$result = $generator->process_image( $post_id, 'manual' );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ) );
		} else {
			// Clear stats cache.
			delete_transient( 'aialtg_stats' );

			wp_send_json_success( array(
				'message'     => __( 'Info Saved!', AIALTG_TEXT_DOMAIN ),
				'alt_text'    => $result['alt_text'],
				'title'       => $result['title'],
				'caption'     => $result['caption'],
				'description' => $result['description'],
			) );
		}
	}

	/**
	 * AJAX Handler for Resetting Progress.
	 */
	public function handle_reset_request() {
		if ( ! isset( $_POST['nonce'] ) ) {
			wp_send_json_error( array( 'message' => __( 'Nonce missing', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'aialtg_reset_nonce' ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', AIALTG_TEXT_DOMAIN ) ) );
		}

		global $wpdb;
		// Delete the meta key from all posts to reset "processed" status.
		// Use direct query for performance on large datasets to avoid loading IDs into memory.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key
		$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_aialtg_processed' ) );

		// Clean up error logs as well when resetting.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.SlowDBQuery.slow_db_query_meta_key
		$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => '_aialtg_error_log' ) );

		// Clear cached stats.
		delete_transient( 'aialtg_stats' );

		wp_send_json_success( array( 'message' => __( 'Progress reset successfully.', AIALTG_TEXT_DOMAIN ) ) );
	}

	/**
	 * AJAX Handler for Retrying Failed Images.
	 */
	public function handle_retry_failed_request() {
		if ( ! isset( $_POST['nonce'] ) ) {
			wp_send_json_error( array( 'message' => __( 'Nonce missing', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'aialtg_retry_failed_nonce' ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', AIALTG_TEXT_DOMAIN ) ) );
		}

		global $wpdb;
		// Delete _aialtg_processed meta ONLY where value is 'failed'.
		// This puts them back into the "Not Processed" (Pending) queue for the Cron job.

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$count = $wpdb->query( $wpdb->prepare(
			"DELETE FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
			'_aialtg_processed',
			'failed'
		) );

		// Clear cached stats.
		delete_transient( 'aialtg_stats' );

		if ( $count ) {
			/* translators: %d: Number of images moved to queue */
			$message = sprintf( __( 'Moved %d failed images back to the processing queue.', AIALTG_TEXT_DOMAIN ), $count );
		} else {
			$message = __( 'No failed images found.', AIALTG_TEXT_DOMAIN );
		}

		wp_send_json_success( array( 'message' => $message ) );
	}

	/**
	 * AJAX Handler for Scanning and Fixing JSON Errors.
	 *
	 * Detects images where raw JSON (e.g. {"alt_text": ...}) was accidentally saved as the Alt Text or Title.
	 * Resets their processed status so they are regenerated.
	 */
	public function handle_fix_json_errors() {
		if ( ! isset( $_POST['nonce'] ) ) {
			wp_send_json_error( array( 'message' => __( 'Nonce missing', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'aialtg_fix_json_nonce' ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', AIALTG_TEXT_DOMAIN ) ) );
		}

		global $wpdb;

		// Perform a targeted query to find processed images that look like they contain raw JSON.
		// We look for common JSON keys/markers in Alt Text meta or Post Title.
		// Patterns: {"alt_text", {"title", ```json
		// We join with our processed flag to only target handled images.
		// Updated to find "alt_text" or "title" key strings even if there are spaces/newlines.

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$results = $wpdb->get_results( "
			SELECT p.ID
			FROM {$wpdb->posts} p
			LEFT JOIN {$wpdb->postmeta} pm_alt ON (p.ID = pm_alt.post_id AND pm_alt.meta_key = '_wp_attachment_image_alt')
			INNER JOIN {$wpdb->postmeta} pm_proc ON (p.ID = pm_proc.post_id AND pm_proc.meta_key = '_aialtg_processed')
			WHERE
			(pm_alt.meta_value LIKE '%\"alt_text\"%' OR pm_alt.meta_value LIKE '%```json%')
			OR
			(p.post_title LIKE '%\"title\"%' OR p.post_title LIKE '%```json%')
			OR
			(p.post_excerpt LIKE '%\"caption\"%' OR p.post_excerpt LIKE '%```json%')
			OR
			(p.post_content LIKE '%\"description\"%' OR p.post_content LIKE '%```json%')
		" );

		$count = 0;
		if ( $results ) {
			foreach ( $results as $row ) {
				// We don't delete the bad text, we just reset the 'processed' flag.
				// The Cron will overwrite the text on next run.
				delete_post_meta( $row->ID, '_aialtg_processed' );
				// Clean up error log too if it exists.
				delete_post_meta( $row->ID, '_aialtg_error_log' );
				$count++;
			}
			// Clear stats cache so the UI updates pending count.
			delete_transient( 'aialtg_stats' );
		}

		if ( $count > 0 ) {
			/* translators: %d: number of images fixed */
			$message = sprintf( __( 'Found and reset %d images with bad JSON data. They are now pending regeneration.', AIALTG_TEXT_DOMAIN ), $count );
		} else {
			$message = __( 'No JSON errors found.', AIALTG_TEXT_DOMAIN );
		}

		wp_send_json_success( array( 'message' => $message ) );
	}

	/**
	 * AJAX Handler for fetching models from the selected provider.
	 */
	public function handle_get_models_request() {
		$provider = isset( $_POST['provider'] ) ? sanitize_text_field( wp_unslash( $_POST['provider'] ) ) : 'openrouter';

		if ( ! isset( $_POST['nonce'] ) ) {
			wp_send_json_error( array( 'message' => __( 'Nonce missing', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'aialtg_models_nonce' ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed', AIALTG_TEXT_DOMAIN ) ) );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied', AIALTG_TEXT_DOMAIN ) ) );
		}

		// Check transient cache first.
		$cache_key = 'aialtg_models_' . $provider;
		$models   = get_transient( $cache_key );

		if ( false === $models ) {
			// Get the models URL for this provider.
			$providers   = Aialtg_Settings::get_providers();
			$models_url = isset( $providers[ $provider ]['models_url'] ) ? $providers[ $provider ]['models_url'] : $providers['openrouter']['models_url'];

			// Some providers require authentication to list models.
			$opts = array( 'timeout' => 15 );
			if ( ! empty( $providers[ $provider ]['needs_auth_for_models'] ) ) {
				$options = get_option( 'aialtg_settings' );
				$options = is_array( $options ) ? $options : array();
				$api_key = Aialtg_Helpers::resolve_api_key( $options );
				if ( empty( $api_key ) ) {
					wp_send_json_error( array( 'message' => __( 'API key is required to fetch models for this provider.', AIALTG_TEXT_DOMAIN ) ) );
				}
				$opts['headers'] = array( 'Authorization' => 'Bearer ' . $api_key );
			}

			$response = wp_remote_get( $models_url, $opts );

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

			$response_code = wp_remote_retrieve_response_code( $response );
			if ( 200 !== $response_code ) {
				wp_send_json_error( array( 'message' => sprintf( __( 'API returned HTTP %d', AIALTG_TEXT_DOMAIN ), $response_code ) ) );
			}

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

			if ( ! is_array( $data ) || ! isset( $data['data'] ) ) {
				wp_send_json_error( array( 'message' => __( 'Invalid data format from API', AIALTG_TEXT_DOMAIN ) ) );
			}

			$models = $data['data'];

			// Cache models list for 24 hours.
			set_transient( $cache_key, $models, DAY_IN_SECONDS );
		}

		wp_send_json_success( array( 'models' => $models ) );
	}

	/**
	 * Enqueue Assets.
	 *
	 * @param string $hook Admin hook.
	 */
	public function enqueue_admin_assets( $hook ) {
		// Enqueue on Upload (Media Library) AND our Settings page.
		if ( 'upload.php' !== $hook && 'toplevel_page_' . AIALTG_SLUG !== $hook ) {
			if ( 'post.php' === $hook ) {
				global $post;
				if ( ! $post || 'attachment' !== $post->post_type ) {
					return;
				}
			} else {
				return;
			}
		}

		wp_enqueue_style( 'aialtg-admin-css', plugins_url( 'assets/css/admin.css', __FILE__ ), array(), AIALTG_VERSION );
		wp_enqueue_script( 'aialtg-admin-js', plugins_url( 'assets/js/admin.js', __FILE__ ), array( 'jquery' ), AIALTG_VERSION, true );

		wp_localize_script( 'aialtg-admin-js', 'aialtg_vars', array(
			'processing'            => __( 'Processing...', AIALTG_TEXT_DOMAIN ),
			'regenerate'            => __( 'Regenerate', AIALTG_TEXT_DOMAIN ),
			'network_error'         => __( 'Network error', AIALTG_TEXT_DOMAIN ),
			'reset_confirm'         => __( 'Are you sure you want to reset the progress? The cron job will re-analyze all images. This does not delete existing Alt Texts.', AIALTG_TEXT_DOMAIN ),
			'providerPlaceholders'  => Aialtg_Settings::get_provider_placeholders(),
			'visionKeywords'        => Aialtg_Helpers::vision_keywords(),
		) );
	}
}

register_activation_hook( __FILE__, array( 'Aialtg_Image_Descriptor', 'activation_logic' ) );
register_deactivation_hook( __FILE__, array( 'Aialtg_Image_Descriptor', 'deactivation_cleanup' ) );

new Aialtg_Image_Descriptor();