<?php

namespace HelpGent\App\Repositories;

defined( 'ABSPATH' ) || exit;

use HelpGent\App\Exceptions\RequestValidatorException;
use HelpGent\App\Models\ElementAnswers;
use HelpGent\App\Models\Response;
use HelpGent\WpMVC\Exceptions\Exception;
use stdClass;

class QuizRepository {
    const META_TOTAL_POINTS         = 'quiz_total_points';
    const META_MAX_POINTS           = 'quiz_max_points';
    const META_PERCENTAGE           = 'quiz_percentage';
    const META_GRADE                = 'quiz_grade';
    const META_SUMMARY_VERSION      = 'quiz_summary_version';
    const META_STATUS               = 'quiz_status';
    const META_PENDING_MANUAL_COUNT = 'quiz_pending_manual_count';
    const META_CORRECT_COUNT        = 'quiz_correct_count';
    const META_QUESTION_COUNT       = 'quiz_question_count';
    const META_STARTED_AT           = 'quiz_started_at';
    const META_EXPIRES_AT           = 'quiz_expires_at';
    const SUMMARY_VERSION           = '3';

    protected ResponseRepository $response_repository;

    public function __construct( ResponseRepository $response_repository ) {
        $this->response_repository = $response_repository;
    }

    public function after_response_created( int $response_id, array $data ): void {
        $form_id = intval( $data['form_id'] ?? 0 );

        if ( $form_id < 1 ) {
            return;
        }

        /**
         * @var FormRepository $form_repository
         */
        $form_repository = helpgent_singleton( FormRepository::class );
        $form            = $form_repository->get_by_id( $form_id );

        if ( ! $form ) {
            return;
        }

        $form         = helpgent_migrate_old_form( $form, false );
        $form_content = is_array( $form->content ) ? $form->content : [];

        if ( ! $this->is_quiz_form( $form_content ) ) {
            return;
        }

        $seconds = $this->get_quiz_timer_seconds( $form_content );

        if ( $seconds < 1 ) {
            return;
        }

        $started_at = helpgent_now();
        $expires_at = helpgent_now()->add_timestamp( $seconds );

        $this->response_repository->update_meta(
            $response_id,
            static::META_STARTED_AT,
            $started_at->to_date_time_string()
        );
        $this->response_repository->update_meta(
            $response_id,
            static::META_EXPIRES_AT,
            $expires_at->to_date_time_string()
        );
    }

    public function sanitize_public_form( stdClass $form ): stdClass {
        if ( empty( $form->content ) ) {
            return $form;
        }

        $content_is_json = is_string( $form->content );
        $form_content    = $content_is_json ? json_decode( $form->content, true ) : $form->content;

        if ( ! is_array( $form_content ) || empty( $form_content['elements'] ) ) {
            return $form;
        }

        $form_content['elements'] = array_map(
            function( $element ) {
                if ( empty( $element['attributes'] ) || ! is_array( $element['attributes'] ) ) {
                    return $element;
                }

                if ( ! empty( $element['attributes']['options'] ) && is_array( $element['attributes']['options'] ) ) {
                    $element['attributes']['options'] = array_map(
                        function( $option ) {
                            if ( ! is_array( $option ) ) {
                                return $option;
                            }

                            unset( $option['quiz_is_correct'], $option['quiz_points'] );

                            return $option;
                        },
                        $element['attributes']['options']
                    );
                }

                unset( $element['attributes']['quiz_correct_answer'] );

                return $element;
            },
            $form_content['elements']
        );

        $form->content = $content_is_json ? wp_json_encode( $form_content ) : $form_content;

        return $form;
    }

    public function assert_response_not_expired( int $form_id, ?string $response_token = null, $client_end_ms = null ): void {
        if ( $form_id > 0 && ! empty( $response_token ) ) {
            $response = helpgent_get_response_by_token( $response_token, $form_id );

            if ( $response && $this->is_response_expired( intval( $response->id ) ) ) {
                throw new Exception( esc_html__( 'Quiz time expired.', 'helpgent' ), 403 );
            }
        }

        if ( null === $client_end_ms || '' === $client_end_ms ) {
            return;
        }

        $end_ms = intval( $client_end_ms );
        $now_ms = intval( microtime( true ) * 1000 );

        if ( $end_ms > 0 && $now_ms > $end_ms ) {
            throw new Exception( esc_html__( 'Quiz time expired.', 'helpgent' ), 403 );
        }
    }

    public function is_response_expired( int $response_id ): bool {
        $expires_at = $this->response_repository->get_meta_value( $response_id, static::META_EXPIRES_AT );

        if ( empty( $expires_at ) || ! is_string( $expires_at ) ) {
            return false;
        }

        $expires_at_time = strtotime( $expires_at );

        if ( false === $expires_at_time ) {
            return false;
        }

        return time() > $expires_at_time;
    }

    public function get_response_form_content( int $response_id ): array {
        $form_content = $this->response_repository->get_meta_value( $response_id, 'form_content' );

        if ( empty( $form_content ) || ! is_string( $form_content ) ) {
            $response = $this->response_repository->get_by_id( $response_id );

            if ( ! $response ) {
                return [];
            }

            /**
             * @var FormRepository $form_repository
             */
            $form_repository = helpgent_singleton( FormRepository::class );
            $form            = $form_repository->get_by_id( intval( $response->form_id ) );

            if ( ! $form ) {
                return [];
            }

            $form_content = $form->content;
        }

        $snapshot          = new stdClass();
        $snapshot->content = $form_content;
        $snapshot          = helpgent_migrate_old_form( $snapshot, false );

        return is_array( $snapshot->content ) ? $snapshot->content : [];
    }

    public function is_quiz_form( array $form_content ): bool {
        return helpgent_get_nested_value( 'global.is_quiz_enabled', $form_content, '0' ) === '1';
    }

    public function is_quiz_element_type( string $element_type ): bool {
        return in_array(
            $element_type,
            [
                'single_select',
                'dropdown',
                'yes_no',
                'number',
                'multi_select',
                'picture_select',
                'short_text',
                'long_text',
                'file_upload',
            ],
            true
        );
    }

    public function is_manual_grade_element_type( string $element_type ): bool {
        return in_array( $element_type, [ 'short_text', 'long_text', 'file_upload' ], true );
    }

    public function get_response_summary( int $response_id, bool $sync_if_missing = true ): array {
        $form_content = $this->get_response_form_content( $response_id );

        if ( ! $this->is_quiz_form( $form_content ) ) {
            return $this->get_empty_summary();
        }

        $summary = [
            'is_quiz'               => true,
            'total_points'          => intval( $this->response_repository->get_meta_value( $response_id, static::META_TOTAL_POINTS ) ),
            'max_points'            => intval( $this->response_repository->get_meta_value( $response_id, static::META_MAX_POINTS ) ),
            'percentage'            => floatval( $this->response_repository->get_meta_value( $response_id, static::META_PERCENTAGE ) ),
            'grade'                 => (string) $this->response_repository->get_meta_value( $response_id, static::META_GRADE ),
            'status'                => (string) $this->response_repository->get_meta_value( $response_id, static::META_STATUS ),
            'pending_manual_count'  => intval( $this->response_repository->get_meta_value( $response_id, static::META_PENDING_MANUAL_COUNT ) ),
            'correct_count'         => intval( $this->response_repository->get_meta_value( $response_id, static::META_CORRECT_COUNT ) ),
            'question_count'        => intval( $this->response_repository->get_meta_value( $response_id, static::META_QUESTION_COUNT ) ),
            'is_grade_active'       => $this->is_grade_system_active( $form_content ),
            'is_retake_active'      => helpgent_get_nested_value( 'global.quiz_retake.isActive', $form_content, '0' ) === '1',
            'started_at'            => $this->response_repository->get_meta_value( $response_id, static::META_STARTED_AT ) ?: '',
            'expires_at'            => $this->response_repository->get_meta_value( $response_id, static::META_EXPIRES_AT ) ?: '',
        ];

        $has_saved_summary =
            $summary['status'] !== '' ||
            $summary['question_count'] > 0 ||
            $summary['max_points'] > 0;
        $summary_version = (string) $this->response_repository->get_meta_value( $response_id, static::META_SUMMARY_VERSION );

        if ( $sync_if_missing && ( ! $has_saved_summary || static::SUMMARY_VERSION !== $summary_version ) ) {
            return $this->sync_response( $response_id );
        }

        return $this->format_summary( $summary );
    }

    public function sync_response( int $response_id ): array {
        $form_content = $this->get_response_form_content( $response_id );

        if ( ! $this->is_quiz_form( $form_content ) ) {
            return $this->get_empty_summary();
        }

        $elements = $this->get_elements_by_id( $form_content );
        $answers  = ElementAnswers::query()
            ->select( [ 'id', 'element_id', 'element_type', 'value', 'points' ] )
            ->where( 'response_id', $response_id )
            ->where_in( 'element_type', array_values( $this->get_quiz_element_types() ) )
            ->get();
        $answers_by_element_id = [];

        foreach ( $answers as $answer ) {
            $answers_by_element_id[ $answer->element_id ] = $answer;
        }

        $summary = [
            'is_quiz'              => true,
            'total_points'         => 0,
            'max_points'           => 0,
            'percentage'           => 0,
            'grade'                => '',
            'status'               => 'completed',
            'pending_manual_count' => 0,
            'correct_count'        => 0,
            'question_count'       => 0,
            'is_grade_active'      => $this->is_grade_system_active( $form_content ),
            'is_retake_active'     => helpgent_get_nested_value( 'global.quiz_retake.isActive', $form_content, '0' ) === '1',
            'started_at'           => $this->response_repository->get_meta_value( $response_id, static::META_STARTED_AT ) ?: '',
            'expires_at'           => $this->response_repository->get_meta_value( $response_id, static::META_EXPIRES_AT ) ?: '',
        ];

        foreach ( $this->get_quiz_elements( $form_content ) as $element ) {
            $element_id = strval( $element['id'] ?? '' );

            if ( '' === $element_id ) {
                continue;
            }

            $answer = $answers_by_element_id[ $element_id ] ?? null;
            $result = $answer && ! empty( $elements[ $element_id ] )
                ? $this->evaluate_answer( $answer, $elements[ $element_id ] )
                : $this->evaluate_unanswered_element( $element );

            if ( empty( $result ) ) {
                continue;
            }

            $summary['question_count'] += 1;
            $summary['max_points']     += intval( $this->get_element_max_points( $element ) );

            if ( null === $result['points'] ) {
                $summary['status'] = 'pending_manual_grading';
                $summary['pending_manual_count'] += 1;
                if ( $answer ) {
                    $this->update_answer_points( intval( $answer->id ), null );
                }
                continue;
            }

            $points = intval( $result['points'] );

            $summary['total_points'] += $points;

            if ( ! empty( $result['is_correct'] ) ) {
                $summary['correct_count'] += 1;
            }

            if ( $answer ) {
                $this->update_answer_points( intval( $answer->id ), $points );
            }
        }

        if ( $summary['max_points'] > 0 ) {
            $summary['percentage'] = round( ( $summary['total_points'] / $summary['max_points'] ) * 100, 2 );
        }

        if ( 'completed' === $summary['status'] && $summary['is_grade_active'] ) {
            $summary['grade'] = $this->resolve_grade_label( $form_content, $summary['total_points'] );
        }

        $this->persist_summary( $response_id, $summary );

        return $this->format_summary( $summary );
    }

    public function augment_answer( stdClass $answer, array $element, int $response_id ): stdClass {
        $form_content = $this->get_response_form_content( $response_id );

        $answer->is_quiz_question = false;

        if ( ! $this->is_quiz_form( $form_content ) || ! $this->is_quiz_element_type( $answer->element_type ) ) {
            return $answer;
        }

        $result = $this->evaluate_answer( $answer, $element );

        if ( empty( $result ) ) {
            return $answer;
        }

        $answer->is_quiz_question = true;
        $answer->quiz_state       = $result['quiz_state'];
        $answer->is_correct       = $result['is_correct'];
        $answer->points           = $result['points'];
        $answer->max_points       = $result['max_points'];
        $answer->correct_answer   = $result['correct_answer'];
        $answer->is_manual_grade  = $this->is_manual_grade_element_type( $answer->element_type );

        return $answer;
    }

    public function get_feedback_result( int $form_id, string $element_id, $value, ?string $response_token = null ): array {
        $form_content = $this->get_feedback_form_content( $form_id, $response_token );

        if ( ! $this->is_quiz_form( $form_content ) ) {
            throw new Exception( esc_html__( 'Quiz form not found.', 'helpgent' ), 404 );
        }

        $elements = $this->get_elements_by_id( $form_content );
        $element  = $elements[ $element_id ] ?? null;

        if ( empty( $element ) ) {
            throw new Exception( esc_html__( 'Element not found.', 'helpgent' ), 404 );
        }

        if ( ! $this->is_quiz_element_type( strval( $element['element_type'] ?? '' ) ) ) {
            throw new Exception( esc_html__( 'Instant feedback is not available for this element.', 'helpgent' ), 422 );
        }

        $answer        = new stdClass();
        $answer->value = $value;
        $result        = $this->evaluate_answer( $answer, $element );

        if ( empty( $result ) ) {
            throw new Exception( esc_html__( 'Unable to evaluate quiz answer.', 'helpgent' ), 422 );
        }

        return [
            'element_id'      => $element_id,
            'element_type'    => strval( $element['element_type'] ?? '' ),
            'quiz_state'      => $result['quiz_state'],
            'is_correct'      => ! empty( $result['is_correct'] ),
            'points'          => $result['points'],
            'max_points'      => $result['max_points'],
            'correct_answer'  => $result['correct_answer'],
        ];
    }

    public function update_manual_points( int $form_id, int $response_id, string $element_id, int $points ): array {
        $response = $this->response_repository->get_by_id( $response_id );

        if ( ! $response || intval( $response->form_id ) !== $form_id ) {
            throw new Exception( esc_html__( 'Response not found.', 'helpgent' ), 404 );
        }

        $form_content = $this->get_response_form_content( $response_id );
        $elements     = $this->get_elements_by_id( $form_content );

        if ( empty( $elements[ $element_id ] ) ) {
            throw new Exception( esc_html__( 'Element not found.', 'helpgent' ), 404 );
        }

        $element = $elements[ $element_id ];

        if ( ! $this->is_manual_grade_element_type( $element['element_type'] ) ) {
            throw new Exception( esc_html__( 'This answer cannot be graded manually.', 'helpgent' ), 422 );
        }

        $max_points = $this->get_element_max_points( $element );

        if ( $points < 0 || $points > $max_points ) {
            throw new RequestValidatorException(
                '',
                422,
                null,
                [
                    'points' => [
                        sprintf(
                            /* translators: %d: max points */
                            esc_html__( 'Points must be between 0 and %d.', 'helpgent' ),
                            $max_points
                        ),
                    ],
                ]
            );
        }

        $answer = ElementAnswers::query()
            ->where( 'response_id', $response_id )
            ->where( 'element_id', $element_id )
            ->where( 'element_type', $element['element_type'] )
            ->first();

        if ( ! $answer ) {
            throw new Exception( esc_html__( 'Answer not found.', 'helpgent' ), 404 );
        }

        $this->update_answer_points( intval( $answer->id ), $points );
        $quiz_summary = $this->sync_response( $response_id );

        return [
            'quiz' => $quiz_summary,
        ];
    }

    public function get_review_answers( int $response_id, ?string $element_id = null ): array {
        $form_content = $this->get_response_form_content( $response_id );

        if ( ! $this->is_quiz_form( $form_content ) ) {
            return [];
        }

        /**
         * @var AnswerRepository $answer_repository
         */
        $answer_repository = helpgent_singleton( AnswerRepository::class );
        $stored_answers    = array_values(
            array_filter(
                $answer_repository->get( $response_id ),
                function( $answer ) {
                    return ! empty( $answer->is_quiz_question );
                }
            )
        );
        $answers_by_element_id = [];

        foreach ( $stored_answers as $answer ) {
            $answers_by_element_id[ $answer->element_id ] = $answer;
        }

        $review_answers = [];

        foreach ( $this->get_quiz_elements( $form_content ) as $element ) {
            $current_element_id = strval( $element['id'] ?? '' );

            if ( '' === $current_element_id ) {
                continue;
            }

            if ( ! empty( $element_id ) && $current_element_id !== $element_id ) {
                continue;
            }

            $review_answers[] = $answers_by_element_id[ $current_element_id ] ?? $this->build_unanswered_review_answer( $element );
        }

        return array_values( $review_answers );
    }

    public function get_form_quiz_summary( int $form_id ): ?array {
        $responses = Response::query()
            ->select( [ 'id' ] )
            ->where( 'form_id', $form_id )
            ->where( 'is_archive', 0 )
            ->where( 'is_completed', 1 )
            ->get();

        $summary = [
            'responses_count'        => 0,
            'average_points'         => 0,
            'average_percentage'     => 0,
            'correct_rate'           => 0,
            'pending_manual_count'   => 0,
            'grade_distribution'     => [],
        ];

        $total_points      = 0;
        $total_percentage  = 0;
        $total_correct     = 0;
        $total_questions   = 0;
        $grade_distribution = [];

        foreach ( $responses as $response ) {
            $response_summary = $this->get_response_summary( intval( $response->id ) );

            if ( empty( $response_summary['is_quiz'] ) ) {
                continue;
            }

            $summary['responses_count'] += 1;
            $summary['pending_manual_count'] += intval( $response_summary['pending_manual_count'] );
            $total_points     += intval( $response_summary['total_points'] );
            $total_percentage += floatval( $response_summary['percentage'] );
            $total_correct    += intval( $response_summary['correct_count'] );
            $total_questions  += intval( $response_summary['question_count'] );

            if ( ! empty( $response_summary['grade'] ) ) {
                if ( ! isset( $grade_distribution[ $response_summary['grade'] ] ) ) {
                    $grade_distribution[ $response_summary['grade'] ] = 0;
                }

                $grade_distribution[ $response_summary['grade'] ] += 1;
            }
        }

        if ( $summary['responses_count'] < 1 ) {
            return null;
        }

        $summary['average_points']     = round( $total_points / $summary['responses_count'], 2 );
        $summary['average_percentage'] = round( $total_percentage / $summary['responses_count'], 2 );
        $summary['correct_rate']       = $total_questions > 0
            ? round( ( $total_correct / $total_questions ) * 100, 2 )
            : 0;
        $summary['grade_distribution'] = array_map(
            function( $label, $total ) {
                return [
                    'label' => $label,
                    'total' => $total,
                ];
            },
            array_keys( $grade_distribution ),
            array_values( $grade_distribution )
        );

        return $summary;
    }

    public function get_element_quiz_summary( int $form_id, array $element ): ?array {
        if ( ! $this->is_quiz_element_type( $element['element_type'] ) ) {
            return null;
        }

        $answers = ElementAnswers::query()
            ->select( [ 'id', 'value', 'points' ] )
            ->where( 'form_id', $form_id )
            ->where( 'element_id', $element['id'] )
            ->where( 'element_type', $element['element_type'] )
            ->get();

        $summary = [
            'max_points'          => $this->get_element_max_points( $element ),
            'type'                => $this->is_manual_grade_element_type( $element['element_type'] ) ? 'manual' : 'auto',
            'total_answered'      => count( $answers ),
            'correct_count'       => 0,
            'incorrect_count'     => 0,
            'correct_rate'        => 0,
            'pending_manual_count'=> 0,
            'average_points'      => 0,
        ];

        if ( empty( $answers ) ) {
            return $summary;
        }

        $total_points = 0;
        $graded_count = 0;

        foreach ( $answers as $answer ) {
            $result = $this->evaluate_answer( $answer, $element );

            if ( empty( $result ) ) {
                continue;
            }

            if ( null === $result['points'] ) {
                $summary['pending_manual_count'] += 1;
                continue;
            }

            $graded_count += 1;
            $total_points += intval( $result['points'] );

            if ( ! empty( $result['is_correct'] ) ) {
                $summary['correct_count'] += 1;
            } else {
                $summary['incorrect_count'] += 1;
            }
        }

        if ( $graded_count > 0 ) {
            $summary['average_points'] = round( $total_points / $graded_count, 2 );
        }

        if ( ! $this->is_manual_grade_element_type( $element['element_type'] ) && $summary['total_answered'] > 0 ) {
            $summary['correct_rate'] = round( ( $summary['correct_count'] / $summary['total_answered'] ) * 100, 2 );
        }

        return $summary;
    }

    public function get_element_overview( array $form_content, array $element ): ?array {
        if ( ! $this->is_quiz_form( $form_content ) || ! $this->is_quiz_element_type( $element['element_type'] ) ) {
            return null;
        }

        return [
            'type'       => $this->is_manual_grade_element_type( $element['element_type'] ) ? 'manual' : 'auto',
            'max_points' => $this->get_element_max_points( $element ),
        ];
    }

    protected function get_empty_summary(): array {
        return [
            'is_quiz'              => false,
            'total_points'         => 0,
            'max_points'           => 0,
            'percentage'           => 0,
            'grade'                => '',
            'status'               => '',
            'pending_manual_count' => 0,
            'correct_count'        => 0,
            'question_count'       => 0,
            'is_grade_active'      => false,
            'is_retake_active'     => false,
            'started_at'           => '',
            'expires_at'           => '',
        ];
    }

    protected function get_feedback_form_content( int $form_id, ?string $response_token = null ): array {
        if ( ! empty( $response_token ) ) {
            $response = helpgent_get_response_by_token( $response_token, $form_id );

            if ( $response ) {
                return $this->get_response_form_content( intval( $response->id ) );
            }
        }

        /**
         * @var FormRepository $form_repository
         */
        $form_repository = helpgent_singleton( FormRepository::class );
        $form            = $form_repository->get_by_id_publish( $form_id );

        if ( ! $form ) {
            return [];
        }

        $snapshot          = new stdClass();
        $snapshot->content = $form->content;
        $snapshot          = helpgent_migrate_old_form( $snapshot, false );

        return is_array( $snapshot->content ) ? $snapshot->content : [];
    }

    protected function persist_summary( int $response_id, array $summary ): void {
        $this->response_repository->update_meta( $response_id, static::META_TOTAL_POINTS, (string) intval( $summary['total_points'] ) );
        $this->response_repository->update_meta( $response_id, static::META_MAX_POINTS, (string) intval( $summary['max_points'] ) );
        $this->response_repository->update_meta( $response_id, static::META_PERCENTAGE, (string) floatval( $summary['percentage'] ) );
        $this->response_repository->update_meta( $response_id, static::META_GRADE, (string) $summary['grade'] );
        $this->response_repository->update_meta( $response_id, static::META_SUMMARY_VERSION, static::SUMMARY_VERSION );
        $this->response_repository->update_meta( $response_id, static::META_STATUS, (string) $summary['status'] );
        $this->response_repository->update_meta( $response_id, static::META_PENDING_MANUAL_COUNT, (string) intval( $summary['pending_manual_count'] ) );
        $this->response_repository->update_meta( $response_id, static::META_CORRECT_COUNT, (string) intval( $summary['correct_count'] ) );
        $this->response_repository->update_meta( $response_id, static::META_QUESTION_COUNT, (string) intval( $summary['question_count'] ) );
    }

    protected function format_summary( array $summary ): array {
        $summary['score_text']      = intval( $summary['max_points'] ) > 0
            ? sprintf(
                '%d/%d',
                intval( $summary['total_points'] ),
                intval( $summary['max_points'] )
            )
            : (string) intval( $summary['total_points'] );
        $summary['percentage_text'] = rtrim( rtrim( number_format( floatval( $summary['percentage'] ), 2, '.', '' ), '0' ), '.' ) . '%';

        return $summary;
    }

    protected function is_grade_system_active( array $form_content ): bool {
        return helpgent_get_nested_value( 'global.quiz_grade_system.isActive', $form_content, '0' ) === '1';
    }

    protected function resolve_grade_label( array $form_content, float $score ): string {
        $grades = helpgent_get_nested_value( 'global.quiz_grade_system.grades', $form_content, [] );

        if ( empty( $grades ) || ! is_array( $grades ) ) {
            return '';
        }

        foreach ( $grades as $grade ) {
            if ( ! is_array( $grade ) ) {
                continue;
            }

            $min = isset( $grade['min'] ) ? floatval( $grade['min'] ) : null;
            $max = isset( $grade['max'] ) ? floatval( $grade['max'] ) : null;

            if ( null === $min || null === $max ) {
                continue;
            }

            if ( $score >= $min && $score <= $max ) {
                return isset( $grade['label'] ) ? sanitize_text_field( $grade['label'] ) : '';
            }
        }

        return '';
    }

    protected function update_answer_points( int $answer_id, ?int $points ): void {
        ElementAnswers::query()->where( 'id', $answer_id )->update(
            [
                'points'     => $points,
                'updated_at' => helpgent_now(),
            ]
        );
    }

    protected function get_quiz_elements( array $form_content ): array {
        return array_values(
            array_filter(
                $form_content['elements'] ?? [],
                function( $element ) {
                    return ! empty( $element['element_type'] ) &&
                        $this->is_quiz_element_type( $element['element_type'] );
                }
            )
        );
    }

    protected function get_elements_by_id( array $form_content ): array {
        $elements = [];

        foreach ( $form_content['elements'] ?? [] as $element ) {
            if ( empty( $element['id'] ) ) {
                continue;
            }

            $elements[ $element['id'] ] = $element;
        }

        return $elements;
    }

    protected function get_quiz_timer_seconds( array $form_content ): int {
        if ( helpgent_get_nested_value( 'global.quiz_show_timer.isActive', $form_content, '0' ) !== '1' ) {
            return 0;
        }

        if ( helpgent_get_nested_value( 'global.quiz_time_limit.isActive', $form_content, '0' ) !== '1' ) {
            return 0;
        }

        return $this->minutes_float_to_seconds(
            helpgent_get_nested_value( 'global.quiz_time_limit.value', $form_content, 0 )
        );
    }

    protected function minutes_float_to_seconds( $value ): int {
        $value = floatval( $value );

        if ( $value <= 0 ) {
            return 0;
        }

        $minutes      = floor( $value );
        $seconds_part = round( ( $value - $minutes ) * 60 );

        return intval( ( $minutes * 60 ) + $seconds_part );
    }

    protected function get_quiz_element_types(): array {
        return [
            'single_select',
            'dropdown',
            'yes_no',
            'number',
            'multi_select',
            'picture_select',
            'short_text',
            'long_text',
            'file_upload',
        ];
    }

    protected function get_element_max_points( array $element ): int {
        $attributes = $element['attributes'] ?? [];
        $type       = $element['element_type'] ?? '';

        if ( in_array( $type, [ 'single_select', 'dropdown', 'yes_no', 'multi_select', 'picture_select' ], true ) ) {
            $options = $attributes['options'] ?? [];

            if ( ! is_array( $options ) ) {
                return 0;
            }

            $correct_options = array_filter(
                $options,
                function( $option ) {
                    return is_array( $option ) && ( $option['quiz_is_correct'] ?? '0' ) === '1';
                }
            );

            if ( empty( $correct_options ) ) {
                return 0;
            }

            if ( in_array( $type, [ 'multi_select', 'picture_select' ], true ) ) {
                return array_sum(
                    array_map(
                        function( $option ) {
                            return intval( $option['quiz_points'] ?? 0 );
                        },
                        $correct_options
                    )
                );
            }

            return max(
                array_map(
                    function( $option ) {
                        return intval( $option['quiz_points'] ?? 0 );
                    },
                    $correct_options
                )
            );
        }

        return intval( $attributes['quiz_points'] ?? 0 );
    }

    protected function build_unanswered_review_answer( array $element ): stdClass {
        $answer                = new stdClass();
        $answer->id            = null;
        $answer->element_id    = strval( $element['id'] ?? '' );
        $answer->element_type  = strval( $element['element_type'] ?? '' );
        $answer->value         = in_array( $answer->element_type, [ 'multi_select', 'picture_select' ], true ) ? [] : null;
        $answer->element       = $element;
        $answer->element_title = isset( $element['attributes']['title'] )
            ? $element['attributes']['title']
            : esc_html__( 'Unknown', 'helpgent' );
        $answer->options       = $element['attributes']['options'] ?? [];
        $answer->attachments   = [];

        $result = $this->evaluate_unanswered_element( $element );

        $answer->is_quiz_question = true;
        $answer->quiz_state       = $result['quiz_state'] ?? 'unanswered';
        $answer->is_correct       = $result['is_correct'] ?? false;
        $answer->points           = $result['points'] ?? 0;
        $answer->max_points       = $result['max_points'] ?? 0;
        $answer->correct_answer   = $result['correct_answer'] ?? null;
        $answer->is_manual_grade  = false;

        return $answer;
    }

    protected function evaluate_unanswered_element( array $element ): ?array {
        $element_type = $element['element_type'] ?? '';

        if ( ! $this->is_quiz_element_type( $element_type ) ) {
            return null;
        }

        if ( $this->is_manual_grade_element_type( $element_type ) ) {
            return [
                'points'         => 0,
                'max_points'     => $this->get_element_max_points( $element ),
                'quiz_state'     => 'unanswered',
                'is_correct'     => false,
                'correct_answer' => null,
            ];
        }

        $answer        = new stdClass();
        $answer->value = in_array( $element_type, [ 'multi_select', 'picture_select' ], true ) ? [] : null;
        $result        = $this->evaluate_answer( $answer, $element );

        if ( empty( $result ) ) {
            return null;
        }

        $result['points']     = 0;
        $result['quiz_state'] = 'unanswered';
        $result['is_correct'] = false;

        return $result;
    }

    protected function evaluate_answer( $answer, array $element ): ?array {
        $element_type = $element['element_type'] ?? '';

        if ( ! $this->is_quiz_element_type( $element_type ) ) {
            return null;
        }

        if ( $this->is_manual_grade_element_type( $element_type ) ) {
            $max_points = $this->get_element_max_points( $element );
            $points     = isset( $answer->points ) && null !== $answer->points ? intval( $answer->points ) : null;

            return [
                'points'         => $points,
                'max_points'     => $max_points,
                'quiz_state'     => null === $points ? 'manual-answer' : 'manual-graded',
                'is_correct'     => null !== $points && $points === $max_points,
                'correct_answer' => null,
            ];
        }

        switch ( $element_type ) {
            case 'single_select':
            case 'dropdown':
                return $this->evaluate_single_choice_answer( $answer, $element, 'id' );

            case 'yes_no':
                return $this->evaluate_single_choice_answer( $answer, $element, 'value' );

            case 'multi_select':
            case 'picture_select':
                return $this->evaluate_multi_choice_answer( $answer, $element );

            case 'number':
                return $this->evaluate_number_answer( $answer, $element );
        }

        return null;
    }

    protected function evaluate_single_choice_answer( $answer, array $element, string $compare_key ): array {
        $options        = $element['attributes']['options'] ?? [];
        $selected_value = isset( $answer->value ) ? (string) $answer->value : '';
        $correct_option = null;

        foreach ( $options as $option ) {
            if ( ! is_array( $option ) || ( $option['quiz_is_correct'] ?? '0' ) !== '1' ) {
                continue;
            }

            $correct_option = $option;
            break;
        }

        $max_points = $correct_option ? intval( $correct_option['quiz_points'] ?? 0 ) : 0;
        $is_correct = $correct_option && $selected_value === strval( $correct_option[ $compare_key ] ?? '' );

        return [
            'points'         => $is_correct ? $max_points : 0,
            'max_points'     => $max_points,
            'quiz_state'     => $is_correct ? 'correct' : 'incorrect',
            'is_correct'     => $is_correct,
            'correct_answer' => $correct_option ? [ $correct_option ] : [],
        ];
    }

    protected function evaluate_multi_choice_answer( $answer, array $element ): array {
        $options = $element['attributes']['options'] ?? [];
        $value   = $answer->value ?? [];

        if ( is_string( $value ) ) {
            $decoded = json_decode( $value, true );
            $value   = is_array( $decoded ) ? $decoded : [];
        }

        if ( ! is_array( $value ) ) {
            $value = [];
        }

        $selected_values = array_map( 'strval', array_values( $value ) );
        sort( $selected_values );

        $correct_options = array_values(
            array_filter(
                $options,
                function( $option ) {
                    return is_array( $option ) && ( $option['quiz_is_correct'] ?? '0' ) === '1';
                }
            )
        );

        $correct_values = array_map(
            function( $option ) {
                return strval( $option['id'] ?? '' );
            },
            $correct_options
        );
        sort( $correct_values );

        $max_points = array_sum(
            array_map(
                function( $option ) {
                    return intval( $option['quiz_points'] ?? 0 );
                },
                $correct_options
            )
        );

        $is_correct = $selected_values === $correct_values;

        return [
            'points'         => $is_correct ? $max_points : 0,
            'max_points'     => $max_points,
            'quiz_state'     => $is_correct ? 'correct' : 'incorrect',
            'is_correct'     => $is_correct,
            'correct_answer' => $correct_options,
        ];
    }

    protected function evaluate_number_answer( $answer, array $element ): array {
        $correct_value = helpgent_get_nested_value( 'attributes.quiz_correct_answer', $element, null );
        $max_points    = $this->get_element_max_points( $element );
        $answer_value  = $answer->value ?? null;

        $is_correct = null !== $correct_value &&
            null !== $answer_value &&
            is_numeric( $correct_value ) &&
            is_numeric( $answer_value ) &&
            floatval( $correct_value ) === floatval( $answer_value );

        return [
            'points'         => $is_correct ? $max_points : 0,
            'max_points'     => $max_points,
            'quiz_state'     => $is_correct ? 'correct' : 'incorrect',
            'is_correct'     => $is_correct,
            'correct_answer' => $correct_value,
        ];
    }
}
