<?php
/**
 * Plugin Name: Altify AI – Auto ALT Text Generator
 * Plugin URI: https://profiles.wordpress.org/ravikhadka/
 * Description: AI and offline ALT text generator for WordPress images with bulk tools for Media Library and WooCommerce.
 * Version: 1.0.3
 * Requires at least: 6.0
 * Requires PHP: 7.4
 * Author: Ravi Khadka
 * Author URI: https://profiles.wordpress.org/ravikhadka/
 * License: GPL-2.0-or-later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: altify-ai-auto-alt-text-generator
 * Domain Path: /languages
 */

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

if (!defined('ALTIFY_AI_VERSION')) {
	define('ALTIFY_AI_VERSION', '1.0.3');
}

if (!defined('ALTIFY_AI_PLUGIN_FILE')) {
	define('ALTIFY_AI_PLUGIN_FILE', __FILE__);
}

if (!defined('ALTIFY_AI_PLUGIN_DIR')) {
	define('ALTIFY_AI_PLUGIN_DIR', plugin_dir_path(__FILE__));
}

if (!defined('ALTIFY_AI_PLUGIN_URL')) {
	define('ALTIFY_AI_PLUGIN_URL', plugin_dir_url(__FILE__));
}

require_once ALTIFY_AI_PLUGIN_DIR . 'includes/helpers.php';

if (!function_exists('altify_ai_requirements_met')) {
	/**
	 * Validate minimum plugin requirements.
	 *
	 * @return bool
	 */
	function altify_ai_requirements_met() {
		global $wp_version;

		if (version_compare(PHP_VERSION, '7.4', '<')) {
			return false;
		}

		if (!isset($wp_version) || version_compare($wp_version, '6.0', '<')) {
			return false;
		}

		return true;
	}
}

if (!function_exists('altify_ai_requirements_notice')) {
	/**
	 * Show a requirements notice in admin.
	 *
	 * @return void
	 */
	function altify_ai_requirements_notice() {
		echo '<div class="notice notice-error"><p>' . esc_html__('Altify AI requires WordPress 6.0+ and PHP 7.4+.', 'altify-ai-auto-alt-text-generator') . '</p></div>';
	}
}

if (!altify_ai_requirements_met()) {
	add_action('admin_notices', 'altify_ai_requirements_notice');
	return;
}

require_once ALTIFY_AI_PLUGIN_DIR . 'includes/class-altify-ai-logger.php';
require_once ALTIFY_AI_PLUGIN_DIR . 'includes/class-altify-ai-client.php';
require_once ALTIFY_AI_PLUGIN_DIR . 'includes/class-altify-ai-woocommerce.php';
require_once ALTIFY_AI_PLUGIN_DIR . 'includes/class-altify-ai-bulk.php';
require_once ALTIFY_AI_PLUGIN_DIR . 'includes/class-altify-ai-settings.php';

/**
 * Main plugin bootstrap.
 */
class Altify_AI_Plugin {
	/**
	 * Upload generation event hook.
	 */
	const UPLOAD_EVENT_HOOK = 'altify_ai_generate_alt_event';

	/**
	 * Daily automation event hook.
	 */
	const DAILY_SCAN_EVENT_HOOK = 'altify_ai_daily_scan_event';

	/**
	 * Daily automation status option key.
	 */
	const DAILY_SCAN_STATUS_OPTION = 'altify_ai_daily_scan_status';

	/**
	 * Daily automation lock key.
	 */
	const DAILY_SCAN_LOCK_TRANSIENT = 'altify_ai_daily_scan_lock';

	/**
	 * Singleton instance.
	 *
	 * @var Altify_AI_Plugin|null
	 */
	private static $instance = null;

	/**
	 * Logger instance.
	 *
	 * @var Altify_AI_Logger
	 */
	private $logger;

	/**
	 * OpenAI client.
	 *
	 * @var Altify_AI_Client
	 */
	private $client;

	/**
	 * WooCommerce helper.
	 *
	 * @var Altify_AI_WooCommerce
	 */
	private $woocommerce;

	/**
	 * Bulk processor.
	 *
	 * @var Altify_AI_Bulk
	 */
	private $bulk;

	/**
	 * Admin settings.
	 *
	 * @var Altify_AI_Settings
	 */
	private $settings;

	/**
	 * Get singleton instance.
	 *
	 * @return Altify_AI_Plugin
	 */
	public static function instance() {
		if (null === self::$instance) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Plugin constructor.
	 */
	private function __construct() {
		$this->logger      = new Altify_AI_Logger();
		$this->woocommerce = new Altify_AI_WooCommerce($this->logger);
		$this->client      = new Altify_AI_Client($this->logger, $this->woocommerce);
		$this->bulk        = new Altify_AI_Bulk($this->client, $this->woocommerce, $this->logger);
		$this->settings    = new Altify_AI_Settings($this->client, $this->bulk, $this->woocommerce, $this->logger);

		add_action('add_attachment', array($this, 'queue_attachment_generation'), 20);
		add_action(self::UPLOAD_EVENT_HOOK, array($this, 'handle_scheduled_generation'), 10, 1);
		add_action(self::DAILY_SCAN_EVENT_HOOK, array($this, 'handle_daily_scan_event'));
		add_action('init', array(__CLASS__, 'ensure_daily_scan_schedule'));
	}

	/**
	 * Add default options on activation.
	 *
	 * @return void
	 */
	public static function activate() {
		$current = get_option('altify_ai_settings', null);
		if (!is_array($current)) {
			add_option('altify_ai_settings', altify_ai_get_default_settings(), '', false);
		}

		self::ensure_daily_scan_schedule();
	}

	/**
	 * Clear scheduled jobs on deactivation.
	 *
	 * @return void
	 */
	public static function deactivate() {
		wp_clear_scheduled_hook(self::UPLOAD_EVENT_HOOK);
		self::clear_daily_scan_schedule();
	}

	/**
	 * Queue auto ALT generation shortly after upload.
	 *
	 * @param int $attachment_id Attachment ID.
	 *
	 * @return void
	 */
	public function queue_attachment_generation($attachment_id) {
		$attachment_id = absint($attachment_id);
		if (!$attachment_id || !altify_ai_is_image_attachment($attachment_id)) {
			return;
		}

		$settings = altify_ai_get_settings();
		if (empty($settings['auto_generate_on_upload'])) {
			return;
		}

		$args = array($attachment_id);
		if (!wp_next_scheduled(self::UPLOAD_EVENT_HOOK, $args)) {
			wp_schedule_single_event(time() + 20, self::UPLOAD_EVENT_HOOK, $args);
		}
	}

	/**
	 * Run scheduled ALT generation.
	 *
	 * @param int $attachment_id Attachment ID.
	 *
	 * @return void
	 */
	public function handle_scheduled_generation($attachment_id) {
		$attachment_id = absint($attachment_id);
		if (!$attachment_id || !altify_ai_is_image_attachment($attachment_id)) {
			return;
		}

		$metadata = wp_get_attachment_metadata($attachment_id);
		if (empty($metadata)) {
			$retry_key   = '_altify_ai_metadata_retry_count';
			$retry_count = absint(get_post_meta($attachment_id, $retry_key, true));

			if ($retry_count < 2) {
				update_post_meta($attachment_id, $retry_key, $retry_count + 1);
				$args = array($attachment_id);
				if (!wp_next_scheduled(self::UPLOAD_EVENT_HOOK, $args)) {
					wp_schedule_single_event(time() + 25, self::UPLOAD_EVENT_HOOK, $args);
				}
				return;
			}
		}

		delete_post_meta($attachment_id, '_altify_ai_metadata_retry_count');

		$settings = altify_ai_get_settings();
		if (empty($settings['auto_generate_on_upload'])) {
			return;
		}

		$upload_mode = altify_ai_normalize_generation_mode(
			isset($settings['upload_generation_mode']) ? $settings['upload_generation_mode'] : 'offline_first',
			'offline_first'
		);

		$this->client->generate_and_save_alt_with_mode(
			$attachment_id,
			$upload_mode,
			array('source' => 'upload')
		);
	}

	/**
	 * Execute daily scheduled scan event.
	 *
	 * @return void
	 */
	public function handle_daily_scan_event() {
		$this->run_daily_scan('cron');
	}

	/**
	 * Run one daily automation scan.
	 *
	 * @param string $trigger Trigger source (cron/manual).
	 *
	 * @return array
	 */
	public function run_daily_scan($trigger = 'manual') {
		$trigger = sanitize_key((string) $trigger);
		$trigger = ('cron' === $trigger) ? 'cron' : 'manual';

		$lock_exists = get_transient(self::DAILY_SCAN_LOCK_TRANSIENT);
		if (!empty($lock_exists)) {
			return array(
				'success' => false,
				'message' => __('A scan is already running. Please wait for it to finish.', 'altify-ai-auto-alt-text-generator'),
				'status'  => $this->get_daily_scan_status(),
			);
		}

		set_transient(self::DAILY_SCAN_LOCK_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS);

		try {
			$settings = altify_ai_get_settings();
			$scope    = altify_ai_normalize_scan_scope(isset($settings['daily_scan_scope']) ? $settings['daily_scan_scope'] : 'missing');
			$mode     = altify_ai_normalize_generation_mode(isset($settings['daily_scan_mode']) ? $settings['daily_scan_mode'] : 'offline_first', 'offline_first');
			$max_run  = isset($settings['daily_scan_max_images']) ? absint($settings['daily_scan_max_images']) : 50;
			$max_run  = max(1, min(500, $max_run));

			$current_status = $this->get_daily_scan_status();
			$offset         = ('all' === $scope) ? absint($current_status['all_scope_offset']) : 0;
			$source         = ('cron' === $trigger) ? 'daily_scan_cron' : 'daily_scan_manual';

			$scan_result = $this->bulk->run_automation_scan(
				array(
					'scope'           => $scope,
					'generation_mode' => $mode,
					'max_images'      => $max_run,
					'offset'          => $offset,
					'source'          => $source,
				)
			);

			$batch = !empty($scan_result['batch']) && is_array($scan_result['batch']) ? $scan_result['batch'] : array();
			$processed = isset($batch['processed']) ? absint($batch['processed']) : 0;
			$success   = isset($batch['success']) ? absint($batch['success']) : 0;
			$skipped   = isset($batch['skipped']) ? absint($batch['skipped']) : 0;
			$failed    = isset($batch['failed']) ? absint($batch['failed']) : 0;
			$fallback  = isset($batch['fallback']) ? absint($batch['fallback']) : 0;
			$cached    = isset($batch['cached']) ? absint($batch['cached']) : 0;
			$method_counts = !empty($batch['method_counts']) && is_array($batch['method_counts']) ? $batch['method_counts'] : array();
			$last_error = '';
			if (!empty($scan_result['diagnostics']['last_error'])) {
				$last_error = sanitize_text_field((string) $scan_result['diagnostics']['last_error']);
			}

			$status_payload = array(
				'last_run_timestamp' => (int) current_time('timestamp', true),
				'last_run_gmt'       => current_time('mysql', true),
				'last_run_source'    => $source,
				'last_scope'         => $scope,
				'last_mode'          => $mode,
				'processed'          => $processed,
				'success'            => $success,
				'skipped'            => $skipped,
				'failed'             => $failed,
				'fallback'           => $fallback,
				'cached'             => $cached,
				'last_error'         => $last_error,
				'method_counts'      => $method_counts,
				'all_scope_offset'   => ('all' === $scope && isset($scan_result['next_offset'])) ? absint($scan_result['next_offset']) : 0,
			);
			update_option(self::DAILY_SCAN_STATUS_OPTION, $status_payload, false);

			$message = sprintf(
				/* translators: 1: processed count, 2: success count, 3: skipped count, 4: failed count */
				__('Daily scan completed. Processed: %1$d, Success: %2$d, Skipped: %3$d, Failed: %4$d.', 'altify-ai-auto-alt-text-generator'),
				$processed,
				$success,
				$skipped,
				$failed
			);

			$this->logger->log(
				'info',
				$message,
				array(
					'source'            => $source,
					'generation_method' => $mode,
					'scope'             => $scope,
					'processed'         => $processed,
					'success'           => $success,
					'skipped'           => $skipped,
					'failed'            => $failed,
					'last_error'        => $last_error,
					'method_counts'     => $method_counts,
				)
			);

			return array(
				'success' => true,
				'message' => $message,
				'status'  => $this->get_daily_scan_status(),
			);
		} finally {
			delete_transient(self::DAILY_SCAN_LOCK_TRANSIENT);
		}
	}

	/**
	 * Get daily automation status payload for UI.
	 *
	 * @return array
	 */
	public function get_daily_scan_status() {
		$settings = altify_ai_get_settings();
		$saved    = get_option(self::DAILY_SCAN_STATUS_OPTION, array());
		$saved    = is_array($saved) ? $saved : array();

		$last_run_timestamp = !empty($saved['last_run_timestamp']) ? absint($saved['last_run_timestamp']) : 0;
		if (!$last_run_timestamp && !empty($saved['last_run_gmt'])) {
			$derived_timestamp = strtotime((string) $saved['last_run_gmt'] . ' UTC');
			if (false !== $derived_timestamp) {
				$last_run_timestamp = absint($derived_timestamp);
			}
		}

		$next_run_timestamp = wp_next_scheduled(self::DAILY_SCAN_EVENT_HOOK);
		$next_run_timestamp = $next_run_timestamp ? absint($next_run_timestamp) : 0;
		$date_time_format   = trim(get_option('date_format') . ' ' . get_option('time_format'));
		$timezone           = wp_timezone();

		$mode  = !empty($saved['last_mode']) ? sanitize_key((string) $saved['last_mode']) : (isset($settings['daily_scan_mode']) ? $settings['daily_scan_mode'] : 'offline_first');
		$scope = !empty($saved['last_scope']) ? sanitize_key((string) $saved['last_scope']) : (isset($settings['daily_scan_scope']) ? $settings['daily_scan_scope'] : 'missing');
		$mode  = altify_ai_normalize_generation_mode($mode, 'offline_first');
		$scope = altify_ai_normalize_scan_scope($scope);

		return array(
			'last_run_timestamp' => $last_run_timestamp,
			'last_run_display'   => $last_run_timestamp ? wp_date($date_time_format, $last_run_timestamp, $timezone) : __('Never', 'altify-ai-auto-alt-text-generator'),
			'next_run_timestamp' => $next_run_timestamp,
			'next_run_display'   => $next_run_timestamp ? wp_date($date_time_format, $next_run_timestamp, $timezone) : __('Not scheduled', 'altify-ai-auto-alt-text-generator'),
			'processed'          => !empty($saved['processed']) ? absint($saved['processed']) : 0,
			'success'            => !empty($saved['success']) ? absint($saved['success']) : 0,
			'skipped'            => !empty($saved['skipped']) ? absint($saved['skipped']) : 0,
			'failed'             => !empty($saved['failed']) ? absint($saved['failed']) : 0,
			'fallback'           => !empty($saved['fallback']) ? absint($saved['fallback']) : 0,
			'cached'             => !empty($saved['cached']) ? absint($saved['cached']) : 0,
			'last_mode'          => $mode,
			'last_mode_label'    => altify_ai_get_generation_mode_label($mode),
			'last_scope'         => $scope,
			'last_scope_label'   => altify_ai_get_scan_scope_label($scope),
			'last_run_source'    => !empty($saved['last_run_source']) ? sanitize_key((string) $saved['last_run_source']) : '',
			'last_error'         => !empty($saved['last_error']) ? sanitize_text_field((string) $saved['last_error']) : '',
			'all_scope_offset'   => !empty($saved['all_scope_offset']) ? absint($saved['all_scope_offset']) : 0,
		);
	}

	/**
	 * Ensure daily cron is scheduled according to settings.
	 *
	 * @param array|null $settings Optional settings payload.
	 *
	 * @return void
	 */
	public static function ensure_daily_scan_schedule($settings = null) {
		$settings = is_array($settings) ? wp_parse_args($settings, altify_ai_get_default_settings()) : altify_ai_get_settings();
		$enabled  = !empty($settings['daily_scan_enabled']);
		$next_run = wp_next_scheduled(self::DAILY_SCAN_EVENT_HOOK);

		if (!$enabled) {
			if ($next_run) {
				self::clear_daily_scan_schedule();
			}
			return;
		}

		if (!$next_run) {
			$time_value = altify_ai_normalize_time_hhmm(isset($settings['daily_scan_time']) ? $settings['daily_scan_time'] : '02:00', '02:00');
			$timestamp  = altify_ai_get_next_daily_scan_timestamp($time_value);
			wp_schedule_event($timestamp, 'daily', self::DAILY_SCAN_EVENT_HOOK);
		}
	}

	/**
	 * Force rescheduling of daily cron according to updated settings.
	 *
	 * @param array|null $settings Optional settings payload.
	 *
	 * @return void
	 */
	public static function reschedule_daily_scan($settings = null) {
		self::clear_daily_scan_schedule();
		self::ensure_daily_scan_schedule($settings);
	}

	/**
	 * Remove all scheduled daily scan events.
	 *
	 * @return void
	 */
	private static function clear_daily_scan_schedule() {
		$timestamp = wp_next_scheduled(self::DAILY_SCAN_EVENT_HOOK);
		while ($timestamp) {
			wp_unschedule_event($timestamp, self::DAILY_SCAN_EVENT_HOOK);
			$timestamp = wp_next_scheduled(self::DAILY_SCAN_EVENT_HOOK);
		}
	}
}

register_activation_hook(__FILE__, array('Altify_AI_Plugin', 'activate'));
register_deactivation_hook(__FILE__, array('Altify_AI_Plugin', 'deactivate'));

/**
 * Get plugin instance.
 *
 * @return Altify_AI_Plugin
 */
function altify_ai() {
	return Altify_AI_Plugin::instance();
}

altify_ai();

if (!function_exists('altify_ai_plugin_action_links')) {
	/**
	 * Add Settings link on Plugins screen.
	 *
	 * @param array $links Existing action links.
	 *
	 * @return array
	 */
	function altify_ai_plugin_action_links($links) {
		$settings_url = admin_url('admin.php?page=altify-ai-settings');
		$settings     = '<a href="' . esc_url($settings_url) . '">' . esc_html__('Settings', 'altify-ai-auto-alt-text-generator') . '</a>';
		array_unshift($links, $settings);

		return $links;
	}
}

if (!function_exists('altify_ai_plugin_row_meta')) {
	/**
	 * Add plugin row meta links.
	 *
	 * @param array  $links Existing row links.
	 * @param string $file  Plugin file.
	 *
	 * @return array
	 */
	function altify_ai_plugin_row_meta($links, $file) {
		if (plugin_basename(__FILE__) !== $file) {
			return $links;
		}

		$links[] = '<a href="' . esc_url('https://buymeacoffee.com/developerravi') . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Donate', 'altify-ai-auto-alt-text-generator') . '</a>';

		return $links;
	}
}

add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'altify_ai_plugin_action_links');
add_filter('plugin_row_meta', 'altify_ai_plugin_row_meta', 10, 2);
