<?php
namespace MetForm_Pro\Core\Analytics\Helpers;
defined( 'ABSPATH' ) || exit;

class Quiz_Analytics {

    /**
     * Get quiz analytics data.
     *
     * @param int    $form_id    Form ID. 0 = all quiz forms.
     * @param string $start_date Start date (Y-m-d).
     * @param string $end_date   End date (Y-m-d).
     * @return array|\WP_Error
     */
    public static function get_quiz_analytics( $form_id = 0, $start_date = '', $end_date = '' ) {
        $form_ids = self::resolve_quiz_form_ids( $form_id );
        if ( is_wp_error( $form_ids ) ) {
            return $form_ids;
        }

        if ( empty( $form_ids ) ) {
            return [
                'success' => true,
                'message' => 'No quiz forms found.',
                'data'    => self::get_empty_analytics(),
            ];
        }

        $entries = self::get_quiz_entries( $form_ids, $start_date, $end_date );
        if ( empty( $entries ) ) {
            $empty_analytics = self::get_empty_analytics();
            $empty_analytics['total_questions'] = 0;

            return [
                'success' => true,
                'message' => 'No entries found for the specified criteria.',
                'data'    => $empty_analytics,
            ];
        }

        // Calculate and return analytics.
        return [
            'success' => true,
            'data'    => self::process_quiz_entries( $entries, $form_ids ),
        ];
    }

    /**
     * Get question-wise answer statistics for quiz analytics.
     *
     * @param int    $form_id           Form ID. 0 = all quiz forms.
     * @param string $start_date        Start date (Y-m-d).
     * @param string $end_date          End date (Y-m-d).
     * @param int    $common_answer_cap Number of top common answers for text-field.
     * @return array|\WP_Error
     */
    public static function get_quiz_answer_statistics( $form_id = 0, $start_date = '', $end_date = '', $common_answer_cap = 6 ) {
        $common_answer_cap = max( 1, (int) $common_answer_cap );

        $form_ids = self::resolve_quiz_form_ids( $form_id );
        if ( is_wp_error( $form_ids ) ) {
            return $form_ids;
        }

        if ( empty( $form_ids ) ) {
            return [
                'success' => true,
                'message' => 'No quiz forms found.',
                'data'    => [
                    'questions'     => [],
                    'total_entries' => 0,
                ],
            ];
        }

        $entries = self::get_quiz_entries( $form_ids, $start_date, $end_date );

        $statistics_map = self::build_question_statistics_map( $form_ids );
        if ( empty( $statistics_map['questions'] ) ) {
            return [
                'success' => true,
                'message' => 'No supported quiz questions found.',
                'data'    => [
                    'questions'     => [],
                    'total_entries' => count( $entries ),
                ],
            ];
        }

        // Only accumulate when entries exist; questions still returned with 0s when there are none.
        if ( ! empty( $entries ) ) {
            self::accumulate_question_statistics( $statistics_map, $entries );
        }

        $questions = self::format_question_statistics( $statistics_map['questions'], $common_answer_cap );

        return [
            'success' => true,
            'data'    => [
                'questions'     => $questions,
                'total_entries' => count( $entries ),
            ],
        ];
    }

    /**
     * Get quiz question difficulty analysis.
     *
     * Difficulty bands:
     * - easy:      correct > 80%
     * - medium:    50% to 80%
     * - hard:      30% to <50%
     * - very hard: <30%
     *
     * @param int    $form_id    Form ID. 0 = all quiz forms.
     * @param string $start_date Start date (Y-m-d).
     * @param string $end_date   End date (Y-m-d).
     * @return array|\WP_Error
     */
    public static function get_quiz_difficulty_analysis( $form_id = 0, $start_date = '', $end_date = '' ) {
        $form_ids = self::resolve_quiz_form_ids( $form_id );
        if ( is_wp_error( $form_ids ) ) {
            return $form_ids;
        }

        if ( empty( $form_ids ) ) {
            return [
                'success' => true,
                'message' => 'No quiz forms found.',
                'data'    => [
                    'questions' => [],
                ],
            ];
        }

        $entries = self::get_quiz_entries( $form_ids, $start_date, $end_date );
        if ( empty( $entries ) ) {
            return [
                'success' => true,
                'message' => 'No entries found for the specified criteria.',
                'data'    => [
                    'questions' => [],
                ],
            ];
        }

        $statistics_map = self::build_question_statistics_map( $form_ids );
        if ( empty( $statistics_map['questions'] ) ) {
            return [
                'success' => true,
                'message' => 'No supported quiz questions found.',
                'data'    => [
                    'questions' => [],
                ],
            ];
        }

        $ranking = self::build_difficulty_ranking( $statistics_map, $entries );

        return [
            'success' => true,
            'data'    => [
                'questions' => $ranking,
            ],
        ];
    }

    /**
     * Get quiz score distribution (percentage bucket counts).
     *
     * Buckets: 0-10, 11-20, 21-30, 31-40, 41-50, 51-60, 61-70, 71-80, 81-90, 91-100
     *
     * @param int    $form_id    Form ID. 0 = all quiz forms.
     * @param string $start_date Start date (Y-m-d).
     * @param string $end_date   End date (Y-m-d).
     * @return array|\WP_Error
     */
    public static function get_quiz_score_distribution( $form_id = 0, $start_date = '', $end_date = '' ) {
        $form_ids = self::resolve_quiz_form_ids( $form_id );
        if ( is_wp_error( $form_ids ) ) {
            return $form_ids;
        }
        $entries = self::get_quiz_entries( $form_ids, $start_date, $end_date );

        if ( empty( $form_ids ) || empty( $entries ) ) {
            return [
                'success' => true,
                'message' => 'No quiz forms found.',
                'data'    => [
                    'score_distribution' => self::get_default_score_distribution(),
                    'total_entries'      => 0,
                    'valid_scores'       => 0,
                ],
            ];
        }

        // Pre-load full marks per form.
        $form_total_marks = [];
        foreach ( $form_ids as $fid ) {
            $quiz_stats = self::get_form_quiz_stats( $fid );
            $form_total_marks[ (int) $fid ] = $quiz_stats['total_marks'];
        }

        $percentage_scores = [];
        foreach ( $entries as $entry ) {
            $entry_form_id = (int) get_post_meta( $entry->ID, 'metform_entries__form_id', true );
            $form_data     = get_post_meta( $entry->ID, 'metform_entries__form_data', true );

            if ( ! $form_data || ! is_array( $form_data ) ) {
                continue;
            }

            if ( ! isset( $form_data['quiz-marks'] ) ) {
                continue;
            }

            $quiz_marks       = floatval( $form_data['quiz-marks'] );
            $total_form_marks = $form_total_marks[ $entry_form_id ] ?? 0;
            if ( $total_form_marks <= 0 ) {
                continue;
            }

            $percentage_scores[] = round( ( $quiz_marks / $total_form_marks ) * 100, 2 );
        }

        return [
            'success' => true,
            'data'    => [
                'score_distribution' => self::build_score_distribution( $percentage_scores ),
                'total_entries'      => count( $entries ),
                'valid_scores'       => count( $percentage_scores ),
            ],
        ];
    }

    /**
     * Process quiz entries to calculate analytics.
     *
     * @param array $entries  WP_Post objects (metform-entry).
     * @param array $form_ids The form IDs whose entries were fetched.
     * @return array
     */
    private static function process_quiz_entries( $entries, $form_ids ) {
        $raw_scores        = [];
        $total_obtained    = 0;
        $total_full_marks  = 0;
        $valid_score_count = 0;
        $correct_count     = 0;
        $wrong_count       = 0;
        $skipped_questions = [];
        $used_form_ids     = [];
        $total_entries     = count( $entries );

        // Pre-load full marks and question count per form.
        $form_total_marks     = [];
        $form_question_fields = [];

        foreach ( $form_ids as $fid ) {
            $quiz_stats                   = self::get_form_quiz_stats( $fid );
            $form_total_marks[ $fid ]     = $quiz_stats['total_marks'];
            $form_question_fields[ $fid ] = $quiz_stats['question_fields'];
        }
        // Loop every entry.
        foreach ( $entries as $entry ) {
            $entry_form_id = (int) get_post_meta( $entry->ID, 'metform_entries__form_id', true );
            $form_data     = get_post_meta( $entry->ID, 'metform_entries__form_data', true );

            if ( ! $form_data || ! is_array( $form_data ) ) {
                continue;
            }

            if ( ! isset( $form_data['quiz-marks'] ) ) {
                continue;
            }

            $quiz_marks       = floatval( $form_data['quiz-marks'] );
            $total_form_marks = $form_total_marks[ $entry_form_id ] ?? 0;
            if ( $total_form_marks <= 0 ) {
                continue;
            }

            $raw_scores[]     = $quiz_marks;
            $total_obtained  += $quiz_marks;
            $total_full_marks += $total_form_marks;
            $valid_score_count++;
            $used_form_ids[ $entry_form_id ] = true;

            // Correct/wrong tallies.
            $right_answers = ! empty( $form_data['right-answer'] )
                ? array_filter( explode( ',', $form_data['right-answer'] ) )
                : [];
            $wrong_answers = ! empty( $form_data['wrong-answer'] )
                ? array_filter( explode( ',', $form_data['wrong-answer'] ) )
                : [];

            $correct_count += count( $right_answers );
            $wrong_count   += count( $wrong_answers );

            // Skipped questions.
            $question_fields = $form_question_fields[ $entry_form_id ] ?? [];
            foreach ( $question_fields as $field_name ) {
                $is_answered = isset( $form_data[ $field_name ] )
                    && ( $form_data[ $field_name ] !== '' || $form_data[ $field_name ] === '0' );

                if ( ! $is_answered ) {
                    // This question was skipped in this entry. Increment skip count for this field.
                    $skipped_questions[ $field_name ] = ( $skipped_questions[ $field_name ] ?? 0 ) + 1;
                }
            }
        }

        if ( $valid_score_count === 0 || $total_full_marks <= 0 ) {
            $empty_analytics = self::get_empty_analytics();
            $empty_analytics['total_questions'] = 0;
            $empty_analytics['total_entries'] = $total_entries;
            return $empty_analytics;
        }
        $total_questions_count = self::get_total_questions_count_for_forms( array_keys( $used_form_ids ) );
        $average_score = round( ( $total_obtained / $total_full_marks ) * 100, 2 );
        $highest_score = round( max( $raw_scores ), 2 );
        $lowest_score  = round( min( $raw_scores ), 2 );

        $correct_percentage = null;
        $wrong_percentage   = null;
        $total_answered     = $correct_count + $wrong_count;
        if ( $total_answered > 0 ) {
            $correct_percentage = round( ( $correct_count / $total_answered ) * 100, 2 );
            $wrong_percentage   = round( ( $wrong_count   / $total_answered ) * 100, 2 );
        }

        $most_skipped_question = null;
        if ( ! empty( $skipped_questions ) ) {
            arsort( $skipped_questions );
            $most_skipped_field      = key( $skipped_questions );
            $most_skipped_count      = $skipped_questions[ $most_skipped_field ];
            $most_skipped_percentage = round( ( $most_skipped_count / $total_entries ) * 100, 2 );

            $most_skipped_question = [
                'field_name' => $most_skipped_field,
                'skip_count' => $most_skipped_count,
                'percentage' => $most_skipped_percentage,
            ];
        }

        return [
            'average_score_percentage' => $average_score,
            'highest_score'            => $highest_score,
            'lowest_score'             => $lowest_score,
            'correct_percentage'       => $correct_percentage,
            'wrong_percentage'         => $wrong_percentage,
            'correct_count'            => $correct_count,
            'wrong_count'              => $wrong_count,
            'total_questions'          => $total_questions_count,
            'total_entries'            => $total_entries,
            'most_skipped_question'    => $most_skipped_question,
        ];
    }

    /**
     * Count total quiz questions from selected forms.
     *
     * @param array $form_ids
     * @return int
     */
    private static function get_total_questions_count_for_forms( $form_ids ) {
        $total_questions_count = 0;
        foreach ( $form_ids as $fid ) {
            $quiz_stats = self::get_form_quiz_stats( $fid );
            $total_questions_count += $quiz_stats['question_count'];
        }

        return $total_questions_count;
    }

    /**
     * Read quiz stats directly from raw Elementor data.
     *
     * Normalises JSON once at the entry point, then walks the element
     * tree iteratively. No recursion and no per-element type coercion.
     *
     * @param int $form_id
    * @return array{question_count:int,total_marks:float,question_fields:array}
     */
    private static function get_form_quiz_stats( $form_id ) {
        static $stats_cache = [];

        $form_id = (int) $form_id;
        if ( isset( $stats_cache[ $form_id ] ) ) {
            return $stats_cache[ $form_id ];
        }

        $stats = [
            'question_count' => 0,
            'total_marks'    => 0,
            'question_fields' => [],
        ];

        $raw = get_post_meta( $form_id, '_elementor_data', true );
        if ( empty( $raw ) ) {
            $stats_cache[ $form_id ] = $stats;
            return $stats;
        }

        $elements = is_string( $raw )
            ? json_decode( $raw, true )
            : json_decode( json_encode( $raw ), true );
        
        // If we can't decode into an array, return zero stats.
        if ( ! is_array( $elements ) ) {
            $stats_cache[ $form_id ] = $stats;
            return $stats;
        }

        $stack = $elements;

        while ( ! empty( $stack ) ) {
            $element = array_pop( $stack );

            if ( ! is_array( $element ) ) {
                continue;
            }

            if ( ( $element['elType'] ?? '' ) === 'widget' ) {
                $settings = $element['settings'] ?? [];
                if ( ! is_array( $settings ) ) {
                    $settings = [];
                }

                $positive = floatval( $settings['mf_quiz_point'] ?? 0 );
                $negative = floatval( $settings['mf_quiz_negative_point'] ?? 0 );

                if ( $positive != 0 || $negative != 0 ) {
                    $stats['question_count']++;
                    $stats['total_marks'] += $positive;

                    $field_name = $settings['mf_input_name'] ?? '';
                    if ( is_string( $field_name ) && $field_name !== '' ) {
                        $stats['question_fields'][] = $field_name;
                    }
                }
            }

            if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) {
                foreach ( $element['elements'] as $child ) {
                    $stack[] = $child;
                }
            }
        }

        if ( ! empty( $stats['question_fields'] ) ) {
            $stats['question_fields'] = array_values( array_unique( $stats['question_fields'] ) );
        }

        $stats_cache[ $form_id ] = $stats;
        return $stats;
    }

    /**
     * Resolve quiz form IDs from a specific form ID or all published forms.
     *
     * @param int $form_id
     * @return array|\WP_Error
     */
    private static function resolve_quiz_form_ids( $form_id = 0 ) {
        $form_id = (int) $form_id;

        if ( $form_id > 0 ) {
            $form = get_post( $form_id );
            if ( ! $form || $form->post_type !== 'metform-form' || $form->post_status !== 'publish' ) {
                return new \WP_Error( 'invalid_form', 'Form not found.', [ 'status' => 404 ] );
            }

            $form_settings = \MetForm\Core\Forms\Action::instance()->get_all_data( $form_id );
            if ( ! isset( $form_settings['form_type'] ) || $form_settings['form_type'] !== 'quiz-form' ) {
                return new \WP_Error( 'invalid_form_type', 'The specified form is not a quiz form.', [ 'status' => 400 ] );
            }

            return [ $form_id ];
        }

        /**
         * Cap the number of forms scanned when resolving quiz forms.
         *
         * @param int $limit Maximum forms to load. Use -1 for unlimited.
         */
        $forms_limit = (int) apply_filters( 'metform_analytics_forms_query_limit', 2000 );

        $all_forms = get_posts( [
            'post_type'   => 'metform-form',
            'post_status' => 'publish',
            'numberposts' => $forms_limit,
        ] );

        $form_ids = [];
        foreach ( $all_forms as $form ) {
            $settings = \MetForm\Core\Forms\Action::instance()->get_all_data( $form->ID );
            if ( isset( $settings['form_type'] ) && $settings['form_type'] === 'quiz-form' ) {
                $form_ids[] = (int) $form->ID;
            }
        }

        return $form_ids;
    }

    /**
     * Fetch quiz entries for resolved form IDs and date range.
     *
     * @param array  $form_ids
     * @param string $start_date
     * @param string $end_date
     * @return array
     */
    private static function get_quiz_entries( $form_ids, $start_date = '', $end_date = '' ) {
        /**
         * Cap the number of quiz entries loaded into memory at once.
         *
         * Loading every entry (posts_per_page => -1) can exhaust PHP memory on
         * forms with very large entry counts. Entries are ordered newest-first,
         * so the most recent entries (up to this cap) are analysed. Sites with
         * adequate memory can raise or remove the cap via this filter.
         *
         * @param int $limit Maximum entries to load. Use -1 for unlimited.
         */
        $entries_limit = (int) apply_filters( 'metform_analytics_quiz_entries_limit', 5000 );

        $entries_query = [
            'post_type'      => 'metform-entry',
            'post_status'    => 'publish',
            'posts_per_page' => $entries_limit,
            'orderby'        => 'date',
            'order'          => 'DESC',
            'no_found_rows'  => true,
            'meta_query'     => [
                [
                    'key'     => 'metform_entries__form_id',
                    'value'   => $form_ids,
                    'compare' => 'IN',
                    'type'    => 'NUMERIC',
                ],
            ],
        ];

        // Support partial date filters too (only start_date or only end_date).
        if ( ! empty( $start_date ) || ! empty( $end_date ) ) {
            $range = [
                'inclusive' => true,
            ];

            if ( ! empty( $start_date ) ) {
                $range['after'] = $start_date;
            }

            if ( ! empty( $end_date ) ) {
                $range['before'] = $end_date;
            }

            $entries_query['date_query'] = [ $range ];
        }

        return get_posts( $entries_query );
    }

    /**
     * Build question statistics map from all selected forms.
     *
     * @param array $form_ids
     * @return array{questions:array,by_form:array}
     */
    private static function build_question_statistics_map( $form_ids ) {
        $questions = [];
        $by_form   = [];
        $id        = 1;

        foreach ( $form_ids as $form_id ) {
            $definitions = self::get_form_quiz_question_definitions( $form_id );

            foreach ( $definitions as $definition ) {
                $question_id = $id++;

                $definition['id'] = $question_id;
                $definition['total_answered'] = 0;
                $definition['option_counts'] = [];
                $definition['text_answer_counts'] = [];

                if ( ! empty( $definition['options'] ) ) {
                    foreach ( $definition['options'] as $option ) {
                        $definition['option_counts'][ $option['value'] ] = 0;
                    }
                }

                $questions[ $question_id ] = $definition;
                $by_form[ $form_id ][] = $question_id;
            }
        }

        return [
            'questions' => $questions,
            'by_form'   => $by_form,
        ];
    }

    /**
     * Parse quiz question definitions from a form's Elementor structure.
     *
     * @param int $form_id
     * @return array
     */
    private static function get_form_quiz_question_definitions( $form_id ) {
        static $cache = [];

        $form_id = (int) $form_id;
        if ( isset( $cache[ $form_id ] ) ) {
            return $cache[ $form_id ];
        }

        $raw = get_post_meta( $form_id, '_elementor_data', true );
        if ( empty( $raw ) ) {
            $cache[ $form_id ] = [];
            return [];
        }

        $elements = is_string( $raw )
            ? json_decode( $raw, true )
            : json_decode( json_encode( $raw ), true );

        if ( ! is_array( $elements ) ) {
            $cache[ $form_id ] = [];
            return [];
        }

        $definitions = [];
        $stack = $elements;

        while ( ! empty( $stack ) ) {
            $element = array_pop( $stack );
            if ( ! is_array( $element ) ) {
                continue;
            }

            if ( ( $element['elType'] ?? '' ) === 'widget' ) {
                $widget_type = $element['widgetType'] ?? '';
                $settings    = $element['settings'] ?? [];

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

                $positive = floatval( $settings['mf_quiz_point'] ?? 0 );
                $negative = floatval( $settings['mf_quiz_negative_point'] ?? 0 );

                if ( $positive != 0 || $negative != 0 ) {
                    $question_type = self::map_widget_to_question_type( $widget_type, $settings );
                    if ( $question_type !== '' && $question_type !== 'number-field' ) {
                        $field_name = isset( $settings['mf_input_name'] ) ? trim( (string) $settings['mf_input_name'] ) : '';
                        if ( $field_name !== '' ) {
                            $options = self::extract_question_options( $widget_type, $settings );
                            $correct_answer = self::extract_correct_answer( $question_type, $settings, $options );

                            $definitions[] = [
                                'form_id'        => $form_id,
                                'field_name'     => $field_name,
                                'type'           => $question_type,
                                'question'       => isset( $settings['mf_input_label'] ) && $settings['mf_input_label'] !== ''
                                    ? (string) $settings['mf_input_label']
                                    : $field_name,
                                'correct_answer' => $correct_answer,
                                'options'        => $options,
                            ];
                        }
                    }
                }
            }

            if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) {
                foreach ( $element['elements'] as $child ) {
                    $stack[] = $child;
                }
            }
        }

        $cache[ $form_id ] = $definitions;
        return $definitions;
    }

    /**
     * Aggregate per-question answer counts from entries.
     *
     * @param array $statistics_map
     * @param array $entries
     * @return void
     */
    private static function accumulate_question_statistics( &$statistics_map, $entries ) {
        if ( empty( $statistics_map['questions'] ) || empty( $entries ) ) {
            return;
        }

        foreach ( $entries as $entry ) {
            $entry_form_id = (int) get_post_meta( $entry->ID, 'metform_entries__form_id', true );
            $form_data     = get_post_meta( $entry->ID, 'metform_entries__form_data', true );

            if ( ! isset( $statistics_map['by_form'][ $entry_form_id ] ) || ! is_array( $form_data ) ) {
                continue;
            }

            foreach ( $statistics_map['by_form'][ $entry_form_id ] as $question_id ) {
                if ( ! isset( $statistics_map['questions'][ $question_id ] ) ) {
                    continue;
                }

                $question = &$statistics_map['questions'][ $question_id ];
                $field_name = $question['field_name'];

                if ( ! array_key_exists( $field_name, $form_data ) ) {
                    unset( $question );
                    continue;
                }

                $raw_answer = $form_data[ $field_name ];

                if ( $question['type'] === 'text-field' ) {
                    $answer_text = self::normalize_text_answer( $raw_answer );
                    if ( $answer_text === '' ) {
                        unset( $question );
                        continue;
                    }

                    $question['total_answered']++;
                    $question['text_answer_counts'][ $answer_text ] = ( $question['text_answer_counts'][ $answer_text ] ?? 0 ) + 1;
                    unset( $question );
                    continue;
                }

                $answer_values = self::normalize_answer_values( $raw_answer );
                if ( empty( $answer_values ) ) {
                    unset( $question );
                    continue;
                }

                $question['total_answered']++;

                foreach ( $answer_values as $answer_value ) {
                    if ( ! isset( $question['option_counts'][ $answer_value ] ) ) {
                        $new_index = count( $question['options'] );
                        $new_key = self::get_option_key( $new_index );

                        $question['options'][] = [
                            'key'        => $new_key,
                            'text'       => $answer_value,
                            'value'      => $answer_value,
                            'is_correct' => false,
                        ];
                        $question['option_counts'][ $answer_value ] = 0;
                    }

                    $question['option_counts'][ $answer_value ]++;
                }

                unset( $question );
            }
        }
    }

    /**
     * Transform internal statistics map into API response format.
     *
     * @param array $questions
     * @param int   $common_answer_cap
     * @return array
     */
    private static function format_question_statistics( $questions, $common_answer_cap ) {
        $formatted = [];

        foreach ( $questions as $question ) {
            $item = [
                'id'             => (int) $question['id'],
                'form_id'        => (int) $question['form_id'],
                'type'           => $question['type'],
                'field_name'     => $question['field_name'],
                'question'       => html_entity_decode( $question['question'], ENT_QUOTES, 'UTF-8' ),
                'correct_answer' => $question['correct_answer'],
                'total_answered' => (int) $question['total_answered'],
            ];

            if ( $question['type'] === 'text-field' ) {
                arsort( $question['text_answer_counts'] );

                $common_answers = [];
                $index = 0;
                foreach ( $question['text_answer_counts'] as $answer_text => $count ) {
                    if ( $index >= $common_answer_cap ) {
                        break;
                    }

                    $common_answers[] = [
                        'key'        => self::get_option_key( $index ),
                        'text'       => html_entity_decode( $answer_text, ENT_QUOTES, 'UTF-8' ),
                        'count'      => (int) $count,
                        'percentage' => $question['total_answered'] > 0
                            ? round( ( $count / $question['total_answered'] ) * 100, 2 )
                            : 0,
                    ];

                    $index++;
                }

                $item['common_answer'] = $common_answers;
            } else {
                $options = [];
                foreach ( $question['options'] as $option ) {
                    $count = (int) ( $question['option_counts'][ $option['value'] ] ?? 0 );

                    $options[] = [
                        'key'        => $option['key'],
                        'text'       => html_entity_decode( $option['text'], ENT_QUOTES, 'UTF-8' ),
                        'value'      => html_entity_decode( $option['value'], ENT_QUOTES, 'UTF-8' ),
                        'count'      => $count,
                        'percentage' => $question['total_answered'] > 0
                            ? round( ( $count / $question['total_answered'] ) * 100, 2 )
                            : 0,
                        'is_correct' => (bool) $option['is_correct'],
                    ];
                }

                $item['options'] = $options;
            }

            $formatted[] = $item;
        }

        return $formatted;
    }

    /**
     * Map widget type to quiz question type.
     *
     * @param string $widget_type
     * @param array  $settings
     * @return string
     */
    private static function map_widget_to_question_type( $widget_type, $settings ) {
        switch ( $widget_type ) {
            case 'mf-radio':
                return 'radio';

            case 'mf-select':
                return 'dropdown';

            case 'mf-checkbox':
            case 'mf-multi-select':
                return 'checkbox';

            case 'mf-toggle-select':
            case 'mf-image-select':
                return ( $settings['mf_input_options_multiselect'] ?? 'radio' ) === 'checkbox'
                    ? 'checkbox'
                    : 'radio';

            case 'mf-text':
                return 'text-field';

            case 'mf-number':
                return 'number-field';

            default:
                return '';
        }
    }

    /**
     * Extract option list from widget settings.
     *
     * @param string $widget_type
     * @param array  $settings
     * @return array
     */
    private static function extract_question_options( $widget_type, $settings ) {
        $options = [];
        $input_list = $settings['mf_input_list'] ?? [];

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

        $index = 0;
        foreach ( $input_list as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }

            if ( $widget_type === 'mf-multi-select' ) {
                $option_value = isset( $item['value'] ) ? trim( (string) $item['value'] ) : '';
                $option_text  = isset( $item['label'] ) ? trim( (string) $item['label'] ) : $option_value;
            } else {
                $option_value = isset( $item['mf_input_option_value'] )
                    ? trim( (string) $item['mf_input_option_value'] )
                    : ( isset( $item['value'] ) ? trim( (string) $item['value'] ) : '' );
                $option_text  = isset( $item['mf_input_option_text'] )
                    ? trim( (string) $item['mf_input_option_text'] )
                    : ( isset( $item['label'] ) ? trim( (string) $item['label'] ) : $option_value );
            }

            if ( $option_value === '' && $option_text === '' ) {
                continue;
            }

            if ( $option_value === '' ) {
                $option_value = $option_text;
            }

            if ( $option_text === '' ) {
                $option_text = $option_value;
            }

            $options[] = [
                'key'        => self::get_option_key( $index ),
                'text'       => $option_text,
                'value'      => $option_value,
                'is_correct' => isset( $item['mf_quiz_question_answer'] ) && $item['mf_quiz_question_answer'] === 'yes',
            ];

            $index++;
        }

        return $options;
    }

    /**
     * Build correct answer metadata per question.
     *
     * @param string $question_type
     * @param array  $settings
     * @param array  $options
     * @return array
     */
    private static function extract_correct_answer( $question_type, $settings, $options ) {
        if ( $question_type === 'text-field' ) {
            $answer = isset( $settings['mf_quiz_question_answer'] )
                ? trim( (string) $settings['mf_quiz_question_answer'] )
                : '';

            return $answer !== '' ? [ $answer ] : [];
        }

        $correct_keys = [];
        foreach ( $options as $option ) {
            if ( ! empty( $option['is_correct'] ) ) {
                $correct_keys[] = $option['key'];
            }
        }

        return $correct_keys;
    }

    /**
     * Normalize answer values to a clean array of strings.
     *
     * @param mixed $raw_answer
     * @return array
     */
    private static function normalize_answer_values( $raw_answer ) {
        if ( is_array( $raw_answer ) ) {
            $values = [];
            foreach ( $raw_answer as $value ) {
                if ( is_scalar( $value ) ) {
                    $value = trim( (string) $value );
                    if ( $value !== '' ) {
                        $values[] = $value;
                    }
                }
            }

            return array_values( array_unique( $values ) );
        }

        if ( ! is_scalar( $raw_answer ) ) {
            return [];
        }

        $value = trim( (string) $raw_answer );
        if ( $value === '' ) {
            return [];
        }

        $first_char = substr( $value, 0, 1 );
        if ( ( $first_char === '[' && substr( $value, -1 ) === ']' ) || ( $first_char === '{' && substr( $value, -1 ) === '}' ) ) {
            $decoded = json_decode( $value, true );
            if ( is_array( $decoded ) ) {
                return self::normalize_answer_values( $decoded );
            }
        }

        if ( strpos( $value, ',' ) !== false ) {
            $parts = array_map( 'trim', explode( ',', $value ) );
            $parts = array_filter( $parts, static function ( $part ) {
                return $part !== '';
            } );

            return array_values( array_unique( $parts ) );
        }

        return [ $value ];
    }

    /**
     * Normalize a free-text answer.
     *
     * @param mixed $raw_answer
     * @return string
     */
    private static function normalize_text_answer( $raw_answer ) {
        if ( is_array( $raw_answer ) ) {
            $raw_answer = implode( ', ', self::normalize_answer_values( $raw_answer ) );
        }

        if ( ! is_scalar( $raw_answer ) ) {
            return '';
        }

        $answer = trim( (string) $raw_answer );
        if ( $answer === '' ) {
            return '';
        }

        return preg_replace( '/\s+/', ' ', $answer );
    }

    /**
     * Build a stable option key label (A, B, ... Z, O27, O28...).
     *
     * @param int $index
     * @return string
     */
    private static function get_option_key( $index ) {
        $index = (int) $index;

        if ( $index >= 0 && $index < 26 ) {
            return chr( 65 + $index );
        }

        return 'O' . ( $index + 1 );
    }

    /**
     * Build per-question difficulty ranking rows.
     *
     * @param array $statistics_map
     * @param array $entries
     * @return array
     */
    private static function build_difficulty_ranking( $statistics_map, $entries ) {
        $questions = $statistics_map['questions'];

        foreach ( $questions as &$question ) {
            $question['total_entries'] = 0;
            $question['attempted'] = 0;
            $question['correct_count'] = 0;
            $question['skipped_count'] = 0;
        }
        unset( $question );

        if ( ! empty( $entries ) ) {
            foreach ( $entries as $entry ) {
                $entry_form_id = (int) get_post_meta( $entry->ID, 'metform_entries__form_id', true );
                $form_data     = get_post_meta( $entry->ID, 'metform_entries__form_data', true );

                if ( ! isset( $statistics_map['by_form'][ $entry_form_id ] ) || ! is_array( $form_data ) ) {
                    continue;
                }

                foreach ( $statistics_map['by_form'][ $entry_form_id ] as $question_id ) {
                    if ( ! isset( $questions[ $question_id ] ) ) {
                        continue;
                    }

                    $q = &$questions[ $question_id ];
                    $field_name = $q['field_name'];

                    $q['total_entries']++;

                    if ( ! array_key_exists( $field_name, $form_data ) ) {
                        $q['skipped_count']++;
                        unset( $q );
                        continue;
                    }

                    $raw_answer = $form_data[ $field_name ];
                    if ( ! self::has_answer_value( $q['type'], $raw_answer ) ) {
                        $q['skipped_count']++;
                        unset( $q );
                        continue;
                    }

                    $q['attempted']++;

                    if ( self::is_correct_question_answer( $q, $raw_answer ) ) {
                        $q['correct_count']++;
                    }

                    unset( $q );
                }
            }
        }

        $ranking = [];
        foreach ( $questions as $question ) {
            $total_entries = (int) $question['total_entries'];
            $attempted = (int) $question['attempted'];
            $correct_percentage = $attempted > 0
                ? round( ( (int) $question['correct_count'] / $attempted ) * 100, 2 )
                : 0;
            $skip_rate = $total_entries > 0
                ? round( ( (int) $question['skipped_count'] / $total_entries ) * 100, 2 )
                : 0;

            $ranking[] = [
                'id'          => (int) $question['id'],
                'form_id'     => (int) $question['form_id'],
                'question'    => $question['question'],
                'question_name' => $question['question'],
                'correct'     => $correct_percentage,
                'difficulty'  => self::get_difficulty_label( $correct_percentage ),
                'skip_rate'   => $skip_rate,
            ];
        }

        usort( $ranking, static function( $a, $b ) {
            if ( $a['correct'] === $b['correct'] ) {
                return $a['id'] <=> $b['id'];
            }

            return $b['correct'] <=> $a['correct'];
        } );

        return $ranking;
    }

    /**
     * Determine whether an answer exists for question evaluation.
     *
     * @param string $question_type
     * @param mixed  $raw_answer
     * @return bool
     */
    private static function has_answer_value( $question_type, $raw_answer ) {
        if ( $question_type === 'text-field' ) {
            return self::normalize_text_answer( $raw_answer ) !== '';
        }

        return ! empty( self::normalize_answer_values( $raw_answer ) );
    }

    /**
     * Check whether one question answer is correct.
     *
     * @param array $question
     * @param mixed $raw_answer
     * @return bool
     */
    private static function is_correct_question_answer( $question, $raw_answer ) {
        if ( $question['type'] === 'text-field' ) {
            $submitted = strtolower( self::normalize_text_answer( $raw_answer ) );

            if ( $submitted === '' || empty( $question['correct_answer'] ) ) {
                return false;
            }

            $expected = strtolower( trim( (string) $question['correct_answer'][0] ) );
            return $expected !== '' && $submitted === $expected;
        }

        $submitted_values = self::normalize_answer_values( $raw_answer );
        if ( empty( $submitted_values ) ) {
            return false;
        }

        $correct_values = [];
        foreach ( $question['options'] as $option ) {
            if ( ! empty( $option['is_correct'] ) ) {
                $correct_values[] = (string) $option['value'];
            }
        }

        if ( empty( $correct_values ) ) {
            return false;
        }

        sort( $submitted_values );
        sort( $correct_values );

        return $submitted_values === $correct_values;
    }

    /**
     * Convert correct percentage to difficulty label.
     *
     * @param float $correct_percentage
     * @return string
     */
    private static function get_difficulty_label( $correct_percentage ) {
        $correct_percentage = (float) $correct_percentage;

        if ( $correct_percentage > 80 ) {
            return 'easy';
        }

        if ( $correct_percentage >= 50 ) {
            return 'medium';
        }

        if ( $correct_percentage >= 30 ) {
            return 'hard';
        }

        return 'very hard';
    }

    /**
     * Empty analytics skeleton.
     *
     * @return array
     */
    private static function get_empty_analytics() {
        return [
            'average_score_percentage' => 0,
            'highest_score'            => 0,
            'lowest_score'             => 0,
            'correct_percentage'       => 0,
            'wrong_percentage'         => 0,
            'correct_count'            => 0,
            'wrong_count'              => 0,
            'total_questions'          => 0,
            'total_entries'            => 0,
            'most_skipped_question'    => null,
        ];
    }

    /**
     * Return initialized score distribution buckets.
     *
     * @return array
     */
    private static function get_default_score_distribution() {
        return [
            '0-10'   => 0,
            '11-20'  => 0,
            '21-30'  => 0,
            '31-40'  => 0,
            '41-50'  => 0,
            '51-60'  => 0,
            '61-70'  => 0,
            '71-80'  => 0,
            '81-90'  => 0,
            '91-100' => 0,
        ];
    }

    /**
     * Build score distribution from per-entry score percentages.
     *
     * @param array $percentage_scores
     * @return array
     */
    private static function build_score_distribution( $percentage_scores ) {
        $distribution = self::get_default_score_distribution();

        foreach ( $percentage_scores as $score ) {
            if ( ! is_numeric( $score ) ) {
                continue;
            }

            $bucket = self::resolve_score_bucket_label( (float) $score );
            if ( $bucket === '' || ! isset( $distribution[ $bucket ] ) ) {
                continue;
            }

            $distribution[ $bucket ]++;
        }

        return $distribution;
    }

    /**
     * Resolve score bucket label for a percentage.
     *
     * @param float $score
     * @return string
     */
    private static function resolve_score_bucket_label( $score ) {
        $score = (float) $score;

        // Keep score percentages within chart range.
        if ( $score < 0 ) {
            $score = 0;
        } elseif ( $score > 100 ) {
            $score = 100;
        }

        if ( $score <= 10 ) {
            return '0-10';
        }

        if ( $score <= 20 ) {
            return '11-20';
        }

        if ( $score <= 30 ) {
            return '21-30';
        }

        if ( $score <= 40 ) {
            return '31-40';
        }

        if ( $score <= 50 ) {
            return '41-50';
        }

        if ( $score <= 60 ) {
            return '51-60';
        }

        if ( $score <= 70 ) {
            return '61-70';
        }

        if ( $score <= 80 ) {
            return '71-80';
        }

        if ( $score <= 90 ) {
            return '81-90';
        }

        return '91-100';
    }
}