<template>
  <DialogWidget
    ref="dialog"
    :title="$t('socialnetwork.vocab.practice.title')"
    :show-close="false"
    :buttons="buttons"
    :modal="true"
    :isTitleTranslated="false"
    width="64em"
    height="40em"
    name="VocabPracticeDialog"
    display="flex"
  >
    <div class="layout">
      <div class="left">
        <div class="opts">
          <label class="chk">
            <input type="checkbox" v-model="allVocabs" :disabled="srsMode" @change="reloadPool" />
            {{ $t('socialnetwork.vocab.practice.allVocabs') }}
          </label>
          <label class="chk">
            <input type="checkbox" v-model="simpleMode" @change="onSimpleModeChanged" />
            {{ $t('socialnetwork.vocab.practice.simple') }}
          </label>
        </div>

        <div v-if="loading">{{ $t('general.loading') }}</div>
        <div v-else-if="pool.length === 0">
          {{ $t('socialnetwork.vocab.practice.noPool') }}
        </div>
        <div v-else-if="srsMode && srsQueueIds.length === 0" class="srs-finished">
          <div class="srs-finished__title">{{ $t('socialnetwork.vocab.practice.srsFinishedTitle') }}</div>
          <div class="srs-finished__desc">{{ $t('socialnetwork.vocab.practice.srsFinishedDesc') }}</div>
        </div>
        <div v-else>
          <div class="prompt">
            <div class="dir">{{ directionLabel }}</div>
            <div class="word">{{ currentPrompt }}</div>
          </div>

          <div v-if="answered" class="feedback" :class="{ ok: lastCorrect, bad: !lastCorrect }">
            <div v-if="lastCorrect">{{ $t('socialnetwork.vocab.practice.correct') }}</div>
            <div v-else>
              {{ $t('socialnetwork.vocab.practice.wrong') }}
            </div>
          </div>

          <div v-if="showSrsRatingButtons" class="srs-rating">
            <div class="srs-rating__title">{{ $t('socialnetwork.vocab.practice.srsRateTitle') }}</div>
            <button
              v-for="option in srsRatingOptions"
              :key="option.value"
              type="button"
              class="srs-rating__button"
              :class="`srs-rating__button--${option.value}`"
              :disabled="locked"
              @click="submitSrsRating(option.value)"
            >
              <strong>{{ option.label }}</strong>
              <span>{{ option.hint }}</span>
            </button>
          </div>

          <div v-if="!answered" class="answerArea">
            <div v-if="simpleMode" class="choices">
              <button
                v-for="opt in choiceOptions"
                :key="opt"
                class="choiceBtn"
                :disabled="locked"
                @click="submitChoice(opt)"
              >
                {{ opt }}
              </button>
            </div>
            <div v-else class="typing">
              <input
                ref="answerInput"
                v-model="typedAnswer"
                type="text"
                :disabled="locked"
                @keydown.enter.prevent="submitTyped"
              />
              <button :disabled="locked || typedAnswer.trim().length === 0" @click="submitTyped">
                {{ $t('socialnetwork.vocab.practice.check') }}
              </button>
            </div>
          </div>

          <div class="controls">
            <button v-if="showNextButton" @click="next">
              {{ $t('socialnetwork.vocab.practice.next') }}
            </button>
            <button v-else-if="showSkipButton" @click="skip">
              {{ $t('socialnetwork.vocab.practice.skip') }}
            </button>
            <button v-if="current && !isCurrentMarkedHard" @click="markCurrentAsHard">
              {{ $t('socialnetwork.vocab.practice.markHard') }}
            </button>
            <button v-else-if="current && isCurrentMarkedHard" @click="unmarkCurrentAsHard">
              {{ $t('socialnetwork.vocab.practice.unmarkHard') }}
            </button>
          </div>

          <div v-if="lastWrongReview" class="solution-card solution-card--persistent">
            <div class="answersTitle">{{ $t('socialnetwork.vocab.practice.wrongReviewTitle') }}</div>
            <div class="solution-row">
              <span>{{ $t('socialnetwork.vocab.practice.askedVocab') }}</span>
              <strong>{{ lastWrongReview.prompt }}</strong>
            </div>
            <div v-if="lastWrongReview.submittedAnswer" class="solution-row">
              <span>{{ $t('socialnetwork.vocab.practice.yourAnswer') }}</span>
              <strong>{{ lastWrongReview.submittedAnswer }}</strong>
            </div>
            <div class="answersTitle">{{ $t('socialnetwork.vocab.practice.correctSolutions') }}</div>
            <ul>
              <li v-for="a in lastWrongReview.answers" :key="a">{{ a }}</li>
            </ul>
          </div>
        </div>
      </div>

      <div class="right">
        <div class="stat">
          <div class="statTitle">{{ $t('socialnetwork.vocab.practice.stats') }}</div>
          <div v-if="srsMode" class="statRow">
            <span class="k">{{ $t('socialnetwork.vocab.practice.dueToday') }}</span>
            <span class="v">{{ srsTotalDue }}</span>
          </div>
          <div v-if="srsMode" class="statRow">
            <span class="k">{{ $t('socialnetwork.vocab.practice.done') }}</span>
            <span class="v">{{ srsDoneCount }}</span>
          </div>
          <div v-if="srsMode" class="statRow">
            <span class="k">{{ $t('socialnetwork.vocab.practice.remaining') }}</span>
            <span class="v">{{ srsRemainingCount }}</span>
          </div>
          <div class="statRow">
            <span class="k">{{ $t('socialnetwork.vocab.practice.success') }}</span>
            <span class="v">{{ correctCount }} ({{ successPercent }}%)</span>
          </div>
          <div class="statRow">
            <span class="k">{{ $t('socialnetwork.vocab.practice.fail') }}</span>
            <span class="v">{{ wrongCount }} ({{ failPercent }}%)</span>
          </div>
          <div class="statRow">
            <span class="k">{{ $t('socialnetwork.vocab.practice.hardCount') }}</span>
            <span class="v">{{ hardCount }}</span>
          </div>
        </div>
      </div>
    </div>
  </DialogWidget>
</template>

<script>
import DialogWidget from '@/components/DialogWidget.vue';
import apiClient from '@/utils/axios.js';

const PRACTICE_MIN_EXPOSURES = 3;
const SRS_SESSION_STORAGE_VERSION = 2;
const HARD_REQUIRED_CONSECUTIVE_CORRECT = 5;
const SRS_AGAIN_REINSERT_OFFSET = 3;

export default {
  name: 'VocabPracticeDialog',
  components: { DialogWidget },
  data() {
    return {
      openParams: null, // { languageId, chapterId, lessonId, courseId }
      onClose: null,
      loading: false,
      allVocabs: false,
      srsMode: false,
      initialPool: null,
      simpleMode: false,
      pool: [],

      // SRS session (Langzeitgedächtnis) progress & persistence
      // Stored per day and course so the user can close/reopen without starting over.
      srsSession: null, // { version, dateKey, courseId, initialTotalDue, initialDueIds, doneIds, correctCount, wrongCount }
      srsQueueIds: [], // remaining due ids for this session, in due order

      // session stats
      correctCount: 0,
      wrongCount: 0,
      perId: {}, // { [id]: { c, w, streak, lastAsked } }
      lastIds: [],

      // current question
      current: null, // { id, learning, reference }
      direction: 'L2R', // L2R: learning->reference, R2L: reference->learning
      pendingRetry: null, // { id, direction } for immediate retry after wrong answers
      acceptableAnswers: [],
      submittedAnswer: '',
      lastWrongReview: null,
      choiceOptions: [],
      typedAnswer: '',
      answered: false,
      lastCorrect: false,
      locked: false,
      autoAdvanceTimer: null,
      hardVocabMap: {}, // { [normalizedPairKey]: { learning, reference, markedAt } }
      hardPhaseActive: false,
      hardMasteryByKey: {}, // { [hardKey]: consecutiveCorrect }
      cycleAskedIds: [],
    };
  },
  computed: {
    buttons() {
      return [{ text: this.$t('message.close'), action: this.close }];
    },
    totalCount() {
      return this.correctCount + this.wrongCount;
    },
    successPercent() {
      if (this.totalCount === 0) return 0;
      return Math.round((this.correctCount / this.totalCount) * 100);
    },
    failPercent() {
      if (this.totalCount === 0) return 0;
      return Math.round((this.wrongCount / this.totalCount) * 100);
    },
    currentPrompt() {
      if (!this.current) return '';
      return this.direction === 'L2R' ? this.current.learning : this.current.reference;
    },
    directionLabel() {
      return this.direction === 'L2R'
        ? this.$t('socialnetwork.vocab.practice.dirLearningToRef')
        : this.$t('socialnetwork.vocab.practice.dirRefToLearning');
    },
    showNextButton() {
      // Nur bei falscher Antwort auf "Weiter" warten
      return this.answered && !this.lastCorrect && !this.srsMode;
    },
    showSkipButton() {
      return !this.answered;
    },
    showSrsRatingButtons() {
      return this.srsMode && this.answered && !this.locked;
    },
    visibleCorrectAnswers() {
      const answers = Array.isArray(this.acceptableAnswers) ? this.acceptableAnswers.filter(Boolean) : [];
      if (answers.length > 0) {
        return answers;
      }
      if (!this.current) {
        return [];
      }
      const fallback = this.direction === 'L2R' ? this.current.reference : this.current.learning;
      return this.expandAnswerVariants(fallback);
    },
    srsRatingOptions() {
      if (!this.answered) {
        return [];
      }
      if (!this.lastCorrect) {
        return [
          {
            value: 'again',
            label: this.$t('socialnetwork.vocab.practice.srsAgain'),
            hint: this.$t('socialnetwork.vocab.practice.srsAgainHint')
          }
        ];
      }
      return [
        {
          value: 'hard',
          label: this.$t('socialnetwork.vocab.practice.srsHard'),
          hint: this.$t('socialnetwork.vocab.practice.srsHardHint')
        },
        {
          value: 'good',
          label: this.$t('socialnetwork.vocab.practice.srsGood'),
          hint: this.$t('socialnetwork.vocab.practice.srsGoodHint')
        },
        {
          value: 'easy',
          label: this.$t('socialnetwork.vocab.practice.srsEasy'),
          hint: this.$t('socialnetwork.vocab.practice.srsEasyHint')
        }
      ];
    },
    srsTotalDue() {
      return Number(this.srsSession?.initialTotalDue || 0) || 0;
    },
    srsDoneCount() {
      const done = Array.isArray(this.srsSession?.doneIds) ? this.srsSession.doneIds.length : 0;
      return Math.min(done, this.srsTotalDue || 0);
    },
    srsRemainingCount() {
      const total = this.srsTotalDue || 0;
      return Math.max(0, total - this.srsDoneCount);
    },
    hardCount() {
      return Object.keys(this.hardVocabMap || {}).length;
    },
    isCurrentMarkedHard() {
      if (!this.current) return false;
      return this.isItemMarkedHard(this.current);
    },
    hardPoolItems() {
      if (!Array.isArray(this.pool) || this.pool.length === 0) return [];
      return this.pool.filter((item) => this.isItemMarkedHard(item));
    },
    hardRemainingCount() {
      return this.hardPoolItems.filter((item) => {
        const key = this.getHardKey(item);
        return (Number(this.hardMasteryByKey[key]) || 0) < HARD_REQUIRED_CONSECUTIVE_CORRECT;
      }).length;
    }
  },
  methods: {
    hardStorageKey() {
      const courseId = this.openParams?.courseId;
      if (!courseId) return null;
      return `yourpart:vocab:hardList:${courseId}`;
    },
    loadHardVocabMap() {
      const key = this.hardStorageKey();
      if (!key) {
        this.hardVocabMap = {};
        return;
      }
      try {
        const raw = localStorage.getItem(key);
        const parsed = raw ? JSON.parse(raw) : {};
        this.hardVocabMap = parsed && typeof parsed === 'object' ? parsed : {};
      } catch (_) {
        this.hardVocabMap = {};
      }
    },
    saveHardVocabMap() {
      const key = this.hardStorageKey();
      if (!key) return;
      try {
        localStorage.setItem(key, JSON.stringify(this.hardVocabMap || {}));
      } catch (_) {
        // ignore quota/private-mode issues
      }
      try {
        window.dispatchEvent(new CustomEvent('yourpart:hardvocab:changed', {
          detail: {
            courseId: this.openParams?.courseId || null,
            storageKey: key
          }
        }));
      } catch (_) {
        // ignore environments without CustomEvent
      }
    },
    getHardKey(item) {
      const learning = this.normalize(item?.learning || '');
      const reference = this.normalize(item?.reference || '');
      return `${learning}|${reference}`;
    },
    findMatchingHardKey(item) {
      const exactKey = this.getHardKey(item);
      if (this.hardVocabMap[exactKey]) return exactKey;

      const itemLearning = this.normalize(item?.learning || '');
      const itemReference = this.normalize(item?.reference || '');
      if (!itemLearning || !itemReference) return null;

      const entries = Object.entries(this.hardVocabMap || {});
      for (const [key, value] of entries) {
        const learningAlts = this.splitPhraseAlternatives(value?.learning || '')
          .map((part) => this.normalize(part))
          .filter(Boolean);
        const referenceAlts = this.splitPhraseAlternatives(value?.reference || '')
          .map((part) => this.normalize(part))
          .filter(Boolean);
        if (!learningAlts.length || !referenceAlts.length) continue;
        if (learningAlts.includes(itemLearning) && referenceAlts.includes(itemReference)) {
          return key;
        }
      }
      return null;
    },
    isItemMarkedHard(item) {
      return Boolean(this.findMatchingHardKey(item));
    },
    removeHardEntriesForItem(item) {
      const itemLearning = this.normalize(item?.learning || '');
      const itemReference = this.normalize(item?.reference || '');
      if (!itemLearning || !itemReference) return;

      const toDelete = [];
      Object.entries(this.hardVocabMap || {}).forEach(([key, value]) => {
        const learningAlts = this.splitPhraseAlternatives(value?.learning || '')
          .map((part) => this.normalize(part))
          .filter(Boolean);
        const referenceAlts = this.splitPhraseAlternatives(value?.reference || '')
          .map((part) => this.normalize(part))
          .filter(Boolean);
        if (learningAlts.includes(itemLearning) && referenceAlts.includes(itemReference)) {
          toDelete.push(key);
        }
      });
      if (!toDelete.length) return;

      const next = { ...this.hardVocabMap };
      const nextMastery = { ...this.hardMasteryByKey };
      toDelete.forEach((key) => {
        delete next[key];
        delete nextMastery[key];
      });
      this.hardVocabMap = next;
      this.hardMasteryByKey = nextMastery;
    },
    markCurrentAsHard() {
      if (!this.current) return;
      this.removeHardEntriesForItem(this.current);
      const key = this.getHardKey(this.current);
      this.hardVocabMap = {
        ...this.hardVocabMap,
        [key]: {
          learning: this.current.learning,
          reference: this.current.reference,
          markedAt: new Date().toISOString()
        }
      };
      if (this.hardMasteryByKey[key] == null) {
        this.hardMasteryByKey[key] = 0;
      }
      this.saveHardVocabMap();
    },
    unmarkCurrentAsHard() {
      if (!this.current) return;
      const matchingKey = this.findMatchingHardKey(this.current);
      if (!matchingKey) return;
      this.removeHardEntriesForItem(this.current);
      if (this.hardPhaseActive && this.hardRemainingCount <= 0) {
        this.hardPhaseActive = false;
      }
      this.saveHardVocabMap();
    },
    addCurrentToCycle() {
      if (!this.current?.id) return;
      if (this.cycleAskedIds.includes(this.current.id)) return;
      this.cycleAskedIds = [...this.cycleAskedIds, this.current.id];
    },
    maybeStartHardPhase() {
      if (this.srsMode || this.hardPhaseActive) return;
      if (!this.pool.length || !this.hardPoolItems.length) return;
      if (this.cycleAskedIds.length < this.pool.length) return;
      this.hardPhaseActive = true;
    },
    maybeFinishHardPhase() {
      if (!this.hardPhaseActive) return;
      if (this.hardRemainingCount > 0) return;
      this.hardPhaseActive = false;
      this.cycleAskedIds = [];
    },
    getLocalDateKey() {
      const d = new Date();
      const y = d.getFullYear();
      const m = String(d.getMonth() + 1).padStart(2, '0');
      const day = String(d.getDate()).padStart(2, '0');
      return `${y}-${m}-${day}`;
    },
    srsStorageKey() {
      const courseId = this.openParams?.courseId;
      if (!courseId) return null;
      return `yourpart:vocab:srsSession:v${SRS_SESSION_STORAGE_VERSION}:${courseId}:${this.getLocalDateKey()}`;
    },
    loadSrsSession() {
      const key = this.srsStorageKey();
      if (!key) return null;
      try {
        const raw = localStorage.getItem(key);
        if (!raw) return null;
        const parsed = JSON.parse(raw);
        if (!parsed || parsed.version !== SRS_SESSION_STORAGE_VERSION) return null;
        if (parsed.courseId !== this.openParams?.courseId) return null;
        if (parsed.dateKey !== this.getLocalDateKey()) return null;
        return parsed;
      } catch (_) {
        return null;
      }
    },
    saveSrsSession() {
      if (!this.srsMode || !this.openParams?.courseId) return;
      const key = this.srsStorageKey();
      if (!key) return;
      try {
        const session = this.srsSession || null;
        if (!session) return;
        session.correctCount = this.correctCount;
        session.wrongCount = this.wrongCount;
        localStorage.setItem(key, JSON.stringify(session));
      } catch (_) {
        // ignore storage issues (private mode, quota, etc.)
      }
    },
    initSrsSessionFromPool() {
      if (!this.srsMode || !this.openParams?.courseId) {
        this.srsSession = null;
        this.srsQueueIds = [];
        return;
      }
      const dueIds = (this.pool || []).map((it) => it.id);
      const stored = this.loadSrsSession();

      // If the previous session is already complete, start fresh (new batch of due items).
      if (stored && Number(stored.initialTotalDue || 0) > 0 && Array.isArray(stored.doneIds) && stored.doneIds.length >= stored.initialTotalDue) {
        this.srsSession = null;
      } else if (stored) {
        this.srsSession = stored;
      } else {
        this.srsSession = null;
      }

      if (!this.srsSession) {
        this.srsSession = {
          version: SRS_SESSION_STORAGE_VERSION,
          dateKey: this.getLocalDateKey(),
          courseId: this.openParams.courseId,
          initialTotalDue: dueIds.length,
          initialDueIds: dueIds,
          doneIds: [],
          correctCount: 0,
          wrongCount: 0,
        };
        this.correctCount = 0;
        this.wrongCount = 0;
      } else {
        // Resume: keep initialTotalDue stable (what the user started with), and prune queue to the currently due ids.
        this.correctCount = Number(this.srsSession.correctCount || 0) || 0;
        this.wrongCount = Number(this.srsSession.wrongCount || 0) || 0;
      }

      const doneSet = new Set(Array.isArray(this.srsSession.doneIds) ? this.srsSession.doneIds : []);
      this.srsQueueIds = dueIds.filter((id) => !doneSet.has(id));
      this.saveSrsSession();
    },
    open({ languageId, chapterId, lessonId, courseId, initialPool = null, srsMode = false, onClose = null }) {
      if (this.autoAdvanceTimer) {
        clearTimeout(this.autoAdvanceTimer);
        this.autoAdvanceTimer = null;
      }
      this.openParams = { languageId, chapterId, lessonId, courseId };
      this.onClose = typeof onClose === 'function' ? onClose : null;
      this.srsMode = Boolean(srsMode);
      this.initialPool = Array.isArray(initialPool) ? initialPool : null;
      this.allVocabs = false;
      this.simpleMode = false;
      this.srsSession = null;
      this.srsQueueIds = [];
      this.correctCount = 0;
      this.wrongCount = 0;
      this.perId = {};
      this.lastIds = [];
      this.lastWrongReview = null;
      this.pendingRetry = null;
      this.pool = [];
      this.hardVocabMap = {};
      this.hardPhaseActive = false;
      this.hardMasteryByKey = {};
      this.cycleAskedIds = [];
      this.locked = false;
      this.resetQuestion();
      this.$refs.dialog.open();
      this.$nextTick(() => {
        document.addEventListener('keydown', this.handleKeyDown);
      });
      this.loadHardVocabMap();
      this.reloadPool();
    },
    close() {
      if (this.autoAdvanceTimer) {
        clearTimeout(this.autoAdvanceTimer);
        this.autoAdvanceTimer = null;
      }
      this.saveSrsSession();
      const cb = this.onClose;
      this.onClose = null;
      document.removeEventListener('keydown', this.handleKeyDown);
      this.$refs.dialog.close();
      try {
        if (cb) cb();
      } catch (_) {}
    },
    handleKeyDown(event) {
      // Enter soll bei "Weiter" (falsch beantwortet) funktionieren.
      // Im Tippmodus soll Enter weiterhin "Prüfen" auslösen (Input hat eigenen handler).
      if (event.key !== 'Enter' && event.keyCode !== 13) return;

      if (this.showNextButton) {
        event.preventDefault();
        this.next();
        return;
      }

      // Falls man im Tippmodus ist und der Fokus NICHT im Input liegt, erlauben wir Enter als "Prüfen".
      if (!this.answered && !this.simpleMode && !this.locked) {
        const tag = event.target?.tagName?.toLowerCase?.();
        if (tag !== 'input' && tag !== 'textarea') {
          event.preventDefault();
          this.submitTyped();
        }
      }
    },
    normalize(s) {
      const normalized = String(s || '')
        .trim()
        .toLowerCase()
        .normalize('NFKC')
        .replace(/[\p{P}\p{S}]+/gu, ' ')
        .replace(/\s+/g, ' ')
        .trim();
      return normalized.replace(/\s+/g, '');
    },
    isInstructionLikeText(value) {
      const text = String(value || '').trim();
      if (!text) return false;
      const wordCount = text.split(/\s+/).filter(Boolean).length;
      if (wordCount < 3) return false;

      const normalized = text.toLowerCase().normalize('NFKC');
      const startsWithTaskVerb = /^(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|erzaehle|beschreibe|bilde|wähle|waehle|ordne|übersetze|uebersetze|nenne|nenn|beginne|verwende|nutze|reagiere|kombiniere|spiele|löse|loese|beantworte|ergänze|ergaenze|formuliere|lies|entscheide|zeige)\b/i.test(normalized);
      const startsWithTakeTask = /^nimm\b/i.test(normalized)
        && (
          /\b(ein|eine|einen|zwei|drei|vier|fünf|fuenf|sechs|sieben|acht|neun|zehn|\d+)\b/i.test(normalized)
          || /\b(w[oö]rter|verben|gegenstände|gegenstaende|sätze|saetze|muster|beispiele)\b/i.test(normalized)
        );
      const containsTaskChain = /\b(und|,)\s*(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|erzaehle|beschreibe|bilde|wähle|waehle|ordne|übersetze|uebersetze|nenne|nenn|verwende|nutze|reagiere|kombiniere|spiele|löse|loese|beantworte|ergänze|ergaenze|formuliere|lies|entscheide|zeige)\b/i.test(normalized);
      const containsPracticeMarker = /\b(laut|jeweils|zu jedem|zu jeder|umgebung|alltagsszene|rollenspiel|mini-dialog|szene)\b/i.test(normalized);

      return startsWithTaskVerb || startsWithTakeTask || (containsTaskChain && containsPracticeMarker);
    },
    isTrainablePair(learning, reference) {
      if (!learning || !reference || this.normalize(learning) === this.normalize(reference)) {
        return false;
      }
      if (this.looksLikeFragmentMismatch(learning, reference)) {
        return false;
      }
      return !this.isInstructionLikeText(learning) && !this.isInstructionLikeText(reference);
    },
    wordCount(value) {
      return String(value || '')
        .trim()
        .replace(/[\p{P}\p{S}]+/gu, ' ')
        .split(/\s+/)
        .filter(Boolean)
        .length;
    },
    looksLikeFragmentMismatch(left, right) {
      const leftWords = this.wordCount(left);
      const rightWords = this.wordCount(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);
    },
    splitPhraseAlternatives(value) {
      const text = String(value || '').trim();
      if (!text) return [];
      const parts = text
        .split(/\s+\/\s+/)
        .map((part) => String(part || '').trim())
        .filter(Boolean);
      return parts.length >= 2 ? parts : [text];
    },
    expandPoolItemAlternatives(item) {
      const learning = String(item?.learning || '').trim();
      const reference = String(item?.reference || '').trim();
      const learningParts = this.splitPhraseAlternatives(learning);
      const referenceParts = this.splitPhraseAlternatives(reference);

      // Common case: one prompt with multiple valid translations.
      // Split into separate vocab cards so each answer is trainable.
      if (learningParts.length === 1 && referenceParts.length > 1) {
        return referenceParts.map((ref) => ({ ...item, learning, reference: ref }));
      }
      if (referenceParts.length === 1 && learningParts.length > 1) {
        return learningParts.map((lrn) => ({ ...item, learning: lrn, reference }));
      }

      // If both sides contain parallel alternatives of equal length, keep pairs aligned.
      if (learningParts.length > 1 && learningParts.length === referenceParts.length) {
        return learningParts.map((lrn, idx) => ({ ...item, learning: lrn, reference: referenceParts[idx] }));
      }

      return [{ ...item, learning, reference }];
    },
    normalizePool(items = []) {
      const seen = new Set();
      return (Array.isArray(items) ? items : [])
        .flatMap((item, index) => this.expandPoolItemAlternatives(item).map((candidate, altIndex) => ({ candidate, index, altIndex })))
        .map(({ candidate, index, altIndex }) => {
          const learning = String(candidate?.learning || '').trim();
          const reference = String(candidate?.reference || '').trim();
          if (!this.isTrainablePair(learning, reference)) return null;
          const key = `${this.normalize(learning)}|${this.normalize(reference)}`;
          if (seen.has(key)) return null;
          seen.add(key);
          return {
            ...candidate,
            id: candidate?.id || candidate?.itemKey || candidate?.key || `${key}|${index}|${altIndex}`,
            learning,
            reference
          };
        })
        .filter(Boolean);
    },
    resetQuestion() {
      this.current = null;
      this.direction = this.openParams?.lessonId ? 'L2R' : (Math.random() < 0.5 ? 'L2R' : 'R2L');
      this.acceptableAnswers = [];
      this.submittedAnswer = '';
      this.choiceOptions = [];
      this.typedAnswer = '';
      this.answered = false;
      this.lastCorrect = false;
      this.locked = false;
    },
    onSimpleModeChanged() {
      if (this.autoAdvanceTimer) {
        clearTimeout(this.autoAdvanceTimer);
        this.autoAdvanceTimer = null;
      }
      this.locked = false;
      this.answered = false;
      this.lastCorrect = false;
      this.typedAnswer = '';
      this.submittedAnswer = '';

      if (!this.pool || this.pool.length === 0) return;

      // Wenn wir aktuell keine Frage haben, sofort eine neue ziehen.
      if (!this.current) {
        this.next();
        return;
      }

      // Aktuelle Frage behalten, nur UI/Antwortmodus neu aufbauen
      const prompt = this.currentPrompt;
      this.acceptableAnswers = this.getAnswersForPrompt(prompt, this.direction);
      if (this.simpleMode) {
        this.buildChoices();
      } else {
        this.choiceOptions = [];
        this.$nextTick(() => this.$refs.answerInput?.focus?.());
      }
    },
    async reloadPool() {
      if (!this.openParams) return;
      if (this.initialPool) {
        this.loading = false;
        this.pool = this.normalizePool(this.initialPool);
        if (this.srsMode) {
          this.initSrsSessionFromPool();
        }
        this.next();
        return;
      }
      this.loading = true;
      try {
        let res;
        if (this.openParams.lessonId) {
          if (this.allVocabs && this.openParams.courseId) {
            res = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/completed-lesson-vocabs`, {
              params: {
                untilLessonId: this.openParams.lessonId
              }
            });
            this.pool = this.normalizePool(res.data?.vocabs || []);
          } else {
            res = await apiClient.get(`/api/vocab/lessons/${this.openParams.lessonId}/vocab-pool`);
            this.pool = this.normalizePool(res.data?.vocabs || []);
          }
        } else if (this.allVocabs) {
          res = await apiClient.get(`/api/vocab/languages/${this.openParams.languageId}/vocabs`);
          this.pool = this.normalizePool(res.data?.vocabs || []);
        } else {
          res = await apiClient.get(`/api/vocab/chapters/${this.openParams.chapterId}/vocabs`);
          this.pool = this.normalizePool(res.data?.vocabs || []);
        }
      } catch (e) {
        console.error('Reload pool failed:', e);
        this.pool = [];
      } finally {
        this.loading = false;
        if (this.srsMode) {
          this.initSrsSessionFromPool();
        }
        this.next();
      }
    },
    getAnswersForPrompt(prompt, direction) {
      const p = this.normalize(prompt);
      const answers = new Set();
      for (const item of this.pool) {
        const itemPrompt = direction === 'L2R' ? item.learning : item.reference;
        if (this.normalize(itemPrompt) === p) {
          const a = direction === 'L2R' ? item.reference : item.learning;
          this.expandAnswerVariants(a).forEach((variant) => answers.add(variant));
        }
      }
      return Array.from(answers);
    },
    expandSingleAnswerVariants(answer) {
      const base = String(answer || '').trim();
      if (!base) return [];

      const words = base.split(/\s+/).map((word) => {
        if (!word.includes('/')) {
          return [word];
        }

        const match = word.match(/^([([{„"'“‘]*)(.*?)([)\]}.,!?;:»"'”’]*)$/);
        const prefix = match?.[1] || '';
        const core = match?.[2] || word;
        const suffix = match?.[3] || '';
        const parts = core
          .split('/')
          .map((part) => part.trim())
          .filter(Boolean);

        if (parts.length < 2 || parts.length > 4) {
          return [word];
        }

        return parts.map((part) => `${prefix}${part}${suffix}`);
      });

      const variants = [''];
      for (const options of words) {
        const next = [];
        for (const current of variants) {
          for (const option of options) {
            next.push(`${current}${current ? ' ' : ''}${option}`);
          }
        }
        if (next.length > 24) {
          return [base];
        }
        variants.splice(0, variants.length, ...next);
      }

      return [...new Set([base, ...variants])];
    },
    expandAnswerVariants(answer) {
      const base = String(answer || '').trim();
      if (!base) return [];

      // Handle full-answer alternatives like "A / B" as separate valid answers.
      // Word-level slash expansion alone does not cover this reliably.
      const phraseAlternatives = this.splitPhraseAlternatives(base);

      if (phraseAlternatives.length >= 2) {
        const expanded = phraseAlternatives.flatMap((part) => this.expandSingleAnswerVariants(part));
        return [...new Set(expanded)];
      }

      return this.expandSingleAnswerVariants(base);
    },
    computeWeight(item) {
      const st = this.perId[item.id] || { c: 0, w: 0, streak: 0, lastAsked: 0 };
      let w = 1;
      w += st.w * 2.5;
      w *= Math.pow(0.7, st.c);
      if (st.streak > 0) {
        w *= Math.pow(0.8, st.streak);
      } else if (st.streak < 0) {
        w *= 1 + Math.min(5, Math.abs(st.streak));
      }
      if (this.lastIds.includes(item.id)) w *= 0.1;
      return Math.max(0.05, Math.min(50, w));
    },
    pickNextItem() {
      const items = this.pool;
      if (!items || items.length === 0) return null;
      if (this.srsMode) {
        const nextId = Array.isArray(this.srsQueueIds) ? this.srsQueueIds[0] : null;
        if (!nextId) return null;
        return items.find((it) => it.id === nextId) || null;
      }
      if (this.hardPhaseActive) {
        const hardItems = this.hardPoolItems.filter((item) => {
          const key = this.getHardKey(item);
          return (Number(this.hardMasteryByKey[key]) || 0) < HARD_REQUIRED_CONSECUTIVE_CORRECT;
        });
        if (hardItems.length === 0) {
          this.hardPhaseActive = false;
          return null;
        }
        const rankedHard = hardItems
          .map((item) => {
            const key = this.getHardKey(item);
            const mastery = Number(this.hardMasteryByKey[key]) || 0;
            const st = this.perId[item.id] || { c: 0, w: 0, streak: 0, lastAsked: 0 };
            return {
              item,
              mastery,
              wrong: Number(st.w) || 0,
              attempts: (Number(st.c) || 0) + (Number(st.w) || 0),
            };
          })
          .sort((a, b) => {
            if (a.mastery !== b.mastery) return a.mastery - b.mastery;
            if (a.wrong !== b.wrong) return b.wrong - a.wrong;
            if (a.attempts !== b.attempts) return a.attempts - b.attempts;
            return Math.random() - 0.5;
          });
        return rankedHard[0]?.item || hardItems[Math.floor(Math.random() * hardItems.length)];
      }
      if (this.pendingRetry?.id) {
        const retryItem = items.find((it) => it.id === this.pendingRetry.id);
        if (retryItem) {
          return retryItem;
        }
        this.pendingRetry = null;
      }
      const recent = new Set(this.lastIds);
      const underexposed = items
        .map((item) => {
          const st = this.perId[item.id] || { c: 0, w: 0, streak: 0, lastAsked: 0 };
          return {
            item,
            attempts: (Number(st.c) || 0) + (Number(st.w) || 0),
            wrong: Number(st.w) || 0
          };
        })
        .filter((entry) => entry.attempts < (this.srsMode ? 1 : PRACTICE_MIN_EXPOSURES) && !recent.has(entry.item.id))
        .sort((a, b) => {
          if (a.attempts !== b.attempts) return a.attempts - b.attempts;
          if (a.wrong !== b.wrong) return b.wrong - a.wrong;
          return String(a.item.id).localeCompare(String(b.item.id));
        });
      if (underexposed.length > 0) {
        return underexposed[0].item;
      }

      const weights = items.map((it) => this.computeWeight(it));
      const sum = weights.reduce((a, b) => a + b, 0);
      let r = Math.random() * sum;
      for (let i = 0; i < items.length; i++) {
        r -= weights[i];
        if (r <= 0) return items[i];
      }
      return items[items.length - 1];
    },
    buildChoices() {
      const prompt = this.currentPrompt;
      const acceptable = this.getAnswersForPrompt(prompt, this.direction);
      this.acceptableAnswers = acceptable;

      const options = new Set();
      // 1) mindestens eine richtige Übersetzung
      options.add(acceptable[0] || (this.direction === 'L2R' ? this.current.reference : this.current.learning));
      // 2) weitere Übersetzungen (Mehrdeutigkeiten) fürs gleiche Wort
      for (const a of acceptable) {
        if (options.size >= 3) break;
        options.add(a);
      }
      // 3) Distraktoren aus anderen Wörtern
      const allAnswers = this.pool.map((it) => (this.direction === 'L2R' ? it.reference : it.learning));
      for (let i = 0; i < 50 && options.size < 4; i++) {
        const cand = allAnswers[Math.floor(Math.random() * allAnswers.length)];
        if (!acceptable.map(this.normalize).includes(this.normalize(cand))) {
          options.add(cand);
        }
      }

      const arr = Array.from(options);
      // shuffle
      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]];
      }
      this.choiceOptions = arr;
    },
    async playSound(ok) {
      try {
        const audio = new Audio(ok ? '/sounds/success.mp3' : '/sounds/fail.mp3');
        await audio.play();
      } catch (_) {
        // ignore autoplay issues
      }
    },
    reportSrsReview(isCorrect, rating = null) {
      if (!this.current || !this.openParams?.courseId) {
        return Promise.resolve();
      }
      return apiClient.post('/api/vocab/srs/review', {
        courseId: this.openParams.courseId || this.current.courseId,
        lessonId: this.openParams.lessonId || this.current.lessonId || null,
        itemKey: this.current.itemKey || null,
        learning: this.current.learning,
        reference: this.current.reference,
        direction: this.direction,
        correct: Boolean(isCorrect),
        rating
      }).catch((error) => {
        console.warn('[VocabPracticeDialog] SRS review could not be saved:', error);
      });
    },
    markResult(isCorrect) {
      this.answered = true;
      this.lastCorrect = isCorrect;
      if (isCorrect) {
        this.correctCount += 1;
        this.lastWrongReview = null;
        this.pendingRetry = null;
      } else {
        this.wrongCount += 1;
        const answers = this.visibleCorrectAnswers.length > 0
          ? [...this.visibleCorrectAnswers]
          : (this.acceptableAnswers || []).filter(Boolean);
        this.lastWrongReview = {
          prompt: this.currentPrompt,
          submittedAnswer: this.submittedAnswer,
          answers
        };
        if (!this.srsMode && this.current?.id) {
          this.pendingRetry = {
            id: this.current.id,
            direction: this.direction
          };
        }
      }
      if (!this.srsMode) {
        this.reportSrsReview(isCorrect);
      }

      const id = this.current?.id;
      if (!id) return;
      const st = this.perId[id] || { c: 0, w: 0, streak: 0, lastAsked: 0 };
      if (isCorrect) {
        // Hard-phase drills are tracked separately via hardMasteryByKey.
        // We avoid inflating normal success counters here, otherwise the
        // graduated vocab can become too unlikely in regular rotation.
        if (!this.hardPhaseActive) {
          st.c += 1;
          st.streak = st.streak >= 0 ? st.streak + 1 : 1;
        }
        if (this.hardPhaseActive && this.current) {
          const key = this.getHardKey(this.current);
          this.hardMasteryByKey[key] = Math.max(0, Number(this.hardMasteryByKey[key]) || 0) + 1;
          const mapKey = this.findMatchingHardKey(this.current);
          if ((Number(this.hardMasteryByKey[key]) || 0) >= HARD_REQUIRED_CONSECUTIVE_CORRECT && mapKey) {
            const next = { ...this.hardVocabMap };
            delete next[mapKey];
            this.hardVocabMap = next;
            this.saveHardVocabMap();
          }
        }
      } else {
        st.w += 1;
        st.streak = st.streak <= 0 ? st.streak - 1 : -1;
        if (this.hardPhaseActive && this.current) {
          const key = this.getHardKey(this.current);
          this.hardMasteryByKey[key] = 0;
        }
      }
      st.lastAsked = Date.now();
      this.perId[id] = st;

      this.lastIds.unshift(id);
      this.lastIds = this.lastIds.slice(0, 3);
    },
    async submitSrsRating(rating) {
      if (!this.srsMode || !this.answered || this.locked) {
        return;
      }
      this.locked = true;
      await this.reportSrsReview(this.lastCorrect, rating);

      const id = this.current?.id;
      const treatAsAgain = rating === 'again' || !this.lastCorrect;

      if (id && this.srsSession) {
        if (treatAsAgain) {
          // Wrong answers are not done yet: requeue near the end so the user
          // sees them again before the session finishes.
          if (Array.isArray(this.srsQueueIds)) {
            const remaining = this.srsQueueIds.filter((x) => x !== id);
            const insertAt = Math.min(remaining.length, SRS_AGAIN_REINSERT_OFFSET);
            remaining.splice(insertAt, 0, id);
            this.srsQueueIds = remaining;
          }
          if (Array.isArray(this.srsSession.doneIds)) {
            this.srsSession.doneIds = this.srsSession.doneIds.filter((x) => x !== id);
          }
        } else {
          const done = Array.isArray(this.srsSession.doneIds) ? this.srsSession.doneIds : [];
          if (!done.includes(id)) {
            done.push(id);
            this.srsSession.doneIds = done;
          }
          if (Array.isArray(this.srsQueueIds) && this.srsQueueIds[0] === id) {
            this.srsQueueIds = this.srsQueueIds.slice(1);
          } else if (Array.isArray(this.srsQueueIds)) {
            this.srsQueueIds = this.srsQueueIds.filter((x) => x !== id);
          }
        }
        this.saveSrsSession();
      }

      this.next();
    },
    submitChoice(opt) {
      if (this.locked) return;
      this.submittedAnswer = String(opt || '').trim();
      const ok = this.acceptableAnswers.map(this.normalize).includes(this.normalize(opt));
      this.markResult(ok);
      this.playSound(ok);
      if (ok && !this.srsMode) {
        // Direkt weiter zur nächsten Frage (kein Klick nötig)
        this.locked = true;
        this.autoAdvanceTimer = setTimeout(() => {
          this.autoAdvanceTimer = null;
          this.next();
        }, 350);
      }
    },
    submitTyped() {
      if (this.locked) return;
      this.submittedAnswer = String(this.typedAnswer || '').trim();
      const ans = this.normalize(this.typedAnswer);
      const ok = this.acceptableAnswers.map(this.normalize).includes(ans);
      this.markResult(ok);
      this.playSound(ok);
      if (ok && !this.srsMode) {
        this.locked = true;
        this.autoAdvanceTimer = setTimeout(() => {
          this.autoAdvanceTimer = null;
          this.next();
        }, 350);
      }
    },
    skip() {
      this.pendingRetry = null;
      this.next();
    },
    next() {
      if (this.autoAdvanceTimer) {
        clearTimeout(this.autoAdvanceTimer);
        this.autoAdvanceTimer = null;
      }
      if (!this.pool || this.pool.length === 0) {
        this.resetQuestion();
        return;
      }
      if (this.srsMode && Array.isArray(this.srsQueueIds) && this.srsQueueIds.length === 0) {
        this.resetQuestion();
        return;
      }
      if (this.current?.id) {
        this.addCurrentToCycle();
      }
      this.maybeStartHardPhase();
      this.maybeFinishHardPhase();
      const retryDirection = this.pendingRetry?.direction || null;
      this.resetQuestion();
      if (retryDirection) {
        this.direction = retryDirection;
      }
      this.current = this.pickNextItem();
      if (!this.current) {
        this.maybeFinishHardPhase();
        if (!this.hardPhaseActive) {
          this.current = this.pickNextItem();
        }
      }
      if (!this.current) return;
      const prompt = this.currentPrompt;
      this.acceptableAnswers = this.getAnswersForPrompt(prompt, this.direction);
      if (this.simpleMode) this.buildChoices();
      this.$nextTick(() => {
        if (!this.simpleMode) this.$refs.answerInput?.focus?.();
      });
    },
  },
};
</script>

<style scoped>
.layout {
  display: flex;
  gap: 16px;
  height: 100%;
}
.left {
  flex: 1;
  min-width: 0;
}
.right {
  width: 16em;
  border-left: 1px solid #ddd;
  padding-left: 12px;
}
.opts {
  display: flex;
  gap: 16px;
  margin-bottom: 10px;
}
.chk {
  display: inline-flex;
  gap: 6px;
  align-items: center;
}
.prompt {
  padding: 10px;
  background: #fff;
  border: 1px solid #ccc;
  margin-bottom: 10px;
}
.dir {
  color: #555;
  font-size: 0.9em;
}
.word {
  font-size: 1.8em;
  font-weight: bold;
}
.typing {
  display: flex;
  gap: 8px;
  align-items: center;
}
.typing input {
  flex: 1;
  padding: 6px;
}
.choices {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.choiceBtn {
  padding: 8px;
}
.controls {
  margin-top: 12px;
}
.srs-finished {
  padding: 14px;
  background: #fff;
  border: 1px solid #ccc;
}
.srs-finished__title {
  font-size: 1.1em;
  font-weight: 700;
  margin-bottom: 6px;
}
.srs-finished__desc {
  color: #555;
}
.srs-rating {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 8px;
  margin: 12px 0;
}
.srs-rating__title {
  grid-column: 1 / -1;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--color-text-secondary, #5f554e);
}
.srs-rating__button {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  padding: 8px 10px;
  border: 1px solid var(--color-border, #d7d0c8);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.9);
  cursor: pointer;
  text-align: left;
}
.srs-rating__button span {
  font-size: 0.74rem;
  color: var(--color-text-secondary, #6b625b);
}
.srs-rating__button--again {
  border-color: rgba(198, 75, 75, 0.45);
}
.srs-rating__button--hard {
  border-color: rgba(210, 153, 74, 0.5);
}
.srs-rating__button--good {
  border-color: rgba(90, 145, 95, 0.45);
}
.srs-rating__button--easy {
  border-color: rgba(78, 139, 188, 0.45);
}
.feedback {
  padding: 10px;
  border: 1px solid #ccc;
  margin-bottom: 10px;
}
.feedback.ok {
  background: #e8ffe8;
  border-color: #7bbe55;
}
.feedback.bad {
  background: #ffecec;
  border-color: #d33;
}
.solution-card {
  margin: 0 0 12px;
  padding: 12px;
  border: 1px solid #d33;
  background: #fff8f8;
}
.solution-card--persistent {
  margin-top: 12px;
}
.solution-card ul {
  margin: 6px 0 0;
  padding-left: 20px;
}
.solution-row {
  display: grid;
  gap: 3px;
  margin: 8px 0;
  color: #5f554e;
}
.solution-row strong {
  color: #2b2520;
}
.submitted-answer {
  display: grid;
  gap: 3px;
  margin-bottom: 10px;
  color: #5f554e;
}
.submitted-answer strong {
  color: #2b2520;
}
.answersTitle {
  margin-top: 6px;
  font-weight: bold;
}
.statTitle {
  font-weight: bold;
  margin-bottom: 8px;
}
.statRow {
  display: flex;
  justify-content: space-between;
  margin-bottom: 6px;
}
.k {
  color: #333;
}
.v {
  font-weight: bold;
}
</style>
