<template>
  <div class="vocab-lesson-view">
    <div v-if="loading">{{ $t('general.loading') }}</div>
    <div v-else-if="lesson">
      <div class="lesson-header">
        <button @click="back" class="btn-back">{{ $t('general.back') }}</button>
        <h2>{{ lesson.title }}</h2>
        <button
          type="button"
          class="btn-reset-lesson"
          :disabled="resettingLessonProgress"
          @click="confirmResetLessonProgress"
        >
          {{ resettingLessonProgress ? $t('general.loading') : $t('socialnetwork.vocab.courses.resetLessonProgress') }}
        </button>
      </div>
      
      <!-- Tabs für Lernen und Übungen -->
      <div class="lesson-tabs">
        <button 
          :class="{ active: activeTab === 'learn' }" 
          @click="activeTab = 'learn'"
          class="tab-button"
        >
          {{ $t('socialnetwork.vocab.courses.learn') }}
        </button>
        <button 
          :class="{ active: activeTab === 'exercises' }"
          :disabled="!canAccessExercises"
          @click="openExercisesTab"
          class="tab-button"
        >
          {{ $t('socialnetwork.vocab.courses.exercises') }}
          <span v-if="hasExercises" class="exercise-count">({{ effectiveExercises?.length || 0 }})</span>
        </button>
      </div>

      <!-- Lernen-Tab -->
      <div v-if="activeTab === 'learn'" class="learn-section">
        <div class="lesson-overview-card">
          <div>
            <h3>{{ $t('socialnetwork.vocab.courses.learnVocabulary') }}</h3>
            <p class="lesson-overview-text">
              {{ $t('socialnetwork.vocab.courses.lessonOverviewText') }}
            </p>
          </div>
          <div class="lesson-meta-grid">
            <div class="lesson-meta-item">
              <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonTypeLabel') }}</span>
              <strong>{{ getLessonTypeLabel(lesson.lessonType) }}</strong>
            </div>
            <div class="lesson-meta-item" v-if="lessonPedagogy.didacticMode">
              <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaFocus') }}</span>
              <strong>{{ getDidacticModeLabel(lessonPedagogy.didacticMode) }}</strong>
            </div>
            <div class="lesson-meta-item">
              <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.recommendedDuration') }}</span>
              <strong>{{ formatTargetMinutes(lesson.targetMinutes) }}</strong>
            </div>
            <div class="lesson-meta-item">
              <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.exerciseLoad') }}</span>
              <strong>{{ effectiveExercises?.length || 0 }} {{ $t('socialnetwork.vocab.courses.exercisesShort') }}</strong>
            </div>
          </div>
          <div
            v-if="lessonReviewBadgeLabel"
            class="lesson-review-status"
            :class="lessonReviewStatusClass"
          >
            <div class="lesson-review-status__top">
              <span class="lesson-review-status__badge">{{ lessonReviewBadgeLabel }}</span>
              <strong>{{ lessonReviewHeadline }}</strong>
            </div>
            <p>{{ lessonReviewHint }}</p>
          </div>
          <details
            v-if="lessonPedagogy.phaseLabel || lessonPedagogy.newUnitTarget || lessonPedagogy.reviewWeight != null"
            class="lesson-overview-more"
          >
            <summary class="lesson-overview-more__summary">
              {{ $t('socialnetwork.vocab.courses.lessonDetailsToggle') }}
            </summary>
            <div class="lesson-overview-more__grid">
              <div class="lesson-meta-item" v-if="lessonPedagogy.phaseLabel">
                <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaPhase') }}</span>
                <strong>{{ getPhaseLabel(lessonPedagogy.phaseLabel) }}</strong>
              </div>
              <div class="lesson-meta-item" v-if="lessonPedagogy.newUnitTarget">
                <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaNewUnits') }}</span>
                <strong>{{ lessonPedagogy.newUnitTarget }}</strong>
              </div>
              <div class="lesson-meta-item" v-if="lessonPedagogy.reviewWeight != null">
                <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonMetaReview') }}</span>
                <strong>{{ lessonPedagogy.reviewWeight }}%</strong>
              </div>
            </div>
          </details>
        </div>

        <div v-if="lessonPedagogy.isIntensiveReview" class="lesson-intensity-banner">
          <strong>{{ $t('socialnetwork.vocab.courses.intensiveReviewTitle') }}</strong>
          <p>{{ $t('socialnetwork.vocab.courses.intensiveReviewIntro') }}</p>
        </div>

        <div
          v-if="showDailyEnoughBanner"
          class="lesson-enough-banner"
        >
          <strong>{{ $t('socialnetwork.vocab.courses.dailyEnoughTitle') }}</strong>
          <p>{{ $t('socialnetwork.vocab.courses.dailyEnoughBody') }}</p>
          <div class="lesson-enough-banner__actions">
            <button type="button" class="btn-secondary" @click="dismissDailyEnoughBanner">
              {{ $t('socialnetwork.vocab.courses.dailyEnoughStop') }}
            </button>
            <button type="button" class="btn-primary" @click="continueDailyEnoughBanner">
              {{ $t('socialnetwork.vocab.courses.dailyEnoughContinue') }}
            </button>
          </div>
        </div>

        <section class="lesson-primary-flow surface-card">
          <div class="lesson-primary-flow__header">
            <span class="lesson-primary-flow__eyebrow">{{ $t('socialnetwork.vocab.courses.learningPathLabel') }}</span>
            <h4 class="lesson-primary-flow__title">{{ $t('socialnetwork.vocab.courses.learningPathTitle') }}</h4>
            <p class="lesson-primary-flow__intro">{{ $t('socialnetwork.vocab.courses.learningPathIntro') }}</p>
          </div>

          <!-- Zwei Durchgänge: dieselben Kernmuster schrittweise vor dem Trainer -->
          <div
            v-if="prepItems.length > 0 && !vocabTrainerActive"
            class="vocab-prep-pass didactic-card"
          >
            <h4>{{ $t('socialnetwork.vocab.courses.vocabPrepTitle') }}</h4>
            <template v-if="lessonPrepStage < 2 && currentPrepItem">
              <p class="vocab-prep-pass__step">
                {{ $t('socialnetwork.vocab.courses.vocabPrepProgress', { pass: lessonPrepStage + 1, current: lessonPrepIndex + 1, total: prepItems.length }) }}
              </p>
              <p>
                {{ lessonPrepStage === 0
                  ? $t('socialnetwork.vocab.courses.vocabPrepStep1')
                  : $t('socialnetwork.vocab.courses.vocabPrepStep2') }}
              </p>
              <div class="vocab-prep-card">
                <div class="vocab-prep-card__row">
                  <span class="vocab-prep-card__label">{{ prepTargetLabel }}</span>
                  <div class="vocab-prep-card__target">{{ currentPrepItem.target }}</div>
                </div>
                <div class="vocab-prep-card__row">
                  <span class="vocab-prep-card__label">{{ prepGlossLabel }}</span>
                  <div class="vocab-prep-card__gloss">{{ currentPrepItem.gloss || '—' }}</div>
                </div>
              </div>
              <button type="button" class="btn-prep-pass" @click="advancePrepPass">
                {{ isLastPrepItemInPass
                  ? (lessonPrepStage === 0
                    ? $t('socialnetwork.vocab.courses.vocabPrepConfirm1')
                    : $t('socialnetwork.vocab.courses.vocabPrepConfirm2'))
                  : $t('socialnetwork.vocab.courses.vocabPrepNextItem') }}
              </button>
            </template>
            <p v-else class="vocab-prep-pass__ready">{{ $t('socialnetwork.vocab.courses.vocabPrepReady') }}</p>
          </div>

          <!-- Gesamtübersicht: gleicher Lernsatz wie Vorbereitung und Trainer -->
          <details
            v-if="lesson && lessonVocab.length > 0 && !vocabTrainerActive"
            class="vocab-list vocab-list--overview"
            :open="lessonPrepStage >= 2"
          >
            <summary class="vocab-list__summary">
              {{ $t('socialnetwork.vocab.courses.vocabOverviewToggle') }}
            </summary>
            <p class="vocab-info-text">{{ $t('socialnetwork.vocab.courses.vocabInfoText') }}</p>
            <div class="vocab-items">
              <div v-for="(vocab, index) in paginatedLessonVocab" :key="`v-${vocabOverviewPagerMeta.page}-${index}`" class="vocab-item">
                <strong>{{ vocab.learning || '—' }}</strong>
                <span class="separator">→</span>
                <span>{{ vocab.reference }}</span>
              </div>
            </div>
            <div v-if="vocabOverviewTotalPages > 1" class="vocab-items-pager">
              <button
                type="button"
                class="vocab-items-pager__btn"
                :disabled="vocabOverviewPagerMeta.page <= 1"
                @click="vocabOverviewGoPrev"
              >
                {{ $t('socialnetwork.vocab.courses.vocabOverviewPrev') }}
              </button>
              <span class="vocab-items-pager__meta">{{ $t('socialnetwork.vocab.courses.vocabOverviewPager', vocabOverviewPagerMeta) }}</span>
              <button
                type="button"
                class="vocab-items-pager__btn"
                :disabled="vocabOverviewPagerMeta.page >= vocabOverviewPagerMeta.pages"
                @click="vocabOverviewGoNext"
              >
                {{ $t('socialnetwork.vocab.courses.vocabOverviewNext') }}
              </button>
            </div>
          </details>

          <div v-if="visibleGrammarExplanations.length > 0" class="lesson-grammar-impulse didactic-card">
            <div class="lesson-grammar-impulse__header">
              <span class="lesson-primary-flow__eyebrow">{{ $t('socialnetwork.vocab.courses.grammarImpulse') }}</span>
              <h4>{{ $t('socialnetwork.vocab.courses.learningGrammarTitle') }}</h4>
            </div>
            <p class="lesson-grammar-impulse__intro">{{ $t('socialnetwork.vocab.courses.learningGrammarIntro') }}</p>
            <div class="lesson-grammar-impulse__list">
              <article
                v-for="(explanation, index) in visibleGrammarExplanations.slice(0, 2)"
                :key="'primary-grammar-' + index"
                class="lesson-grammar-impulse__item"
              >
                <strong>{{ explanation.title || $t('socialnetwork.vocab.courses.grammarImpulse') }}</strong>
                <p>{{ explanation.text }}</p>
                <p v-if="explanation.example" class="grammar-example">{{ explanation.example }}</p>
              </article>
            </div>
          </div>

          <!-- Vokabeltrainer -->
          <div v-if="trainableLessonVocab.length > 0" class="vocab-trainer-section">
            <h4>{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}</h4>
            <div v-if="hasPreviousVocab" class="review-priority-note">
              <strong>{{ $t('socialnetwork.vocab.courses.reviewPriorityTitle') }}</strong>
              <p>{{ $t('socialnetwork.vocab.courses.reviewPriorityIntro') }}</p>
            </div>
            <div v-if="hasExercises && !canAccessExercises" class="exercise-lock-note">
              <strong>{{ $t('socialnetwork.vocab.courses.exerciseLockTitle') }}</strong>
              <p>{{ exerciseUnlockHint }}</p>
            </div>
            <div v-if="!vocabTrainerActive" class="vocab-trainer-start">
              <template v-if="canStartVocabTrainerPrep">
                <p>{{ hasPreviousVocab ? $t('socialnetwork.vocab.courses.trainerStartWithReview') : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}</p>
                <button @click="startVocabTrainer" class="btn-start-trainer">
                  {{ hasPreviousVocab ? $t('socialnetwork.vocab.courses.startLesson') : $t('socialnetwork.vocab.courses.startVocabTrainer') }}
                </button>
              </template>
              <p v-else class="vocab-trainer-locked-hint">{{ $t('socialnetwork.vocab.courses.vocabTrainerLockedHint') }}</p>
            </div>
            <div v-else class="vocab-trainer-active">
              <div class="vocab-trainer-stats">
                <div class="stats-row">
                  <span>{{ $t('socialnetwork.vocab.courses.correct') }}: {{ vocabTrainerCorrect }}</span>
                  <span>{{ $t('socialnetwork.vocab.courses.wrong') }}: {{ vocabTrainerWrong }}</span>
                  <span>{{ $t('socialnetwork.vocab.courses.totalAttempts') }}: {{ vocabTrainerTotalAttempts }}</span>
                  <span v-if="vocabTrainerTotalAttempts > 0" class="success-rate">
                    {{ $t('socialnetwork.vocab.courses.successRate') }}: {{ Math.round((vocabTrainerCorrect / vocabTrainerTotalAttempts) * 100) }}%
                  </span>
                </div>
                <div class="stats-row">
                  <span class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'current' }">
                    {{ $t('socialnetwork.vocab.courses.currentLesson') }}
                  </span>
                  <span v-if="previousVocab && previousVocab.length > 0" class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'mixed' }">
                    {{ $t('socialnetwork.vocab.courses.mixedReview') }}
                  </span>
                  <span
                    class="mode-badge mode-clickable"
                    :class="{ 'mode-active': vocabTrainerMode === 'multiple_choice', 'mode-completed': vocabTrainerMode === 'typing' }"
                    @click="switchBackToMultipleChoice"
                  >
                    {{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }}
                  </span>
                  <span
                    class="mode-badge mode-clickable"
                    :class="{ 'mode-active': vocabTrainerMode === 'typing' }"
                    @click="switchToTyping"
                  >
                    {{ $t('socialnetwork.vocab.courses.modeTyping') }}
                  </span>
                  <button @click="stopVocabTrainer" class="btn-stop-trainer">{{ $t('socialnetwork.vocab.courses.stopTrainer') }}</button>
                </div>
                <div v-if="hasPreviousVocab" class="stats-row trainer-progress-row">
                  <span>{{ $t('socialnetwork.vocab.courses.trainerProgressNewContent', { current: vocabTrainerCurrentAttempts, target: trainerNewFocusTarget }) }}</span>
                  <span>{{ $t('socialnetwork.vocab.courses.trainerProgressReview', { count: vocabTrainerReviewAttempts }) }}</span>
                  <span>{{ $t('socialnetwork.vocab.courses.trainerProgressMixShare', { percent: Math.round(currentReviewShare * 100) }) }}</span>
                </div>
              </div>
              <div v-if="currentVocabQuestion" class="vocab-question">
                <div class="vocab-prompt">
                  <div class="vocab-direction">{{ vocabTrainerDirection === 'L2R' ? $t('socialnetwork.vocab.courses.translateTo') : $t('socialnetwork.vocab.courses.translateFrom') }}</div>
                  <div class="vocab-word">{{ currentVocabQuestion.prompt }}</div>
                </div>
                <div v-if="vocabTrainerAnswered" class="vocab-feedback" :class="{ correct: vocabTrainerLastCorrect, wrong: !vocabTrainerLastCorrect }">
                  <div v-if="vocabTrainerLastCorrect">{{ $t('socialnetwork.vocab.courses.correct') }}!</div>
                  <div v-else>
                    {{ $t('socialnetwork.vocab.courses.wrong') }}. {{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ currentVocabQuestion.answer }}
                  </div>
                </div>
                <!-- Multiple Choice Modus -->
                <div v-if="vocabTrainerMode === 'multiple_choice' && !vocabTrainerAnswered" class="vocab-answer-area multiple-choice">
                  <div class="choice-buttons">
                    <button
                      v-for="(option, index) in vocabTrainerChoiceOptions"
                      :key="index"
                      @click="selectVocabChoice(option)"
                      class="choice-button"
                      :class="{ selected: vocabTrainerSelectedChoice === option }"
                    >
                      {{ option }}
                    </button>
                  </div>
                </div>
                <!-- Texteingabe Modus -->
                <div v-if="vocabTrainerMode === 'typing' && (!vocabTrainerAnswered || !vocabTrainerLastCorrect)" class="vocab-answer-area typing">
                  <div class="mode-switch-notice">
                    <button @click="switchBackToMultipleChoice" class="btn-switch-mode">
                      {{ $t('socialnetwork.vocab.courses.switchBackToMultipleChoice') }}
                    </button>
                  </div>
                  <input 
                    v-model="vocabTrainerAnswer" 
                    @keydown.enter.prevent="checkVocabAnswer"
                    :placeholder="$t('socialnetwork.vocab.courses.enterAnswer')"
                    class="vocab-input"
                    ref="vocabInput"
                  />
                  <button @click="checkVocabAnswer" :disabled="!vocabTrainerAnswer.trim()" class="btn-check">
                    {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
                  </button>
                </div>
                <div v-if="vocabTrainerLastWrongReview" class="vocab-wrong-review">
                  <strong>{{ $t('socialnetwork.vocab.courses.trainerWrongReviewTitle') }}</strong>
                  <dl>
                    <div>
                      <dt>{{ $t('socialnetwork.vocab.courses.trainerAskedVocab') }}</dt>
                      <dd>{{ vocabTrainerLastWrongReview.prompt }}</dd>
                    </div>
                    <div>
                      <dt>{{ $t('socialnetwork.vocab.courses.trainerYourAnswer') }}</dt>
                      <dd>{{ vocabTrainerLastWrongReview.userAnswer }}</dd>
                    </div>
                    <div>
                      <dt>{{ $t('socialnetwork.vocab.courses.correctAnswer') }}</dt>
                      <dd>{{ vocabTrainerLastWrongReview.answer }}</dd>
                    </div>
                  </dl>
                </div>
                <!-- "Weiter"-Button nur bei falscher Antwort (bei richtiger Antwort wird automatisch weiter gemacht) -->
                <div v-if="vocabTrainerMode === 'multiple_choice' && vocabTrainerAnswered && !vocabTrainerLastCorrect" class="vocab-next">
                  <button @click="continueAfterVocabAnswer">{{ $t('socialnetwork.vocab.courses.next') }}</button>
                </div>
              </div>
            </div>
          </div>

          <!-- Hinweis wenn keine Vokabeln vorhanden -->
          <div v-else-if="lesson && lessonVocab.length === 0" class="no-vocab-info">
            <p>{{ $t('socialnetwork.vocab.courses.noVocabInfo') }}</p>
          </div>

          <!-- Button um zu Übungen zu wechseln -->
          <div v-if="hasExercises && canAccessExercises" class="continue-to-exercises">
            <button @click="openExercisesTab" class="btn-continue">
              {{ $t('socialnetwork.vocab.courses.startExercises') }}
            </button>
          </div>
        </section>

        <details class="lesson-deepen-section surface-card">
          <summary class="lesson-deepen-section__summary">
            {{ $t('socialnetwork.vocab.courses.deepenSectionTitle') }}
          </summary>
          <div class="learn-grid">
            <div
              v-if="(lesson && lesson.description) || lessonDidactics.learningGoals.length > 0"
              class="didactic-card lesson-intro-combined"
            >
              <div v-if="lesson && lesson.description" class="lesson-intro-block">
                <h4>{{ $t('socialnetwork.vocab.courses.lessonDescription') }}</h4>
                <p>{{ lesson.description }}</p>
              </div>
              <div v-if="lessonDidactics.learningGoals.length > 0" class="lesson-intro-block">
                <h4>{{ $t('socialnetwork.vocab.courses.learningGoals') }}</h4>
                <ul class="didactic-list">
                  <li v-for="(goal, index) in lessonDidactics.learningGoals" :key="'goal-' + index">{{ goal }}</li>
                </ul>
              </div>
            </div>

            <div v-if="normalizedCorePatterns.length > 0" class="didactic-card">
              <h4>{{ $t('socialnetwork.vocab.courses.corePatterns') }}</h4>
              <p v-if="corePatternsHaveGloss" class="core-patterns-hint">
                {{ $t('socialnetwork.vocab.courses.corePatternsHint') }}
              </p>
              <div class="pattern-list">
                <div v-for="(pattern, index) in normalizedCorePatterns" :key="'pattern-' + index" class="pattern-item">
                  <div class="pattern-target">{{ pattern.target }}</div>
                  <div v-if="pattern.gloss" class="pattern-gloss">{{ pattern.gloss }}</div>
                </div>
              </div>
            </div>

            <div v-if="lessonDidactics.grammarFocus.length > 0" class="grammar-explanations didactic-card">
              <h4>{{ $t('socialnetwork.vocab.courses.grammarExplanations') }}</h4>
              <div v-for="(explanation, index) in lessonDidactics.grammarFocus" :key="'grammar-' + index" class="grammar-explanation-item">
                <strong>{{ explanation.title || $t('socialnetwork.vocab.courses.grammarImpulse') }}</strong>
                <p>{{ explanation.text }}</p>
                <p v-if="explanation.example" class="grammar-example">{{ explanation.example }}</p>
              </div>
            </div>

            <div v-if="lessonDidactics.speakingPrompts.length > 0" class="didactic-card">
              <h4>{{ $t('socialnetwork.vocab.courses.speakingTasks') }}</h4>
              <div v-for="(prompt, index) in lessonDidactics.speakingPrompts" :key="'speaking-' + index" class="speaking-prompt-item">
                <strong>{{ prompt.title || $t('socialnetwork.vocab.courses.speakingPrompt') }}</strong>
                <p>{{ prompt.prompt }}</p>
                <p v-if="prompt.cue" class="speaking-cue">{{ prompt.cue }}</p>
              </div>
            </div>

            <div v-if="lessonDidactics.practicalTasks.length > 0" class="didactic-card">
              <h4>{{ $t('socialnetwork.vocab.courses.practicalTasks') }}</h4>
              <div v-for="(task, index) in lessonDidactics.practicalTasks" :key="'task-' + index" class="practical-task-item">
                <strong>{{ task.title }}</strong>
                <p>{{ task.text }}</p>
              </div>
            </div>

            <div v-if="lesson && lesson.culturalNotes" class="cultural-notes didactic-card">
              <h4>{{ $t('socialnetwork.vocab.courses.culturalNotes') }}</h4>
              <p>{{ lesson.culturalNotes }}</p>
            </div>
          </div>
        </details>

        <details class="lesson-assistant-section surface-card">
          <summary class="lesson-deepen-section__summary">
            {{ $t('socialnetwork.vocab.courses.assistantSectionTitle') }}
          </summary>
          <div ref="assistantCard" class="didactic-card language-assistant-card" :class="{ 'language-assistant-card--focused': isAssistantFocused }">
            <div class="language-assistant-card__header">
              <div>
                <h4>{{ $t('socialnetwork.vocab.courses.languageAssistantTitle') }}</h4>
                <p class="language-assistant-card__intro">{{ $t('socialnetwork.vocab.courses.languageAssistantIntro') }}</p>
              </div>
              <button @click="openLanguageAssistantSettings" class="button-secondary language-assistant-card__settings">
                {{ $t('socialnetwork.vocab.courses.languageAssistantSettings') }}
              </button>
            </div>

            <div v-if="assistantLoading" class="language-assistant-card__state">
              {{ $t('general.loading') }}
            </div>

            <div v-else-if="!assistantAvailable" class="language-assistant-card__state">
              <p>{{ $t('socialnetwork.vocab.courses.languageAssistantSetupHint') }}</p>
            </div>

            <div v-else class="language-assistant-panel">
              <div class="language-assistant-panel__modes">
                <button
                  v-for="mode in assistantModes"
                  :key="mode.value"
                  type="button"
                  class="assistant-mode-button"
                  :class="{ active: assistantMode === mode.value }"
                  @click="assistantMode = mode.value"
                >
                  {{ mode.label }}
                </button>
              </div>

              <div class="language-assistant-panel__presets">
                <button type="button" class="assistant-preset-button" @click="sendPresetPrompt('explain')">
                  {{ $t('socialnetwork.vocab.courses.languageAssistantPromptExplain') }}
                </button>
                <button type="button" class="assistant-preset-button" @click="sendPresetPrompt('practice')">
                  {{ $t('socialnetwork.vocab.courses.languageAssistantPromptPractice') }}
                </button>
                <button type="button" class="assistant-preset-button" @click="sendPresetPrompt('correct')">
                  {{ $t('socialnetwork.vocab.courses.languageAssistantPromptCorrect') }}
                </button>
              </div>

              <div v-if="assistantMessages.length > 0" class="language-assistant-chat">
                <article
                  v-for="(message, index) in assistantMessages"
                  :key="`${message.role}-${index}`"
                  class="assistant-message"
                  :class="`assistant-message--${message.role}`"
                >
                  <strong>{{ message.role === 'assistant' ? $t('socialnetwork.vocab.courses.languageAssistantSpeakerAi') : $t('socialnetwork.vocab.courses.languageAssistantSpeakerYou') }}</strong>
                  <p>{{ message.content }}</p>
                </article>
              </div>

              <div v-if="assistantSubmitting" class="language-assistant-waiting" role="status" aria-live="polite">
                <span class="language-assistant-waiting__spinner" aria-hidden="true"></span>
                <div>
                  <strong>{{ $t('socialnetwork.vocab.courses.languageAssistantWaitingTitle') }}</strong>
                  <p>{{ $t('socialnetwork.vocab.courses.languageAssistantWaitingBody') }}</p>
                  <small>{{ $t('socialnetwork.vocab.courses.languageAssistantWaitingElapsed', { seconds: assistantWaitElapsedSeconds }) }}</small>
                </div>
              </div>

              <label class="language-assistant-panel__input">
                <span>{{ $t('socialnetwork.vocab.courses.languageAssistantInputLabel') }}</span>
                <textarea
                  v-model="assistantInput"
                  :placeholder="$t('socialnetwork.vocab.courses.languageAssistantInputPlaceholder')"
                  rows="4"
                />
              </label>

              <p v-if="assistantError" class="form-error">{{ assistantError }}</p>

              <div class="language-assistant-panel__actions">
                <button
                  type="button"
                  @click="sendAssistantMessage()"
                  :disabled="assistantSubmitting || !assistantInput.trim()"
                >
                  {{ assistantSubmitting ? $t('socialnetwork.vocab.courses.languageAssistantSending') : $t('socialnetwork.vocab.courses.languageAssistantSend') }}
                </button>
              </div>
            </div>
          </div>
        </details>
      </div>

      <!-- Übungen-Tab (Kapitel-Prüfung) -->
      <div v-if="activeTab === 'exercises'" class="grammar-exercises">
        <div v-if="lesson && effectiveExercises && effectiveExercises.length > 0">
          <div class="exercise-flow-header surface-card">
            <div>
              <span class="exercise-flow-header__eyebrow">{{ $t('socialnetwork.vocab.courses.exercises') }}</span>
              <h3>{{ $t('socialnetwork.vocab.courses.grammarExercises') }}</h3>
              <p class="exercise-flow-header__intro">{{ $t('socialnetwork.vocab.courses.exerciseFlowIntro') }}</p>
              <p class="exercise-flow-header__hint">{{ exerciseStatusHint }}</p>
            </div>
            <div class="exercise-flow-header__stats">
              <div class="exercise-flow-stat">
                <span>{{ $t('socialnetwork.vocab.courses.exerciseProgressLabel') }}</span>
                <strong>{{ exerciseCorrectCount }}/{{ effectiveExercises.length }}</strong>
              </div>
              <div class="exercise-flow-stat">
                <span>{{ $t('socialnetwork.vocab.courses.successRate') }}</span>
                <strong>{{ exerciseProgressPercent }}%</strong>
              </div>
              <div class="exercise-flow-stat">
                <span>{{ $t('socialnetwork.vocab.courses.exerciseTargetLabel') }}</span>
                <strong>{{ exerciseTargetScore }}%</strong>
              </div>
            </div>
          </div>
          <div v-if="visibleGrammarExplanations.length > 0" class="exercise-grammar-card surface-card">
            <div class="exercise-grammar-card__header">
              <span class="exercise-flow-header__eyebrow">{{ $t('socialnetwork.vocab.courses.grammarExplanations') }}</span>
              <strong>{{ $t('socialnetwork.vocab.courses.exerciseGrammarLead') }}</strong>
            </div>
            <div class="exercise-grammar-card__list">
              <article
                v-for="(explanation, index) in visibleGrammarExplanations.slice(0, 2)"
                :key="'exercise-grammar-' + index"
                class="exercise-grammar-card__item"
              >
                <strong>{{ explanation.title || $t('socialnetwork.vocab.courses.grammarImpulse') }}</strong>
                <p>{{ explanation.text }}</p>
                <p v-if="explanation.example" class="grammar-example">{{ explanation.example }}</p>
              </article>
            </div>
          </div>
          <div
            v-if="sequentialPanelActive && scrambledChapterExamExercises.length > 0"
            class="exercise-sequential-nav surface-card"
          >
            <p class="exercise-sequential-nav__progress">
              {{ $t('socialnetwork.vocab.courses.exerciseSequentialProgress', {
                current: exerciseSequentialIndex + 1,
                total: scrambledChapterExamExercises.length
              }) }}
            </p>
            <div class="exercise-sequential-nav__buttons">
              <button
                type="button"
                class="btn-seq"
                :disabled="exerciseSequentialIndex <= 0"
                @click="stepExercisePanel(-1)"
              >
                {{ $t('socialnetwork.vocab.courses.exerciseSequentialBack') }}
              </button>
              <button
                type="button"
                class="btn-seq btn-seq--primary"
                :disabled="!canStepExercisePanelForward"
                @click="stepExercisePanel(1)"
              >
                {{ $t('socialnetwork.vocab.courses.exerciseSequentialNext') }}
              </button>
            </div>
          </div>
          <div class="exercise-flow-list">
          <div
            v-for="(exercise, index) in exercisesPanelExercises"
            :key="exercise.id"
            class="exercise-item surface-card"
            :class="{
              'exercise-item--answered': exerciseResults[exercise.id],
              'exercise-item--correct': exerciseResults[exercise.id]?.correct,
              'exercise-item--wrong': exerciseResults[exercise.id] && !exerciseResults[exercise.id]?.correct
            }"
          >
          <div class="exercise-item__header">
            <div>
              <span class="exercise-item__index">{{ $t('socialnetwork.vocab.courses.exerciseCardLabel', { number: exercisePanelDisplayNumber(index) }) }}</span>
              <h4>{{ exercise.title }}</h4>
            </div>
            <span
              class="exercise-item__status"
              :class="{
                'exercise-item__status--open': !exerciseResults[exercise.id],
                'exercise-item__status--correct': exerciseResults[exercise.id]?.correct,
                'exercise-item__status--wrong': exerciseResults[exercise.id] && !exerciseResults[exercise.id]?.correct
              }"
            >
              {{ !exerciseResults[exercise.id]
                ? $t('socialnetwork.vocab.courses.exerciseStatusOpen')
                : (exerciseResults[exercise.id]?.correct
                  ? $t('socialnetwork.vocab.courses.exerciseStatusCorrect')
                  : $t('socialnetwork.vocab.courses.exerciseStatusRetry')) }}
            </span>
          </div>
          <p v-if="exercise.instruction" class="exercise-instruction">{{ exercise.instruction }}</p>
          
          <!-- Multiple Choice Übung -->
          <div v-if="getExerciseType(exercise) === 'multiple_choice'" class="multiple-choice-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <div class="options">
              <label v-for="(option, index) in getOptions(exercise)" :key="index" class="option-label">
                <input 
                  type="radio" 
                  :name="'exercise-' + exercise.id" 
                  :value="index"
                  v-model="exerciseAnswers[exercise.id]"
                />
                <span>{{ option }}</span>
              </label>
            </div>
            <button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id] && exerciseAnswers[exercise.id] !== 0">
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
                {{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
          </div>

          <!-- Gap Fill Übung -->
          <div v-else-if="getExerciseType(exercise) === 'gap_fill'" class="gap-fill-exercise">
            <p class="exercise-text" v-html="formatGapFill(exercise)"></p>
            <div class="gap-inputs">
              <input 
                v-for="(gap, index) in getGapCount(exercise)" 
                :key="index"
                v-model="exerciseAnswers[exercise.id][index]"
                :placeholder="$t('socialnetwork.vocab.courses.enterAnswer')"
                class="gap-input"
                :ref="getGapInputRef(exercise.id, index)"
                @keydown.enter.prevent="onGapInputEnter(exercise, index)"
              />
            </div>
            <button @click="checkAnswer(exercise.id)" :disabled="!hasAllGapsFilled(exercise)">
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
                {{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
          </div>

          <!-- Transformation Übung -->
          <div v-else-if="getExerciseType(exercise) === 'transformation'" class="transformation-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <input 
              v-model="exerciseAnswers[exercise.id]" 
              :placeholder="$t('socialnetwork.vocab.courses.enterAnswer')"
              class="transformation-input"
              :ref="'exercise-input-' + exercise.id"
              @keydown.enter.prevent="onSingleInputEnter(exercise)"
            />
            <button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id]">
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
                {{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
          </div>

          <div v-else-if="getExerciseType(exercise) === 'sentence_building'" class="sentence-building-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <div v-if="getQuestionData(exercise)?.tokens?.length" class="token-list">
              <span v-for="(token, index) in getQuestionData(exercise).tokens" :key="index" class="token-chip">{{ token }}</span>
            </div>
            <input
              v-model="exerciseAnswers[exercise.id]"
              :placeholder="$t('socialnetwork.vocab.courses.buildSentencePlaceholder')"
              class="transformation-input"
              :ref="'exercise-input-' + exercise.id"
              @keydown.enter.prevent="onSingleInputEnter(exercise)"
            />
            <button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id]">
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.modelSentence') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
          </div>

          <div v-else-if="getExerciseType(exercise) === 'dialog_completion'" class="dialog-completion-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <div v-if="getQuestionData(exercise)?.dialog" class="dialog-snippet">
              <p v-for="(line, index) in getQuestionData(exercise).dialog" :key="index">{{ line }}</p>
            </div>
            <input
              v-model="exerciseAnswers[exercise.id]"
              :placeholder="$t('socialnetwork.vocab.courses.completeDialogPlaceholder')"
              class="transformation-input"
              :ref="'exercise-input-' + exercise.id"
              @keydown.enter.prevent="onSingleInputEnter(exercise)"
            />
            <button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id]">
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.modelDialogLine') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
          </div>

          <div v-else-if="getExerciseType(exercise) === 'situational_response'" class="situational-response-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <textarea
              v-model="exerciseAnswers[exercise.id]"
              :placeholder="$t('socialnetwork.vocab.courses.situationalResponsePlaceholder')"
              class="response-textarea"
            />
            <button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id]">
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.modelResponse') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
                {{ $t('socialnetwork.vocab.courses.keywords') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
          </div>

          <div v-else-if="getExerciseType(exercise) === 'pattern_drill'" class="pattern-drill-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <p v-if="getQuestionData(exercise)?.pattern" class="pattern-drill-hint">
              {{ $t('socialnetwork.vocab.courses.patternPrompt') }}: {{ getQuestionData(exercise).pattern }}
            </p>
            <input
              v-model="exerciseAnswers[exercise.id]"
              :placeholder="$t('socialnetwork.vocab.courses.patternDrillPlaceholder')"
              class="transformation-input"
            />
            <button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id]">
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.modelPattern') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
          </div>

          <!-- Reading Aloud Übung -->
          <div v-else-if="getExerciseType(exercise) === 'reading_aloud'" class="reading-aloud-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <p class="exercise-instruction">
              {{ isSpeechRecognitionSupported ? $t('socialnetwork.vocab.courses.readingAloudInstruction') : $t('socialnetwork.vocab.courses.speakingFallbackInstruction') }}
            </p>
            <div v-if="isSpeechRecognitionSupported" class="reading-aloud-controls">
              <button 
                v-if="!isRecording(exercise.id)" 
                @click="startReadingAloud(exercise.id)" 
                class="btn-record"
                :disabled="!isSpeechRecognitionSupported"
              >
                {{ $t('socialnetwork.vocab.courses.startRecording') }}
              </button>
              <button 
                v-else 
                @click="stopReadingAloud(exercise.id)" 
                class="btn-stop-record"
              >
                {{ $t('socialnetwork.vocab.courses.stopRecording') }}
              </button>
            </div>
            <div v-else class="speech-fallback">
              <textarea
                v-model="exerciseAnswers[exercise.id]"
                :placeholder="$t('socialnetwork.vocab.courses.speakingFallbackPlaceholder')"
                class="response-textarea"
              />
            </div>
            <div v-if="recordingStatus[exercise.id]" class="recording-status" :class="{ 'recording': isRecording(exercise.id) }">
              <span v-if="isRecording(exercise.id)">{{ $t('socialnetwork.vocab.courses.recording') }}...</span>
              <span v-else>{{ recordingStatus[exercise.id] }}</span>
            </div>
            <div v-if="recognizedText[exercise.id]" class="recognized-text">
              <strong>{{ $t('socialnetwork.vocab.courses.recognizedText') }}:</strong>
              <p>{{ recognizedText[exercise.id] }}</p>
            </div>
            <button 
              v-if="(recognizedText[exercise.id] && !isRecording(exercise.id)) || (!isSpeechRecognitionSupported && exerciseAnswers[exercise.id])" 
              @click="checkAnswer(exercise.id)" 
              class="btn-check"
            >
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
                {{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
              </p>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
            <div v-if="!isSpeechRecognitionSupported" class="speech-not-supported">
              <p>{{ $t('socialnetwork.vocab.courses.speechRecognitionNotSupported') }}</p>
            </div>
          </div>

          <!-- Speaking From Memory Übung -->
          <div v-else-if="getExerciseType(exercise) === 'speaking_from_memory'" class="speaking-from-memory-exercise">
            <p v-if="getVisibleQuestionText(exercise)" class="exercise-question">{{ getVisibleQuestionText(exercise) }}</p>
            <p class="exercise-instruction">
              {{ isSpeechRecognitionSupported ? $t('socialnetwork.vocab.courses.speakingFromMemoryInstruction') : $t('socialnetwork.vocab.courses.speakingFallbackInstruction') }}
            </p>
            <div v-if="getQuestionData(exercise)?.keywords" class="keywords-hint">
              <strong>{{ $t('socialnetwork.vocab.courses.keywords') }}:</strong>
              <span v-for="(keyword, idx) in getQuestionData(exercise).keywords" :key="idx" class="keyword-tag">{{ keyword }}</span>
            </div>
            <div v-if="isSpeechRecognitionSupported" class="speaking-controls">
              <button 
                v-if="!isRecording(exercise.id)" 
                @click="startSpeakingFromMemory(exercise.id)" 
                class="btn-record"
                :disabled="!isSpeechRecognitionSupported"
              >
                {{ $t('socialnetwork.vocab.courses.startSpeaking') }}
              </button>
              <button 
                v-else 
                @click="stopSpeakingFromMemory(exercise.id)" 
                class="btn-stop-record"
              >
                {{ $t('socialnetwork.vocab.courses.stopRecording') }}
              </button>
            </div>
            <div v-else class="speech-fallback">
              <textarea
                v-model="exerciseAnswers[exercise.id]"
                :placeholder="$t('socialnetwork.vocab.courses.speakingFallbackPlaceholder')"
                class="response-textarea"
              />
            </div>
            <div v-if="recordingStatus[exercise.id]" class="recording-status" :class="{ 'recording': isRecording(exercise.id) }">
              <span v-if="isRecording(exercise.id)">{{ $t('socialnetwork.vocab.courses.recording') }}...</span>
              <span v-else>{{ recordingStatus[exercise.id] }}</span>
            </div>
            <div v-if="recognizedText[exercise.id]" class="recognized-text">
              <strong>{{ $t('socialnetwork.vocab.courses.recognizedText') }}:</strong>
              <p>{{ recognizedText[exercise.id] }}</p>
            </div>
            <button 
              v-if="(recognizedText[exercise.id] && !isRecording(exercise.id)) || (!isSpeechRecognitionSupported && exerciseAnswers[exercise.id])" 
              @click="checkAnswer(exercise.id)" 
              class="btn-check"
            >
              {{ $t('socialnetwork.vocab.courses.checkAnswer') }}
            </button>
            <div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
              <strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
              <p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
            </div>
            <div v-if="!isSpeechRecognitionSupported" class="speech-not-supported">
              <p>{{ $t('socialnetwork.vocab.courses.speechRecognitionNotSupported') }}</p>
            </div>
          </div>

          <!-- Fallback für unbekannte Typen -->
          <div v-else class="unknown-exercise">
            <p>{{ $t('socialnetwork.vocab.courses.unknownExerciseTypeNotice') }}</p>
            <p class="unknown-exercise__type">{{ $t('socialnetwork.vocab.courses.unknownExerciseTypeLabel', { type: getExerciseType(exercise) }) }}</p>
          </div>
          </div>
          </div>
        </div>
        <div v-else-if="lesson && (!effectiveExercises || effectiveExercises.length === 0)">
          <p>{{ $t('socialnetwork.vocab.courses.noExercises') }}</p>
        </div>
      </div>
    </div>
  </div>
  
  <!-- Dialog für Navigation zur nächsten Lektion -->
  <div v-if="showNextLessonDialog" class="dialog-overlay" @click.self="cancelNavigateToNextLesson">
    <div class="dialog" style="width: 400px; height: auto;">
      <div class="dialog-header">
        <span class="dialog-title">{{ $t('socialnetwork.vocab.courses.lessonCompleted') }}</span>
        <span class="dialog-close" @click="cancelNavigateToNextLesson">✖</span>
      </div>
      <div class="dialog-body">
        <p>{{ $t('socialnetwork.vocab.courses.goToNextLesson') }}</p>
      </div>
      <div class="dialog-footer">
        <button @click="confirmNavigateToNextLesson" class="dialog-button">{{ $t('general.yes') }}</button>
        <button @click="cancelNavigateToNextLesson" class="dialog-button">{{ $t('general.no') }}</button>
      </div>
    </div>
  </div>
  
  <!-- Dialog für Kurs-Abschluss -->
  <div v-if="showCompletionDialog" class="dialog-overlay" @click.self="closeCompletionDialog">
    <div class="dialog" style="width: 400px; height: auto;">
      <div class="dialog-header">
        <span class="dialog-title">{{ $t('socialnetwork.vocab.courses.allLessonsCompleted') }}</span>
        <span class="dialog-close" @click="closeCompletionDialog">✖</span>
      </div>
      <div class="dialog-body">
        <p>{{ $t('socialnetwork.vocab.courses.allLessonsCompleted') }}</p>
      </div>
      <div class="dialog-footer">
        <button @click="closeCompletionDialog" class="dialog-button">{{ $t('general.ok') }}</button>
      </div>
    </div>
  </div>
  
  <!-- Dialog für Fehler -->
  <div v-if="showErrorDialog" class="dialog-overlay" @click.self="closeErrorDialog">
    <div class="dialog" style="width: 400px; height: auto;">
      <div class="dialog-header">
        <span class="dialog-title">{{ $t('error-title') }}</span>
        <span class="dialog-close" @click="closeErrorDialog">✖</span>
      </div>
      <div class="dialog-body">
        <p>{{ errorMessage }}</p>
      </div>
      <div class="dialog-footer">
        <button @click="closeErrorDialog" class="dialog-button">{{ $t('general.ok') }}</button>
      </div>
    </div>
  </div>

  <!-- Nach falscher Kapitel-Antwort: zuerst Lösung, dann optional zum Üben -->
  <div v-if="showExerciseReinforcementDialog" class="dialog-overlay">
    <div class="dialog" style="width: 440px; height: auto;">
      <div class="dialog-header">
        <span class="dialog-title">{{ $t('socialnetwork.vocab.courses.exerciseWrongTitle') }}</span>
      </div>
      <div class="dialog-body">
        <p v-if="exerciseReinforcementCorrectAnswer" class="exercise-reinforcement-correct">
          <strong>{{ $t('socialnetwork.vocab.courses.correctAnswer') }}:</strong>
          {{ exerciseReinforcementCorrectAnswer }}
        </p>
        <p>{{ exerciseReinforcementMessage }}</p>
      </div>
      <div class="dialog-footer dialog-footer--stack">
        <button type="button" class="dialog-button dialog-button--primary" @click="confirmExerciseReinforcement">
          {{ $t('socialnetwork.vocab.courses.exerciseReinforcementGoPracticeAck') }}
        </button>
        <button type="button" class="dialog-button" @click="closeExerciseReinforcementDialog">
          {{ $t('socialnetwork.vocab.courses.exerciseReinforcementStayAck') }}
        </button>
      </div>
    </div>
  </div>

  <!-- Kapitel-Prüfung: Mindestziel am Ende nicht erreicht -->
  <div v-if="showChapterExamFailedDialog" class="dialog-overlay" @click.self="closeChapterExamFailedDialog">
    <div class="dialog" style="width: 440px; height: auto;">
      <div class="dialog-header">
        <span class="dialog-title">{{ $t('socialnetwork.vocab.courses.exerciseExamFailedTitle') }}</span>
        <span class="dialog-close" @click="closeChapterExamFailedDialog">✖</span>
      </div>
      <div class="dialog-body">
        <p>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedBody', {
          score: chapterExamFailedScore,
          target: exerciseTargetScore,
          count: exerciseRetryUnlockAttempts
        }) }}</p>
        <div v-if="chapterExamFailedDetails.length > 0" class="exam-failed-review">
          <h4>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedReviewTitle') }}</h4>
          <ul>
            <li v-for="(item, index) in chapterExamFailedDetails" :key="`failed-${index}-${item.id}`">
              <p class="exam-failed-review__question">
                <strong>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedQuestionLabel') }} {{ index + 1 }}:</strong>
                {{ item.question }}
              </p>
              <p class="exam-failed-review__answer">
                <strong>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedYourAnswerLabel') }}:</strong>
                {{ item.userAnswer || $t('socialnetwork.vocab.courses.exerciseExamFailedNoAnswer') }}
              </p>
              <p class="exam-failed-review__answer">
                <strong>{{ $t('socialnetwork.vocab.courses.correctAnswer') }}:</strong>
                {{ item.correctAnswer || '—' }}
              </p>
            </li>
          </ul>
        </div>
      </div>
      <div class="dialog-footer">
        <button type="button" class="dialog-button dialog-button--primary" @click="closeChapterExamFailedDialog">
          {{ $t('socialnetwork.vocab.courses.exerciseExamFailedOk') }}
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';
import apiClient from '@/utils/axios.js';

const debugLog = () => {};
const LESSON_STATE_VERSION = 1;
const VOCAB_REPEAT_INTERVALS = [1, 2, 4];
const VOCAB_MIN_CURRENT_EXPOSURES = 3;
/** Mindest-Erfolgsquote im Vokabeltrainer (gesamt), damit die Kapitel-Prüfung freigeschaltet wird. */
const EXERCISE_UNLOCK_MIN_SUCCESS_PERCENT = 70;
/** Max. Zeilen pro Seite in der einklappbaren Vokabel-Gesamtübersicht */
const VOCAB_OVERVIEW_PAGE_SIZE = 40;

export default {
  name: 'VocabLessonView',
  props: {
    courseId: {
      type: String,
      required: true
    },
    lessonId: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      loading: false,
      lesson: null,
      exerciseAnswers: {},
      exerciseResults: {},
      activeTab: 'learn', // Standardmäßig "Lernen"-Tab
      // Vokabeltrainer
      vocabTrainerActive: false,
      vocabTrainerPool: [],
      vocabTrainerMode: 'multiple_choice', // 'multiple_choice' oder 'typing'
      vocabTrainerAutoSwitchedToTyping: false, // Track ob automatisch zu Typing gewechselt wurde
      vocabTrainerCorrect: 0,
      vocabTrainerWrong: 0,
      vocabTrainerTotalAttempts: 0,
      vocabTrainerStats: {}, // { [vocabKey]: { attempts: 0, correct: 0, wrong: 0 } }
      vocabTrainerRepeatQueue: [],
      vocabTrainerChoiceOptions: [],
      vocabTrainerPhase: 'current', // 'current' = aktuelle Lektion, 'mixed' = gemischt mit alten
      vocabTrainerMixedPool: [], // Pool aus alten Lektionsvokabeln
      vocabTrainerMixedAttempts: 0, // Zähler für Mixed-Phase
      vocabTrainerCurrentAttempts: 0,
      vocabTrainerReviewAttempts: 0,
      exercisePreparationCompleted: false,
      /** 0 = noch kein Durchgang, 1 = erste Durchsicht, 2 = zweite Durchsicht — dann Vokabeltrainer */
      lessonPrepStage: 0,
      lessonPrepIndex: 0,
      currentVocabQuestion: null,
      vocabTrainerAnswer: '',
      vocabTrainerSelectedChoice: null,
      vocabTrainerAnswered: false,
      vocabTrainerLastCorrect: false,
      vocabTrainerContinueTimer: null,
      vocabTrainerLastWrongReview: null,
      vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning
      isCheckingLessonCompletion: false, // Flag um Endlosschleife zu verhindern
      isNavigatingToNext: false, // Flag um mehrfache Navigation zu verhindern
      showNextLessonDialog: false,
      // Speech Recognition für Reading Aloud und Speaking From Memory
      speechRecognition: null,
      activeRecognition: {}, // { [exerciseId]: SpeechRecognition instance }
      recognizedText: {}, // { [exerciseId]: string }
      recordingStatus: {}, // { [exerciseId]: string }
      isSpeechRecognitionSupported: false,
      exerciseRetryPending: false,
      exerciseRetryPendingSinceAttempts: 0,
      assistantLoading: false,
      assistantSubmitting: false,
      assistantSettings: null,
      assistantMessages: [],
      assistantInput: '',
      assistantError: '',
      assistantMode: 'practice',
      assistantWaitStartedAt: null,
      assistantWaitElapsedSeconds: 0,
      assistantWaitTimer: null,
      isAssistantFocused: false,
      nextLessonId: null,
      showCompletionDialog: false,
      showErrorDialog: false,
      errorMessage: '',
      showExerciseReinforcementDialog: false,
      exerciseReinforcementPrepMode: false,
      exerciseReinforcementCorrectAnswer: '',
      exerciseReinforcementMessage: '',
      showChapterExamFailedDialog: false,
      chapterExamFailedScore: 0,
      chapterExamFailedDetails: [],
      /** Index in scrambledChapterExamExercises bei Ein-Frage-Ansicht */
      exerciseSequentialIndex: 0,
      dailyEnoughBannerDismissed: false,
      /** Aus vorherigen Lektionen (MC-Optionen nach Fragentyp Ziel-/Muttersprache) */
      distractorPool: { target: [], native: [] },
      courseLanguageName: '',
      courseNativeLanguageName: '',
      /** Fortschritt aller Kurslektionen inkl. lessonState für Spezial-Trainer-Boost */
      courseProgressList: [],
      /** { [exerciseId]: { options: string[], useTextAnswer: boolean } } */
      mcRandomizedOptions: {},
      lessonStatePersistenceReady: false,
      lessonStateSaveTimer: null,
      lessonStateSaveInFlight: false,
      pendingLessonStatePayload: null,
      resettingLessonProgress: false,
      /** Seitennummer (1-basiert) für die Vokabel-Gesamtübersicht */
      vocabOverviewPage: 1
    };
  },
  computed: {
    ...mapGetters(['user']),
    hasExercises() {
      const exercises = this.effectiveExercises;
      return exercises && Array.isArray(exercises) && exercises.length > 0;
    },
    hasPreviousVocab() {
      return Array.isArray(this.previousVocab) && this.previousVocab.length > 0;
    },
    lessonComplexityWeight() {
      const lessonType = this.lesson?.lessonType;
      if (['dialogue', 'phrases', 'survival', 'grammar'].includes(lessonType)) {
        return 1.2;
      }
      if (lessonType === 'review' || lessonType === 'vocab_review') {
        return 0.9;
      }
      return 1;
    },
    trainerNewFocusTarget() {
      const vocabCount = this.trainableLessonVocab?.length || 0;
      const exerciseCount = this.effectiveExercises?.length || 0;
      const durationBonus = Math.max(0, Math.round((this.lesson?.targetMinutes || 0) / 5) - 1);
      const coverageTarget = vocabCount * this.trainerMinimumCurrentExposures;
      const baseTarget = Math.ceil((Math.max(vocabCount, 4) * 1.35) + (exerciseCount * 0.35) + durationBonus);
      const weightedTarget = Math.ceil(baseTarget * this.lessonComplexityWeight);
      return Math.max(6, Math.min(160, Math.max(weightedTarget, coverageTarget)));
    },
    trainerMinimumCurrentExposures() {
      const mode = this.lessonPedagogy?.didacticMode || this.lesson?.lessonType || '';
      if (mode === 'intensive_review' || mode === 'review' || mode === 'vocab_review') {
        return 2;
      }
      if (['grammar', 'dialogue', 'phrases', 'survival'].includes(this.lesson?.lessonType)) {
        return 4;
      }
      return VOCAB_MIN_CURRENT_EXPOSURES;
    },
    trainerReviewBlendStart() {
      return Math.max(3, Math.ceil(this.trainerNewFocusTarget * 0.4));
    },
    trainerReviewRampWindow() {
      return Math.max(4, this.trainerNewFocusTarget - this.trainerReviewBlendStart);
    },
    trainerExerciseUnlockAttempts() {
      const unlockTarget = this.trainerNewFocusTarget + Math.ceil((this.effectiveExercises?.length || 0) * 0.25);
      return Math.max(6, Math.min(140, unlockTarget));
    },
    exerciseUnlockMinSuccessPercent() {
      return EXERCISE_UNLOCK_MIN_SUCCESS_PERCENT;
    },
    currentReviewShare() {
      if (!this.hasPreviousVocab) {
        return 0;
      }
      const lessonType = String(this.lesson?.lessonType || '').toLowerCase();
      const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase();
      const isReviewLesson = ['review', 'vocab_review'].includes(lessonType)
        || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode);

      if (isReviewLesson) {
        // In Wiederholungslektionen soll altes Material frueher und staerker einfliesen.
        // Start: ~35%, Ramp-up bis max ~75%.
        const blendStart = Math.max(2, Math.ceil(this.trainerNewFocusTarget * 0.2));
        const rampWindow = Math.max(6, Math.ceil(this.trainerNewFocusTarget * 0.5));
        const progressPastBlendStart = Math.max(0, this.vocabTrainerCurrentAttempts - blendStart);
        const normalizedRamp = Math.min(1, progressPastBlendStart / rampWindow);
        return Math.min(0.75, 0.35 + (normalizedRamp * 0.4));
      }

      const progressPastBlendStart = Math.max(0, this.vocabTrainerCurrentAttempts - this.trainerReviewBlendStart);
      const normalizedRamp = Math.min(1, progressPastBlendStart / this.trainerReviewRampWindow);
      return Math.min(0.55, normalizedRamp * 0.55);
    },
    typingCurrentLessonShare() {
      if (!this.hasPreviousVocab) {
        return 1;
      }
      const lessonType = String(this.lesson?.lessonType || '').toLowerCase();
      const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase();
      const isReviewLesson = ['review', 'vocab_review'].includes(lessonType)
        || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode);

      if (isReviewLesson) {
        return Math.max(0.25, 1 - this.currentReviewShare);
      }

      // Normale Lektion: Progressives Streuen im Typing-Modus
      // Start 100% aktuelle Lektion -> dann 90% -> später 50%.
      const startRampAt = Math.max(3, Math.ceil(this.trainerNewFocusTarget * 0.3));
      const midRampAt = Math.max(startRampAt + 1, Math.ceil(this.trainerNewFocusTarget * 0.6));
      const attempts = Math.max(0, Number(this.vocabTrainerCurrentAttempts) || 0);

      if (attempts < startRampAt) {
        return 1;
      }
      if (attempts < midRampAt) {
        return 0.9;
      }
      return 0.5;
    },
    canAccessExercises() {
      if (!this.hasExercises) return false;
      if (this.exerciseNeedsReinforcement) return false;
      const isReview = this.lesson?.lessonType === 'review' || this.lesson?.lessonType === 'vocab_review';
      if (isReview) return true;
      if (this.trainableLessonVocab.length === 0 && this.prepItems.length > 0) {
        return this.lessonPrepStage >= 2;
      }
      return this.exercisePreparationCompleted;
    },
    exerciseUnlockHint() {
      if (this.exerciseNeedsReinforcement) {
        return this.$t('socialnetwork.vocab.courses.exerciseReinforcementHint', {
          count: this.exerciseRetryRemainingAttempts
        });
      }
      if (this.trainableLessonVocab.length === 0 && this.prepItems.length > 0) {
        return this.$t('socialnetwork.vocab.courses.exerciseUnlockHintAfterPrep');
      }
      const core = this.$t('socialnetwork.vocab.courses.exerciseUnlockHintTrainerCore', {
        newTarget: this.trainerNewFocusTarget,
        attempts: this.trainerExerciseUnlockAttempts,
        rate: this.exerciseUnlockMinSuccessPercent
      });
      if (this.hasPreviousVocab) {
        return `${core} ${this.$t('socialnetwork.vocab.courses.exerciseUnlockHintTrainerMixSuffix')}`;
      }
      return core;
    },
    /** Für Wiederholungslektionen: Übungen aus vorherigen Lektionen (Kapitelprüfung). Sonst: eigene Grammatik-Übungen. */
    effectiveExercises() {
      if (!this.lesson) return [];
      const isReview = this.lesson.lessonType === 'review' || this.lesson.lessonType === 'vocab_review';
      if (isReview && this.lesson.reviewVocabExercises && Array.isArray(this.lesson.reviewVocabExercises) && this.lesson.reviewVocabExercises.length > 0) {
        return this.lesson.reviewVocabExercises;
      }
      if (this.lesson.grammarExercises && Array.isArray(this.lesson.grammarExercises)) {
        return this.lesson.grammarExercises;
      }
      return [];
    },
    exerciseCorrectCount() {
      return this.effectiveExercises.filter((exercise) => Boolean(this.exerciseResults[exercise.id]?.correct)).length;
    },
    exerciseAnsweredCount() {
      return this.effectiveExercises.filter((exercise) => Boolean(this.exerciseResults[exercise.id])).length;
    },
    exerciseProgressPercent() {
      if (!this.effectiveExercises.length) return 0;
      return Math.round((this.exerciseCorrectCount / this.effectiveExercises.length) * 100);
    },
    exerciseTargetScore() {
      return Number(this.lesson?.targetScorePercent) || 80;
    },
    /** Kapitel-Prüfung: eine Frage pro Ansicht (Essen & Trinken: deterministisch gemischt). */
    scrambledChapterExamExercises() {
      const raw = this.effectiveExercises;
      if (!raw.length) return [];
      if ((this.lesson?.title || '').trim() === 'Essen & Trinken') {
        return this._deterministicShuffle(raw.slice(), Number(this.lessonId) || 1);
      }
      return raw;
    },
    sequentialPanelActive() {
      return (this.scrambledChapterExamExercises?.length || 0) > 1;
    },
    exercisesPanelExercises() {
      const list = this.scrambledChapterExamExercises;
      if (!list.length) return [];
      if (!this.sequentialPanelActive) return list;
      const idx = Math.max(0, Math.min(this.exerciseSequentialIndex, list.length - 1));
      return [list[idx]];
    },
    canStepExercisePanelForward() {
      const list = this.scrambledChapterExamExercises;
      if (!list.length) return false;
      const ex = list[this.exerciseSequentialIndex];
      if (!ex) return false;
      return Boolean(this.exerciseResults[ex.id]) && this.exerciseSequentialIndex < list.length - 1;
    },
    exerciseRetryUnlockAttempts() {
      return Math.min(8, Math.max(2, Math.ceil(this.trainerNewFocusTarget * 0.25)));
    },
    exerciseRetryRemainingAttempts() {
      if (!this.exerciseRetryPending) return 0;
      const sinceWrong = this.vocabTrainerTotalAttempts - this.exerciseRetryPendingSinceAttempts;
      return Math.max(0, this.exerciseRetryUnlockAttempts - sinceWrong);
    },
    exerciseNeedsReinforcement() {
      return this.exerciseRetryPending && this.exerciseRetryRemainingAttempts > 0;
    },
    visibleGrammarExplanations() {
      const didacticFocus = Array.isArray(this.lessonDidactics?.grammarFocus) ? this.lessonDidactics.grammarFocus : [];
      if (didacticFocus.length > 0) {
        return didacticFocus;
      }
      return this.grammarExplanations;
    },
    exerciseStatusHint() {
      if (!this.effectiveExercises.length) return '';
      if (this.exerciseNeedsReinforcement) {
        return this.$t('socialnetwork.vocab.courses.exerciseReinforcementHint', {
          count: this.exerciseRetryRemainingAttempts
        });
      }
      if (this.exerciseAnsweredCount < this.effectiveExercises.length) {
        return this.$t('socialnetwork.vocab.courses.exerciseAnswerAllHint', {
          answered: this.exerciseAnsweredCount,
          total: this.effectiveExercises.length,
          target: this.exerciseTargetScore
        });
      }
      if (this.exerciseProgressPercent >= this.exerciseTargetScore) {
        return this.$t('socialnetwork.vocab.courses.exercisePassedHint', {
          score: this.exerciseProgressPercent,
          target: this.exerciseTargetScore
        });
      }
      return this.$t('socialnetwork.vocab.courses.exerciseNeedMoreCorrectHint', {
        score: this.exerciseProgressPercent,
        target: this.exerciseTargetScore
      });
    },
    grammarExplanations() {
      // Extrahiere Grammatik-Erklärungen aus den Übungen
      try {
        const exercises = this.effectiveExercises;
        if (!exercises || !Array.isArray(exercises) || exercises.length === 0) {
          return [];
        }
        
        const explanations = [];
        const seen = new Set();
        
        exercises.forEach(exercise => {
          if (exercise.explanation && !seen.has(exercise.explanation)) {
            seen.add(exercise.explanation);
            explanations.push({
              title: exercise.title || '',
              text: exercise.explanation
            });
          }
        });
        
        return explanations;
      } catch (e) {
        console.error('Fehler beim Extrahieren der Grammatik-Erklärungen:', e);
        return [];
      }
    },
    /** Vokabeln aus vorherigen Lektionen (für gemischte Wiederholung im Vokabeltrainer) */
    previousVocab() {
      try {
        if (!this.lesson || !this.lesson.previousLessonExercises) return [];
        return this._extractVocabFromExercises(this.lesson.previousLessonExercises);
      } catch (e) {
        console.error('Fehler in previousVocab:', e);
        return [];
      }
    },
    importantVocab() {
      // Extrahiere wichtige Begriffe aus den Übungen
      try {
        // Bei Wiederholungslektionen: Verwende Vokabeln aus vorherigen Lektionen (effectiveExercises = reviewVocabExercises)
        // Normale Lektion: Verwende effectiveExercises (grammarExercises)
        const exercises = this.effectiveExercises;
        if (!exercises || !Array.isArray(exercises) || exercises.length === 0) {
          debugLog('[importantVocab] Keine Übungen vorhanden');
          return [];
        }
        return this._extractVocabFromExercises(exercises);
      } catch (e) {
        console.error('Fehler in importantVocab computed property:', e);
        return [];
      }
    },
    lessonVocab() {
      const vocabByReference = new Map();
      const targetTokenWeight = new Map();
      const nativeTokenWeight = new Map();
      const addTokens = (text, map, weight = 1) => {
        const tokens = String(text || '')
          .toLowerCase()
          .normalize('NFKC')
          .replace(/[\p{P}\p{S}]+/gu, ' ')
          .split(/\s+/)
          .map((t) => t.trim())
          .filter((t) => t.length >= 2);
        tokens.forEach((token) => {
          map.set(token, (map.get(token) || 0) + weight);
        });
      };
      const sideScore = (text, map) => {
        const tokens = String(text || '')
          .toLowerCase()
          .normalize('NFKC')
          .replace(/[\p{P}\p{S}]+/gu, ' ')
          .split(/\s+/)
          .map((t) => t.trim())
          .filter((t) => t.length >= 2);
        return tokens.reduce((sum, token) => sum + (map.get(token) || 0), 0);
      };
      (this.normalizedCorePatterns || []).forEach((p) => {
        addTokens(p?.target, targetTokenWeight, 3);
        addTokens(p?.gloss, nativeTokenWeight, 3);
      });
      (this.importantVocab || []).forEach((v) => {
        addTokens(v?.reference, targetTokenWeight, 1);
        addTokens(v?.learning, nativeTokenWeight, 1);
      });
      const orientPair = (learning, reference) => {
        const l = String(learning || '').trim();
        const r = String(reference || '').trim();
        if (!l || !r) return { learning: l, reference: r };
        const directScore = sideScore(r, targetTokenWeight) + sideScore(l, nativeTokenWeight);
        const swappedScore = sideScore(l, targetTokenWeight) + sideScore(r, nativeTokenWeight);
        if (swappedScore > directScore) {
          return { learning: r, reference: l };
        }
        return { learning: l, reference: r };
      };
      const addEntry = (entry) => {
        const oriented = orientPair(entry?.learning, entry?.reference);
        const reference = String(oriented.reference || '').trim();
        const learning = String(oriented.learning || '').trim();
        if (!this.isTrainableLessonVocabPair(learning, reference)) return;
        const key = this.normalizeLessonVocabTerm(reference);
        if (!vocabByReference.has(key)) {
          const variants = new Set();
          if (learning) variants.add(learning);
          vocabByReference.set(key, { learning, reference, learningVariants: variants });
          return;
        }
        const existing = vocabByReference.get(key);
        if (learning) {
          if (!existing.learning) {
            existing.learning = learning;
          }
          if (existing.learningVariants instanceof Set) {
            existing.learningVariants.add(learning);
          } else {
            existing.learningVariants = new Set([existing.learning, learning].filter(Boolean));
          }
        }
      };

      this.normalizedCorePatterns.forEach((item) => {
        addEntry({
          learning: item.gloss || '',
          reference: item.target || ''
        });
      });

      this.importantVocab.forEach((item) => {
        addEntry(item);
      });

      return Array.from(vocabByReference.values()).map((entry) => {
        const variants = entry.learningVariants instanceof Set
          ? Array.from(entry.learningVariants)
          : (Array.isArray(entry.learningVariants) ? entry.learningVariants : []);
        const uniqueVariants = [...new Set([entry.learning, ...variants].filter(Boolean))];
        return {
          learning: entry.learning,
          reference: entry.reference,
          // Für den Trainer: alternative Übersetzungen als gleichwertig akzeptieren.
          learningVariants: uniqueVariants
        };
      });
    },
    vocabOverviewTotalPages() {
      const n = this.lessonVocab.length;
      if (n === 0) {
        return 1;
      }
      return Math.ceil(n / VOCAB_OVERVIEW_PAGE_SIZE);
    },
    paginatedLessonVocab() {
      const list = this.lessonVocab;
      const totalPages = this.vocabOverviewTotalPages;
      const page = Math.min(Math.max(1, this.vocabOverviewPage), totalPages);
      const start = (page - 1) * VOCAB_OVERVIEW_PAGE_SIZE;
      return list.slice(start, start + VOCAB_OVERVIEW_PAGE_SIZE);
    },
    vocabOverviewPagerMeta() {
      const n = this.lessonVocab.length;
      const totalPages = this.vocabOverviewTotalPages;
      const page = Math.min(Math.max(1, this.vocabOverviewPage), totalPages);
      if (n === 0) {
        return { from: 0, to: 0, page: 1, pages: 1, total: 0 };
      }
      const from = (page - 1) * VOCAB_OVERVIEW_PAGE_SIZE + 1;
      const to = Math.min(page * VOCAB_OVERVIEW_PAGE_SIZE, n);
      return { from, to, page, pages: totalPages, total: n };
    },
    trainableLessonVocab() {
      return this.lessonVocab.filter((entry) => this.isTrainableLessonVocabPair(entry.learning, entry.reference));
    },
    lessonDidactics() {
      return this.lesson?.didactics || {
        learningGoals: [],
        corePatterns: [],
        grammarFocus: [],
        speakingPrompts: [],
        practicalTasks: []
      };
    },
    normalizedCorePatterns() {
      const raw = this.lessonDidactics.corePatterns || [];
      return raw
        .map((p) => this.normalizeCorePatternEntry(p))
        .filter(Boolean);
    },
    prepTargetLabel() {
      return String(
        this.courseLanguageName
        || this.lesson?.course?.languageName
        || this.lesson?.languageName
        || this.$t('socialnetwork.vocab.courses.vocabPrepTargetLabel')
      ).toUpperCase();
    },
    prepGlossLabel() {
      return String(
        this.courseNativeLanguageName
        || this.lesson?.course?.nativeLanguageName
        || this.lesson?.nativeLanguageName
        || this.$t('socialnetwork.vocab.courses.vocabPrepGlossLabel')
      ).toUpperCase();
    },
    prepItems() {
      // Vorbereitung nur mit echten Paaren (Zielsprache + Übersetzung),
      // damit weder leere Gloss-Zeilen noch übergroße Listen entstehen.
      const out = [];
      const seen = new Set();
      const targetTokenWeight = new Map();
      const nativeTokenWeight = new Map();
      const addTokens = (text, map, weight = 1) => {
        const tokens = String(text || '')
          .toLowerCase()
          .normalize('NFKC')
          .replace(/[\p{P}\p{S}]+/gu, ' ')
          .split(/\s+/)
          .map((t) => t.trim())
          .filter((t) => t.length >= 2);
        tokens.forEach((token) => {
          map.set(token, (map.get(token) || 0) + weight);
        });
      };
      // Core patterns gelten als qualitativ beste Quelle -> höheres Gewicht
      (this.normalizedCorePatterns || []).forEach((p) => {
        addTokens(p?.target, targetTokenWeight, 3);
        addTokens(p?.gloss, nativeTokenWeight, 3);
      });
      // Übungs-Extraktion als Zusatzsignal
      (this.importantVocab || []).forEach((v) => {
        addTokens(v?.reference, targetTokenWeight, 1);
        addTokens(v?.learning, nativeTokenWeight, 1);
      });
      const sideScore = (text, map) => {
        const tokens = String(text || '')
          .toLowerCase()
          .normalize('NFKC')
          .replace(/[\p{P}\p{S}]+/gu, ' ')
          .split(/\s+/)
          .map((t) => t.trim())
          .filter((t) => t.length >= 2);
        return tokens.reduce((sum, token) => sum + (map.get(token) || 0), 0);
      };
      const orientPair = (target, gloss) => {
        const t = String(target || '').trim();
        const g = String(gloss || '').trim();
        if (!t || !g) return { target: t, gloss: g };
        const directScore = sideScore(t, targetTokenWeight) + sideScore(g, nativeTokenWeight);
        const swappedScore = sideScore(g, targetTokenWeight) + sideScore(t, nativeTokenWeight);
        if (swappedScore > directScore) {
          return { target: g, gloss: t };
        }
        return { target: t, gloss: g };
      };
      const pushUnique = (target, gloss) => {
        const oriented = orientPair(target, gloss);
        const t = String(oriented.target || '').trim();
        const g = String(oriented.gloss || '').trim();
        if (!this.isTrainableLessonVocabPair(g, t)) return;
        const key = `${this.normalizeLessonVocabTerm(t)}|${this.normalizeLessonVocabTerm(g)}`;
        if (seen.has(key)) return;
        seen.add(key);
        out.push({ target: t, gloss: g });
      };

      // 1) Didaktisch gepflegte Kernmuster zuerst (höchste Qualität)
      this.normalizedCorePatterns.forEach((p) => {
        pushUnique(p?.target, p?.gloss);
      });

      // 2) Ergänzung aus extrahierten Übungsvokabeln, aber nur saubere Paare
      this.lessonVocab.forEach((item) => {
        pushUnique(item?.reference, item?.learning);
      });

      // Begrenzen, damit die Vorbereitungsrunde kompakt bleibt
      return out.slice(0, 30);
    },
    currentPrepItem() {
      return this.prepItems[this.lessonPrepIndex] || null;
    },
    isLastPrepItemInPass() {
      if (this.prepItems.length === 0) {
        return true;
      }
      return this.lessonPrepIndex >= this.prepItems.length - 1;
    },
    corePatternsHaveGloss() {
      return this.normalizedCorePatterns.some((p) => p.gloss);
    },
    canStartVocabTrainerPrep() {
      if (!this.trainableLessonVocab || this.trainableLessonVocab.length === 0) {
        return false;
      }
      if (this.prepItems.length > 0 && this.lessonPrepStage < 2) {
        return false;
      }
      return true;
    },
    lessonPedagogy() {
      return this.lesson?.pedagogy || {
        didacticMode: null,
        phaseLabel: null,
        blockNumber: null,
        difficultyWeight: null,
        newUnitTarget: null,
        reviewWeight: null,
        isIntensiveReview: false
      };
    },
    lessonProgress() {
      return this.lesson?.progress || null;
    },
    lessonReviewBadgeLabel() {
      const progress = this.lessonProgress;
      if (!progress?.completed) {
        return '';
      }
      return this.getReviewStageLabel(progress);
    },
    lessonReviewStatusClass() {
      const progress = this.lessonProgress;
      if (!progress?.completed) {
        return '';
      }
      if (progress.reviewCompleted) {
        return 'lesson-review-status--done';
      }
      if (progress.reviewDue) {
        return 'lesson-review-status--due';
      }
      return 'lesson-review-status--scheduled';
    },
    lessonReviewHeadline() {
      const progress = this.lessonProgress;
      if (!progress?.completed) {
        return '';
      }
      if (progress.reviewCompleted) {
        return this.$t('socialnetwork.vocab.courses.lessonReviewHeadlineDone');
      }
      if (progress.reviewDue) {
        return this.$t('socialnetwork.vocab.courses.lessonReviewHeadlineDue');
      }
      return this.$t('socialnetwork.vocab.courses.lessonReviewHeadlineScheduled');
    },
    lessonReviewHint() {
      const progress = this.lessonProgress;
      if (!progress?.completed) {
        return '';
      }
      if (progress.reviewCompleted) {
        return this.$t('socialnetwork.vocab.courses.lessonReviewHintDone');
      }
      return this.$t('socialnetwork.vocab.courses.lessonReviewHintNextDue', {
        due: this.formatLessonReviewDue(progress.reviewNextDueAt)
      });
    },
    showDailyEnoughBanner() {
      if (this.dailyEnoughBannerDismissed) return false;
      const progress = this.lessonProgress;
      // Wenn eine Review-Welle fällig ist, ist "für heute genug" nicht korrekt.
      if (progress?.completed && progress?.reviewDue) return false;

      if (progress?.completed) return true;

      // Falls der Fortschritt noch nicht gespeichert ist: Übungen sind vollständig + bestanden.
      if (this.hasExercises && this.effectiveExercises.length > 0) {
        const allAnswered = this.exerciseAnsweredCount >= this.effectiveExercises.length;
        if (allAnswered && this.exerciseProgressPercent >= this.exerciseTargetScore) {
          return true;
        }
      }
      return false;
    },
    assistantAvailable() {
      if (!this.assistantSettings) {
        return false;
      }
      const enabled = this.assistantSettings.enabled !== false;
      const hasBaseUrl = Boolean(this.assistantSettings.baseUrl);
      return enabled && (this.assistantSettings.hasKey || hasBaseUrl);
    },
    assistantModes() {
      return [
        { value: 'practice', label: this.$t('socialnetwork.vocab.courses.languageAssistantModePractice') },
        { value: 'explain', label: this.$t('socialnetwork.vocab.courses.languageAssistantModeExplain') },
        { value: 'correct', label: this.$t('socialnetwork.vocab.courses.languageAssistantModeCorrect') }
      ];
    },
    persistedLessonStateSnapshot() {
      return {
        activeTab: this.activeTab,
        exerciseAnswers: this.exportPersistedExerciseAnswers(),
        exerciseResults: this.exerciseResults,
        exercisePreparationCompleted: this.exercisePreparationCompleted,
        lessonPrepStage: this.lessonPrepStage,
        lessonPrepIndex: this.lessonPrepIndex,
        vocabTrainerActive: this.vocabTrainerActive,
        vocabTrainerMode: this.vocabTrainerMode,
        vocabTrainerAutoSwitchedToTyping: this.vocabTrainerAutoSwitchedToTyping,
        vocabTrainerCorrect: this.vocabTrainerCorrect,
        vocabTrainerWrong: this.vocabTrainerWrong,
        vocabTrainerTotalAttempts: this.vocabTrainerTotalAttempts,
        vocabTrainerStats: this.vocabTrainerStats,
        vocabTrainerRepeatQueue: this.vocabTrainerRepeatQueue,
        vocabTrainerCurrentAttempts: this.vocabTrainerCurrentAttempts,
        vocabTrainerReviewAttempts: this.vocabTrainerReviewAttempts,
        exerciseRetryPending: this.exerciseRetryPending,
        exerciseRetryPendingSinceAttempts: this.exerciseRetryPendingSinceAttempts,
        exerciseSequentialIndex: this.exerciseSequentialIndex
      };
    }
  },
  watch: {
    persistedLessonStateSnapshot: {
      handler() {
        this.persistLessonState();
      },
      deep: true
    },
    async courseId(newVal, oldVal) {
      if (newVal !== oldVal) {
        await this.persistLessonState({ immediate: true, lessonIdOverride: this.lesson?.id });
        // Reset Flags beim Kurswechsel
        this.isCheckingLessonCompletion = false;
        this.isNavigatingToNext = false;
        this.loadLesson();
      }
    },
    async lessonId(newVal, oldVal) {
      if (newVal !== oldVal) {
        await this.persistLessonState({ immediate: true, lessonIdOverride: oldVal });
        // Reset Flags beim Lektionswechsel
        this.isCheckingLessonCompletion = false;
        this.isNavigatingToNext = false;
        this.loadLesson();
      }
    },
    lessonVocab: {
      handler() {
        const totalPages = this.vocabOverviewTotalPages;
        if (this.vocabOverviewPage > totalPages) {
          this.vocabOverviewPage = Math.max(1, totalPages);
        }
      },
      deep: true
    }
  },
  methods: {
    dismissDailyEnoughBanner() {
      this.dailyEnoughBannerDismissed = true;
    },
    continueDailyEnoughBanner() {
      this.dailyEnoughBannerDismissed = true;
      if (!this.vocabTrainerActive && this.trainableLessonVocab.length > 0) {
        // Startet Trainer nur, wenn die Vorbereitung abgeschlossen ist.
        if (this.canStartVocabTrainerPrep) {
          this.startVocabTrainer();
        } else {
          this.vocabTrainerActive = true;
          this.vocabTrainerPool = [...this.trainableLessonVocab];
          this.$nextTick(() => this.nextVocabQuestion());
        }
      }
    },
    vocabOverviewGoPrev() {
      if (this.vocabOverviewPagerMeta.page > 1) {
        this.vocabOverviewPage -= 1;
      }
    },
    vocabOverviewGoNext() {
      if (this.vocabOverviewPagerMeta.page < this.vocabOverviewPagerMeta.pages) {
        this.vocabOverviewPage += 1;
      }
    },
    exportPersistedExerciseAnswers() {
      const exportedAnswers = {};
      this.effectiveExercises.forEach((exercise) => {
        const currentAnswer = this.exerciseAnswers[exercise.id];
        if (currentAnswer === undefined || currentAnswer === null || currentAnswer === '') {
          return;
        }

        if (this.getExerciseType(exercise) === 'multiple_choice') {
          if (typeof currentAnswer === 'string' && Number.isNaN(Number(currentAnswer))) {
            exportedAnswers[exercise.id] = currentAnswer;
            return;
          }
          const optionIndex = Number(currentAnswer);
          const selectedOption = this.getOptions(exercise)[optionIndex];
          if (selectedOption !== undefined) {
            exportedAnswers[exercise.id] = String(selectedOption);
          }
          return;
        }

        if (Array.isArray(currentAnswer)) {
          exportedAnswers[exercise.id] = currentAnswer.map((entry) => String(entry ?? ''));
          return;
        }

        exportedAnswers[exercise.id] = String(currentAnswer);
      });
      return exportedAnswers;
    },
    getLessonStateStorageKey() {
      if (typeof window === 'undefined' || !window.localStorage) {
        return '';
      }
      const userId = this.user?.id || 'guest';
      return `vocab-lesson-state:${LESSON_STATE_VERSION}:${userId}:${this.courseId}:${this.lessonId}`;
    },
    clearLocalLessonStateCache() {
      const storageKey = this.getLessonStateStorageKey();
      if (!storageKey) {
        return;
      }
      try {
        window.localStorage.removeItem(storageKey);
      } catch (error) {
        console.warn('[VocabLessonView] Lokaler Lektions-Cache konnte nicht gelöscht werden:', error);
      }
    },
    confirmResetLessonProgress() {
      if (!this.lessonId || this.resettingLessonProgress) {
        return;
      }
      if (!window.confirm(this.$t('socialnetwork.vocab.courses.resetLessonProgressConfirm'))) {
        return;
      }
      this.resetLessonProgressOnServer();
    },
    async resetLessonProgressOnServer() {
      if (!this.lessonId || this.resettingLessonProgress) {
        return;
      }
      this.resettingLessonProgress = true;
      try {
        await apiClient.delete(`/api/vocab/lessons/${this.lessonId}/progress`);
        this.clearLocalLessonStateCache();
        // Nach dem Reset soll der Nutzer wieder im Üben-/Lernen-Tab starten.
        this.activeTab = 'learn';
        if (this.$route?.query?.tab && this.$route.query.tab !== 'learn') {
          this.$router.replace({
            query: { ...this.$route.query, tab: 'learn' }
          });
        }
        this.$root?.$refs?.messageDialog?.open?.('tr:socialnetwork.vocab.courses.resetLessonProgressSuccess');
        await this.loadLesson();
      } catch (e) {
        console.error('[VocabLessonView] Lektion zurücksetzen fehlgeschlagen:', e);
        const msg = e?.response?.data?.error || this.$t('socialnetwork.vocab.courses.resetLessonProgressError');
        this.$root?.$refs?.messageDialog?.open?.(msg);
      } finally {
        this.resettingLessonProgress = false;
      }
    },
    buildPersistedLessonState() {
      return {
        version: LESSON_STATE_VERSION,
        updatedAt: new Date().toISOString(),
        ...this.persistedLessonStateSnapshot
      };
    },
    readLocalLessonState() {
      const storageKey = this.getLessonStateStorageKey();
      if (!storageKey) {
        return null;
      }
      try {
        const raw = window.localStorage.getItem(storageKey);
        return raw ? JSON.parse(raw) : null;
      } catch (error) {
        console.warn('[VocabLessonView] Konnte gespeicherten Lektionszustand nicht lesen:', error);
        return null;
      }
    },
    async flushLessonStateToServer({ lessonIdOverride = null, payloadOverride = null } = {}) {
      if (this.lessonStateSaveInFlight) {
        return;
      }

      const payload = payloadOverride || this.pendingLessonStatePayload;
      const lessonId = lessonIdOverride || this.lessonId;
      if (!this.lessonStatePersistenceReady || !payload || !lessonId) {
        return;
      }
      if (!payloadOverride) {
        this.pendingLessonStatePayload = null;
      }
      this.lessonStateSaveInFlight = true;

      try {
        const { data } = await apiClient.put(`/api/vocab/lessons/${lessonId}/progress`, {
          lessonState: payload
        });
        if (!lessonIdOverride && this.lesson) {
          this.lesson.progress = data;
        }
      } catch (error) {
        console.warn('[VocabLessonView] Konnte Lektionszustand nicht serverseitig speichern:', error);
        if (!payloadOverride) {
          this.pendingLessonStatePayload = payload;
        }
      } finally {
        this.lessonStateSaveInFlight = false;
        if (this.pendingLessonStatePayload) {
          this.lessonStateSaveTimer = window.setTimeout(() => {
            this.flushLessonStateToServer();
          }, 800);
        }
      }
    },
    async persistLessonState({ immediate = false, lessonIdOverride = null } = {}) {
      if (!this.lessonStatePersistenceReady) {
        return;
      }
      const payload = this.buildPersistedLessonState();
      const shouldWriteLocalCache = !lessonIdOverride || String(lessonIdOverride) === String(this.lessonId);
      const storageKey = shouldWriteLocalCache ? this.getLessonStateStorageKey() : '';
      if (storageKey) {
        try {
          window.localStorage.setItem(storageKey, JSON.stringify(payload));
        } catch (error) {
          console.warn('[VocabLessonView] Konnte Lektionszustand nicht lokal speichern:', error);
        }
      }
      this.pendingLessonStatePayload = payload;
      if (this.lessonStateSaveTimer) {
        window.clearTimeout(this.lessonStateSaveTimer);
        this.lessonStateSaveTimer = null;
      }
      if (immediate) {
        await this.flushLessonStateToServer({ lessonIdOverride, payloadOverride: payload });
        return;
      }
      this.lessonStateSaveTimer = window.setTimeout(() => {
        this.flushLessonStateToServer();
      }, 450);
    },
    normalizePersistedExerciseAnswers(savedAnswers) {
      const normalizedAnswers = { ...this.exerciseAnswers };
      if (!savedAnswers || typeof savedAnswers !== 'object') {
        return normalizedAnswers;
      }
      this.effectiveExercises.forEach((exercise) => {
        const saved = savedAnswers[exercise.id];
        if (saved === undefined) {
          return;
        }
        if (this.getExerciseType(exercise) === 'multiple_choice') {
          const options = this.getOptions(exercise);
          if (typeof saved === 'number' && Number.isFinite(saved) && saved >= 0 && saved < options.length) {
            normalizedAnswers[exercise.id] = Math.trunc(saved);
            return;
          }
          const savedText = String(saved ?? '').trim();
          const restoredIndex = options.findIndex((option) => String(option).trim() === savedText);
          normalizedAnswers[exercise.id] = restoredIndex >= 0 ? restoredIndex : '';
          return;
        }
        if (this.getExerciseType(exercise) === 'gap_fill') {
          const gapCount = this.getGapCount(exercise);
          const values = Array.isArray(saved) ? saved.slice(0, gapCount) : [];
          while (values.length < gapCount) {
            values.push('');
          }
          normalizedAnswers[exercise.id] = values;
          return;
        }
        if (typeof saved === 'string' || typeof saved === 'number') {
          normalizedAnswers[exercise.id] = saved;
        }
      });
      return normalizedAnswers;
    },
    restoreLessonState() {
      const serverState = this.lesson?.progress?.lessonState;
      const localFallbackState = this.readLocalLessonState();
      const parsedState = serverState
        && typeof serverState === 'object'
        && !Array.isArray(serverState)
        && serverState.version === LESSON_STATE_VERSION
        ? serverState
        : localFallbackState;

      if (!parsedState || parsedState.version !== LESSON_STATE_VERSION) {
        // Ohne gespeicherten Zustand soll eine Lektion immer im Üben-/Lernen-Flow starten,
        // sonst bleibt z.B. der Kapitel-Prüfungs-Tab "kleben" (u.a. nach Reset).
        this.activeTab = 'learn';
        this.lessonStatePersistenceReady = true;
        return;
      }

      this.exerciseAnswers = this.normalizePersistedExerciseAnswers(parsedState.exerciseAnswers);

      const restoredResults = {};
      this.effectiveExercises.forEach((exercise) => {
        restoredResults[exercise.id] = Object.prototype.hasOwnProperty.call(parsedState.exerciseResults || {}, exercise.id)
          ? parsedState.exerciseResults[exercise.id]
          : null;
      });
      this.exerciseResults = restoredResults;

      this.exercisePreparationCompleted = Boolean(parsedState.exercisePreparationCompleted);
      this.lessonPrepStage = Math.min(2, Math.max(0, Number(parsedState.lessonPrepStage) || 0));
      this.lessonPrepIndex = Math.max(0, Math.min(this.prepItems.length - 1, Number(parsedState.lessonPrepIndex) || 0));
      this.activeTab = parsedState.activeTab === 'exercises' ? 'exercises' : 'learn';

      this.vocabTrainerActive = Boolean(parsedState.vocabTrainerActive);
      this.vocabTrainerMode = parsedState.vocabTrainerMode === 'typing' ? 'typing' : 'multiple_choice';
      this.vocabTrainerAutoSwitchedToTyping = Boolean(parsedState.vocabTrainerAutoSwitchedToTyping);
      this.vocabTrainerCorrect = Math.max(0, Number(parsedState.vocabTrainerCorrect) || 0);
      this.vocabTrainerWrong = Math.max(0, Number(parsedState.vocabTrainerWrong) || 0);
      this.vocabTrainerTotalAttempts = Math.max(0, Number(parsedState.vocabTrainerTotalAttempts) || 0);
      this.vocabTrainerStats = parsedState.vocabTrainerStats && typeof parsedState.vocabTrainerStats === 'object'
        ? parsedState.vocabTrainerStats
        : {};
      this.vocabTrainerCurrentAttempts = Math.max(0, Number(parsedState.vocabTrainerCurrentAttempts) || 0);
      this.vocabTrainerReviewAttempts = Math.max(0, Number(parsedState.vocabTrainerReviewAttempts) || 0);
      this.exerciseRetryPending = Boolean(parsedState.exerciseRetryPending);
      this.exerciseRetryPendingSinceAttempts = Math.max(0, Number(parsedState.exerciseRetryPendingSinceAttempts) || 0);
      const maxIdx = Math.max(0, this.scrambledChapterExamExercises.length - 1);
      this.exerciseSequentialIndex = Math.max(0, Math.min(maxIdx, Number(parsedState.exerciseSequentialIndex) || 0));
      this.vocabTrainerMixedPool = this._buildMixedPool();
      const knownRepeatKeys = new Set([...this.trainableLessonVocab, ...this.vocabTrainerMixedPool].map((entry) => this.getVocabKey(entry)));
      this.vocabTrainerRepeatQueue = this.normalizeRepeatQueue(parsedState.vocabTrainerRepeatQueue)
        .filter((entry) => knownRepeatKeys.has(entry.key));
      this.vocabTrainerMixedAttempts = 0;
      this.vocabTrainerPhase = this.hasPreviousVocab && this.currentReviewShare > 0 ? 'mixed' : 'current';
      this.currentVocabQuestion = null;
      this.vocabTrainerChoiceOptions = [];
      this.vocabTrainerAnswer = '';
      this.vocabTrainerSelectedChoice = null;
      this.vocabTrainerAnswered = false;
      this.vocabTrainerLastCorrect = false;
      this.vocabTrainerDirection = 'L2R';

      this.vocabTrainerPool = this.vocabTrainerMode === 'typing'
        ? [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool]
        : [...this.trainableLessonVocab];

      this.updateExerciseUnlockState();

      if (this.$route.query.assistant || this.$route.query.tab === 'learn') {
        this.activeTab = 'learn';
      } else if (this.$route.query.tab === 'exercises' && this.canAccessExercises) {
        this.activeTab = 'exercises';
      } else if (this.activeTab === 'exercises' && !this.canAccessExercises) {
        this.activeTab = 'learn';
      }

      this.syncExerciseSequentialIndexToFirstUnanswered();

      this.lessonStatePersistenceReady = true;
      if (this.vocabTrainerActive && this.vocabTrainerPool.length > 0) {
        this.$nextTick(() => {
          if (this.vocabTrainerActive && !this.currentVocabQuestion) {
            this.nextVocabQuestion();
          }
        });
      }
      this.persistLessonState();
    },
    normalizeLessonVocabTerm(value) {
      return String(value || '')
        .trim()
        .toLowerCase()
        .replace(/\s+/g, ' ')
        .replace(/^[.,!?;:]+|[.,!?;:]+$/g, '')
        .trim();
    },
    lessonVocabWordCount(value) {
      return String(value || '')
        .trim()
        .replace(/[\p{P}\p{S}]+/gu, ' ')
        .split(/\s+/)
        .filter(Boolean)
        .length;
    },
    looksLikeLessonVocabFragmentMismatch(left, right) {
      const leftWords = this.lessonVocabWordCount(left);
      const rightWords = this.lessonVocabWordCount(right);
      const leftText = String(left || '').trim();
      const rightText = String(right || '').trim();
      const leftLooksSentence = leftWords >= 3 || /[?!.].+\S/.test(leftText);
      const rightLooksSentence = rightWords >= 3 || /[?!.].+\S/.test(rightText);
      const leftLooksShortFragment = leftWords <= 1 && leftText.length <= 12;
      const rightLooksShortFragment = rightWords <= 1 && rightText.length <= 12;

      return (leftLooksShortFragment && rightLooksSentence) || (rightLooksShortFragment && leftLooksSentence);
    },
    isTrainableLessonVocabPair(learning, reference) {
      const l = String(learning || '').trim();
      const r = String(reference || '').trim();
      if (!l || !r || this.normalizeLessonVocabTerm(l) === this.normalizeLessonVocabTerm(r)) {
        return false;
      }
      return !this.looksLikeLessonVocabFragmentMismatch(l, r);
    },
    normalizeCorePatternEntry(p) {
      if (p && typeof p === 'object' && p.target) {
        return {
          target: String(p.target).trim(),
          gloss: String(p.gloss || '').trim()
        };
      }
      const s = String(p || '').trim();
      if (!s) return null;
      const i = s.indexOf('|');
      if (i !== -1) {
        return {
          target: s.slice(0, i).trim(),
          gloss: s.slice(i + 1).trim()
        };
      }
      return { target: s, gloss: '' };
    },
    corePatternToDisplayString(p) {
      const n = this.normalizeCorePatternEntry(p);
      if (!n) return '';
      return n.gloss ? `${n.target} (${n.gloss})` : n.target;
    },
    _deterministicShuffle(arr, seed) {
      const out = arr.slice();
      let s = Number(seed) || 1;
      const rnd = () => {
        s = (s * 1103515245 + 12345) & 0x7fffffff;
        return s / 0x7fffffff;
      };
      for (let i = out.length - 1; i > 0; i--) {
        const j = Math.floor(rnd() * (i + 1));
        [out[i], out[j]] = [out[j], out[i]];
      }
      return out;
    },
    exercisePanelDisplayNumber(panelIndex) {
      if (this.sequentialPanelActive) {
        return this.exerciseSequentialIndex + 1;
      }
      return panelIndex + 1;
    },
    stepExercisePanel(delta) {
      const list = this.scrambledChapterExamExercises;
      if (!list.length) return;
      const next = this.exerciseSequentialIndex + delta;
      if (next < 0 || next >= list.length) return;
      this.exerciseSequentialIndex = next;
      this.persistLessonState();
    },
    /**
     * Kapitel-Prüfung (eine Frage pro Schritt): Wenn gespeicherte Antworten existieren, aber der
     * Index noch auf einer schon beantworteten Frage steht, zur ersten noch offenen Frage springen.
     */
    syncExerciseSequentialIndexToFirstUnanswered() {
      const list = this.scrambledChapterExamExercises;
      if (!list.length || !this.sequentialPanelActive) {
        return;
      }
      const firstUnanswered = list.findIndex((exercise) => !this.exerciseResults[exercise.id]);
      if (firstUnanswered === -1) {
        return;
      }
      this.exerciseSequentialIndex = firstUnanswered;
    },
    confirmExerciseReinforcement() {
      this.showExerciseReinforcementDialog = false;
      if (this.exerciseReinforcementPrepMode) {
        this.lessonPrepStage = 0;
        this.lessonPrepIndex = 0;
      } else {
        this.exerciseRetryPending = true;
        this.exerciseRetryPendingSinceAttempts = this.vocabTrainerTotalAttempts;
      }
      this.exerciseReinforcementPrepMode = false;
      this.exerciseReinforcementCorrectAnswer = '';
      this.exerciseReinforcementMessage = '';
      this.activeTab = 'learn';
      this.$nextTick(() => {
        const scrollEl = document.querySelector('.app-content__scroll.contentscroll');
        if (scrollEl) scrollEl.scrollTop = 0;
        else window.scrollTo(0, 0);
      });
      this.persistLessonState();
    },
    closeExerciseReinforcementDialog() {
      this.showExerciseReinforcementDialog = false;
      this.exerciseReinforcementPrepMode = false;
      this.exerciseReinforcementCorrectAnswer = '';
      this.exerciseReinforcementMessage = '';
    },
    closeChapterExamFailedDialog() {
      this.showChapterExamFailedDialog = false;
    },
    formatExerciseAnswerForReview(exercise, rawAnswer) {
      if (rawAnswer === null || rawAnswer === undefined) return '';
      const type = this.getExerciseType(exercise);
      if (type === 'multiple_choice') {
        const options = this.getOptions(exercise);
        const idx = Number(rawAnswer);
        if (Number.isInteger(idx) && idx >= 0 && idx < options.length) {
          return String(options[idx] ?? '');
        }
      }
      if (Array.isArray(rawAnswer)) {
        return rawAnswer
          .map((v) => String(v || '').trim())
          .filter(Boolean)
          .join(' | ');
      }
      return String(rawAnswer || '').trim();
    },
    buildChapterExamFailedDetails() {
      const all = this.effectiveExercises;
      if (!Array.isArray(all) || all.length === 0) return [];
      const out = [];
      all.forEach((exercise) => {
        const result = this.exerciseResults[exercise.id];
        if (!result || result.correct) return;
        const question = String(this.getQuestionText(exercise) || exercise.title || '').trim();
        const userAnswerRaw = this.exerciseAnswers[exercise.id];
        out.push({
          id: exercise.id,
          question,
          userAnswer: this.formatExerciseAnswerForReview(exercise, userAnswerRaw),
          correctAnswer: String(result.correctAnswer || '').trim()
        });
      });
      return out;
    },
    clearChapterExamAttemptState() {
      const all = this.effectiveExercises;
      if (!all.length) return;
      const results = { ...this.exerciseResults };
      const answers = { ...this.exerciseAnswers };
      all.forEach((e) => {
        delete results[e.id];
        delete answers[e.id];
      });
      this.exerciseResults = results;
      this.exerciseAnswers = answers;
      this.exerciseSequentialIndex = 0;
      this.buildMcRandomizedOptions();
    },
    handleChapterExamFailed(score) {
      this.chapterExamFailedDetails = this.buildChapterExamFailedDetails();
      this.exerciseRetryPending = true;
      this.exerciseRetryPendingSinceAttempts = this.vocabTrainerTotalAttempts;
      this.clearChapterExamAttemptState();
      this.chapterExamFailedScore = score;
      this.showChapterExamFailedDialog = true;
      this.activeTab = 'learn';
      this.$nextTick(() => {
        const scrollEl = document.querySelector('.app-content__scroll.contentscroll');
        if (scrollEl) scrollEl.scrollTop = 0;
        else window.scrollTo(0, 0);
      });
      this.persistLessonState();
    },
    finalizeChapterExamIfComplete() {
      const allExercises = this.effectiveExercises;
      if (!this.lesson || !allExercises.length) return;
      const allAnswered = allExercises.every((exercise) => Boolean(this.exerciseResults[exercise.id]));
      if (!allAnswered) return;

      const correctExercises = allExercises.filter((exercise) => this.exerciseResults[exercise.id]?.correct).length;
      const score = Math.round((correctExercises / allExercises.length) * 100);
      const target = this.exerciseTargetScore;

      if (score >= target && !this.exerciseNeedsReinforcement) {
        void this.checkLessonCompletion();
        return;
      }
      this.handleChapterExamFailed(score);
    },
    afterChapterExamAnswerSubmitted(exerciseId) {
      this.$nextTick(() => {
        if (this.sequentialPanelActive) {
          const list = this.scrambledChapterExamExercises;
          const idx = list.findIndex((e) => e.id === exerciseId);
          const result = this.exerciseResults[exerciseId];
          // Nur bei richtiger Antwort automatisch weiter: Bei Fehler bleibt die Karte sichtbar,
          // damit „Falsch“ und die Musterlösung angezeigt werden; Weiter geht es per Nav-Button.
          if (
            idx >= 0
            && idx < list.length - 1
            && result
            && result.correct
          ) {
            this.exerciseSequentialIndex = idx + 1;
          }
        }
        this.persistLessonState();
        this.finalizeChapterExamIfComplete();
      });
    },
    advancePrepPass() {
      if (this.lessonPrepStage >= 2 || this.prepItems.length === 0) {
        return;
      }
      if (!this.isLastPrepItemInPass) {
        this.lessonPrepIndex += 1;
        return;
      }
      this.lessonPrepStage += 1;
      this.lessonPrepIndex = 0;
    },
    openExercisesTab() {
      if (!this.canAccessExercises) {
        this.activeTab = 'learn';
        this.showErrorDialog = true;
        this.errorMessage = this.exerciseUnlockHint;
        return;
      }
      this.activeTab = 'exercises';
      this.$nextTick(() => {
        this.syncExerciseSequentialIndexToFirstUnanswered();
        const scrollEl = document.querySelector('.app-content__scroll.contentscroll');
        if (scrollEl) {
          scrollEl.scrollTop = 0;
        } else {
          window.scrollTo(0, 0);
        }
      });
    },
    updateExerciseUnlockState() {
      if (this.exerciseRetryPending && this.exerciseRetryRemainingAttempts <= 0) {
        this.exerciseRetryPending = false;
      }
      if (this.exercisePreparationCompleted) {
        return;
      }
      if (!this.hasExercises) {
        this.exercisePreparationCompleted = true;
        return;
      }

      const minimumAttempts = this.trainerExerciseUnlockAttempts;
      const successRate = this.vocabTrainerTotalAttempts > 0
        ? (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100
        : 0;
      const currentLessonReady = this.vocabTrainerCurrentAttempts >= this.trainerNewFocusTarget;

      if (
        currentLessonReady
        && this.vocabTrainerTotalAttempts >= minimumAttempts
        && successRate >= EXERCISE_UNLOCK_MIN_SUCCESS_PERCENT
      ) {
        this.exercisePreparationCompleted = true;
      }
    },
    _extractVocabFromExercises(exercises) {
      // Sicherstellen, dass exercises ein Array ist
      if (!exercises) {
        console.warn('[_extractVocabFromExercises] exercises ist null/undefined:', exercises);
        return [];
      }
      
      // Konvertiere Vue Proxy oder Objekt zu Array
      let exercisesArray = exercises;
      
      // Prüfe ob es bereits ein Array ist (auch Vue Proxy-Arrays)
      const isArrayLike = exercises.length !== undefined && typeof exercises.length === 'number';
      
      if (!Array.isArray(exercises) && isArrayLike) {
        debugLog('[_extractVocabFromExercises] exercises ist kein Array, aber array-like, konvertiere...');
        // Versuche Array.from (funktioniert mit iterierbaren Objekten und Array-like Objekten)
        try {
          exercisesArray = Array.from(exercises);
        } catch (e) {
          // Fallback: Manuelle Konvertierung
          try {
            exercisesArray = [];
            for (let i = 0; i < exercises.length; i++) {
              exercisesArray.push(exercises[i]);
            }
          } catch (e2) {
            console.error('[_extractVocabFromExercises] Kann exercises nicht zu Array konvertieren:', exercises, e2);
            return [];
          }
        }
      } else if (!Array.isArray(exercises)) {
        console.error('[_extractVocabFromExercises] exercises ist weder Array noch array-like:', exercises);
        return [];
      }
      
      const vocabMap = new Map();
      
      exercisesArray.forEach((exercise, idx) => {
          try {
            debugLog(`[importantVocab] Verarbeite Übung ${idx + 1}:`, exercise.title);
            // Extrahiere aus questionData
            const qData = this.getQuestionData(exercise);
            const aData = this.getAnswerData(exercise);
            
            debugLog(`[importantVocab] qData:`, qData);
            debugLog(`[importantVocab] aData:`, aData);
            
            if (qData && aData) {
              // Für Multiple Choice: Extrahiere Optionen und richtige Antwort
              if (this.getExerciseType(exercise) === 'multiple_choice') {
                const options = qData.options || [];
                const correctIndex = aData.correctAnswer !== undefined ? aData.correctAnswer : (aData.correct || 0);
                const correctAnswer = options[correctIndex] || '';
                
                debugLog(`[importantVocab] Multiple Choice - options:`, options, `correctIndex:`, correctIndex, `correctAnswer:`, correctAnswer);
                
                if (correctAnswer) {
                  // Versuche die Frage zu analysieren (z.B. "Wie sagt man 'X' auf Bisaya?" oder "Was bedeutet 'X'?")
                  const question = qData.question || qData.text || '';
                  debugLog(`[importantVocab] Frage:`, question);
                  
                  // Pattern 1: Muttersprache → Zielsprache (MC: richtige Option = Zielsprache)
                  let match = question.match(/Wie sagt man ['"]([^'"]+)['"]/i);
                  if (!match) match = question.match(/Wie heißt ['"]([^'"]+)['"]/i);
                  if (!match) match = question.match(/How do you say ['"]([^'"]+)['"]/i);
                  if (match) {
                    const nativeWord = match[1]; // Das Wort in der Muttersprache
                    // Nur hinzufügen, wenn Muttersprache und Bisaya unterschiedlich sind (verhindert "ko" -> "ko")
                    if (nativeWord && correctAnswer && nativeWord.trim() !== correctAnswer.trim()) {
                      debugLog(`[importantVocab] Pattern 1 gefunden - Muttersprache:`, nativeWord, `Bisaya:`, correctAnswer);
                      // learning = Muttersprache (was man lernt), reference = Bisaya (Zielsprache)
                      vocabMap.set(`${nativeWord}-${correctAnswer}`, { learning: nativeWord, reference: correctAnswer });
                    } else {
                      debugLog(`[importantVocab] Pattern 1 übersprungen - Muttersprache und Bisaya sind gleich:`, nativeWord, correctAnswer);
                    }
                  } else {
                    // Pattern 2: Zielsprache im Satz → richtige Option = Muttersprache
                    match = question.match(/Was bedeutet ['"]([^'"]+)['"]/i);
                    if (!match) match = question.match(/Was heißt ['"]([^'"]+)['"]/i);
                    if (match) {
                      const bisayaWord = match[1];
                      // Nur hinzufügen, wenn Bisaya und Muttersprache unterschiedlich sind (verhindert "ko" -> "ko")
                      if (bisayaWord && correctAnswer && bisayaWord.trim() !== correctAnswer.trim()) {
                        debugLog(`[importantVocab] Pattern 2 gefunden - Bisaya:`, bisayaWord, `Muttersprache:`, correctAnswer);
                        // learning = Muttersprache (was man lernt), reference = Bisaya (Zielsprache)
                        vocabMap.set(`${correctAnswer}-${bisayaWord}`, { learning: correctAnswer, reference: bisayaWord });
                      } else {
                        debugLog(`[importantVocab] Pattern 2 übersprungen - Bisaya und Muttersprache sind gleich:`, bisayaWord, correctAnswer);
                      }
                    } else {
                      debugLog(`[importantVocab] Kein Pattern gefunden, Überspringe diese Übung`);
                      // Überspringe, wenn wir die Richtung nicht erkennen können
                    }
                  }
                }
              }
              
              // Für Gap Fill: Extrahiere richtige Antworten
              if (this.getExerciseType(exercise) === 'gap_fill') {
                const answers = aData.answers || (aData.correct ? (Array.isArray(aData.correct) ? aData.correct : [aData.correct]) : []);
                debugLog(`[importantVocab] Gap Fill - answers:`, answers);
                if (answers.length > 0) {
                  // Versuche aus dem Text Kontext zu extrahieren
                  // Gap Fill hat normalerweise Format: "{gap} (Muttersprache) | {gap} (Muttersprache) | ..."
                  const text = qData.text || '';
                  // Extrahiere Wörter in Klammern als Muttersprache
                  const matches = text.matchAll(/\(([^)]+)\)/g);
                  const nativeWords = Array.from(matches, m => m[1]);
                  
                  debugLog(`[importantVocab] Gap Fill - text:`, text, `nativeWords:`, nativeWords);
                  
                  // Nur extrahieren, wenn Muttersprache-Hinweise (Klammern) vorhanden sind
                  if (nativeWords.length > 0) {
                    // Wenn möglich: rekonstruiere die vollständige Phrase um die Lücke herum.
                    // Beispiel: "{gap} ka mubalik? (Bitte wiederholen)" + "Palihug" => "Palihug ka mubalik?"
                    const stripHints = (s) => String(s || '')
                      .replace(/\([^)]*\)/g, '')
                      .replace(/\[[^\]]*\]/g, '')
                      .replace(/（[^）]*）/g, '');
                    const stopChars = [',', '.', '|', ';', ':', '\n'];
                    const lastStopIndex = (s) => Math.max(...stopChars.map((c) => s.lastIndexOf(c)));
                    const firstStopIndex = (s) => {
                      const indices = stopChars
                        .map((c) => s.indexOf(c))
                        .filter((idx) => idx >= 0);
                      return indices.length ? Math.min(...indices) : -1;
                    };
                    const parts = String(text).split('{gap}');

                    answers.forEach((answer, index) => {
                      if (answer && answer.trim()) {
                        const nativeWord = nativeWords[index];
                        const nativeText = String(nativeWord || '').trim();
                        const answerText = String(answer || '').trim();
                        const nativeWordCount = nativeText.split(/\s+/).filter(Boolean).length;
                        const answerWordCount = answerText.split(/\s+/).filter(Boolean).length;
                        const nativeLooksSentence = /[?.!]/.test(nativeText) || nativeWordCount >= 4;
                        const answerLooksShortToken = answerWordCount <= 2;
                        const likelyFragmentMismatch = nativeLooksSentence && answerLooksShortToken;
                        // Prefer full phrase reconstruction when we have matching "{gap}" placeholders.
                        let reconstructed = '';
                        if (parts.length === answers.length + 1) {
                          const leftRaw = stripHints(parts[index] || '');
                          const rightRaw = stripHints(parts[index + 1] || '');
                          const leftCut = lastStopIndex(leftRaw);
                          const leftTail = leftCut >= 0 ? leftRaw.slice(leftCut + 1) : leftRaw;
                          const rightCut = firstStopIndex(rightRaw);
                          const rightHead = rightCut >= 0 ? rightRaw.slice(0, rightCut) : rightRaw;
                          reconstructed = `${leftTail}${answerText}${rightHead}`
                            .replace(/\s+/g, ' ')
                            .trim();
                        }

                        const reconstructedIsUseful =
                          reconstructed &&
                          reconstructed !== answerText &&
                          (reconstructed.includes(' ') || /[?!]/.test(reconstructed));

                        if (nativeText && (reconstructedIsUseful || answerText) && nativeText !== answerText && !likelyFragmentMismatch) {
                          const targetText = reconstructedIsUseful ? reconstructed : answerText;
                          // Sicherheitsfilter: wir wollen primär echte Phrasen, nicht einzelne Tokens als "Vokabelkarte".
                          const targetWordCount = targetText.split(/\s+/).filter(Boolean).length;
                          if (targetWordCount < 2 && !/[?!]/.test(targetText)) {
                            debugLog(`[importantVocab] Gap Fill übersprungen - Ziel wirkt zu kurz:`, nativeText, targetText);
                            return;
                          }
                          // Muttersprache (learning) -> Zielsprache (reference)
                          vocabMap.set(`${nativeText}-${targetText}`, { learning: nativeText, reference: targetText });
                          debugLog(`[importantVocab] Gap Fill extrahiert - Muttersprache:`, nativeText, `Bisaya:`, targetText);
                        } else {
                          debugLog(`[importantVocab] Gap Fill übersprungen - Satz/Fragment-Mismatch oder gleich:`, nativeText, answerText, reconstructed);
                        }
                      }
                    });
                  } else {
                    debugLog(`[importantVocab] Gap Fill übersprungen - keine Muttersprache-Hinweise (Klammern) gefunden`);
                  }
                }
              }
            }
          } catch (e) {
            console.warn('Fehler beim Extrahieren von Vokabeln aus Übung:', e, exercise);
          }
        });
      
      const result = Array.from(vocabMap.values());
      debugLog(`[_extractVocabFromExercises] Ergebnis:`, result.length, 'Vokabeln');
      return result;
    },
    async loadLesson() {
      // Verhindere mehrfaches Laden
      if (this.loading) {
        debugLog('[VocabLessonView] loadLesson übersprungen - bereits am Laden');
        return;
      }
      
      debugLog('[VocabLessonView] loadLesson gestartet für lessonId:', this.lessonId);
      this.loading = true;
      this.lessonStatePersistenceReady = false;
      if (this.lessonStateSaveTimer) {
        window.clearTimeout(this.lessonStateSaveTimer);
        this.lessonStateSaveTimer = null;
      }
      this.pendingLessonStatePayload = null;
      // Setze Antworten und Ergebnisse zurück
      this.exerciseAnswers = {};
      this.exerciseResults = {};
      this.assistantMessages = [];
      this.assistantInput = '';
      this.assistantError = '';
      this.exerciseRetryPending = false;
      this.exerciseRetryPendingSinceAttempts = 0;
      this.exerciseSequentialIndex = 0;
      this.dailyEnoughBannerDismissed = false;
      this.vocabOverviewPage = 1;
      this.exercisePreparationCompleted = false;
      this.lessonPrepStage = 0;
      this.lessonPrepIndex = 0;
      this.vocabTrainerActive = false;
      this.vocabTrainerPool = [];
      this.vocabTrainerMixedPool = [];
      this.vocabTrainerRepeatQueue = [];
      this.vocabTrainerPhase = 'current';
      this.vocabTrainerCurrentAttempts = 0;
      this.vocabTrainerReviewAttempts = 0;
      this.courseProgressList = [];
      this.courseLanguageName = '';
      this.courseNativeLanguageName = '';
      this.distractorPool = { target: [], native: [] };
      this.mcRandomizedOptions = {};
      // Reset Flags
      this.isCheckingLessonCompletion = false;
      this.isNavigatingToNext = false;
      
      // Prüfe ob 'tab' Query-Parameter vorhanden ist (für Navigation zur nächsten Lektion)
      const tabParam = this.$route.query.tab;
      if (tabParam === 'learn') {
        this.activeTab = 'learn';
      }
      if (tabParam === 'exercises') {
        this.activeTab = 'learn';
      }
      if (this.$route.query.assistant) {
        this.activeTab = 'learn';
      }
      
      try {
        const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
        this.lesson = res.data;
        await this.loadCourseLanguageNames();
        await this.loadCourseProgressForBoost();
        debugLog('[VocabLessonView] Geladene Lektion:', this.lesson?.id, this.lesson?.title);
        if (this.$route.query.assistant) {
          this.$nextTick(() => {
            this.focusAssistantCard();
          });
        }
        try {
          const poolRes = await apiClient.get(`/api/vocab/courses/${this.courseId}/distractor-pool`, {
            params: { beforeLessonId: this.lessonId }
          });
          this.distractorPool = poolRes.data || { target: [], native: [] };
        } catch (poolErr) {
          console.warn('[VocabLessonView] Distraktor-Pool nicht geladen:', poolErr);
          this.distractorPool = { target: [], native: [] };
        }
        await this.$nextTick();
        let exercises = this.effectiveExercises;
        if (!exercises || exercises.length === 0) {
          debugLog('[VocabLessonView] Lade Übungen separat...');
          await this.loadGrammarExercises();
          exercises = this.effectiveExercises;
        }
        if (exercises && exercises.length > 0) {
          debugLog('[VocabLessonView] Übungen für Kapitel-Prüfung:', exercises.length);
          this.initializeExercises(exercises);
          this.buildMcRandomizedOptions();
        }
        this.restoreLessonState();
        debugLog('[VocabLessonView] loadLesson abgeschlossen');
      } catch (e) {
        console.error('[VocabLessonView] Fehler beim Laden der Lektion:', e);
        this.lessonStatePersistenceReady = true;
      } finally {
        this.loading = false;
      }
    },
    async loadCourseProgressForBoost() {
      try {
        const { data } = await apiClient.get(`/api/vocab/courses/${this.courseId}/progress`);
        this.courseProgressList = Array.isArray(data) ? data : [];
      } catch (e) {
        this.courseProgressList = [];
      }
    },
    async loadCourseLanguageNames() {
      try {
        const { data } = await apiClient.get(`/api/vocab/courses/${this.courseId}`);
        this.courseLanguageName = String(data?.languageName || '').trim();
        this.courseNativeLanguageName = String(data?.nativeLanguageName || '').trim();
      } catch (e) {
        this.courseLanguageName = '';
        this.courseNativeLanguageName = '';
      }
    },
    focusAssistantCard() {
      const target = this.$refs.assistantCard;
      if (!target || typeof target.scrollIntoView !== 'function') {
        return;
      }
      target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      this.isAssistantFocused = true;
      window.setTimeout(() => {
        this.isAssistantFocused = false;
      }, 2200);
    },
    async loadAssistantSettings() {
      this.assistantLoading = true;
      try {
        const { data } = await apiClient.get('/api/settings/llm');
        this.assistantSettings = data;
      } catch (e) {
        this.assistantSettings = null;
      } finally {
        this.assistantLoading = false;
      }
    },
    openLanguageAssistantSettings() {
      this.$router.push('/settings/language-assistant');
    },
    buildAssistantPrompt(preset) {
      const lessonTitle = this.lesson?.title || this.$t('socialnetwork.vocab.courses.thisLesson');
      const firstPattern = this.lessonDidactics.corePatterns?.[0];
      const firstPatternStr = firstPattern ? this.corePatternToDisplayString(firstPattern) : '';
      const firstGrammar = this.lessonDidactics.grammarFocus?.[0]?.text;

      if (preset === 'explain') {
        return `${this.$t('socialnetwork.vocab.courses.languageAssistantPresetExplainStart')} "${lessonTitle}". ${firstPatternStr ? `${this.$t('socialnetwork.vocab.courses.languageAssistantPatternHint')} ${firstPatternStr}.` : ''} ${firstGrammar || ''}`.trim();
      }
      if (preset === 'correct') {
        return this.$t('socialnetwork.vocab.courses.languageAssistantPresetCorrectStart', { lesson: lessonTitle });
      }
      return this.$t('socialnetwork.vocab.courses.languageAssistantPresetPracticeStart', { lesson: lessonTitle });
    },
    async sendPresetPrompt(preset) {
      this.assistantMode = preset === 'explain' ? 'explain' : (preset === 'correct' ? 'correct' : 'practice');
      await this.sendAssistantMessage(this.buildAssistantPrompt(preset));
    },
    async sendAssistantMessage(customMessage = null) {
      const message = String(customMessage || this.assistantInput || '').trim();
      if (!message || this.assistantSubmitting || !this.assistantAvailable) {
        return;
      }

      this.assistantError = '';
      this.assistantSubmitting = true;
      this.startAssistantWaitTimer();
      this.assistantMessages.push({ role: 'user', content: message });
      if (!customMessage) {
        this.assistantInput = '';
      }

      try {
        const history = this.assistantMessages.slice(0, -1).slice(-8);
        const { data } = await apiClient.post(`/api/vocab/lessons/${this.lessonId}/assistant`, {
          message,
          mode: this.assistantMode,
          history
        });
        this.assistantMessages.push({
          role: 'assistant',
          content: data.reply
        });
      } catch (e) {
        this.assistantError = e.response?.data?.error || e.message || this.$t('socialnetwork.vocab.courses.languageAssistantError');
      } finally {
        this.assistantSubmitting = false;
        this.stopAssistantWaitTimer();
      }
    },
    startAssistantWaitTimer() {
      this.stopAssistantWaitTimer();
      this.assistantWaitStartedAt = Date.now();
      this.assistantWaitElapsedSeconds = 0;
      this.assistantWaitTimer = window.setInterval(() => {
        if (!this.assistantWaitStartedAt) {
          this.assistantWaitElapsedSeconds = 0;
          return;
        }
        this.assistantWaitElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.assistantWaitStartedAt) / 1000));
      }, 1000);
    },
    stopAssistantWaitTimer() {
      if (this.assistantWaitTimer) {
        window.clearInterval(this.assistantWaitTimer);
        this.assistantWaitTimer = null;
      }
      this.assistantWaitStartedAt = null;
      this.assistantWaitElapsedSeconds = 0;
    },
    initializeExercises(exercises) {
      // Initialisiere Antwort-Arrays für Gap Fill Übungen
      exercises.forEach(exercise => {
        const exerciseType = this.getExerciseType(exercise);
        if (exerciseType === 'gap_fill') {
          const gapCount = this.getGapCount(exercise);
          this.exerciseAnswers[exercise.id] = new Array(gapCount).fill('');
        } else {
          this.exerciseAnswers[exercise.id] = '';
        }
        this.exerciseResults[exercise.id] = null;
      });
    },
    async loadGrammarExercises() {
      try {
        const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}/grammar-exercises`);
        const exercises = res.data || [];
        this.lesson.grammarExercises = exercises;
      } catch (e) {
        console.error('Konnte Grammatik-Übungen nicht laden:', e);
        this.lesson.grammarExercises = [];
      }
    },
    getExerciseType(exercise) {
      // Hole den Typ aus questionData oder exerciseType
      if (exercise.questionData) {
        const qData = typeof exercise.questionData === 'string' 
          ? JSON.parse(exercise.questionData) 
          : exercise.questionData;
        return qData.type;
      }
      // Fallback: Prüfe exerciseTypeId
      const typeMap = {
        1: 'gap_fill',
        2: 'multiple_choice',
        3: 'sentence_building',
        4: 'transformation',
        5: 'conjugation',
        6: 'declension',
        7: 'reading_aloud',
        8: 'speaking_from_memory',
        9: 'dialog_completion',
        10: 'situational_response',
        11: 'pattern_drill'
      };
      return typeMap[exercise.exerciseTypeId] || 'unknown';
    },
    getLessonTypeLabel(lessonType) {
      const labels = {
        vocab: this.$t('socialnetwork.vocab.courses.lessonTypeVocab'),
        grammar: this.$t('socialnetwork.vocab.courses.lessonTypeGrammar'),
        conversation: this.$t('socialnetwork.vocab.courses.lessonTypeConversation'),
        culture: this.$t('socialnetwork.vocab.courses.lessonTypeCulture'),
        review: this.$t('socialnetwork.vocab.courses.lessonTypeReview'),
        vocab_review: this.$t('socialnetwork.vocab.courses.lessonTypeReview')
      };
      return labels[lessonType] || lessonType || this.$t('socialnetwork.vocab.courses.lessonTypeVocab');
    },
    getPhaseLabel(phaseLabel) {
      switch (phaseLabel) {
        case 'quickstart':
          return this.$t('socialnetwork.vocab.courses.phaseQuickstart');
        case 'daily_life':
          return this.$t('socialnetwork.vocab.courses.phaseDailyLife');
        case 'stabilization':
          return this.$t('socialnetwork.vocab.courses.phaseStabilization');
        default:
          return this.$t('socialnetwork.vocab.courses.phaseDefault');
      }
    },
    getDidacticModeLabel(didacticMode) {
      switch (didacticMode) {
        case 'core_input':
          return this.$t('socialnetwork.vocab.courses.didacticModeCoreInput');
        case 'guided_dialogue':
          return this.$t('socialnetwork.vocab.courses.didacticModeGuidedDialogue');
        case 'contrast_training':
          return this.$t('socialnetwork.vocab.courses.didacticModeContrastTraining');
        case 'pattern_drill':
          return this.$t('socialnetwork.vocab.courses.didacticModePatternDrill');
        case 'real_life_scenario':
          return this.$t('socialnetwork.vocab.courses.didacticModeRealLifeScenario');
        case 'intensive_review':
          return this.$t('socialnetwork.vocab.courses.didacticModeIntensiveReview');
        case 'checkpoint':
          return this.$t('socialnetwork.vocab.courses.didacticModeCheckpoint');
        default:
          return this.$t('socialnetwork.vocab.courses.didacticModeFocusDefault');
      }
    },
    getReviewStageLabel(progress) {
      const stage = Number(progress?.reviewStage || 0);
      if (stage === 0) return this.$t('socialnetwork.vocab.courses.reviewStageDay1');
      if (stage === 1) return this.$t('socialnetwork.vocab.courses.reviewStageDay3');
      if (stage === 2) return this.$t('socialnetwork.vocab.courses.reviewStageDay7');
      if (stage >= 3) return this.$t('socialnetwork.vocab.courses.reviewStageCompleted');
      return '';
    },
    formatTargetMinutes(targetMinutes) {
      const minutes = Number(targetMinutes);
      if (!minutes) {
        return this.$t('socialnetwork.vocab.courses.durationFlexible');
      }
      return this.$t('socialnetwork.vocab.courses.durationMinutes', { minutes });
    },
    formatLessonReviewDue(reviewNextDueAt) {
      if (!reviewNextDueAt) {
        return this.$t('socialnetwork.vocab.courses.reviewTimeNow');
      }
      const dueTimestamp = new Date(reviewNextDueAt).getTime();
      if (!Number.isFinite(dueTimestamp)) {
        return this.$t('socialnetwork.vocab.courses.reviewTimeNow');
      }
      const diffMs = dueTimestamp - Date.now();
      if (diffMs > 0) {
        const untilDays = Math.ceil(diffMs / (24 * 60 * 60 * 1000));
        if (untilDays <= 1) {
          return this.$t('socialnetwork.vocab.courses.reviewTimeTomorrow');
        }
        return this.$t('socialnetwork.vocab.courses.reviewTimeInDays', { count: untilDays });
      }
      const overdueDays = Math.floor((Date.now() - dueTimestamp) / (24 * 60 * 60 * 1000));
      if (overdueDays <= 0) {
        return this.$t('socialnetwork.vocab.courses.timeToday');
      }
      if (overdueDays === 1) {
        return this.$t('socialnetwork.vocab.courses.timeSinceOneDay');
      }
      return this.$t('socialnetwork.vocab.courses.timeSinceDays', { count: overdueDays });
    },
    getQuestionData(exercise) {
      if (!exercise.questionData) return null;
      return typeof exercise.questionData === 'string' 
        ? JSON.parse(exercise.questionData) 
        : exercise.questionData;
    },
    getAnswerData(exercise) {
      if (!exercise.answerData) return null;
      return typeof exercise.answerData === 'string' 
        ? JSON.parse(exercise.answerData) 
        : exercise.answerData;
    },
    _shuffleArray(arr) {
      const a = [...arr];
      for (let i = a.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
      }
      return a;
    },
    /**
     * Welche Sprache haben die richtigen MC-Antworten? (Backend: vocabService._resolveMcAnswerSide)
     * Nur über questionData.answerLanguage ('target'|'native') oder answerLanguageId (1|2).
     * Ohne diese Felder: 'unknown' — dann kein Distraktor-Randomize (bis Inhalte annotiert sind).
     */
    _resolveMcAnswerSide(questionData) {
      const qd = questionData || {};
      const raw = qd.answerLanguage;
      if (typeof raw === 'string') {
        const s = raw.trim().toLowerCase();
        if (s === 'target' || s === 'learning' || s === 'l2') return 'target';
        if (s === 'native' || s === 'l1') return 'native';
      }
      const id = qd.answerLanguageId;
      if (id === 1 || id === '1') return 'target';
      if (id === 2 || id === '2') return 'native';
      return 'unknown';
    },
    /**
     * Zufällige Distraktoren aus gelernten Vokabeln (vorherige Lektionen), gleiche Sprache wie die richtigen Optionen.
     * Deaktivierbar pro Übung: questionData.randomizeDistractors === false
     */
    randomizeMcOptionsIfPossible(exercise) {
      const q = this.getQuestionData(exercise);
      const a = this.getAnswerData(exercise);
      if (!q || !a || q.type !== 'multiple_choice' || q.randomizeDistractors === false) return null;
      const side = this._resolveMcAnswerSide(q);
      if (side === 'unknown') return null;
      const options = q.options || [];
      let correctIndices = [];
      if (a.correctAnswer !== undefined) {
        correctIndices = Array.isArray(a.correctAnswer) ? a.correctAnswer.map(Number) : [Number(a.correctAnswer)];
      } else if (a.correct !== undefined) {
        correctIndices = Array.isArray(a.correct) ? a.correct.map(Number) : [Number(a.correct)];
      }
      const correctTexts = correctIndices.map((i) => options[i]).filter((t) => t !== undefined && t !== null);
      if (!correctTexts.length) return null;
      const totalSlots = options.length;
      const need = totalSlots - correctTexts.length;
      if (need <= 0) return null;
      const norm = (s) => this.normalizeComparableText(s);
      const correctSet = new Set(correctTexts.map(norm));
      const poolRaw = side === 'target' ? this.distractorPool.target : this.distractorPool.native;
      let poolFiltered = poolRaw.filter((w) => w && !correctSet.has(norm(w)));
      poolFiltered = this._shuffleArray(poolFiltered);
      let picked = poolFiltered.slice(0, need);
      if (picked.length < need) {
        const wrongFromDb = options.filter((_, i) => !correctIndices.includes(i));
        const shuffledWrong = this._shuffleArray(wrongFromDb);
        for (const w of shuffledWrong) {
          if (picked.length >= need) break;
          if (!picked.some((p) => norm(p) === norm(w))) picked.push(w);
        }
      }
      if (picked.length < need) return null;
      const all = this._shuffleArray([...correctTexts, ...picked]);
      return { options: all, useTextAnswer: true };
    },
    /**
     * Wenn keine Distraktor-Ersetzung läuft (z. B. answerLanguage fehlt), nur die gegebenen
     * Optionen mischen. Richtige Antwort bleibt über Optionstext prüfbar (useTextAnswer).
     */
    shuffleMcOptionsDisplayOrder(exercise) {
      const q = this.getQuestionData(exercise);
      if (!q || q.type !== 'multiple_choice' || q.randomizeDistractors === false) {
        return null;
      }
      const options = q.options || [];
      if (options.length < 2) {
        return null;
      }
      return {
        options: this._shuffleArray(options),
        useTextAnswer: true
      };
    },
    buildMcRandomizedOptions() {
      this.mcRandomizedOptions = {};
      const exercises = this.effectiveExercises;
      if (!exercises) return;
      exercises.forEach((ex) => {
        if (this.getExerciseType(ex) !== 'multiple_choice') return;
        const fromPool = this.randomizeMcOptionsIfPossible(ex);
        if (fromPool) {
          this.mcRandomizedOptions[ex.id] = fromPool;
          return;
        }
        const orderOnly = this.shuffleMcOptionsDisplayOrder(ex);
        if (orderOnly) {
          this.mcRandomizedOptions[ex.id] = orderOnly;
        }
      });
    },
    getQuestionText(exercise) {
      const qData = this.getQuestionData(exercise);
      if (!qData) return exercise.title;
      
      if (qData.question) return qData.question;
      
      // Für Transformation-Übungen: Formatiere als "Übersetze 'X' ins Bisaya"
      if (qData.type === 'transformation' && qData.text) {
        const sourceLang = qData.sourceLanguage || 'Deutsch';
        const targetLang = qData.targetLanguage || 'Bisaya';
        return `Übersetze "${qData.text}" ins ${targetLang}`;
      }
      
      if (qData.text) return qData.text;
      return exercise.title;
    },
    getVisibleQuestionText(exercise) {
      const question = String(this.getQuestionText(exercise) || '').trim();
      if (!question) return '';
      const title = String(exercise?.title || '').trim();
      const instruction = String(exercise?.instruction || '').trim();
      const qNorm = this.normalizeComparableText(question);
      if (title && qNorm === this.normalizeComparableText(title)) return '';
      if (instruction && qNorm === this.normalizeComparableText(instruction)) return '';
      return question;
    },
    getOptions(exercise) {
      const custom = this.mcRandomizedOptions[exercise.id];
      if (custom && Array.isArray(custom.options) && custom.options.length > 0) {
        return custom.options;
      }
      const qData = this.getQuestionData(exercise);
      return qData?.options || [];
    },
    formatGapFill(exercise) {
      const qData = this.getQuestionData(exercise);
      if (!qData || !qData.text) return '';
      
      // Ersetze {gap} mit Platzhaltern
      return qData.text.replace(/\{gap\}/g, '<span class="gap">_____</span>');
    },
    getGapCount(exercise) {
      const qData = this.getQuestionData(exercise);
      if (!qData) return 0;
      
      // Zähle {gap} im Text
      const matches = qData.text?.match(/\{gap\}/g);
      return matches ? matches.length : (qData.gaps || 1);
    },
    getGapInputRef(exerciseId, index) {
      return `gap-input-${exerciseId}-${index}`;
    },
    onGapInputEnter(exercise, index) {
      const gapCount = this.getGapCount(exercise);
      // Bei mehreren Feldern: Enter springt weiter, beim letzten wird geprüft
      if (gapCount > 1 && index < gapCount - 1) {
        const nextRefKey = this.getGapInputRef(exercise.id, index + 1);
        const nextRef = this.$refs[nextRefKey];
        if (nextRef && nextRef.focus) {
          nextRef.focus();
        }
        return;
      }
      // Letztes Feld (oder nur ein Feld): Antwort prüfen auslösen
      this.checkAnswer(exercise.id);
    },
    onSingleInputEnter(exercise) {
      const answer = this.exerciseAnswers[exercise.id];
      if (!answer || (typeof answer === 'string' && !answer.trim())) {
        return;
      }
      this.checkAnswer(exercise.id);
    },
    hasAllGapsFilled(exercise) {
      const answers = this.exerciseAnswers[exercise.id];
      if (!answers || !Array.isArray(answers)) return false;
      const gapCount = this.getGapCount(exercise);
      return answers.length === gapCount && answers.every(a => a && a.trim());
    },
    async checkAnswer(exerciseId) {
      try {
        const exercise = this.effectiveExercises.find(e => e.id === exerciseId);
        if (!exercise) return;

        const exerciseType = this.getExerciseType(exercise);
        let answer = this.exerciseAnswers[exerciseId];

        // Formatiere Antwort je nach Typ
        if (exerciseType === 'gap_fill') {
          // Gap Fill: Array von Antworten
          if (!Array.isArray(answer)) {
            answer = [answer];
          }
        } else if (exerciseType === 'multiple_choice') {
          const ro = this.mcRandomizedOptions[exercise.id];
          if (ro && ro.useTextAnswer) {
            const opts = this.getOptions(exercise);
            const idx = Number(answer);
            const pick = opts[idx];
            answer = pick !== undefined && pick !== null ? String(pick) : '';
          } else {
            answer = Number(answer);
          }
        } else if (exerciseType === 'transformation' || exerciseType === 'sentence_building' || exerciseType === 'dialog_completion' || exerciseType === 'pattern_drill') {
          // Transformation: String
          answer = String(answer || '').trim();
        } else if (exerciseType === 'situational_response') {
          answer = String(answer || '').trim();
        } else if (exerciseType === 'reading_aloud' || exerciseType === 'speaking_from_memory') {
          // Reading Aloud / Speaking From Memory: Verwende erkannten Text
          answer = this.recognizedText[exerciseId] || String(answer || '').trim();
        }

        const res = await apiClient.post(`/api/vocab/grammar-exercises/${exerciseId}/check`, { answer });
        this.exerciseResults[exerciseId] = res.data;

        if (!res.data?.correct) {
          const correctText = res.data?.correctAnswer
            ? String(res.data.correctAnswer)
            : '';
          if (this.trainableLessonVocab.length === 0 && this.prepItems.length > 0) {
            this.exerciseReinforcementPrepMode = true;
            this.exerciseReinforcementCorrectAnswer = correctText;
            this.exerciseReinforcementMessage = this.$t('socialnetwork.vocab.courses.exercisePrepReinforcementHint');
            this.showExerciseReinforcementDialog = true;
            return;
          }
          // Kapitel-Prüfung: Lösung inline anzeigen, ohne Sofort-Dialog; nach letzter Frage ggf. Übung + Neustart
          this.afterChapterExamAnswerSubmitted(exerciseId);
          return;
        }

        this.afterChapterExamAnswerSubmitted(exerciseId);
      } catch (e) {
        console.error('Fehler beim Prüfen der Antwort:', e);
        this.showErrorDialog = true;
        this.errorMessage = e.response?.data?.error || 'Fehler beim Prüfen der Antwort';
      }
    },
    async checkLessonCompletion() {
      // Verhindere mehrfache Ausführung
      if (this.isCheckingLessonCompletion || this.isNavigatingToNext) {
        debugLog('[VocabLessonView] checkLessonCompletion übersprungen - bereits in Ausführung');
        return;
      }
      
      // Prüfe ob alle Übungen korrekt beantwortet wurden (effectiveExercises = Kapitel-Prüfung)
      const allExercises = this.effectiveExercises;
      if (!this.lesson || !allExercises || allExercises.length === 0) {
        debugLog('[VocabLessonView] checkLessonCompletion übersprungen - keine Lektion/Übungen');
        return;
      }
      if (allExercises.length === 0) {
        debugLog('[VocabLessonView] checkLessonCompletion übersprungen - keine Übungen');
        return;
      }
      
      const answeredExercises = allExercises.filter((exercise) => Boolean(this.exerciseResults[exercise.id])).length;
      const correctExercises = allExercises.filter((exercise) => this.exerciseResults[exercise.id]?.correct).length;
      const score = Math.round((correctExercises / allExercises.length) * 100);
      const passed = answeredExercises === allExercises.length
        && score >= this.exerciseTargetScore
        && !this.exerciseNeedsReinforcement;

      debugLog('[VocabLessonView] checkLessonCompletion - passed:', passed, 'Beantwortet:', answeredExercises, 'Übungen:', allExercises.length, 'Korrekt:', correctExercises, 'Score:', score);
      
      if (passed && !this.isCheckingLessonCompletion) {
        this.isCheckingLessonCompletion = true;
        debugLog('[VocabLessonView] Alle Übungen abgeschlossen - starte Fortschritts-Update');
        
        try {
          debugLog('[VocabLessonView] Score berechnet:', score, '%');
          
          // Aktualisiere Fortschritt
          const lessonState = this.buildPersistedLessonState();
          const { data } = await apiClient.put(`/api/vocab/lessons/${this.lessonId}/progress`, {
            completed: score >= this.exerciseTargetScore,
            score: score,
            lessonState,
            timeSpentMinutes: 0 // TODO: Zeit tracken
          });
          this.lesson.progress = data;
          
          debugLog('[VocabLessonView] Fortschritt aktualisiert - starte Navigation');
          
          // Weiterleitung zur nächsten Lektion
          await this.navigateToNextLesson();
        } catch (e) {
          console.error('[VocabLessonView] Fehler beim Aktualisieren des Fortschritts:', e);
          this.isCheckingLessonCompletion = false;
        }
      }
    },
    async navigateToNextLesson() {
      // Verhindere mehrfache Navigation
      if (this.isNavigatingToNext) {
        debugLog('[VocabLessonView] Navigation bereits in Gang, überspringe...');
        return;
      }
      this.isNavigatingToNext = true;
      
      try {
        // Lade Kurs mit allen Lektionen
        const courseRes = await apiClient.get(`/api/vocab/courses/${this.courseId}`);
        const course = courseRes.data;
        
        if (!course.lessons || course.lessons.length === 0) {
          debugLog('[VocabLessonView] Keine Lektionen gefunden');
          this.isNavigatingToNext = false;
          this.isCheckingLessonCompletion = false;
          return;
        }
        
        // Finde aktuelle Lektion
        const currentLessonIndex = course.lessons.findIndex(l => l.id === parseInt(this.lessonId));
        
        if (currentLessonIndex >= 0 && currentLessonIndex < course.lessons.length - 1) {
          // Nächste Lektion gefunden
          const nextLesson = course.lessons[currentLessonIndex + 1];
          debugLog('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id);
          
          // Zeige Erfolgs-Meldung und leite weiter
          // Verwende Dialog statt confirm
          this.showNextLessonDialog = true;
          this.nextLessonId = nextLesson.id;
        } else {
          // Letzte Lektion - zeige Abschluss-Meldung
          debugLog('[VocabLessonView] Letzte Lektion erreicht');
          this.showCompletionDialog = true;
          this.isNavigatingToNext = false;
          this.isCheckingLessonCompletion = false;
        }
      } catch (e) {
        console.error('Fehler beim Laden der nächsten Lektion:', e);
        this.isNavigatingToNext = false;
        this.isCheckingLessonCompletion = false;
      }
    },
    back() {
      this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}`);
    },
    confirmNavigateToNextLesson() {
      if (this.nextLessonId) {
        debugLog('[VocabLessonView] Navigiere zur nächsten Lektion:', this.nextLessonId);
        // Setze Flags zurück BEVOR die Navigation stattfindet
        this.isNavigatingToNext = false;
        this.isCheckingLessonCompletion = false;
        this.showNextLessonDialog = false;
        // Verwende replace statt push, um die History nicht zu belasten
        // Setze activeTab auf 'learn' für die nächste Lektion via Query-Parameter
        this.$router.replace(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${this.nextLessonId}?tab=learn`);
      }
    },
    cancelNavigateToNextLesson() {
      debugLog('[VocabLessonView] Navigation abgebrochen');
      this.isNavigatingToNext = false;
      this.isCheckingLessonCompletion = false;
      this.showNextLessonDialog = false;
      this.nextLessonId = null;
    },
    closeCompletionDialog() {
      this.showCompletionDialog = false;
    },
    closeErrorDialog() {
      this.showErrorDialog = false;
      this.errorMessage = '';
    },
    // Vokabeltrainer-Methoden
    startVocabTrainer() {
      debugLog('[VocabLessonView] startVocabTrainer aufgerufen');
      this.clearVocabTrainerContinueTimer();
      if (!this.trainableLessonVocab || this.trainableLessonVocab.length === 0) {
        debugLog('[VocabLessonView] Keine Vokabeln vorhanden');
        return;
      }
      if (!this.canStartVocabTrainerPrep) {
        return;
      }
      debugLog('[VocabLessonView] Vokabeln gefunden:', this.trainableLessonVocab.length);
      debugLog('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0);
      this.vocabTrainerActive = true;
      this.vocabTrainerPool = [...this.trainableLessonVocab];
      this.vocabTrainerMode = 'multiple_choice';
      this.vocabTrainerAutoSwitchedToTyping = false;
      this.vocabTrainerCorrect = 0;
      this.vocabTrainerWrong = 0;
      this.vocabTrainerTotalAttempts = 0;
      this.vocabTrainerCurrentAttempts = 0;
      this.vocabTrainerReviewAttempts = 0;
      this.vocabTrainerStats = {};
      this.vocabTrainerRepeatQueue = [];
      this.vocabTrainerLastWrongReview = null;
      // Bereite Mixed-Pool aus alten Vokabeln vor (ohne Duplikate aus aktueller Lektion)
      this.vocabTrainerMixedPool = this._buildMixedPool();
      this.vocabTrainerPhase = 'current';
      this.vocabTrainerMixedAttempts = 0;
      this.vocabTrainerPool = [...this.trainableLessonVocab];
      debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln');
      debugLog('[VocabLessonView] Rufe nextVocabQuestion auf');
      this.$nextTick(() => {
        this.nextVocabQuestion();
      });
    },
    stopVocabTrainer() {
      this.clearVocabTrainerContinueTimer();
      this.vocabTrainerActive = false;
      this.vocabTrainerMode = 'multiple_choice';
      this.vocabTrainerAutoSwitchedToTyping = false;
      this.vocabTrainerPhase = 'current';
      this.vocabTrainerMixedAttempts = 0;
      this.vocabTrainerCurrentAttempts = 0;
      this.vocabTrainerReviewAttempts = 0;
      this.vocabTrainerMixedPool = [];
      this.vocabTrainerRepeatQueue = [];
      this.currentVocabQuestion = null;
      this.vocabTrainerAnswer = '';
      this.vocabTrainerSelectedChoice = null;
      this.vocabTrainerAnswered = false;
      this.vocabTrainerLastWrongReview = null;
    },
    /** Erstellt den Mixed-Pool aus vorherigen Lektions-Vokabeln (ohne Duplikate der aktuellen Lektion) */
    _buildMixedPool() {
      if (!this.previousVocab || this.previousVocab.length === 0) return [];
      const currentKeys = new Set(this.trainableLessonVocab.map(v => this.getVocabKey(v)));
      const filtered = this.previousVocab.filter(v => !currentKeys.has(this.getVocabKey(v)));
      const filteredByKey = new Map(filtered.map((v) => [this.getVocabKey(v), v]));

      const weakMap = new Map();
      (this.courseProgressList || []).forEach((entry) => {
        const lessonId = Number(entry?.lessonId);
        if (!Number.isFinite(lessonId) || lessonId === Number(this.lessonId)) return;
        const weakList = Array.isArray(entry?.lessonState?.reviewWeakVocab)
          ? entry.lessonState.reviewWeakVocab
          : [];
        weakList.forEach((w) => {
          const learning = String(w?.learning || '').trim();
          const reference = String(w?.reference || '').trim();
          if (!learning || !reference) return;
          const key = `${learning}|${reference}`;
          if (currentKeys.has(key) || !filteredByKey.has(key)) return;
          const prev = weakMap.get(key) || 0;
          weakMap.set(key, prev + Math.max(1, Number(w?.wrongCount) || 1));
        });
      });

      const boosted = [];
      Array.from(weakMap.entries())
        .sort((a, b) => b[1] - a[1])
        .slice(0, 20)
        .forEach(([key, score]) => {
          const vocab = filteredByKey.get(key);
          if (!vocab) return;
          const weight = Math.min(4, Math.max(1, Math.round(score)));
          for (let i = 0; i < weight; i++) {
            boosted.push(vocab);
          }
        });

      const boostedKeySet = new Set(Array.from(weakMap.keys()));
      const rest = filtered.filter((v) => !boostedKeySet.has(this.getVocabKey(v)));
      const shuffled = [...rest].sort(() => Math.random() - 0.5);
      return [...boosted, ...shuffled].slice(0, 40);
    },
    getVocabKey(vocab) {
      return `${vocab.learning}|${vocab.reference}`;
    },
    getVocabStats(vocab) {
      const key = this.getVocabKey(vocab);
      if (!this.vocabTrainerStats[key]) {
        this.vocabTrainerStats[key] = { attempts: 0, correct: 0, wrong: 0 };
      }
      return this.vocabTrainerStats[key];
    },
    normalizeRepeatQueue(queue = []) {
      const repeatIntervals = this.getRepeatIntervals();
      if (!Array.isArray(queue)) {
        return [];
      }
      return queue
        .map((entry) => ({
          key: String(entry?.key || '').trim(),
          dueAfter: Math.max(0, Number(entry?.dueAfter) || 0),
          stageIndex: Math.max(0, Math.min(repeatIntervals.length - 1, Number(entry?.stageIndex) || 0))
        }))
        .filter((entry) => entry.key);
    },
    getRepeatIntervals() {
      const availableCount = new Set(
        [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool].map((vocab) => this.getVocabKey(vocab))
      ).size;
      const maxSpacing = Math.max(1, Math.min(4, availableCount - 1));

      if (maxSpacing <= 1) {
        return [1, 1, 1];
      }
      if (maxSpacing === 2) {
        return [1, 1, 2];
      }
      if (maxSpacing === 3) {
        return [1, 2, 3];
      }
      return VOCAB_REPEAT_INTERVALS;
    },
    queueFailedVocab(vocab) {
      const repeatIntervals = this.getRepeatIntervals();
      const key = this.getVocabKey(vocab);
      const existing = this.vocabTrainerRepeatQueue.find((entry) => entry.key === key);
      if (existing) {
        existing.dueAfter = repeatIntervals[0];
        existing.stageIndex = 0;
        return;
      }
      this.vocabTrainerRepeatQueue.push({
        key,
        dueAfter: repeatIntervals[0],
        stageIndex: 0
      });
    },
    resolveRepeatedVocab(vocab) {
      const repeatIntervals = this.getRepeatIntervals();
      const key = this.getVocabKey(vocab);
      const entryIndex = this.vocabTrainerRepeatQueue.findIndex((entry) => entry.key === key && entry.dueAfter <= 0);
      if (entryIndex === -1) {
        return;
      }
      const entry = this.vocabTrainerRepeatQueue[entryIndex];
      if (entry.stageIndex >= repeatIntervals.length - 1) {
        this.vocabTrainerRepeatQueue.splice(entryIndex, 1);
        return;
      }
      entry.stageIndex += 1;
      entry.dueAfter = repeatIntervals[entry.stageIndex];
    },
    advanceRepeatQueue(completedKey = '') {
      this.vocabTrainerRepeatQueue = this.vocabTrainerRepeatQueue
        .map((entry) => {
          if (entry.key === completedKey) {
            return entry;
          }
          return {
            ...entry,
            dueAfter: Math.max(0, entry.dueAfter - 1)
          };
        })
        .filter((entry) => entry.key);
    },
    getRepeatDueVocab() {
      const dueEntry = this.vocabTrainerRepeatQueue.find((entry) => entry.dueAfter <= 0);
      if (!dueEntry) {
        return null;
      }
      const allVocabs = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool];
      return allVocabs.find((vocab) => this.getVocabKey(vocab) === dueEntry.key) || null;
    },
    getPendingRepeatKeys() {
      return new Set(
        this.vocabTrainerRepeatQueue
          .filter((entry) => entry.dueAfter > 0)
          .map((entry) => entry.key)
      );
    },
    getUnderexposedCurrentVocabs(pool = this.trainableLessonVocab) {
      const uniqueByKey = new Map();
      (pool || []).forEach((vocab) => {
        const key = this.getVocabKey(vocab);
        if (!uniqueByKey.has(key)) {
          uniqueByKey.set(key, vocab);
        }
      });

      const underexposed = Array.from(uniqueByKey.values())
        .map((vocab) => {
          const stats = this.getVocabStats(vocab);
          return {
            vocab,
            attempts: Number(stats.attempts) || 0,
            wrong: Number(stats.wrong) || 0
          };
        })
        .filter((entry) => entry.attempts < this.trainerMinimumCurrentExposures)
        .sort((a, b) => {
          if (a.attempts !== b.attempts) return a.attempts - b.attempts;
          if (a.wrong !== b.wrong) return b.wrong - a.wrong;
          return this.getVocabKey(a.vocab).localeCompare(this.getVocabKey(b.vocab));
        });

      return underexposed.map((entry) => entry.vocab);
    },
    chooseVocabFromPool(pool = []) {
      if (!pool.length) return null;
      const ranked = pool
        .map((vocab) => {
          const stats = this.getVocabStats(vocab);
          return {
            vocab,
            attempts: Number(stats.attempts) || 0,
            wrong: Number(stats.wrong) || 0,
            correct: Number(stats.correct) || 0
          };
        })
        .sort((a, b) => {
          if (a.attempts !== b.attempts) return a.attempts - b.attempts;
          if (a.wrong !== b.wrong) return b.wrong - a.wrong;
          if (a.correct !== b.correct) return a.correct - b.correct;
          return Math.random() - 0.5;
        });
      return ranked[0]?.vocab || pool[Math.floor(Math.random() * pool.length)];
    },
    continueAfterVocabAnswer() {
      this.clearVocabTrainerContinueTimer();
      const completedKey = this.currentVocabQuestion?.key || '';
      this.advanceRepeatQueue(completedKey);
      this.nextVocabQuestion();
    },
    clearVocabTrainerContinueTimer() {
      if (this.vocabTrainerContinueTimer) {
        window.clearTimeout(this.vocabTrainerContinueTimer);
        this.vocabTrainerContinueTimer = null;
      }
    },
    checkVocabModeSwitch() {
      this.updateExerciseUnlockState();

      this.vocabTrainerPhase = this.hasPreviousVocab && this.currentReviewShare > 0 ? 'mixed' : 'current';

      const lessonType = String(this.lesson?.lessonType || '').toLowerCase();
      const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase();
      const isReviewLesson = ['review', 'vocab_review'].includes(lessonType)
        || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode);
      // Reviews sollen nicht nur aus Multiple Choice bestehen:
      // früherer Wechsel zu Typing, damit aktiver Abruf im Vordergrund steht.
      const switchAfterAttempts = isReviewLesson
        ? Math.max(2, Math.ceil(this.trainerExerciseUnlockAttempts * 0.15))
        : this.trainerExerciseUnlockAttempts;
      const requiredSuccessRate = isReviewLesson ? 60 : 80;

      if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= switchAfterAttempts) {
        const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
        if (successRate >= requiredSuccessRate) {
          debugLog('[VocabLessonView] Wechsle zu Typing (Abruf staerken)');
          this.vocabTrainerMode = 'typing';
          this.vocabTrainerAutoSwitchedToTyping = true;
          this.vocabTrainerPool = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool];
          this.vocabTrainerCorrect = 0;
          this.vocabTrainerWrong = 0;
          this.vocabTrainerTotalAttempts = 0;
        }
      }
    },
    switchBackToMultipleChoice() {
      // Wechsle zurück zu Multiple Choice
      this.vocabTrainerMode = 'multiple_choice';
      this.vocabTrainerAutoSwitchedToTyping = false;
      this.vocabTrainerPool = this.vocabTrainerPhase === 'mixed'
        ? [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool]
        : [...this.trainableLessonVocab];
      // Reset Stats für Multiple Choice Modus
      this.vocabTrainerCorrect = 0;
      this.vocabTrainerWrong = 0;
      this.vocabTrainerTotalAttempts = 0;
      // Starte neue Frage im Multiple Choice Modus
      this.nextVocabQuestion();
    },
    switchToTyping() {
      if (!this.vocabTrainerActive || this.vocabTrainerMode === 'typing') {
        return;
      }
      this.vocabTrainerMode = 'typing';
      this.vocabTrainerAutoSwitchedToTyping = false;
      this.vocabTrainerPool = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool];
      this.nextVocabQuestion();
    },
    getEquivalentVocabAnswers(prompt, direction, allVocabs) {
      const normalizedPrompt = this.normalizeVocab(prompt);
      const matches = [];

      allVocabs.forEach((entry) => {
        const source = direction === 'L2R' ? entry.learning : entry.reference;
        const target = direction === 'L2R' ? entry.reference : entry.learning;
        if (!source || !target) return;
        if (this.normalizeVocab(source) !== normalizedPrompt) return;
        if (!matches.includes(target)) {
          matches.push(target);
        }
      });

      return matches;
    },
    /**
     * @param {'L2R'|'R2L'} direction L2R: Antwort = reference (Zielsprache, z. B. Bisaya). R2L: Antwort = learning (Muttersprache, z. B. Deutsch).
     *        Distraktoren kommen immer nur aus derselben Sprache wie die richtige Antwort.
     */
    buildChoiceOptions(correctAnswers, allVocabs, excludePrompt = null, direction = 'L2R') {
      const normalizedCorrectAnswers = Array.isArray(correctAnswers) ? correctAnswers : [correctAnswers];
      const options = new Set(normalizedCorrectAnswers.filter(Boolean));
      const normalizedExcludeSet = new Set();
      normalizedCorrectAnswers.forEach((answer) => {
        normalizedExcludeSet.add(this.normalizeVocab(answer));
      });
      if (excludePrompt) {
        normalizedExcludeSet.add(this.normalizeVocab(excludePrompt));
      }

      const answerField = direction === 'L2R' ? 'reference' : 'learning';

      const allNativeNorm = new Set();
      const allTargetNorm = new Set();
      allVocabs.forEach((v) => {
        if (v.learning) allNativeNorm.add(this.normalizeVocab(v.learning));
        if (v.reference) allTargetNorm.add(this.normalizeVocab(v.reference));
      });
      /** Verhindert gemischte Sprachen in den Optionen (z. B. deutsche Wörter unter Zielsprachen-Distraktoren). */
      const tokenMatchesAnswerLanguage = (token) => {
        const n = this.normalizeVocab(token);
        if (!n) return false;
        if (direction === 'L2R') {
          if (allNativeNorm.has(n) && !allTargetNorm.has(n)) return false;
        } else {
          if (allTargetNorm.has(n) && !allNativeNorm.has(n)) return false;
        }
        return true;
      };

      const allDistractors = [];
      allVocabs.forEach((vocab) => {
        const candidate = vocab[answerField];
        if (!candidate || !candidate.trim()) return;
        const normalized = this.normalizeVocab(candidate);
        if (!normalizedExcludeSet.has(normalized) && tokenMatchesAnswerLanguage(candidate)) {
          allDistractors.push(candidate);
        }
      });

      const uniqueDistractors = [...new Set(allDistractors)];

      let attempts = 0;
      const maxAttempts = 200;

      while (options.size < 4 && uniqueDistractors.length > 0 && attempts < maxAttempts) {
        attempts++;
        const randomIndex = Math.floor(Math.random() * uniqueDistractors.length);
        const distractor = uniqueDistractors[randomIndex];

        if (distractor && distractor.trim()) {
          const normalizedDistractor = this.normalizeVocab(distractor);
          if (!normalizedExcludeSet.has(normalizedDistractor) && !options.has(distractor)) {
            options.add(distractor);
          }
        }
      }

      if (options.size < 4 && allVocabs.length > 0) {
        allVocabs.forEach((vocab) => {
          if (options.size >= 4) return;
          const candidate = vocab[answerField];
          if (candidate && candidate.trim()) {
            const normalized = this.normalizeVocab(candidate);
            if (
              !normalizedExcludeSet.has(normalized) &&
              !options.has(candidate) &&
              tokenMatchesAnswerLanguage(candidate)
            ) {
              options.add(candidate);
            }
          }
        });
      }
      
      // Falls immer noch nicht genug Optionen: Verwende weniger Optionen (mindestens 2)
      if (options.size < 2) {
        console.warn('[buildChoiceOptions] Nicht genug Optionen gefunden, verwende nur', options.size, 'Optionen');
      }
      
      // Shuffle
      const arr = Array.from(options);
      for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];
      }
      return arr;
    },
    nextVocabQuestion() {
      debugLog('[VocabLessonView] nextVocabQuestion aufgerufen');
      this.clearVocabTrainerContinueTimer();
      if (!this.vocabTrainerPool || this.vocabTrainerPool.length === 0) {
        debugLog('[VocabLessonView] Keine Vokabeln im Pool');
        this.currentVocabQuestion = null;
        return;
      }
      
      // Prüfe ob Modus-Wechsel nötig ist
      this.checkVocabModeSwitch();
      
      let questionSource = 'current';
      let sourcePool = this.trainableLessonVocab;
      const dueRepeatVocab = this.getRepeatDueVocab();

      if (dueRepeatVocab) {
        sourcePool = [dueRepeatVocab];
        questionSource = this.vocabTrainerMixedPool.some((entry) => this.getVocabKey(entry) === this.getVocabKey(dueRepeatVocab))
          ? 'review'
          : 'current';
      }

      if (!dueRepeatVocab && this.vocabTrainerMode === 'typing') {
        const typingReviewShare = Math.max(0, 1 - this.typingCurrentLessonShare);
        if (this.vocabTrainerMixedPool.length > 0 && typingReviewShare > 0 && Math.random() < typingReviewShare) {
          sourcePool = this.vocabTrainerMixedPool;
          questionSource = 'review';
        } else {
          sourcePool = this.trainableLessonVocab;
          questionSource = 'current';
        }
      } else if (!dueRepeatVocab && this.vocabTrainerMixedPool.length > 0 && this.currentReviewShare > 0 && Math.random() < this.currentReviewShare) {
        sourcePool = this.vocabTrainerMixedPool;
        questionSource = 'review';
      }

      if (!sourcePool || sourcePool.length === 0) {
        sourcePool = this.trainableLessonVocab;
        questionSource = 'current';
      }

      if (!dueRepeatVocab) {
        const pendingRepeatKeys = this.getPendingRepeatKeys();
        const filteredPool = sourcePool.filter((vocab) => !pendingRepeatKeys.has(this.getVocabKey(vocab)));
        if (filteredPool.length > 0) {
          sourcePool = filteredPool;
        }
      }

      if (!dueRepeatVocab && questionSource === 'current' && this.vocabTrainerMode === 'multiple_choice') {
        const underexposed = this.getUnderexposedCurrentVocabs(sourcePool);
        if (underexposed.length > 0) {
          sourcePool = underexposed;
        }
      }

      const vocab = this.chooseVocabFromPool(sourcePool);
      if (!vocab) {
        this.currentVocabQuestion = null;
        return;
      }
      this.vocabTrainerDirection = Math.random() < 0.5 ? 'L2R' : 'R2L';
      const allTrainerVocabs = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool];
      const direction = this.vocabTrainerDirection;
      const prompt = direction === 'L2R' ? vocab.learning : vocab.reference;
      // Akzeptiere mehrere Übersetzungen für denselben Prompt (z. B. Synonyme / Varianten auf der Lernsprache).
      // Zusätzlich alle im gesamten Trainer-Pool gleichwertigen Ziele (wie früher via getEquivalentVocabAnswers),
      // damit gemischte Pools aus mehreren Lektionen nicht plötzlich gültige Alternativen ablehnen.
      const baseAnswers = direction === 'L2R'
        ? [vocab.reference]
        : (Array.isArray(vocab.learningVariants) && vocab.learningVariants.length > 0
            ? [...vocab.learningVariants]
            : [vocab.learning]);
      const equivalentAnswers = this.getEquivalentVocabAnswers(prompt, direction, allTrainerVocabs);
      const mergedRaw = [...baseAnswers, ...equivalentAnswers].filter(Boolean);
      const seenNorm = new Set();
      const acceptableAnswers = [];
      for (const a of mergedRaw) {
        const n = this.normalizeVocab(a);
        if (!n || seenNorm.has(n)) continue;
        seenNorm.add(n);
        acceptableAnswers.push(a);
      }
      this.currentVocabQuestion = {
        vocab: vocab,
        prompt,
        answers: acceptableAnswers.filter(Boolean),
        answer: acceptableAnswers.filter(Boolean).join(' / ') || (this.vocabTrainerDirection === 'L2R' ? vocab.reference : vocab.learning),
        key: this.getVocabKey(vocab),
        source: questionSource
      };
      
      debugLog('[VocabLessonView] Neue Frage erstellt:', this.currentVocabQuestion.prompt);
      
      // Reset UI
      this.vocabTrainerAnswer = '';
      this.vocabTrainerSelectedChoice = null;
      this.vocabTrainerAnswered = false;
      
      // Erstelle Choice-Optionen für Multiple Choice
      if (this.vocabTrainerMode === 'multiple_choice') {
        debugLog('[VocabLessonView] Erstelle Choice-Optionen...');
        debugLog('[VocabLessonView] Prompt:', this.currentVocabQuestion.prompt);
        debugLog('[VocabLessonView] Answer:', this.currentVocabQuestion.answer);
        // Wichtig: Der Prompt (die Frage) darf nicht in den Optionen erscheinen
        this.vocabTrainerChoiceOptions = this.buildChoiceOptions(
          this.currentVocabQuestion.answers,
          allTrainerVocabs,
          this.currentVocabQuestion.prompt,
          this.vocabTrainerDirection
        );
        debugLog('[VocabLessonView] Choice-Optionen erstellt:', this.vocabTrainerChoiceOptions);
      }
      
      // Fokussiere Eingabefeld im Typing-Modus
      if (this.vocabTrainerMode === 'typing') {
        this.$nextTick(() => {
          this.$refs.vocabInput?.focus?.();
        });
      }
    },
    selectVocabChoice(option) {
      this.vocabTrainerSelectedChoice = option;
      // Bei Multiple Choice: Sofort prüfen
      if (this.vocabTrainerMode === 'multiple_choice') {
        this.checkVocabAnswer();
      }
    },
    normalizeComparableText(value) {
      const normalized = String(value || '')
        .trim()
        .toLowerCase()
        .normalize('NFKC')
        .replace(/[\p{P}\p{S}]+/gu, ' ')
        .replace(/\s+/g, ' ')
        .trim();
      return normalized.replace(/\s+/g, '');
    },
    stripTrailingParentheticalNotes(value) {
      let text = String(value || '').trim();
      // Entfernt didaktische Zusätze am Ende, unabhängig vom konkreten Inhalt,
      // z. B. "(...)", "[...]" oder vollbreite Klammern "（...）".
      const trailingNoteRegex = /\s*(\([^()]*\)|\[[^\[\]]*\]|（[^（）]*）)\s*$/;
      while (trailingNoteRegex.test(text)) {
        text = text.replace(trailingNoteRegex, '').trim();
      }
      return text;
    },
    normalizeVocab(s, options = {}) {
      const { ignoreTrailingParentheticalNotes = false } = options;
      const source = ignoreTrailingParentheticalNotes
        ? this.stripTrailingParentheticalNotes(s)
        : s;
      return this.normalizeComparableText(source);
    },
    reportSrsReviewForCurrentQuestion(isCorrect) {
      if (!this.currentVocabQuestion?.vocab || !this.courseId) {
        return;
      }
      const vocab = this.currentVocabQuestion.vocab;
      apiClient.post('/api/vocab/srs/review', {
        courseId: this.courseId,
        lessonId: vocab.lessonId || this.lessonId,
        itemKey: vocab.itemKey || null,
        learning: vocab.learning,
        reference: vocab.reference,
        direction: this.vocabTrainerDirection,
        correct: Boolean(isCorrect)
      }).catch((error) => {
        console.warn('[VocabLessonView] SRS review could not be saved:', error);
      });
    },
    checkVocabAnswer() {
      if (!this.currentVocabQuestion) return;
      
      let userAnswer = '';
      if (this.vocabTrainerMode === 'multiple_choice') {
        if (!this.vocabTrainerSelectedChoice) return;
        userAnswer = this.vocabTrainerSelectedChoice;
      } else {
        if (!this.vocabTrainerAnswer.trim()) return;
        userAnswer = this.vocabTrainerAnswer;
      }
      
      const useTypingNormalization = this.vocabTrainerMode === 'typing';
      const normalizedUser = this.normalizeVocab(userAnswer, {
        ignoreTrailingParentheticalNotes: useTypingNormalization
      });
      const normalizedCorrectAnswers = (this.currentVocabQuestion.answers || [this.currentVocabQuestion.answer])
        .map(answer => this.normalizeVocab(answer, {
          ignoreTrailingParentheticalNotes: useTypingNormalization
        }));
      this.vocabTrainerLastCorrect = normalizedCorrectAnswers.includes(normalizedUser);
      
      // Update Stats
      const stats = this.getVocabStats(this.currentVocabQuestion.vocab);
      stats.attempts++;
      this.vocabTrainerTotalAttempts++;
      if (this.currentVocabQuestion.source === 'review') {
        this.vocabTrainerReviewAttempts++;
      } else {
        this.vocabTrainerCurrentAttempts++;
      }
      
      if (this.vocabTrainerLastCorrect) {
        this.vocabTrainerLastWrongReview = null;
        this.vocabTrainerCorrect++;
        stats.correct++;
        this.resolveRepeatedVocab(this.currentVocabQuestion.vocab);
      } else {
        this.vocabTrainerLastWrongReview = {
          prompt: this.currentVocabQuestion.prompt,
          userAnswer,
          answer: this.currentVocabQuestion.answer
        };
        this.vocabTrainerWrong++;
        stats.wrong++;
        this.queueFailedVocab(this.currentVocabQuestion.vocab);
      }
      this.reportSrsReviewForCurrentQuestion(this.vocabTrainerLastCorrect);
      
      this.vocabTrainerAnswered = true;
      
      // Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort)
      if (this.vocabTrainerLastCorrect) {
        // Prüfe ob noch Fragen vorhanden sind
        if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0) {
          const delay = this.vocabTrainerMode === 'multiple_choice' ? 1000 : 500; // 1 Sekunde für Multiple Choice, 500ms für Typing
          const answeredQuestionKey = this.currentVocabQuestion?.key || '';
          this.clearVocabTrainerContinueTimer();
          this.vocabTrainerContinueTimer = window.setTimeout(() => {
            this.vocabTrainerContinueTimer = null;
            // Prüfe erneut, ob noch Fragen vorhanden sind (könnte sich geändert haben)
            if (
              this.vocabTrainerPool
              && this.vocabTrainerPool.length > 0
              && this.vocabTrainerActive
              && this.vocabTrainerAnswered
              && this.vocabTrainerLastCorrect
              && this.currentVocabQuestion?.key === answeredQuestionKey
            ) {
              this.continueAfterVocabAnswer();
            }
          }, delay);
        }
      }
      
      // Im Typing-Modus bei falscher Antwort: Eingabefeld fokussieren für erneuten Versuch
      if (this.vocabTrainerMode === 'typing' && !this.vocabTrainerLastCorrect) {
        this.$nextTick(() => {
          this.$refs.vocabInput?.focus?.();
          this.vocabTrainerAnswer = '';
        });
      }
    },
    initSpeechRecognition() {
      // Prüfe Browser-Support für Speech Recognition
      const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
      if (!SpeechRecognition) {
        this.isSpeechRecognitionSupported = false;
        console.warn('Speech Recognition wird von diesem Browser nicht unterstützt');
        return;
      }
      this.isSpeechRecognitionSupported = true;
    },
    isRecording(exerciseId) {
      return !!this.activeRecognition[exerciseId];
    },
    startReadingAloud(exerciseId) {
      const exercise = this.effectiveExercises.find(e => e.id === exerciseId);
      if (!exercise) return;
      
      const qData = this.getQuestionData(exercise);
      const expectedText = qData.text || '';
      
      this.startRecognition(exerciseId, expectedText);
    },
    stopReadingAloud(exerciseId) {
      this.stopRecognition(exerciseId);
    },
    startSpeakingFromMemory(exerciseId) {
      const exercise = this.effectiveExercises.find(e => e.id === exerciseId);
      if (!exercise) return;
      
      const qData = this.getQuestionData(exercise);
      const expectedText = qData.expectedText || qData.text || '';
      
      this.startRecognition(exerciseId, expectedText);
    },
    stopSpeakingFromMemory(exerciseId) {
      this.stopRecognition(exerciseId);
    },
    startRecognition(exerciseId, expectedText) {
      if (!this.isSpeechRecognitionSupported) {
        this.recordingStatus[exerciseId] = this.$t('socialnetwork.vocab.courses.speechRecognitionNotSupported');
        return;
      }
      
      const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
      const recognition = new SpeechRecognition();
      
      // Konfiguriere Recognition
      recognition.continuous = true;
      recognition.interimResults = true;
      recognition.lang = 'de-DE'; // Kann später dynamisch basierend auf Kurs-Sprache gesetzt werden
      
      let finalTranscript = '';
      
      recognition.onresult = (event) => {
        let interimTranscript = '';
        
        for (let i = event.resultIndex; i < event.results.length; i++) {
          const transcript = event.results[i][0].transcript;
          if (event.results[i].isFinal) {
            finalTranscript += transcript + ' ';
          } else {
            interimTranscript += transcript;
          }
        }
        
        // Aktualisiere erkannten Text
        this.recognizedText[exerciseId] = finalTranscript.trim() || interimTranscript;
        this.recordingStatus[exerciseId] = this.$t('socialnetwork.vocab.courses.listening');
      };
      
      recognition.onerror = (event) => {
        console.error('Speech Recognition Fehler:', event.error);
        this.recordingStatus[exerciseId] = this.$t('socialnetwork.vocab.courses.recordingError') + ': ' + event.error;
        this.stopRecognition(exerciseId);
      };
      
      recognition.onend = () => {
        // Speichere finalen Text in exerciseAnswers
        if (finalTranscript.trim()) {
          this.exerciseAnswers[exerciseId] = finalTranscript.trim();
        }
        this.activeRecognition[exerciseId] = null;
        this.recordingStatus[exerciseId] = this.$t('socialnetwork.vocab.courses.recordingStopped');
      };
      
      // Starte Recognition
      try {
        recognition.start();
        this.activeRecognition[exerciseId] = recognition;
        this.recordingStatus[exerciseId] = this.$t('socialnetwork.vocab.courses.recording');
        this.recognizedText[exerciseId] = '';
      } catch (error) {
        console.error('Fehler beim Starten der Speech Recognition:', error);
        this.recordingStatus[exerciseId] = this.$t('socialnetwork.vocab.courses.recordingError') + ': ' + error.message;
      }
    },
    stopRecognition(exerciseId) {
      if (this.activeRecognition[exerciseId]) {
        try {
          this.activeRecognition[exerciseId].stop();
        } catch (error) {
          console.error('Fehler beim Stoppen der Speech Recognition:', error);
        }
        this.activeRecognition[exerciseId] = null;
      }
    }
  },
  async mounted() {
    // Prüfe Speech Recognition Support
    this.initSpeechRecognition();
    await Promise.all([
      this.loadLesson(),
      this.loadAssistantSettings()
    ]);
  },
  beforeUnmount() {
    this.persistLessonState({ immediate: true, lessonIdOverride: this.lesson?.id || this.lessonId });
    if (this.lessonStateSaveTimer) {
      window.clearTimeout(this.lessonStateSaveTimer);
      this.lessonStateSaveTimer = null;
    }
    this.clearVocabTrainerContinueTimer();
    this.stopAssistantWaitTimer();
    // Stoppe alle aktiven Recognition-Instanzen
    Object.keys(this.activeRecognition).forEach(exerciseId => {
      this.stopRecognition(exerciseId);
    });
  }
};
</script>

<style scoped>
.vocab-lesson-view {
  padding: 20px;
}

.language-assistant-card {
  gap: 14px;
}

.language-assistant-card--focused {
  border-color: var(--color-primary-orange);
  box-shadow: 0 0 0 3px rgba(248, 162, 43, 0.18);
}

.language-assistant-card__header {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: flex-start;
  flex-wrap: wrap;
}

.language-assistant-card__intro {
  margin: 6px 0 0;
  color: var(--color-text-secondary);
}

.language-assistant-card__state {
  color: var(--color-text-secondary);
}

.language-assistant-panel {
  display: grid;
  gap: 14px;
}

.language-assistant-panel__modes,
.language-assistant-panel__presets,
.language-assistant-panel__actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.assistant-mode-button,
.assistant-preset-button {
  border: 1px solid var(--color-border);
  background: rgba(255, 255, 255, 0.82);
  border-radius: var(--radius-md);
  padding: 8px 12px;
}

.assistant-mode-button.active {
  border-color: var(--color-primary-orange);
  background: rgba(248, 162, 43, 0.16);
}

.language-assistant-chat {
  display: grid;
  gap: 10px;
}

.assistant-message {
  padding: 12px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
}

.assistant-message strong {
  display: block;
  margin-bottom: 4px;
}

.assistant-message p {
  margin: 0;
  white-space: pre-wrap;
}

.assistant-message--assistant {
  background: rgba(248, 162, 43, 0.08);
}

.assistant-message--user {
  background: rgba(58, 117, 196, 0.08);
}

.language-assistant-waiting {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid rgba(248, 162, 43, 0.35);
  border-radius: var(--radius-md);
  background: rgba(248, 162, 43, 0.1);
}

.language-assistant-waiting__spinner {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border: 3px solid rgba(248, 162, 43, 0.28);
  border-top-color: var(--color-primary-orange);
  border-radius: 50%;
  animation: assistant-spin 0.9s linear infinite;
  flex: 0 0 auto;
}

.language-assistant-waiting p {
  margin: 4px 0;
  color: var(--color-text-secondary);
}

.language-assistant-waiting small {
  color: var(--color-text-secondary);
}

@keyframes assistant-spin {
  to {
    transform: rotate(360deg);
  }
}

.language-assistant-panel__input {
  display: grid;
  gap: 6px;
}

.language-assistant-panel__input textarea {
  width: 100%;
  min-height: 112px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  resize: vertical;
}

.lesson-header {
  display: flex;
  align-items: center;
  gap: 15px;
  margin-bottom: 20px;
}

.lesson-header h2 {
  flex: 1;
  min-width: 0;
  margin: 0;
}

.btn-reset-lesson {
  flex-shrink: 0;
  padding: 8px 14px;
  border: 1px solid rgba(140, 90, 60, 0.35);
  border-radius: 4px;
  background: rgba(255, 248, 240, 0.95);
  color: #6b4420;
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 600;
}

.btn-reset-lesson:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.lesson-overview-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 18px 18px 16px;
  margin-bottom: 16px;
  background: linear-gradient(135deg, #fff8eb 0%, #f7efe2 100%);
  border: 1px solid rgba(160, 120, 40, 0.18);
  border-radius: 12px;
}

.lesson-overview-text {
  margin: 8px 0 0;
  color: #5b4b2f;
}

.lesson-meta-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.lesson-meta-item {
  padding: 12px 14px;
  background: rgba(255, 255, 255, 0.72);
  border-radius: 10px;
}

.lesson-review-status {
  padding: 14px 16px;
  border-radius: 12px;
  border: 1px solid rgba(34, 96, 164, 0.18);
  background: rgba(235, 244, 255, 0.86);
  color: #21598f;
}

.lesson-review-status__top {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.lesson-review-status__badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(34, 96, 164, 0.14);
  font-size: 0.78rem;
  font-weight: 700;
}

.lesson-review-status strong,
.lesson-review-status p {
  margin: 0;
}

.lesson-review-status p {
  margin-top: 8px;
  color: inherit;
}

.lesson-review-status--due {
  border-color: rgba(185, 99, 24, 0.22);
  background: rgba(255, 246, 226, 0.92);
  color: #8d5412;
}

.lesson-review-status--due .lesson-review-status__badge {
  background: rgba(185, 99, 24, 0.16);
}

.lesson-review-status--done {
  border-color: rgba(68, 138, 86, 0.22);
  background: rgba(239, 250, 242, 0.92);
  color: #2f6b3d;
}

.lesson-review-status--done .lesson-review-status__badge {
  background: rgba(68, 138, 86, 0.14);
}

.lesson-meta-label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.82rem;
  color: #7a6848;
}

.lesson-intensity-banner {
  margin-bottom: 18px;
  padding: 14px 16px;
  border-radius: 12px;
  border: 1px solid rgba(207, 78, 78, 0.24);
  background: rgba(255, 242, 242, 0.95);
  color: #8e3d3d;
}

.lesson-intensity-banner strong,
.lesson-intensity-banner p {
  margin: 0;
}

.lesson-intensity-banner p {
  margin-top: 6px;
}

.lesson-enough-banner {
  margin-bottom: 18px;
  padding: 14px 16px;
  border-radius: 12px;
  border: 1px solid rgba(34, 96, 164, 0.18);
  background: rgba(235, 244, 255, 0.72);
  color: #21598f;
}

.lesson-enough-banner strong,
.lesson-enough-banner p {
  margin: 0;
}

.lesson-enough-banner p {
  margin-top: 6px;
}

.lesson-enough-banner__actions {
  margin-top: 12px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.lesson-overview-more {
  border-top: 1px solid rgba(160, 120, 40, 0.18);
  padding-top: 14px;
}

.lesson-overview-more__summary,
.lesson-deepen-section__summary {
  cursor: pointer;
  font-weight: 600;
  color: #5f4313;
}

.lesson-overview-more__grid {
  margin-top: 12px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.learn-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 18px;
}

.didactic-card,
.cultural-notes {
  padding: 18px;
  background: #f8f9fa;
  border-radius: 12px;
  border: 1px solid #e7e7e7;
}

.lesson-intro-combined {
  grid-column: 1 / -1;
}

.lesson-intro-block + .lesson-intro-block {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid #e0e4e8;
}

.lesson-intro-block h4 {
  margin-top: 0;
}

.didactic-list {
  margin: 0;
  padding-left: 20px;
}

.didactic-list li + li {
  margin-top: 8px;
}

.pattern-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.pattern-item {
  padding: 12px 14px;
  border-left: 4px solid #d2831f;
  background: #fff;
  border-radius: 8px;
}

.pattern-target {
  font-weight: 600;
  color: #1a1a1a;
}

.pattern-gloss {
  margin-top: 6px;
  font-size: 0.92rem;
  color: #555;
}

.core-patterns-hint {
  margin: 0 0 12px;
  font-size: 0.9rem;
  color: #666;
}

.vocab-prep-pass {
  margin-bottom: 14px;
  background: linear-gradient(180deg, #fffefd 0%, #fff7ec 100%);
  border: 1px solid rgba(210, 131, 31, 0.18);
}

.vocab-prep-pass__step {
  margin: 0 0 10px;
  font-size: 0.9rem;
  color: #7a6848;
  font-weight: 600;
}

.vocab-prep-card {
  margin-top: 12px;
  padding: 16px 18px;
  background: #fff;
  border: 1px solid #e5dccf;
  border-radius: 10px;
}

.vocab-prep-card__row {
  display: grid;
  grid-template-columns: minmax(0, 110px) 1fr;
  gap: 10px 14px;
  align-items: baseline;
  margin-top: 10px;
}

.vocab-prep-card__row:first-of-type {
  margin-top: 0;
}

.vocab-prep-card__label {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #8a7658;
}

.vocab-prep-card__target {
  font-size: 1.2rem;
  font-weight: 700;
  color: #1f1a16;
}

.vocab-prep-card__gloss {
  font-size: 1rem;
  color: #6a5a44;
}

.vocab-prep-pass .btn-prep-pass {
  margin-top: 8px;
}

.vocab-prep-pass__ready {
  margin: 0;
  color: #2d6a3e;
  font-weight: 600;
}

.lesson-primary-flow {
  margin-top: 18px;
  padding: 18px;
  border: 1px solid rgba(210, 131, 31, 0.2);
  background: linear-gradient(180deg, rgba(255, 251, 245, 0.98), rgba(255, 246, 233, 0.94));
  box-shadow: 0 14px 30px rgba(194, 141, 61, 0.08);
}

.lesson-primary-flow__header {
  margin-bottom: 14px;
}

.lesson-primary-flow__eyebrow {
  display: inline-flex;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(248, 162, 43, 0.16);
  color: #7a4b00;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.lesson-primary-flow__title {
  margin: 10px 0 6px;
  color: #2d2114;
}

.lesson-primary-flow__intro {
  margin: 0;
  color: #6b5535;
}

.lesson-grammar-impulse {
  margin-top: 16px;
  padding: 16px 18px;
  border: 1px solid rgba(121, 100, 56, 0.18);
  background: rgba(255, 255, 255, 0.72);
}

.lesson-grammar-impulse__header {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 10px;
}

.lesson-grammar-impulse__header h4 {
  margin: 0;
  color: #3f2d14;
}

.lesson-grammar-impulse__intro {
  margin: 0 0 12px;
  color: #6b5535;
}

.lesson-grammar-impulse__list {
  display: grid;
  gap: 12px;
}

.lesson-grammar-impulse__item {
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(255, 249, 240, 0.88);
  border: 1px solid rgba(121, 100, 56, 0.12);
}

.lesson-grammar-impulse__item p {
  margin: 6px 0 0;
}

.vocab-trainer-locked-hint {
  margin: 0;
  color: #8a5a00;
}

.lesson-deepen-section,
.lesson-assistant-section {
  margin-top: 20px;
  padding: 16px 18px;
  background: rgba(255, 255, 255, 0.82);
  border: 1px solid rgba(176, 176, 176, 0.2);
}

.lesson-deepen-section .learn-grid,
.lesson-assistant-section .language-assistant-card {
  margin-top: 16px;
}

.grammar-example,
.speaking-cue,
.pattern-drill-hint {
  margin-top: 8px;
  color: #66553a;
  font-style: italic;
}

.speaking-prompt-item + .speaking-prompt-item,
.practical-task-item + .practical-task-item {
  margin-top: 14px;
}

.token-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0;
}

.token-chip {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  background: #eef3ff;
  border: 1px solid #cfdbff;
  border-radius: 999px;
  font-size: 0.95rem;
}

.dialog-snippet {
  margin: 14px 0;
  padding: 14px;
  background: #fff;
  border-radius: 10px;
  border: 1px solid #e6e6e6;
}

.response-textarea {
  width: 100%;
  min-height: 120px;
  padding: 12px;
  border: 1px solid #d0d0d0;
  border-radius: 8px;
  resize: vertical;
}

.btn-back {
  padding: 8px 16px;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--color-text-primary);
  cursor: pointer;
  box-shadow: none;
}

.grammar-exercises {
  margin-top: 30px;
}

.exercise-sequential-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  margin-bottom: 14px;
  border: 1px solid rgba(80, 118, 178, 0.14);
  background: rgba(255, 255, 255, 0.85);
}

.exercise-sequential-nav__progress {
  margin: 0;
  font-weight: 600;
  color: #2a3f5f;
}

.exercise-sequential-nav__buttons {
  display: flex;
  gap: 10px;
}

.btn-seq {
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid rgba(58, 117, 196, 0.35);
  background: #fff;
  color: #27528f;
  font-weight: 600;
  cursor: pointer;
}

.btn-seq:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn-seq--primary {
  background: rgba(58, 117, 196, 0.12);
  border-color: rgba(58, 117, 196, 0.45);
}

.exercise-reinforcement-correct {
  padding: 10px 12px;
  margin: 0 0 12px;
  border-radius: 8px;
  background: rgba(45, 106, 62, 0.08);
  color: #1f3d2a;
}

.dialog-footer--stack {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
}

.dialog-button--primary {
  font-weight: 700;
}

.exam-failed-review {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(133, 100, 4, 0.28);
}

.exam-failed-review h4 {
  margin: 0 0 8px;
  font-size: 0.98rem;
}

.exam-failed-review ul {
  margin: 0;
  padding-left: 18px;
}

.exam-failed-review li {
  margin-bottom: 10px;
}

.exam-failed-review__question,
.exam-failed-review__answer {
  margin: 0 0 4px;
}

.exercise-flow-header {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 18px;
  margin-bottom: 16px;
  border: 1px solid rgba(80, 118, 178, 0.16);
  background: linear-gradient(180deg, rgba(246, 250, 255, 0.98), rgba(236, 244, 255, 0.94));
}

.exercise-flow-header h3 {
  margin: 8px 0 6px;
}

.exercise-flow-header__eyebrow {
  display: inline-flex;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(58, 117, 196, 0.12);
  color: #27528f;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.exercise-flow-header__intro {
  margin: 0;
  color: #4e6280;
}

.exercise-flow-header__hint {
  margin: 8px 0 0;
  color: #39506f;
  font-weight: 500;
}

.exercise-flow-header__stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(110px, 1fr));
  gap: 10px;
  min-width: 360px;
}

.exercise-flow-stat {
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.8);
  border: 1px solid rgba(80, 118, 178, 0.12);
}

.exercise-flow-stat span {
  display: block;
  margin-bottom: 6px;
  color: #60708b;
  font-size: 0.82rem;
}

.exercise-flow-list {
  display: grid;
  gap: 14px;
}

.exercise-grammar-card {
  margin-bottom: 16px;
  padding: 16px 18px;
  border: 1px solid rgba(113, 94, 54, 0.18);
  background: linear-gradient(180deg, rgba(255, 250, 241, 0.98), rgba(255, 246, 229, 0.94));
}

.exercise-grammar-card__header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.exercise-grammar-card__list {
  display: grid;
  gap: 12px;
}

.exercise-grammar-card__item {
  padding: 12px 14px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid rgba(113, 94, 54, 0.12);
}

.exercise-grammar-card__item p {
  margin: 6px 0 0;
}

.exercise-item {
  background: white;
  padding: 18px;
  border: 1px solid #ddd;
  border-radius: 12px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.04);
}

.exercise-item__header {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: flex-start;
  margin-bottom: 10px;
}

.exercise-item__header h4 {
  margin: 4px 0 0;
}

.exercise-item__index {
  display: inline-flex;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(58, 117, 196, 0.08);
  color: #27528f;
  font-size: 0.78rem;
  font-weight: 700;
}

.exercise-item__status {
  flex: 0 0 auto;
  padding: 5px 10px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 700;
}

.exercise-item__status--open {
  background: rgba(96, 112, 139, 0.12);
  color: #55647d;
}

.exercise-item__status--correct {
  background: rgba(40, 167, 69, 0.14);
  color: #1f7a35;
}

.exercise-item__status--wrong {
  background: rgba(220, 53, 69, 0.12);
  color: #a12634;
}

.exercise-item--correct {
  border-color: rgba(40, 167, 69, 0.28);
}

.exercise-item--wrong {
  border-color: rgba(220, 53, 69, 0.18);
}

.exercise-instruction {
  color: #666;
  font-style: italic;
  margin-bottom: 15px;
}

.exercise-question {
  font-weight: 600;
  margin-bottom: 15px;
  font-size: 1.1em;
}

.exercise-text {
  margin-bottom: 15px;
  line-height: 1.6;
}

.gap {
  display: inline-block;
  min-width: 100px;
  border-bottom: 2px solid #333;
  margin: 0 5px;
  padding: 0 5px;
}

.multiple-choice-exercise .options {
  margin: 15px 0;
}

.option-label {
  display: block;
  padding: 10px;
  margin: 8px 0;
  border: 1px solid #ddd;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.option-label:hover {
  background-color: #f5f5f5;
}

.option-label input[type="radio"] {
  margin-right: 10px;
}

.gap-fill-exercise .gap-inputs {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 15px 0;
}

.gap-input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.transformation-input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  margin: 15px 0;
}

.exercise-item button {
  padding: 10px 20px;
  background: var(--color-primary);
  color: #2b1f14;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1em;
  margin-top: 10px;
  transition: background-color 0.2s, box-shadow 0.2s;
  box-shadow: 0 6px 14px rgba(248, 162, 43, 0.18);
}

.exercise-item button:hover:not(:disabled) {
  background: var(--color-primary-hover);
  box-shadow: 0 10px 18px rgba(248, 162, 43, 0.22);
}

.exercise-item button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.exercise-result {
  margin-top: 15px;
  padding: 15px;
  border-radius: 4px;
}

.exercise-result.correct {
  background: #d4edda;
  color: #155724;
  border: 1px solid #c3e6cb;
}

.exercise-result.wrong {
  background: #f8d7da;
  color: #721c24;
  border: 1px solid #f5c6cb;
}

.exercise-explanation {
  margin-top: 10px;
  font-style: italic;
}

.unknown-exercise {
  padding: 15px;
  background: #fff3cd;
  border: 1px solid #ffc107;
  border-radius: 4px;
  margin-top: 10px;
}

.unknown-exercise__type {
  margin-top: 8px;
  font-size: 0.9em;
  color: var(--color-text-secondary);
}

/* Tabs */
.lesson-tabs {
  display: flex;
  gap: 10px;
  margin: 20px 0;
  border-bottom: 2px solid #ddd;
}

.tab-button {
  padding: 10px 20px;
  border: 1px solid transparent;
  background: rgba(255, 255, 255, 0.72);
  cursor: pointer;
  font-size: 1em;
  color: var(--color-text-secondary);
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  transition: all 0.2s;
  box-shadow: none;
}

.tab-button:hover:not(:disabled) {
  color: var(--color-text-primary);
  background: rgba(255, 255, 255, 0.92);
}

.tab-button.active {
  color: #2b1f14;
  background: var(--color-primary);
  border-bottom-color: var(--color-primary);
  font-weight: bold;
}

.tab-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Lernen-Sektion */
.learn-section {
  margin-top: 20px;
  padding: 20px;
  background: #f9f9f9;
  border-radius: 8px;
}

.learn-section h3 {
  margin-top: 0;
  color: #333;
}

.cultural-notes {
  margin: 20px 0;
  padding: 15px;
  background: #e7f3ff;
  border-left: 4px solid #007bff;
  border-radius: 4px;
}

.cultural-notes h4 {
  margin-top: 0;
  color: #007bff;
}

.vocab-list {
  margin: 20px 0;
}

.vocab-list__summary {
  cursor: pointer;
  font-weight: 600;
  color: #5f4313;
  margin-bottom: 12px;
}

.vocab-list[open] .vocab-list__summary {
  margin-bottom: 14px;
}

.vocab-list--overview {
  padding: 14px 16px;
  background: rgba(255, 255, 255, 0.86);
  border: 1px solid #eadfcf;
  border-radius: 12px;
}

.vocab-list h4 {
  margin-bottom: 15px;
  color: #333;
}

.vocab-items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 10px;
}

.vocab-item {
  padding: 10px;
  background: white;
  border: 1px solid #ddd;
  border-radius: 4px;
  display: flex;
  align-items: center;
  gap: 10px;
}

.vocab-item strong {
  color: #007bff;
}

.separator {
  color: #999;
  margin: 0 10px;
}

.vocab-items-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px 16px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid #eadfcf;
}

.vocab-items-pager__btn {
  padding: 6px 14px;
  font-size: 0.9rem;
  border: 1px solid #c9b896;
  border-radius: 8px;
  background: #faf8f3;
  color: #333;
  cursor: pointer;
}

.vocab-items-pager__btn:hover:not(:disabled) {
  background: #f0ebe0;
}

.vocab-items-pager__btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.vocab-items-pager__meta {
  font-size: 0.88rem;
  color: #555;
  text-align: center;
  min-width: min(100%, 14rem);
}

.grammar-explanations {
  margin: 20px 0;
  padding: 15px;
  background: #fff3cd;
  border-left: 4px solid #ffc107;
  border-radius: 4px;
}

.grammar-explanations h4 {
  margin-top: 0;
  color: #856404;
}

.grammar-explanation-item {
  margin: 15px 0;
  padding: 10px;
  background: white;
  border-radius: 4px;
}

.grammar-explanation-item strong {
  display: block;
  margin-bottom: 5px;
  color: #856404;
}

.vocab-trainer-section {
  margin: 20px 0;
  padding: 18px;
  background: #fff;
  border: 1px solid rgba(210, 131, 31, 0.2);
  border-radius: 12px;
  box-shadow: 0 10px 20px rgba(210, 131, 31, 0.08);
}

.vocab-trainer-section h4 {
  margin-top: 0;
  color: #333;
}

.review-priority-note,
.exercise-lock-note {
  margin-bottom: 12px;
  padding: 12px;
  border-radius: 4px;
}

.review-priority-note {
  background: #eef7ff;
  border: 1px solid #b9d8ff;
  color: #234a72;
}

.exercise-lock-note {
  background: #fff3cd;
  border: 1px solid #ffc107;
  color: #856404;
}

.review-priority-note strong,
.exercise-lock-note strong {
  display: block;
  margin-bottom: 4px;
}

.trainer-progress-row {
  font-size: 0.92em;
  color: #5b4636;
}

.vocab-trainer-start {
  text-align: center;
}

.btn-start-trainer {
  padding: 10px 20px;
  background: var(--color-primary);
  color: #2b1f14;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1em;
  margin-top: 10px;
  box-shadow: 0 6px 14px rgba(248, 162, 43, 0.18);
}

.btn-start-trainer:hover {
  background: var(--color-primary-hover);
}

.vocab-trainer-stats {
  margin-bottom: 15px;
  padding: 10px;
  background: #f5f5f5;
  border-radius: 4px;
}

.stats-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 15px;
  margin-bottom: 10px;
}

.stats-row:last-child {
  margin-bottom: 0;
}

.success-rate {
  font-weight: bold;
  color: #28a745;
}

.mode-badge {
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 0.9em;
  background: #e9ecef;
  color: #6c757d;
}

.mode-badge.mode-clickable {
  cursor: pointer;
}

.mode-badge.mode-clickable:hover {
  filter: brightness(0.98);
}

.mode-badge.mode-active {
  background: var(--color-primary);
  color: #2b1f14;
  font-weight: bold;
}

.mode-badge.mode-completed {
  background: #28a745;
  color: white;
}

.btn-stop-trainer {
  padding: 5px 15px;
  background: rgba(177, 59, 53, 0.92);
  color: white;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9em;
}

.btn-stop-trainer:hover {
  background: var(--color-danger-hover);
}

.vocab-question {
  margin-top: 15px;
}

.vocab-prompt {
  padding: 15px;
  background: #f9f9f9;
  border: 1px solid #ddd;
  border-radius: 4px;
  margin-bottom: 15px;
}

.vocab-direction {
  color: #666;
  font-size: 0.9em;
  margin-bottom: 5px;
}

.vocab-word {
  font-size: 1.5em;
  font-weight: bold;
  color: #333;
}

.vocab-answer-area {
  margin-bottom: 15px;
}

.vocab-answer-area.typing {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.mode-switch-notice {
  margin-bottom: 10px;
  padding: 10px;
  background: #fff3cd;
  border: 1px solid #ffc107;
  border-radius: 4px;
  text-align: center;
}

.btn-switch-mode {
  padding: 8px 16px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9em;
  font-weight: 500;
  transition: background 0.2s, border-color 0.2s;
  box-shadow: none;
}

.btn-switch-mode:hover {
  background: rgba(255, 255, 255, 0.98);
  border: 1px solid var(--color-border-strong);
}

.vocab-answer-area.multiple-choice {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.choice-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 10px;
}

.choice-button {
  padding: 12px;
  border: 2px solid #ddd;
  border-radius: 4px;
  background: white;
  cursor: pointer;
  font-size: 1em;
  transition: all 0.2s;
}

.choice-button:hover {
  border-color: var(--color-primary);
  background: rgba(248, 162, 43, 0.08);
}

.choice-button.selected {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: #2b1f14;
}

.vocab-input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1em;
}

.btn-check {
  padding: 10px 20px;
  background: var(--color-primary);
  color: #2b1f14;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1em;
}

.btn-check:hover:not(:disabled) {
  background: var(--color-primary-hover);
}

.btn-check:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.vocab-feedback {
  padding: 15px;
  border-radius: 4px;
  margin-bottom: 15px;
}

.vocab-feedback.correct {
  background: #d4edda;
  color: #155724;
  border: 1px solid #c3e6cb;
}

.vocab-feedback.wrong {
  background: #f8d7da;
  color: #721c24;
  border: 1px solid #f5c6cb;
}

.vocab-wrong-review {
  margin: 12px 0 15px;
  padding: 14px 16px;
  border: 1px solid #ef9a9a;
  border-radius: 6px;
  background: #fff1f1;
  color: #4a1f1f;
}

.vocab-wrong-review strong {
  display: block;
  margin-bottom: 10px;
}

.vocab-wrong-review dl {
  display: grid;
  gap: 8px;
  margin: 0;
}

.vocab-wrong-review dl > div {
  display: grid;
  grid-template-columns: minmax(120px, 0.3fr) 1fr;
  gap: 10px;
}

.vocab-wrong-review dt {
  font-weight: 700;
}

.vocab-wrong-review dd {
  margin: 0;
}

.vocab-next {
  margin-top: 15px;
}

.vocab-next button {
  padding: 10px 20px;
  background: var(--color-primary);
  color: #2b1f14;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1em;
}

.vocab-next button:hover {
  background: var(--color-primary-hover);
}

.vocab-info-text {
  color: #666;
  font-size: 0.9em;
  margin-bottom: 10px;
}

.no-vocab-info {
  padding: 15px;
  background: #fff3cd;
  border-left: 4px solid #ffc107;
  border-radius: 4px;
  color: #856404;
}

.continue-to-exercises {
  margin-top: 30px;
  text-align: center;
}

.btn-continue {
  padding: 12px 24px;
  background: var(--color-primary);
  color: #2b1f14;
  border: 1px solid transparent;
  border-radius: 4px;
  font-size: 1.1em;
  cursor: pointer;
  transition: background 0.2s;
}

.btn-continue:hover {
  background: var(--color-primary-hover);
}

/* Reading Aloud & Speaking From Memory Styles */
.reading-aloud-exercise,
.speaking-from-memory-exercise,
.sentence-building-exercise,
.dialog-completion-exercise,
.situational-response-exercise,
.pattern-drill-exercise {
  padding: 20px;
  background: #f8f9fa;
  border-radius: 8px;
  margin-bottom: 20px;
}

.reading-aloud-controls,
.speaking-controls {
  margin: 20px 0;
  display: flex;
  gap: 10px;
  align-items: center;
}

.btn-record,
.btn-stop-record {
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  font-size: 1em;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-record {
  background: var(--color-primary);
  color: #2b1f14;
}

.btn-record:hover:not(:disabled) {
  background: var(--color-primary-hover);
}

.btn-record:disabled {
  background: #6c757d;
  cursor: not-allowed;
}

.btn-stop-record {
  background: rgba(177, 59, 53, 0.92);
  color: white;
}

.btn-stop-record:hover {
  background: var(--color-danger-hover);
}

.btn-check {
  padding: 10px 20px;
  background: var(--color-primary);
  color: #2b1f14;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  margin-top: 15px;
}

.btn-check:hover {
  background: var(--color-primary-hover);
}

.recording-status {
  margin: 15px 0;
  padding: 10px;
  border-radius: 4px;
  font-weight: 500;
}

.recording-status.recording {
  background: #fff3cd;
  color: #856404;
  animation: pulse 1.5s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

.recognized-text {
  margin: 20px 0;
  padding: 15px;
  background: white;
  border: 2px solid #dee2e6;
  border-radius: 6px;
}

.recognized-text strong {
  display: block;
  margin-bottom: 10px;
  color: #495057;
}

.recognized-text p {
  margin: 0;
  font-size: 1.1em;
  color: #212529;
  line-height: 1.6;
}

.keywords-hint {
  margin: 15px 0;
  padding: 12px;
  background: #e7f3ff;
  border-left: 4px solid #007bff;
  border-radius: 4px;
}

.keywords-hint strong {
  display: block;
  margin-bottom: 8px;
  color: #0056b3;
}

.keyword-tag {
  display: inline-block;
  padding: 4px 10px;
  margin: 4px 4px 0 0;
  background: #007bff;
  color: white;
  border-radius: 12px;
  font-size: 0.9em;
}

.speech-not-supported {
  margin-top: 15px;
  padding: 12px;
  background: #f8d7da;
  border-left: 4px solid #dc3545;
  border-radius: 4px;
  color: #721c24;
}

/* Dialog Styles */
.dialog-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.5);
}

.dialog {
  background: white;
  display: flex;
  flex-direction: column;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  border-radius: 8px;
  max-width: 90%;
  max-height: 90%;
}

.dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px;
  border-bottom: 1px solid #ddd;
  background-color: var(--color-primary-orange);
}

.dialog-title {
  flex-grow: 1;
  font-size: 1.2em;
  font-weight: bold;
}

.dialog-close {
  cursor: pointer;
  font-size: 1.5em;
  margin-left: 10px;
  color: #000;
}

.dialog-body {
  padding: 20px;
  overflow-y: auto;
}

.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 10px 20px;
  border-top: 1px solid #ddd;
}

.dialog-button {
  padding: 8px 16px;
  background: var(--color-primary);
  color: #2b1f14;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1em;
}

.dialog-button:hover {
  background: var(--color-primary-hover);
}

@media (max-width: 900px) {
  .lesson-overview-card {
    flex-direction: column;
  }

  .lesson-meta-grid {
    grid-template-columns: 1fr;
  }

  .learn-section {
    margin-top: 14px;
    padding: 14px;
  }

  .lesson-primary-flow,
  .lesson-deepen-section,
  .lesson-assistant-section {
    padding: 14px;
  }

  .lesson-header {
    gap: 10px;
    margin-bottom: 14px;
  }

  .exercise-flow-header {
    flex-direction: column;
  }

  .exercise-flow-header__stats {
    grid-template-columns: 1fr;
    min-width: 0;
  }

  .exercise-item__header {
    flex-direction: column;
    gap: 8px;
  }
}
</style>
