<?php
/**
 * Front Controller for WP Pro Quiz.
 *
 * @since 1.2.5
 *
 * @package LearnDash\Core
 */

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

use LearnDash\Core\Template\Views;
use LearnDash\Core\Utilities\Cast;

// phpcs:disable WordPress.NamingConventions.ValidVariableName,WordPress.NamingConventions.ValidFunctionName,WordPress.NamingConventions.ValidHookName,PSR2.Classes.PropertyDeclaration.Underscore

/**
 * Front Controller for WP Pro Quiz.
 *
 * @since 1.2.5
 */
class WpProQuiz_Controller_Front {
	/**
	 * @var WpProQuiz_Model_GlobalSettings
	 */
	private $_settings = null;

	public function __construct() {
		$this->loadSettings();

		add_action( 'wp_enqueue_scripts', array( $this, 'loadDefaultScripts' ) );
		add_shortcode( 'LDAdvQuiz', array( $this, 'shortcode' ) );
		add_shortcode( 'LDAdvQuiz_toplist', array( $this, 'shortcodeToplist' ) );
	}

	public function loadDefaultScripts() {
		global $learndash_assets_loaded;

		wp_enqueue_script( 'jquery' );

		$filepath = SFWD_LMS::get_template( 'learndash_quiz_front.css', null, null, true );
		if ( ! empty( $filepath ) ) {
			wp_enqueue_style( 'learndash_quiz_front_css', learndash_template_url_from_path( $filepath ), array(), LEARNDASH_SCRIPT_VERSION_TOKEN );
			wp_style_add_data( 'learndash_quiz_front_css', 'rtl', 'replace' );
			$learndash_assets_loaded['styles']['learndash_quiz_front_css'] = __FUNCTION__;
		}

		if ( $this->_settings->isJsLoadInHead() ) {
			$this->loadJsScripts( false, true, true );
		}
	}

	/**
	 * Loads the JavaScript scripts.
	 *
	 * @since 1.2.5
	 *
	 * @param bool $footer  Whether to load the scripts in the footer. Default true.
	 * @param bool $quiz    Whether to load the quiz scripts. Default true.
	 * @param bool $toplist Whether to load the toplist scripts. Default false.
	 *
	 * @return void
	 */
	private function loadJsScripts( $footer = true, $quiz = true, $toplist = false ) {
		global $learndash_assets_loaded;

		if ( $quiz ) {
			wp_enqueue_script(
				'wpProQuiz_front_javascript',
				plugins_url( 'js/wpProQuiz_front' . learndash_min_asset() . '.js', WPPROQUIZ_FILE ),
				[ 'jquery', 'jquery-ui-sortable', 'learndash-sortable-script', 'wp-i18n' ],
				LEARNDASH_SCRIPT_VERSION_TOKEN,
				$footer
			);
			$learndash_assets_loaded['scripts']['wpProQuiz_front_javascript'] = __FUNCTION__;

			wp_localize_script(
				'wpProQuiz_front_javascript',
				'WpProQuizGlobal',
				[
					'ajaxurl'            => str_replace( array( 'http:', 'https:' ), array( '', '' ), admin_url( 'admin-ajax.php' ) ),
					'loadData'           => esc_html__( 'Loading', 'learndash' ),
					// translators: placeholder: question
					'questionNotSolved'  => sprintf( esc_html_x( 'You must answer this %s.', 'placeholder: question', 'learndash' ), learndash_get_custom_label_lower( 'question' ) ),
					// translators: placeholder: questions, quiz.
					'questionsNotSolved' => sprintf( esc_html_x( 'You must answer all %1$s before you can complete the %2$s.', 'placeholder: questions, quiz', 'learndash' ), learndash_get_custom_label_lower( 'questions' ), learndash_get_custom_label_lower( 'quiz' ) ),
					'fieldsNotFilled'    => esc_html__( 'All fields have to be filled.', 'learndash' ),
					'correctAnswer'      => esc_html__( 'Correct', 'learndash' ),
					'incorrectAnswer'    => sprintf(
						// We are using @@LearnDash_Incorrect@@ as a placeholder to handle parsing in the wpProQuiz_front.js because that is where the answer is parsed.
						// translators: placeholder: span open, correct answer placeholder, span close.
						esc_html__( 'Incorrect, %1$sCorrect Answer: %2$s%3$s', 'learndash' ),
						'<span class="ld-quiz__cloze-results--correct-answer">',
						'@@LearnDash_Incorrect@@',
						'</span>'
					),
				]
			);

			wp_enqueue_script(
				'jquery-cookie',
				plugins_url( 'js/jquery.cookie' . learndash_min_asset() . '.js', WPPROQUIZ_FILE ),
				array( 'jquery', 'jquery-ui-sortable' ),
				'1.4.0',
				$footer
			);
			$learndash_assets_loaded['scripts']['jquery-cookie'] = __FUNCTION__;
		}

		if ( $toplist ) {
			wp_enqueue_script(
				'wpProQuiz_front_javascript_toplist',
				plugins_url( 'js/wpProQuiz_toplist' . learndash_min_asset() . '.js', WPPROQUIZ_FILE ),
				array( 'jquery', 'jquery-ui-sortable' ),
				LEARNDASH_SCRIPT_VERSION_TOKEN,
				$footer
			);
			$learndash_assets_loaded['scripts']['wpProQuiz_front_javascript_toplist'] = __FUNCTION__;

			if ( ! wp_script_is( 'wpProQuiz_front_javascript' ) ) {
				wp_localize_script(
					'wpProQuiz_front_javascript_toplist',
					'WpProQuizGlobal',
					array(
						'ajaxurl'            => str_replace( array( 'http:', 'https:' ), array( '', '' ), admin_url( 'admin-ajax.php' ) ),
						'loadData'           => esc_html__( 'Loading', 'learndash' ),
						// translators: placeholder: question
						'questionNotSolved'  => sprintf( esc_html_x( 'You must answer this %s.', 'placeholder: question', 'learndash' ), learndash_get_custom_label_lower( 'question' ) ),
						// translators: placeholder: questions, quiz.
						'questionsNotSolved' => sprintf( esc_html_x( 'You must answer all %1$s before you can complete the %2$s.', 'placeholder: questions, quiz', 'learndash' ), learndash_get_custom_label_lower( 'questions' ), learndash_get_custom_label_lower( 'quiz' ) ),
						'fieldsNotFilled'    => esc_html__( 'All fields have to be filled.', 'learndash' ),
					)
				);
			}
		}

		if ( ! $this->_settings->isTouchLibraryDeactivate() ) {
			wp_enqueue_script(
				'jquery-ui-touch-punch',
				plugins_url( 'js/jquery.ui.touch-punch.min.js', WPPROQUIZ_FILE ),
				array( 'jquery', 'jquery-ui-sortable' ),
				'0.2.2',
				$footer
			);
			$learndash_assets_loaded['scripts']['jquery-ui-touch-punch'] = __FUNCTION__;
		}
	}

	public function shortcode( $attr = array(), $content = '' ) {
		global $learndash_shortcode_used, $learndash_shortcode_atts;
		$learndash_shortcode_used = true;

		if ( ( isset( $attr[0] ) ) && ( ! empty( $attr[0] ) ) ) {
			if ( ! isset( $attr['quiz_pro_id'] ) ) {
				$attr['quiz_pro_id'] = intval( $attr[0] );
				unset( $attr[0] );
			}
		}

		$attr = shortcode_atts(
			array(
				'quiz_id'     => 0,
				'course_id'   => 0,
				'lesson_id'   => 0,
				'topic_id'    => 0,
				'quiz_pro_id' => 0,
			),
			$attr
		);
		// Just to ensure compliance.
		$attr['quiz_id']     = absint( $attr['quiz_id'] );
		$attr['course_id']   = absint( $attr['course_id'] );
		$attr['lesson_id']   = absint( $attr['lesson_id'] );
		$attr['topic_id']    = absint( $attr['topic_id'] );
		$attr['quiz_pro_id'] = absint( $attr['quiz_pro_id'] );

		if ( ! $this->_settings->isJsLoadInHead() ) {
			$this->loadJsScripts();
		}

		if ( ! empty( $attr['quiz_pro_id'] ) ) {
			ob_start();

			$learndash_shortcode_atts['LDAdvQuiz'] = $attr;
			$this->handleShortCode( $attr );

			$content = ob_get_contents();

			ob_end_clean();
		}

		if ( $this->_settings->isAddRawShortcode() ) {
			return '[raw]' . $content . '[/raw]';
		}

		return $content;
	}

	public function handleShortCode( $atts = array() ) {
		/** This filter is documented in includes/shortcodes/ld_quiz.php */
		$should_override = apply_filters( 'learndash_quiz_shortcode_override_output', false, $atts, $this );

		if ( $should_override ) {
			/** This filter is documented in includes/shortcodes/ld_quiz.php */
			echo apply_filters( 'learndash_quiz_shortcode_output', '', $atts, $this );
			return;
		}

		$atts = shortcode_atts(
			array(
				'quiz_id'     => 0,
				'course_id'   => 0,
				'lesson_id'   => 0,
				'topic_id'    => 0,
				'quiz_pro_id' => 0,
			),
			$atts
		);
		// Just to ensure compliance.
		$atts['quiz_id']     = absint( $atts['quiz_id'] );
		$atts['course_id']   = absint( $atts['course_id'] );
		$atts['lesson_id']   = absint( $atts['lesson_id'] );
		$atts['topic_id']    = absint( $atts['topic_id'] );
		$atts['quiz_pro_id'] = absint( $atts['quiz_pro_id'] );

		$view = new WpProQuiz_View_FrontQuiz();
		$view->set_shortcode_atts( $atts );

		$quizMapper     = new WpProQuiz_Model_QuizMapper();
		$questionMapper = new WpProQuiz_Model_QuestionMapper();
		$categoryMapper = new WpProQuiz_Model_CategoryMapper();
		$formMapper     = new WpProQuiz_Model_FormMapper();

		$quiz         = $quizMapper->fetch( $atts['quiz_pro_id'] );
		$quiz_post_id = $quiz->getPostId();
		if ( ( ! empty( $atts['quiz_id'] ) ) && ( intval( $quiz_post_id ) !== intval( $atts['quiz_id'] ) ) ) {
			$quiz->setPostId( intval( $atts['quiz_id'] ) );
		}

		$maxQuestion = false;

		if ( ( $quiz->isShowMaxQuestion() ) && ( $quiz->getShowMaxQuestionValue() > 0 ) ) {
			$value = $quiz->getShowMaxQuestionValue();

			if ( $quiz->isShowMaxQuestionPercent() ) {
				$count = $questionMapper->count( $atts['quiz_pro_id'] );

				$value = ceil( $count * $value / 100 );
			}

			$question    = $questionMapper->fetchAll( $quiz, true, $value );
			$maxQuestion = true;
		} else {
			$question = $questionMapper->fetchAll( $quiz );
		}

		if ( ( empty( $quiz ) ) || ( empty( $question ) ) ) {
			echo '';

			return;
		}

		$view->quiz     = $quiz;
		$view->question = $question;

		$view->category = $categoryMapper->fetchByQuiz( $quiz );

		$view->forms = $formMapper->fetch( $atts['quiz_pro_id'] );

		if ( $maxQuestion ) {
			$view->showMaxQuestion();
		} else {
			$view->show();
		}
	}

	public function shortcodeToplist( $attr ) {
		global $learndash_shortcode_used;
		$learndash_shortcode_used = true;

		$id      = $attr[0];
		$content = '';

		if ( ! $this->_settings->isJsLoadInHead() ) {
			$this->loadJsScripts( true, false, true );
		}

		if ( is_numeric( $id ) ) {
			ob_start();

			$this->handleShortCodeToplist( $id, isset( $attr['q'] ) );

			$content = ob_get_contents();

			ob_end_clean();
		}

		if ( $this->_settings->isAddRawShortcode() && ! isset( $attr['q'] ) ) {
			return '[raw]' . $content . '[/raw]';
		}

		return $content;
	}

	private function handleShortCodeToplist( $quizId, $inQuiz = false ) {
		$quizMapper = new WpProQuiz_Model_QuizMapper();
		$view       = new WpProQuiz_View_FrontToplist();

		$quiz = $quizMapper->fetch( $quizId );

		if ( $quiz->getId() <= 0 || ! $quiz->isToplistActivated() ) {
			echo '';
			return;
		}

		$view->quiz   = $quiz;
		$view->points = $quizMapper->sumQuestionPoints( $quizId );
		$view->inQuiz = $inQuiz;
		$view->show();
	}

	private function loadSettings() {
		$mapper = new WpProQuiz_Model_GlobalSettingsMapper();

		$this->_settings = $mapper->fetchAll();
	}

	/**
	 * It's a copy of the ajaxQuizLoadData method with the security fix.
	 * Unfortunately, we could not do anything better at the moment.
	 *
	 * @since 4.12.0
	 *
	 * @param array $data Data.
	 * @param mixed $func Function.
	 *
	 * @return false|string
	 */
	public static function ajaxQuizLoadDataWithoutCorrectAnswers( $data, $func ) {
		$quiz_data = json_decode( Cast::to_string( self::ajaxQuizLoadData( $data, $func ) ), true );

		if (
			is_array( $quiz_data )
			&& ! empty( $quiz_data )
			&& ! empty( $quiz_data['json'] )
			&& is_array( $quiz_data['json'] )
		) {
			foreach ( $quiz_data['json'] as &$question ) {
				if ( isset( $question['correct'] ) ) {
					unset( $question['correct'] );
				}
			}
		}

		return wp_json_encode( $quiz_data );
	}

	/**
	 * Handles the AJAX request that loads a quiz's questions, used when the quiz displays a subset
	 * of its questions. Restores the in-progress attempt's questions (recovering answered ones and
	 * replacing any that no longer resolve) and returns the encoded quiz data for the front-end.
	 *
	 * @since 1.2.5
	 *
	 * @param array<string, mixed> $data The AJAX request data (quiz pro ID, quiz post ID, nonce).
	 * @param mixed                $func The requested function name.
	 *
	 * @return string|false|null JSON-encoded quiz data, false on encoding failure, or null when the quiz or its questions are unavailable.
	 */
	public static function ajaxQuizLoadData( $data, $func ) {
		if ( is_user_logged_in() ) {
			$user_id = get_current_user_id();
		} else {
			$user_id = 0;
		}

		if ( isset( $data['quizId'] ) ) {
			$id = Cast::to_int( $data['quizId'] );
		} else {
			$id = 0;
		}

		if ( isset( $data['quiz'] ) ) {
			$quiz_post_id = Cast::to_int( $data['quiz'] );
		} else {
			$quiz_post_id = 0;
		}

		if (
			! isset( $data['quiz_nonce'] )
			|| ! wp_verify_nonce( Cast::to_string( $data['quiz_nonce'] ), 'sfwd-quiz-nonce-' . $quiz_post_id . '-' . $id . '-' . $user_id )
		) {
			die();
		}

		if ( isset( $_POST['course_id'] ) ) {
			$learndash_course_id = absint( $_POST['course_id'] );
		} else {
			$learndash_course_id = (int) learndash_get_course_id();
		}

		$view = new WpProQuiz_View_FrontQuiz();

		$quizMapper     = new WpProQuiz_Model_QuizMapper();
		$questionMapper = new WpProQuiz_Model_QuestionMapper();
		$categoryMapper = new WpProQuiz_Model_CategoryMapper();
		$formMapper     = new WpProQuiz_Model_FormMapper();

		$quiz = $quizMapper->fetch( $id );
		$quiz->setPostId( absint( $quiz_post_id ) );

		if ( $quiz->isShowMaxQuestion() && $quiz->getShowMaxQuestionValue() > 0 ) {
			$learndash_quiz_resume_enabled = false;
			$learndash_quiz_resume_data    = array();

			if ( ! empty( $quiz_post_id ) && $user_id ) {
				$learndash_quiz_resume_enabled = learndash_get_setting( $quiz_post_id, 'quiz_resume' );
				if ( true === $learndash_quiz_resume_enabled ) {
					$learndash_quiz_resume_activity = LDLMS_User_Quiz_Resume::get_user_quiz_resume_activity( $user_id, $quiz_post_id, $learndash_course_id );
					if ( ( is_a( $learndash_quiz_resume_activity, 'LDLMS_Model_Activity' ) ) && ( property_exists( $learndash_quiz_resume_activity, 'activity_id' ) ) && ( ! empty( $learndash_quiz_resume_activity->activity_id ) ) ) {
						$learndash_quiz_resume_id = $learndash_quiz_resume_activity->activity_id;
						if ( ( property_exists( $learndash_quiz_resume_activity, 'activity_meta' ) ) && ( ! empty( $learndash_quiz_resume_activity->activity_meta ) ) ) {
							$learndash_quiz_resume_data = $learndash_quiz_resume_activity->activity_meta;
						}
					}
				}
			}

			$value = $quiz->getShowMaxQuestionValue();

			if ( $quiz->isShowMaxQuestionPercent() ) {
				$count = $questionMapper->count( $id );

				$value = ceil( $count * $value / 100 );
			}

			/*
			 * The order to resume from: the saved subset order if the front-end persisted it,
			 * otherwise the questions that already have an answer (the numeric keys of the resume
			 * data) so an in-progress attempt is never lost when the order was not saved.
			 */
			$learndash_resume_order = array();
			if (
				$learndash_quiz_resume_enabled
				&& ! empty( $learndash_quiz_resume_data['randomOrder'] )
			) {
				$learndash_resume_order = array_map( array( Cast::class, 'to_int' ), array_values( $learndash_quiz_resume_data['randomOrder'] ) );
			} elseif ( $learndash_quiz_resume_enabled ) {
				foreach ( array_keys( (array) $learndash_quiz_resume_data ) as $learndash_resume_key ) {
					if ( is_numeric( $learndash_resume_key ) ) {
						$learndash_resume_order[] = Cast::to_int( $learndash_resume_key );
					}
				}
			}

			if ( ! empty( $learndash_resume_order ) ) {
				/*
				 * Resuming an in-progress attempt: restore the saved/answered questions, replace any
				 * that no longer resolve (deleted/updated) with other valid questions so the quiz
				 * keeps its configured number of questions, and persist the healed order so later
				 * resumes load the same set.
				 */
				$learndash_pool = $questionMapper->fetchAll( $quiz );

				$question = WpProQuiz_Helper_QuestionOrder::get_resume_question_set( $learndash_pool, $learndash_resume_order, Cast::to_int( $value ) );

				$learndash_healed_order = array();
				foreach ( $question as $learndash_healed_question ) {
					$learndash_healed_order[] = $learndash_healed_question->getId();
				}

				if (
					! empty( $learndash_healed_order )
					&& $learndash_healed_order !== $learndash_resume_order
				) {
					LDLMS_User_Quiz_Resume::update_user_quiz_resume_metadata(
						$user_id,
						$quiz_post_id,
						$learndash_course_id,
						0,
						array(
							'randomQuestions' => true,
							'randomOrder'     => $learndash_healed_order,
						)
					);
				}
			} else {
				$question = $questionMapper->fetchAll( $quiz, true, $value );
			}
		} else {
			$question = $questionMapper->fetchAll( $quiz );
		}

		if ( empty( $quiz ) || empty( $question ) ) {
			return null;
		}

		$view->quiz     = $quiz;
		$view->question = $question;
		$view->category = $categoryMapper->fetchByQuiz( $quiz );
		$view->forms    = $formMapper->fetch( $quiz->getId() );

		return wp_json_encode( $view->getQuizData() );
	}
}
