<?php
/**
 * LearnDash background export engine.
 *
 * Queues chunked CSV exports for the Reports settings page through the
 * Learndash_Admin_Action_Scheduler wrapper, surfacing progress as admin notices.
 *
 * @since 5.1.6
 *
 * @package LearnDash\Reports
 */

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

if ( class_exists( 'Learndash_Admin_Background_Export' ) ) {
	return;
}

/**
 * LearnDash background export engine.
 *
 * @since 5.1.6
 */
class Learndash_Admin_Background_Export {
	/**
	 * Action Scheduler hook name used to run a single export chunk.
	 *
	 * @since 5.1.6
	 *
	 * @var string
	 */
	private const EXPORT_SCHEDULER_TASK_NAME = 'learndash_report_export_run_chunk';

	/**
	 * Action Scheduler group for the export chunk queue.
	 *
	 * @since 5.1.6
	 *
	 * @var string
	 */
	private const EXPORT_SCHEDULER_GROUP = 'reports-export';

	/**
	 * Action Scheduler wrapper that queues chunks and renders progress notices.
	 *
	 * @since 5.1.6
	 *
	 * @var Learndash_Admin_Action_Scheduler
	 */
	private $scheduler;

	/**
	 * Shared engine instance exposed to the report handlers via get_instance().
	 *
	 * @since 5.1.6
	 *
	 * @var self|null
	 */
	private static $instance = null;

	/**
	 * Registers the chunk dispatcher on the injected Action Scheduler wrapper.
	 *
	 * @since 5.1.6
	 *
	 * @param Learndash_Admin_Action_Scheduler $scheduler Scheduler wrapper for the export group.
	 */
	public function __construct( Learndash_Admin_Action_Scheduler $scheduler ) {
		$this->scheduler = $scheduler;

		$this->scheduler->register_callback(
			self::EXPORT_SCHEDULER_TASK_NAME,
			array( $this, 'handle_export_chunk_task' ),
			10,
			2
		);
	}

	/**
	 * Builds and stores the shared background export engine.
	 *
	 * @since 5.1.6
	 *
	 * @return self
	 */
	public static function init(): self {
		self::$instance = new self( new Learndash_Admin_Action_Scheduler( self::EXPORT_SCHEDULER_GROUP ) );

		return self::$instance;
	}

	/**
	 * Returns the shared engine instance, or null before init() runs.
	 *
	 * @since 5.1.6
	 *
	 * @return self|null Shared engine instance.
	 */
	public static function get_instance(): ?self {
		return self::$instance;
	}

	/**
	 * Returns whether an export chunk is currently queued or running.
	 *
	 * @since 5.1.6
	 *
	 * @return bool True when an export chunk is queued or running, false otherwise.
	 */
	public function is_export_in_progress(): bool {
		return Learndash_Admin_Action_Scheduler::is_task_in_progress(
			self::EXPORT_SCHEDULER_GROUP,
			self::EXPORT_SCHEDULER_TASK_NAME
		);
	}

	/**
	 * Queues a single export chunk through the Action Scheduler wrapper.
	 *
	 * The chunk callback re-enqueues itself until the work drains. The transient key is passed
	 * as the related-object so the wrapper's progress notices are scoped to this export.
	 *
	 * @since 5.1.6
	 *
	 * @param string $transient_key Transient key holding the in-progress export state.
	 * @param string $slug          Report action slug.
	 *
	 * @return void
	 */
	public function enqueue_export_chunk_task( string $transient_key, string $slug ): void {
		if (
			empty( $transient_key )
			|| empty( $slug )
		) {
			return;
		}

		$this->scheduler->enqueue_task(
			self::EXPORT_SCHEDULER_TASK_NAME,
			array( $transient_key, $slug ),
			$transient_key,
			$this->get_pending_notice_message(),
			$this->get_progress_notice_message()
		);
	}

	/**
	 * Action Scheduler callback: processes one export chunk, re-enqueues while work remains,
	 * and posts the completion or failure notice.
	 *
	 * @since 5.1.6
	 *
	 * @param string $transient_key Transient key holding the in-progress export state.
	 * @param string $slug          Report action slug.
	 *
	 * @return void
	 */
	public function handle_export_chunk_task( string $transient_key = '', string $slug = '' ): void {
		if (
			empty( $transient_key )
			|| empty( $slug )
		) {
			return;
		}

		$handler = $this->resolve_handler_for_slug( $slug );

		if (
			! $handler
			|| ! method_exists( $handler, 'process_export_chunk' )
		) {
			return;
		}

		try {
			$continue = (bool) $handler->process_export_chunk( $transient_key );
		} catch ( Throwable $e ) {
			// @phpstan-ignore-next-line method.notFound -- Throwable::getMessage() is defined on the interface at runtime; PHPStan stubs miss it.
			$this->notify_export_failed( $transient_key, $e->getMessage() );
			return;
		}

		if ( $continue ) {
			$this->enqueue_export_chunk_task( $transient_key, $slug );
			return;
		}

		$transient_data = $handler->get_transient( $transient_key );

		$download_link = is_array( $transient_data ) && isset( $transient_data['report_url'] )
			? (string) $transient_data['report_url']
			: '';

		$this->notify_export_ready( $transient_key, $download_link );
	}

	/**
	 * Resolves the report action handler instance registered for a given slug.
	 *
	 * @since 5.1.6
	 *
	 * @param string $slug Report action slug (e.g. user-quizzes, user-courses).
	 *
	 * @return Learndash_Admin_Settings_Data_Reports|null Handler instance or null when no action matches.
	 */
	public function resolve_handler_for_slug( string $slug ) {
		$base = new Learndash_Admin_Settings_Data_Reports();
		$base->init_report_actions();
		$actions = $base->get_report_actions();

		if (
			! isset( $actions[ $slug ]['instance'] )
			|| ! $actions[ $slug ]['instance'] instanceof Learndash_Admin_Settings_Data_Reports
		) {
			return null;
		}

		return $actions[ $slug ]['instance'];
	}

	/**
	 * Posts the "export ready" success notice with the CSV download link.
	 *
	 * @since 5.1.6
	 *
	 * @param string $transient_key Transient key holding the export state.
	 * @param string $download_link CSV download URL stored on the export transient.
	 *
	 * @return void
	 */
	private function notify_export_ready( string $transient_key, string $download_link ): void {
		$message = sprintf(
			// translators: %s: HTML link to download the exported CSV file.
			__( 'Your report export is ready. %s', 'learndash' ),
			'<a href="' . esc_url( $download_link ) . '">' . esc_html__( 'Download the CSV file.', 'learndash' ) . '</a>'
		);

		Learndash_Admin_Action_Scheduler::add_admin_notice( $message, 'success', $transient_key );
	}

	/**
	 * Posts an error admin notice for a failed export.
	 *
	 * @since 5.1.6
	 *
	 * @param string $transient_key Transient key holding the export state.
	 * @param string $message       User-facing error message.
	 *
	 * @return void
	 */
	private function notify_export_failed( string $transient_key, string $message ): void {
		Learndash_Admin_Action_Scheduler::add_admin_notice( $message, 'error', $transient_key );
	}

	/**
	 * Returns the admin-notice message shown while an export chunk is queued.
	 *
	 * @since 5.1.6
	 *
	 * @return string Pending-state notice message.
	 */
	private function get_pending_notice_message(): string {
		return __( 'Your report export is queued. Refresh this page to check progress.', 'learndash' );
	}

	/**
	 * Returns the admin-notice message shown while an export chunk is running.
	 *
	 * @since 5.1.6
	 *
	 * @return string Running-state notice message.
	 */
	private function get_progress_notice_message(): string {
		return __( 'Your report export is running. Refresh this page to check progress.', 'learndash' );
	}
}
