extends VBoxContainer const Style := preload("res://scripts/ui/ui_style.gd") var main: Control var exam_view: VBoxContainer var mode_view: VBoxContainer var exam_grid: GridContainer var mode_grid: GridContainer var question_bank_button: Button var bank_status_label: Label var bank_error_label: Label var retry_button: Button var current_stage := "exam" var pending_question_bank_launch := false func _ready() -> void: set_anchors_preset(Control.PRESET_FULL_RECT) add_theme_constant_override("separation", 18) resized.connect(_refresh_grid_columns) if not QuestionBankService.questions_changed.is_connected(_on_questions_changed): QuestionBankService.questions_changed.connect(_on_questions_changed) if not QuestionBankService.questions_load_failed.is_connected(_on_questions_load_failed): QuestionBankService.questions_load_failed.connect(_on_questions_load_failed) _build_exam_view() _build_mode_view() _show_stage("exam") func on_show(payload: Variant = null) -> void: if str(payload) == "step3": _show_stage("step3") elif str(payload) == "exam": _show_stage("exam") _refresh_bank_state() func on_hide() -> void: pending_question_bank_launch = false func _build_exam_view() -> void: exam_view = VBoxContainer.new() exam_view.add_theme_constant_override("separation", 18) exam_view.size_flags_vertical = Control.SIZE_EXPAND_FILL add_child(exam_view) var eyebrow := Label.new() eyebrow.text = "CHOOSE YOUR EXAM" Style.set_label(eyebrow, 14, Style.CYAN) exam_view.add_child(eyebrow) var title := Label.new() title.text = "What are you preparing for?" Style.set_label(title, 34, Style.TEXT) exam_view.add_child(title) var subtitle := Label.new() subtitle.text = "Select a path to enter its study modes. More exams will be added as they are ready." subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(subtitle, 17, Style.MUTED) exam_view.add_child(subtitle) exam_grid = GridContainer.new() exam_grid.columns = 2 exam_grid.add_theme_constant_override("h_separation", 16) exam_grid.add_theme_constant_override("v_separation", 16) exam_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL exam_grid.size_flags_vertical = Control.SIZE_EXPAND_FILL exam_view.add_child(exam_grid) _add_exam_option("STEP 1", "Foundational sciences", false) _add_exam_option("STEP 2", "Clinical knowledge", false) _add_exam_option("STEP 3", "Advanced clinical management", true) _add_exam_option("CCS Cases", "Interactive case simulations", false) func _build_mode_view() -> void: mode_view = VBoxContainer.new() mode_view.add_theme_constant_override("separation", 18) mode_view.size_flags_vertical = Control.SIZE_EXPAND_FILL add_child(mode_view) var back_button := Button.new() back_button.text = "< All exams" back_button.alignment = HORIZONTAL_ALIGNMENT_LEFT back_button.custom_minimum_size = Vector2(150, 42) Style.style_chip(back_button, Style.CYAN) UISoundService.set_button_sound(back_button, "back") back_button.pressed.connect(func(): _show_stage("exam")) mode_view.add_child(back_button) var eyebrow := Label.new() eyebrow.text = "STEP 3" Style.set_label(eyebrow, 14, Style.LIME) mode_view.add_child(eyebrow) var title := Label.new() title.text = "Choose a study mode" Style.set_label(title, 34, Style.TEXT) mode_view.add_child(title) var subtitle := Label.new() subtitle.text = "Build clinical judgment through focused questions. Additional modes are in development." subtitle.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(subtitle, 17, Style.MUTED) mode_view.add_child(subtitle) mode_grid = GridContainer.new() mode_grid.columns = 2 mode_grid.add_theme_constant_override("h_separation", 16) mode_grid.add_theme_constant_override("v_separation", 16) mode_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL mode_view.add_child(mode_grid) question_bank_button = Button.new() question_bank_button.text = "Question Bank\nAdaptive core run | Available" question_bank_button.alignment = HORIZONTAL_ALIGNMENT_LEFT question_bank_button.custom_minimum_size = Vector2(360, 150) question_bank_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL question_bank_button.add_theme_font_size_override("font_size", 20) Style.style_primary_button(question_bank_button) question_bank_button.pressed.connect(_select_question_bank) mode_grid.add_child(question_bank_button) var dynamic_button := Button.new() dynamic_button.text = "Dynamic Questions\nAI-generated practice | Coming Soon" dynamic_button.alignment = HORIZONTAL_ALIGNMENT_LEFT dynamic_button.custom_minimum_size = Vector2(360, 150) dynamic_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL dynamic_button.add_theme_font_size_override("font_size", 20) dynamic_button.disabled = true mode_grid.add_child(dynamic_button) bank_status_label = Label.new() bank_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(bank_status_label, 16, Style.CYAN) mode_view.add_child(bank_status_label) bank_error_label = Label.new() bank_error_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Style.set_label(bank_error_label, 15, Style.RED) mode_view.add_child(bank_error_label) retry_button = Button.new() retry_button.text = "Retry Question Bank" retry_button.custom_minimum_size = Vector2(220, 46) retry_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN Style.style_chip(retry_button, Style.AMBER) retry_button.pressed.connect(_retry_question_bank) mode_view.add_child(retry_button) var spacer := Control.new() spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL mode_view.add_child(spacer) func _add_exam_option(option_name: String, description: String, available: bool) -> void: var button := Button.new() button.text = "%s\n%s | %s" % [option_name, description, "Available" if available else "Coming Soon"] button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.custom_minimum_size = Vector2(360, 150) button.size_flags_horizontal = Control.SIZE_EXPAND_FILL button.size_flags_vertical = Control.SIZE_EXPAND_FILL button.add_theme_font_size_override("font_size", 20) button.disabled = not available if available: Style.style_primary_button(button) button.pressed.connect(func(): _show_stage("step3")) exam_grid.add_child(button) func _show_stage(stage_name: String) -> void: current_stage = stage_name pending_question_bank_launch = false if exam_view != null: exam_view.visible = stage_name == "exam" if mode_view != null: mode_view.visible = stage_name == "step3" _refresh_grid_columns() _refresh_bank_state() func _select_question_bank() -> void: pending_question_bank_launch = true var excluded_ids: Array = ProfileService.get_profile().get("correct_question_ids", []).duplicate() QuestionBankService.load_next_random_question(excluded_ids) _refresh_bank_state() func _retry_question_bank() -> void: pending_question_bank_launch = true QuestionBankService.retry_now() _refresh_bank_state() func _on_questions_changed() -> void: _refresh_bank_state() if pending_question_bank_launch and visible and current_stage == "step3" and QuestionBankService.load_state == "ready": pending_question_bank_launch = false call_deferred("_start_question_bank") func _on_questions_load_failed(_message: String) -> void: pending_question_bank_launch = false _refresh_bank_state() func _refresh_bank_state() -> void: if question_bank_button == null: return var state := QuestionBankService.load_state question_bank_button.disabled = pending_question_bank_launch retry_button.visible = state == "failed" or state == "retrying" retry_button.disabled = state == "retrying" bank_error_label.text = QuestionBankService.last_error if state == "failed" or state == "retrying" else "" if pending_question_bank_launch or state == "loading": bank_status_label.text = "Preparing your question bank..." elif state == "retrying": bank_status_label.text = "Question bank unavailable. Retrying in %d seconds." % QuestionBankService.next_retry_seconds elif state == "ready": bank_status_label.text = "Question bank ready" elif state == "failed": bank_status_label.text = "The question bank could not be loaded." else: bank_status_label.text = "Question bank will load when selected." func _start_question_bank() -> void: if not visible or current_stage != "step3" or not QuestionBankService.has_questions(): return if BlockSessionService.start_core_run(): UISoundService.play_sound("question") main.show_screen("player") else: bank_error_label.text = BlockSessionService.core_status_message func _refresh_grid_columns() -> void: var column_count := 1 if size.x < 820.0 else 2 if exam_grid != null: exam_grid.columns = column_count if mode_grid != null: mode_grid.columns = column_count