CCCPaste

testservice

is_restart ? true : false;
        $progressId = $request->progress_id ?? null;

        $practiceProgress = null;
        $lastPracticeProgress = $test->lastUserProgress($progressId);

        if (!$isRestart && $lastPracticeProgress) {
            $alreadyTakenTime = $lastPracticeProgress->taken_time ?? 0;

            $lastPracticeProgress->setRelation('practice', $test);

            if ($this->examService->hasPracticeTimeExpired(
                $lastPracticeProgress,
                $alreadyTakenTime
            )) {
                throw new \Exception('Practice time has expired', 403);
            }

            if ($lastPracticeProgress->status !== CourseProgress::COMPLETED) {
                $practiceProgress = $lastPracticeProgress;
            } else {
                throw new \Exception('Practice Already Completed', 403);
            }
        } else {
            $practiceProgress = $this->examService->createPracticeProgress($test, $request->exam_time_type);
        }

        $modules = $test->modules;
        $module = $modules->first();
        $moduleProgress = $this->examService->getOrCreateModuleProgress($module, $practiceProgress);

        $totalQuestions = $modules->sum(fn ($m) => $m->settings['totalQuestions']);


        $data = [
            'test' => [
                'name' => $test->name,
                'description' => $test->description,
                'max_attempts' => $test->max_attempts
            ],
            'module' => [
                'progress_id' => $moduleProgress->id,
                'name' => $module->name,
                'directions' => $module->directions,
                'isCalculatorEnable' => $module->is_calculator == 1
            ],
            'progress_id' => $practiceProgress->id,
            'taken_time' => $practiceProgress->taken_time,
            'totalNumberOfQuestions' => $totalQuestions,
            'allowed_time' => (optional($test->markingAndTimeAllocation)->value['total_exam_time'] ?? 0),
        ];

        // dd($module->settings['readingComprehension']);
        if ($module->subject->slug == 'ela') {
            $readingCompSection = $module->settings['readingComprehension'];

            $moduleProgress->update(['get_module' => 'readingComprehension']);

            $passage = $readingCompSection['passages'][0];

            if ($passage['type'] == 'passage') {
                $quiz = QuestionBank::find($module->settings['selectedBankId'])
                    ->quizzes()
                    ->with(['quizzes' => function ($query) use ($passage) {
                        $query->with(['options'])->limit($passage['numQuestion']);
                    }])
                    ->where('quizzes.is_grouped_question', 1)
                    ->whereNull('quizzes.parent_id')
                    ->where('quizzes.type', $passage['format'])
                    ->where('quizzes.difficulty', 'medium')
                    ->first();

                if($quiz && $quiz->quizzes) {
                    $this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);

                    $quiz->quizzes->each(function ($q, $i) use ($quiz) {
                        $q->no = $i + 1;
                        $q->setRelation('parent', (new Quiz())->forceFill([
                            'description' => $quiz->description
                        ]));
                    });

                    $data['module']['question'] = QuizResource::collection($quiz->quizzes);
                } else {
                    $data['module']['question'] = [];
                }

                
            } else {
                $quiz = QuestionBank::find($module->settings['selectedBankId'])
                    ->quizzes()
                    ->with('options')
                    // ->where('quizzes.is_grouped_question', 1)
                    ->whereNull('quizzes.parent_id')
                    ->where('quizzes.type', $passage['format'])
                    ->where('quizzes.difficulty', 'medium')
                    ->first();

                if($quiz) {
                    $this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);

                    $quiz->no = 1;

                    $data['module']['question'] = QuizResource::make($quiz);
                } else {
                    $data['module']['question'] = [];
                }
            }
        } else {
            $questionFormat = $module->settings['questionFormats'][0]['key'];

            $quiz = QuestionBank::find($module->settings['selectedBankId'])
                ->quizzes()
                ->with(['options', 'parent.quizzes.options'])
                ->where('quizzes.is_grouped_question', 0)
                ->whereNull('quizzes.parent_id')
                ->where('quizzes.type', $questionFormat)
                ->where('quizzes.difficulty', 'easy')
                ->inRandomOrder()
                ->first();

            $this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);

            $data['module']['question'] = $quiz->parent
                ? QuizResource::collection($quiz->parent->quizzes)
                : QuizResource::make($quiz);
        }


        /* $data = [
            'test' => [
                'name' => $test->name,
                'description' => $test->description,
                'max_attempts' => $test->max_attempts
            ],
            'module' => [
                'progress_id' => $moduleProgress->id,
                'name' => $module->name,
                'directions' => $module->directions,
                'question' => $quiz->parent
                    ? QuizResource::collection($quiz->parent->quizzes)
                    : QuizResource::make($quiz),
            ],
            'progress_id' => $practiceProgress->id,
            'taken_time' => $practiceProgress->taken_time,
            'totalNumberOfQuestions' => $totalQuestions,
            'allowed_time' => (optional($test->markingAndTimeAllocation)->value['total_exam_time'] ?? 0),
        ]; */

        return $data;
    }

    /**
     * SUBMIT ANSWER
     */
    public function submitAnswer($practice_progress, $currentModuleProgress, $givenAnswer, $takenTime, $answer)
    {
        $timeType = $practice_progress->time_type;
        $isTimeEnd = $this->examService->checkHasExamTimeEnd($takenTime, $timeType, $practice_progress->adaptivetest);

        if ($isTimeEnd) {
            throw new \Exception('Module exam time limit exceeded', 403);
        }

        $quiz = $givenAnswer->quiz;
        $isCorrect = $this->matcher->checkAnswer($quiz->type, $quiz, $answer);

        $givenAnswer->update([
            'selected_option' => request('selected_option') ?? null,
            'answer'          => request('answer') ?? null,
            'is_watched'      => true,
            'is_correct'      => $isCorrect,
            'score'           => $isCorrect ? $quiz->credit : 0
        ]);

        $practice_progress->update(['taken_time' => $takenTime]);

        return $this->nextQuestion($practice_progress, $currentModuleProgress, $quiz, $isCorrect);
    }

    /**
     * NEXT QUESTION LOGIC
     */
    public function nextQuestion($practice_progress, $moduleProgress, $currentQuiz, $isCorrect)
    {
        // Handle grouped quiz
        if (
            $currentQuiz->parent
            && $moduleProgress->givenAnswers()
                ->whereIn('quiz_id', $currentQuiz->parent->quizzes->pluck('id'))
                ->where('is_watched', false)
                ->exists()
        ) {
            return [];
        }

        $answeredQuizIds = $moduleProgress->givenAnswers()->pluck('quiz_id')->toArray(); // TODO:: use n1 query for given answers
        $module = $moduleProgress->adaptiveModule;
        $moduleIds = $practice_progress
            ->adaptiveModuleProgresses
            ->pluck('adaptive_module_id')
            ->toArray();

        $isNewModule = false;
        // Module completion check
        if (count($answeredQuizIds) >= $module->settings['totalQuestions']) {
            $isNewModule = true;
            $answeredQuizIds = [];

            $module = $practice_progress->adaptivetest->modules()
                ->with('subject:id,slug')
                ->whereNotIn('id', $moduleIds)
                ->first();

            if (!$module) return [];

            $moduleIds[] = $module->id;
            $moduleProgress = $this->examService->getOrCreateModuleProgress($module, $practice_progress);
        }

        $data = [
            'progress_id' => $moduleProgress->id,
            'name' => $module->name,
            'directions' => $module->directions,
            'isCalculatorEnable' => $module->is_calculator == 1,
            'isNewModule' => $isNewModule
        ];

        if ($module->subject->slug == 'ela') {
            // Difficulty logic
            $levels = ['easy', 'medium', 'hard'];

            //get current section
            $getModuleSection = $moduleProgress->get_module;
            if($getModuleSection == 'readingComprehension') {
                $currentSection = $module->settings[$moduleProgress->get_module];
            } else {
                [$secName, $secPart] = explode('-', $getModuleSection);
                $currentSection = $module->settings[$secName][$secPart];
            }


            if ($currentQuiz->parent) {
                $qIds = $currentQuiz->parent->quizzes->pluck('id'); //siblings quiz ids
                $totalCorrectAnswer = $moduleProgress->givenAnswers()->whereIn('quiz_id', $qIds->toArray())->where('is_correct', true)->count();

                $correctAnsParcentage = ($totalCorrectAnswer / $qIds->count()) * 100;


                //checking current section total question
                //get given passages ids
                $passageIds = $moduleProgress->givenAnswers()->with('quiz:id,parent_id')->get()->pluck('quiz.parent_id')->unique();
                // get current answer passage
                $currentPassage = collect($currentSection['passages'])
                    ->where('type', 'passage')->where('format', $currentQuiz->parent->type)
                    ->first();
                // get given passages
                $usedType = Quiz::whereIn('id', $passageIds)->pluck('type');
                
                //get current difficulty
                $diffIndex = array_search($currentQuiz->parent->difficulty, $levels);

                //get next difficulty
                if ($correctAnsParcentage >= 50 && $isCorrect) {
                    $diffIndex = min($diffIndex + 1, 2);
                } else {
                    $diffIndex = max($diffIndex - 1, 0);
                }

                $nextDifficulty = $levels[$diffIndex];
                //checking next hard question available
                if ($nextDifficulty == 'hard') {
                    // checking hardness parcentage
                    $totalHardQuestionGiven = Quiz::whereIn('id', $passageIds->toArray())->where('difficulty', 'hard')->count();
                    // dd($counthard, $currentSection['totalQuestions'], $module->settings['hardnessPercentage']);
                    $hardParcentageGiven = intval(($totalHardQuestionGiven / $currentSection['totalQuestions']) * 100);
                    //is available to give hard type question
                    $isAvailableToGiveHard = $hardParcentageGiven < $module->settings['hardnessPercentage'];

                    if (!$isAvailableToGiveHard) {
                        $nextDifficulty = $levels[$diffIndex - 1];
                    }
                }
                //count current type passage
                $currentGivenPassageCount = $usedType->filter(fn ($item) => $item === $currentPassage['format'])->count();
                //check given passage greater than current passage
                // dd($currentGivenPassageCount);
                if ($currentGivenPassageCount < $currentPassage['numPassage']) {
                    $quiz = QuestionBank::find($module->settings['selectedBankId'])
                        ->quizzes()
                        ->with(['quizzes' => function ($query) use ($currentPassage) {
                            $query->with(['options'])->limit($currentPassage['numQuestion']);
                        }])
                        ->whereNotIn('quizzes.id', $passageIds->toArray())
                        ->where('quizzes.is_grouped_question', 1)
                        ->whereNull('quizzes.parent_id')
                        ->where('quizzes.type', $currentPassage['format'])
                        ->where('quizzes.difficulty', $nextDifficulty)
                        ->first();

                    if($quiz && $quiz->quizzes) {
                        $this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);

                        $quiz->quizzes->each(function ($q, $i) use ($quiz, $answeredQuizIds) {
                            $q->no = count($answeredQuizIds) + $i + 1;
                            $q->setRelation('parent', (new Quiz())->forceFill([
                                'description' => $quiz->description
                            ]));
                        });

                        $data['question'] = QuizResource::collection($quiz->quizzes);
                    } else {
                        logger("1, not found for" . $currentPassage['format'] . $nextDifficulty);
                        $data['question'] = [];
                    }

                    
                } else {
                    //next pasage finding for current given passage limit exceed
                    $allFormats = collect($currentSection['passages'])->pluck('format');
                    $nextPassageIndex = $allFormats->search($currentQuiz->parent->type);
                    if ($allFormats->count() > $nextPassageIndex + 1) {
                        $next = $currentSection['passages'][$nextPassageIndex + 1];

                        if ($next['type'] == 'passage') {
                            $quiz = QuestionBank::find($module->settings['selectedBankId'])
                                ->quizzes()
                                ->with(['quizzes' => function ($query) use ($next) {
                                    $query->with(['options'])->limit($next['numQuestion']);
                                }])
                                // ->whereNotIn('quizzes.id', $passageIds->toArray())
                                ->where('quizzes.is_grouped_question', 1)
                                ->whereNull('quizzes.parent_id')
                                ->where('quizzes.type', $next['format'])
                                ->where('quizzes.difficulty', $nextDifficulty)
                                ->first();

                            if($quiz && $quiz->quizzes) {
                                $this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);

                                $quiz->quizzes->each(function ($q, $i) use ($quiz, $answeredQuizIds) {
                                    $q->no = count($answeredQuizIds) + $i + 1;
                                    $q->setRelation('parent', (new Quiz())->forceFill([
                                        'description' => $quiz->description
                                    ]));
                                });

                                $data['question'] = QuizResource::collection($quiz->quizzes);
                            } else {
                                logger("2, not found for" . $next['format'] . $nextDifficulty);
                                $data['question'] = [];
                            }
                        } else {
                            $quiz = QuestionBank::find($module->settings['selectedBankId'])
                                ->quizzes()
                                ->with('options')
                                // ->where('quizzes.is_grouped_question', 1)
                                ->whereNotIn('quizzes.id', $answeredQuizIds)
                                ->whereNull('quizzes.parent_id')
                                ->where('quizzes.type', $next['format'])
                                ->where('quizzes.difficulty', $nextDifficulty)
                                ->first();

                            if($quiz) {
                                $this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
                                $quiz->no = count($answeredQuizIds) + 1;
                                $data['question'] = QuizResource::make($quiz);
                            } else {
                                logger("3, not found for" . $next['format'] . $nextDifficulty);
                                $data['question'] = [];
                            }
                        }
                    } else {
                        //change section
                        if($moduleProgress->get_module == 'readingComprehension') {
                            $next = 'revisionEditing-partA';
                            $moduleProgress->update(['get_module' => $next]);
                            //call function for next section question
                        } else if($moduleProgress->get_module == 'revisionEditing-partA') {
                            $next = 'revisionEditing-partB';
                            $moduleProgress->update(['get_module' => $next]);
                            //call function for next section question
                        } else {
                            // end of module
                            return [];// next module load and give question
                        }
                    }
                }
            } else {
                $currentFormat = collect($currentSection['passages'])
                    ->where('type', 'question')->where('format', $currentQuiz->type)
                    ->first();

                $diffIndex = array_search($currentQuiz->difficulty, $levels);

                if ($isCorrect) {
                    $diffIndex = min($diffIndex + 1, 2);
                } else {
                    $diffIndex = max($diffIndex - 1, 0);
                }

                $nextDifficulty = $levels[$diffIndex];

                $givenAnsIds = $moduleProgress->givenAnswers()->whereHas('quiz', fn ($q) => $q->whereNull('parent_id'))->pluck('quiz_id');

                if ($nextDifficulty == 'hard') {
                    // checking hardness parcentage
                    $totalHardQuestionGiven = Quiz::whereIn('id', $givenAnsIds->toArray())->where('difficulty', 'hard')->count();
                    // dd($counthard, $currentSection['totalQuestions'], $module->settings['hardnessPercentage']);
                    $hardParcentageGiven = intval(($totalHardQuestionGiven / $currentSection['totalQuestions']) * 100);
                    //is available to give hard type question
                    $isAvailableToGiveHard = $hardParcentageGiven < $module->settings['hardnessPercentage'];

                    if (!$isAvailableToGiveHard) {
                        $nextDifficulty = $levels[$diffIndex - 1];
                    }
                }

                $givenQuestionIds = $moduleProgress->givenAnswers()->where('type', $currentQuiz->type)->pluck('quiz_id');

                if ($givenQuestionIds->count() < $currentFormat['numQuestion']) {
                    $quiz = QuestionBank::find($module->settings['selectedBankId'])
                        ->quizzes()
                        ->with('options')
                        ->whereNotIn('quizzes.id', $givenQuestionIds->toArray())
                        ->whereNull('quizzes.parent_id')
                        ->where('quizzes.type', $currentFormat['format'])
                        ->where('quizzes.difficulty', $nextDifficulty)
                        ->first();

                    if($quiz) {
                        $this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);

                        $data['question'] = QuizResource::make($quiz);
                    } else {
                        logger("4, not found for" . $currentFormat['format'] . $nextDifficulty);
                        $data['question'] = [];
                    }
                } else {
                    $allFormats = collect($currentSection['passages'])->pluck('format');
                    $nextFormatIndex = $allFormats->search($currentQuiz->type);
                    if ($allFormats->count() > $nextFormatIndex + 1) {
                        $next = $currentSection['passages'][$nextFormatIndex + 1];

                        if ($next['type'] == 'passage') {
                            $quiz = QuestionBank::find($module->settings['selectedBankId'])
                                ->quizzes()
                                ->with(['quizzes' => function ($query) use ($next) {
                                    $query->with(['options'])->limit($next['numQuestion']);
                                }])
                                ->where('quizzes.is_grouped_question', 1)
                                ->whereNull('quizzes.parent_id')
                                ->where('quizzes.type', $next['format'])
                                ->where('quizzes.difficulty', $nextDifficulty)
                                ->first();

                            if($quiz && $quiz->quizzes) {
                                $this->examService->getOrCreateGivenAnswer($quiz->quizzes, $moduleProgress);

                                $quiz->quizzes->each(function ($q) use ($quiz) {
                                    $q->setRelation('parent', (new Quiz())->forceFill([
                                        'description' => $quiz->description
                                    ]));
                                });

                                $data['question'] = QuizResource::collection($quiz->quizzes);
                            } else {
                                logger("5, not found for" . $next['format'] . $nextDifficulty);
                                $data['question'] = [];
                            }
                        } else {
                            $quiz = QuestionBank::find($module->settings['selectedBankId'])
                                ->quizzes()
                                ->with('options')
                                // ->where('quizzes.is_grouped_question', 1)
                                ->whereNull('quizzes.parent_id')
                                ->where('quizzes.type', $next['format'])
                                ->where('quizzes.difficulty', $nextDifficulty)
                                ->first();

                            if($quiz) {
                                $this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);

                                $data['question'] = QuizResource::make($quiz);
                            } else {
                                logger("6, not found for" . $next['format'] . $nextDifficulty);
                                $data['question'] = [];
                            }
                        }
                    } else {
                        //next section
                    }
                }
            }

            // $nextDifficulty = $levels[$isCorrect ? min($diffIndex + 1, 2) : max($diffIndex - 1, 0)];
        } else {
            $bank = QuestionBank::with('quizzes:id,type,question_type,difficulty,is_grouped_question,parent_id')
                ->find($module->settings['selectedBankId']);

            $questionFormats = collect($module->settings['questionFormats']);
            $questionTypes   = collect($module->settings['questionTopics']);

            // Difficulty logic
            $levels = ['easy', 'medium', 'hard'];
            $diffIndex = array_search($currentQuiz->difficulty, $levels);
            $nextDifficulty = $levels[$isCorrect ? min($diffIndex + 1, 2) : max($diffIndex - 1, 0)];

            // Track reached limits
            $usedTypes = $questionFormats->filter(
                fn ($f) =>
                $moduleProgress->givenAnswers()
                    ->whereHas('quiz', fn ($q) => $q->where('type', $f['key']))
                    ->count() >= $f['value']
            )->pluck('key')->toArray();

            $usedQtypes = $questionTypes->filter(
                fn ($qt) =>
                $moduleProgress->givenAnswers()
                    ->whereHas('quiz', fn ($q) => $q->where('question_type', $qt['key']))
                    ->count() >= $qt['value']
            )->pluck('key')->toArray();

            // Find next quiz within limits
            $quiz = $bank->quizzes()
                ->whereNotIn('quizzes.id', $answeredQuizIds)
                ->whereNotIn('quizzes.type', $usedTypes)
                ->whereNotIn('quizzes.question_type', $usedQtypes)
                ->where('quizzes.is_grouped_question', false)
                ->whereNull('quizzes.parent_id')
                ->where('quizzes.difficulty', $nextDifficulty)
                ->inRandomOrder()
                ->first();

            // If not found, move to next module
            if (!$quiz) {
                $nextModule = $practice_progress->adaptivetest->modules()
                    ->whereNotIn('id', $moduleIds)->first();

                if (!$nextModule) return [];

                $isNewModule = true;

                $moduleProgress = $this->examService->getOrCreateModuleProgress($nextModule, $practice_progress);
                $bank = QuestionBank::with('quizzes')->find($nextModule->settings['selectedBankId']);

                $quiz = $bank->quizzes()
                    ->where('quizzes.is_grouped_question', false)
                    ->whereNull('parent_id')
                    ->where('difficulty', $nextDifficulty)
                    ->inRandomOrder()
                    ->first();

                if (!$quiz) return [];
            }

            $this->examService->getOrCreateGivenAnswer($quiz, $moduleProgress);
        }

        return $data;
    }
}