<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { chatBlock, examBlock, fetchQuestionPattern } from '../api.js'
import { usePruefSlot } from '../pruefungCache.js'
import { renderMarkdown, renderMarkdownInline } from '../markdown.js'
import { stufeFuer, malusRegel } from '../levels.js'
import { useChat, istUnten } from '../composables/useChat.js'
import ChatTranscript from './ChatTranscript.vue'

const props = defineProps({
  topic: { type: String, required: true },
  block: { type: String, required: true },
  section: { type: String, default: '' },          // detailed version
  sectionCompact: { type: String, default: '' },   // compact version (key points) — exam/chat context
  provider: { type: String, default: 'claude' },
  status: { type: Object, default: null }, // {good_answers, streak, completed, understood, mastered}
  cap: { type: Number, default: 6 }, // score cap = max of the highest format (6/12/18/30)
  // 'trigger' (list): only tab bar, click opens the fullscreen focus.
  // 'full' (focus): tabs + panel content as before.
  mode: { type: String, default: 'trigger' },
  initialTab: { type: String, default: null },
  ansicht: { type: String, default: 'compact' },   // compact | erklärend — controls the left guide column
})

const emit = defineEmits(['statusChanged', 'openFokus', 'setAnsicht'])

// Learning levels relative to the cap (levels.js): green/blue/purple/gold @ 20/40/60/100 %.
// Exam is ALWAYS random from 5 forms (with pattern); cap = 4×relevant subblocks.
// Four question forms, random. No more easy/hard — the difficulty is set by the backend
// via the learner level (addressee role). gapchoice = term choice · gaptext = free.
const MODES = ['quiz', 'gapchoice', 'gaptext', 'erklaeren']
const st = computed(() => props.status || { good_answers: 0, streak: 0, cap: props.cap })
const score = computed(() => st.value.good_answers || 0)
const cap = computed(() => st.value.cap || props.cap)
const scoreDisplay = computed(() => Math.min(score.value, cap.value))  // legacy data may exceed cap
const level = computed(() => stufeFuer(score.value, cap.value))   // object {key,label,kurz,farbe} | null
const atCap = computed(() => score.value >= cap.value)
// With question pattern random from 5 forms; without pattern (old block) only Explain.
const currentBand = computed(() => patternMode.value ? 'zufall' : 'erklaeren')
const inRandom = computed(() => currentBand.value === 'zufall')
const activeMode = computed(() => currentBand.value)  // 'zufall' | 'erklaeren'
const activeForm = computed(() => inRandom.value ? 'quiz' : 'erklaeren')  // placeholder; drawn object overrides
// Displayed form: follows the active form, freezes while a drawn question is visible.
const displayForm = ref(null)
const shownForm = computed(() => displayForm.value || activeForm.value)
const displayBand = ref(null)
const sectionChange = computed(() => displayBand.value && displayBand.value !== currentBand.value)
// Difficulty of the currently shown widget (from the drawn object).
const currentHard = computed(() => {
  if (shownForm.value === 'quiz' && quizCurrent.value) return quizCurrent.value.schwer
  if (shownForm.value === 'gaptext' && clozeCurrent.value) return clozeCurrent.value.schwer
  return false
})
const modeRule = computed(() => {
  const m = malusRegel(score.value, cap.value)  // '0' | '−1' | '−2' | '−3' by progress
  if (shownForm.value === 'quiz') return currentHard.value ? `x of 4 · +3/${m}` : `1 of 4 · +1/${m}`
  if (shownForm.value === 'gaptext') return currentHard.value ? `free text · +3/${m}` : `term from 4 · +1/${m}`
  return `+1/+2/+3 · error ${m}`
})
const bandTarget = computed(() => cap.value)
const FORM_NAME = { quiz: 'Quiz', gaptext: 'Cloze', erklaeren: 'Explain' }

// --- Toggle area ---
const activeTab = ref(props.mode === 'full' ? props.initialTab : null) // null | 'chat' | 'exam'

// Click on a tab: in the list open the focus, in the focus toggle the tab.
function tabClick(tab) {
  if (props.mode === 'trigger') { emit('openFokus', tab); return }
  activeTab.value = activeTab.value === tab ? null : tab
}

// --- Block chat (ephemeral) ---
const chat = useChat((msgs) => chatBlock({
  topic: props.topic, block: props.block, section: props.section, section_compact: props.sectionCompact,
  messages: msgs, provider: props.provider,
}))

// --- Exam: guided dialog ---
// Phases: 'idle' (request question) | 'question_offen' (answer/ask) | 'bewertet' (discuss/re-evaluate/continue)
// Durable state lives in a SHARED slot per block (module store): survives
// remounting and a late-arriving question is immediately reactive and visible.
const examKey = `${props.topic}::${props.block}`
const slot = usePruefSlot(examKey)
const examMessages = slot.messages // {role, kind: 'question'|'nachfrage'|'answer'|'feedback'|'discussion'|'fehler', content, rating?}
const examPhase = slot.phase
const currentQuestion = slot.aktuelleFrage      // anchors evaluation/discussion
const lastFeedback = slot.letztesFeedback  // context for the discussion about an evaluation
const patternSource = slot.musterQuelle  // immutable full list of question patterns (empty = fallback)
const patternPool = slot.musterPool      // working copy, without replacement; empty → reset
const patternLoaded = slot.musterGeladen
const poolForm = slot.poolForm          // form the pool is filled for
const quizCurrent = slot.quizAktuell    // running quiz question (widget state)
const clozeCurrent = slot.lueckAktuell  // running cloze task (widget state)
// Transient (per instance): input draft, loading spinner.
const examInput = ref('')
const examLoading = ref(false)
const asked_again = ref(false)   // the running question used asked_again → gain max +1
const thoroughMsg = ref(null)  // feedback bubble whose thorough field is open
const thoroughText = ref('')   // optional reason for "check thoroughly"

const examMessagesEl = ref(null)
const examInputEl = ref(null)
const examStick = ref(true)     // only auto-scroll when the user is (almost) at the bottom
let examRun = 0

function onExamScroll() {
  if (examMessagesEl.value) examStick.value = istUnten(examMessagesEl.value)
}

function applyExam(res) {
  // res.streak only set on answer_check (binding, persisted); otherwise leave streak unchanged.
  emit('statusChanged', { block: props.block, ...st.value, good_answers: res.good_answers, streak: res.streak ?? st.value.streak, cap: res.cap ?? cap.value })
}

async function examScroll() {
  await nextTick()
  if (examMessagesEl.value && examStick.value) examMessagesEl.value.scrollTop = examMessagesEl.value.scrollHeight
}

// Only real conversation turns go to the backend; feedback stays a pure UI artifact.
function examDialog() {
  return examMessages.value
    .filter((m) => m.kind !== 'feedback' && m.kind !== 'fehler')
    .map((m) => ({ role: m.role, content: m.content }))
}

async function examSend(payload, onOk) {
  const run = ++examRun
  examStick.value = true // own action = to the end; scrolling up while waiting sets it back to false
  examLoading.value = true
  examScroll()
  try {
    const res = await examBlock({
      topic: props.topic, block: props.block, section: props.section, section_compact: props.sectionCompact,
      provider: props.provider, messages: examDialog(), ...payload,
    })
    if (run !== examRun) return
    onOk(res)
    applyExam(res)
    examScroll()
    nextTick(() => examInputEl.value?.focus())
  } catch {
    if (run === examRun) examMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Didn\'t work — please try again.' })
  } finally {
    if (run === examRun) examLoading.value = false
  }
}

// Show question (fresh or queued). Base/streak is tracked by the server per question.
function showQuestion(text) {
  currentQuestion.value = text
  lastFeedback.value = ''
  asked_again.value = false
  thoroughMsg.value = null
  examMessages.value.push({ role: 'assistant', kind: 'question', content: text })
  examPhase.value = 'question_offen'
}

// Dedup: detect already-asked questions (the generator repeats itself on small blocks).
function normQuestion(t) { return (t || '').toLowerCase().replace(/\s+/g, ' ').trim() }
function askedQuestions() {
  return new Set(examMessages.value.filter((m) => m.kind === 'question').map((m) => normQuestion(m.content)))
}

// --- Questions pool: TARGET pre-formulated question OBJECTS of the active form (in the slot) ---
// Each object: {form, question, options?|sentence?|solution?|alternatives?}. When the
// active form changes, the pool is cleared and refilled for the new form.
const POOL_TARGET = 5
const pool = slot.pool
const inflight = slot.inflight
const examError = ref('')  // error hint for quiz/cloze (no bubble history)
let poolMiss = 0  // fallback only: consecutive duplicates/errors — cap against an infinite loop

// Pattern mode active once loaded and patterns present; otherwise fallback (only Explain).
const patternMode = computed(() => patternLoaded.value && patternSource.value.length > 0)

function objText(o) { return (o && (o.question || o.sentence)) || '' }

// Render cloze sentence: split at "___", parts inline (with $…$ math), gap as a span.
// This keeps the gap and markdown does not touch the underscores.
function clozeSentenceHtml(sentence) {
  sentence = String(sentence || '')
  const m = sentence.match(/_{3,}/)  // first gap (three or more underscores)
  if (!m) return renderMarkdownInline(sentence)
  // Is the gap INSIDE a $…$ formula (odd number of $ before it)? Then do not split
  // (that tears the formula) — instead replace it with a KaTeX line, render the sentence whole.
  const inFormula = ((sentence.slice(0, m.index).match(/\$/g) || []).length % 2) === 1
  if (inFormula) return renderMarkdownInline(sentence.replace(/_{3,}/, '\\rule{2.5em}{0.4pt}'))
  // Gap in text: split + gap span.
  return sentence.split(/_{3,}/).map((t) => renderMarkdownInline(t)).join('<span class="bp-luecke">______</span>')
}

function isKnown(o) {
  const n = normQuestion(objText(o))
  return !n || askedQuestions().has(n) || pool.value.some((f) => normQuestion(objText(f)) === n)
}

// Already-asked + queued questions — fallback mode only (Explain without pattern).
function avoidList() {
  const asked = examMessages.value.filter((m) => m.kind === 'question').map((m) => m.content)
  return [...asked, ...pool.value.map(objText)]
}

// Load pattern sidecar once per block (slot-cached). Empty → fallback.
async function loadPatterns() {
  if (patternLoaded.value) return
  try {
    const res = await fetchQuestionPattern(props.topic, props.block)
    patternSource.value = (res.pattern || []).map((m) => (m.question || '').trim()).filter(Boolean)
    patternLoaded.value = true  // ONLY on success — otherwise a failed attempt freezes the session to Explain-only
  } catch {
    patternSource.value = []     // patternLoaded stays false → next turn retries
  }
}

function mischen(arr) {  // Fisher-Yates
  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
}

// Draw a pattern without replacement; pool empty → reset (reshuffle). null = fallback.
function takePattern() {
  if (!patternSource.value.length) return null
  if (!patternPool.value.length) patternPool.value = mischen(patternSource.value)
  return patternPool.value.shift()
}

// Pool key = form + difficulty; in the random band a mixed pool ('zufall').
const poolKey = computed(() => inRandom.value ? 'zufall' : 'erklaeren')

// Mode of the next question: in the random band (score 19–30) random from all 5 modes.
function nextMode() {
  return inRandom.value ? MODES[Math.floor(Math.random() * MODES.length)] : activeMode.value
}

// Generate a question object of a given mode (from a drawn seed; Explain has a fallback).
function buildSingleQuestion(mode = nextMode()) {
  const form = mode === 'quiz' ? 'quiz' : mode.startsWith('gap') ? 'gaptext' : 'erklaeren'
  const istLueckFrei = mode === 'gaptext'  // free text vs. gapchoice = term from 4
  const pattern = takePattern()
  const base = {
    topic: props.topic, block: props.block, section: props.section,
    section_compact: props.sectionCompact, provider: props.provider,
  }
  if (form === 'quiz' && pattern) {
    return examBlock({ ...base, action: 'quiz_question', pattern })  // single choice, level controls
      .then((r) => ({ form, schwer: false, question: r.question, options: r.options }))
  }
  if (form === 'gaptext' && pattern) {
    return examBlock({ ...base, action: 'gap_question', pattern, schwer: istLueckFrei })
      .then((r) => istLueckFrei
        ? ({ form, schwer: true, question: r.sentence, sentence: r.sentence, solution: r.solution, alternatives: r.alternatives })
        : ({ form, schwer: false, question: r.sentence, sentence: r.sentence, options: r.options }))
  }
  if (pattern) {  // erklaeren from pattern
    return examBlock({ ...base, action: 'question', pattern }).then((r) => ({ form: 'erklaeren', question: r.question }))
  }
  // Fallback (no pattern sidecar): Explain live with an avoid list.
  return examBlock({ ...base, action: 'question', messages: examDialog(), avoid: avoidList() })
    .then((r) => ({ form: 'erklaeren', question: r.question }))
}

// Clear the pool when form or difficulty changed (band boundary or ALT+E).
function checkPoolForm() {
  if (poolForm.value !== poolKey.value) {
    pool.value = []
    poolForm.value = poolKey.value
    poolMiss = 0
  }
}

// Fill the pool in PARALLEL: each draws a DISTINCT pattern → no collision.
function fillPool() {
  checkPoolForm()
  const keyStart = poolKey.value
  const missCap = patternMode.value ? Infinity : 3
  while (pool.value.length + inflight.value < POOL_TARGET && poolMiss < missCap) {
    inflight.value++
    buildSingleQuestion()
      .then((obj) => {
        if (obj && objText(obj) && poolKey.value === keyStart && (patternMode.value || !isKnown(obj))) { pool.value.push(obj); poolMiss = 0 }
        else poolMiss++
      })
      .catch(() => { poolMiss++ })
      .finally(() => { inflight.value--; fillPool() })
  }
}

// Draw a question from the pool — random, never the fastest.
// First load (pool empty + nothing in progress): generate 5, await ALL, then draw.
async function drawQuestion() {
  const firstLoad = !pool.value.length && !inflight.value
  fillPool()  // start/keep up to 5
  examLoading.value = true
  try {
    const missCap = patternMode.value ? Infinity : 3
    if (firstLoad) {
      // only at the start: wait until all 5 are done (speed must not decide the form)
      while ((inflight.value > 0 || pool.value.length < POOL_TARGET) && poolMiss < missCap) {
        if (!inflight.value) fillPool()
        await new Promise((r) => setTimeout(r, 80))
      }
    } else if (!pool.value.length) {
      // pool drained (fast user): wait for the first finished one
      while (!pool.value.length && (inflight.value > 0 || poolMiss < missCap)) {
        if (!inflight.value) fillPool()
        await new Promise((r) => setTimeout(r, 80))
      }
    }
    if (!pool.value.length) return null
    const i = Math.floor(Math.random() * pool.value.length)
    return pool.value.splice(i, 1)[0]
  } finally { examLoading.value = false }
}

// Show a drawn object in the matching form (Explain bubble or quiz/cloze widget).
function showQuestionObj(obj) {
  examError.value = ''
  displayForm.value = obj.form  // bind display to the drawn question (freeze the form)
  displayBand.value = currentBand.value  // band at draw time (before the answer)
  if (obj.form === 'quiz') {
    quizCurrent.value = { question: obj.question, options: obj.options, schwer: obj.schwer, gewaehlt: [], done: false, points: null, rating: null, feedback: '' }
  } else if (obj.form === 'gaptext') {
    clozeCurrent.value = { sentence: obj.sentence, schwer: obj.schwer, options: obj.options || null, solution: obj.solution || '', alternatives: obj.alternatives || [], input: '', gewaehlt: [], done: false, points: null, rating: null, feedback: '' }
    if (obj.schwer) nextTick(() => document.getElementById('bp-gap-input')?.focus())
  } else {
    showQuestion(obj.question)
    nextTick(() => examInputEl.value?.focus())
  }
}

// First / next question: load patterns, roll the form, fetch exactly that form.
async function showNextQuestion() {
  if (examLoading.value) return
  poolMiss = 0
  examError.value = ''
  await loadPatterns()
  const obj = await drawQuestion()  // first load: await all 5; then draw randomly
  if (obj) {
    showQuestionObj(obj)
    examScroll()
  } else if (activeForm.value === 'erklaeren') {
    examMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'No question — please try again.' })
  } else {
    examError.value = 'No question — please try again.'
  }
  fillPool()
}
const requestQuestion = showNextQuestion

// Milestone exceeded: release the freeze (result was visible) without drawing a new
// question → idle state of the new band ("request question").
function toNextSection() {
  displayForm.value = null
  displayBand.value = null
  quizCurrent.value = null
  clozeCurrent.value = null
  examMessages.value = []  // discard the old Explain history (new section)
  examPhase.value = 'idle'
  examError.value = ''
}

// --- Quiz: easy = single select (exactly 1) · hard = multi select. Deterministic. ---
function quizToggle(i) {
  const q = quizCurrent.value
  if (!q || q.done) return
  if (!q.schwer) { q.gewaehlt = q.gewaehlt.includes(i) ? [] : [i]; return }
  const idx = q.gewaehlt.indexOf(i)
  if (idx >= 0) q.gewaehlt.splice(idx, 1)
  else q.gewaehlt.push(i)
}

async function quizAnswer() {
  const q = quizCurrent.value
  if (!q || q.done || examLoading.value) return
  examLoading.value = true
  examError.value = ''
  try {
    const correct = q.options.map((o, i) => (o.correct ? i : -1)).filter((i) => i >= 0)
    const res = await examBlock({
      topic: props.topic, block: props.block, section: props.section, section_compact: props.sectionCompact,
      provider: props.provider, action: 'quiz_answer', question: q.question, cap: props.cap,
      selection: q.gewaehlt, correct, schwer: q.schwer,
    })
    q.done = true; q.points = res.points; q.rating = res.rating; q.feedback = res.feedback
    applyExam(res)
  } catch {
    examError.value = 'Didn\'t work — please try again.'
  } finally {
    examLoading.value = false
  }
}

// --- Cloze: easy = term from 4 (single select) · hard = free text ---
function clozeToggle(i) {
  const l = clozeCurrent.value
  if (!l || l.done || l.schwer) return
  l.gewaehlt = l.gewaehlt.includes(i) ? [] : [i]
}

async function clozeAnswer() {
  const l = clozeCurrent.value
  if (!l || l.done || examLoading.value) return
  if (l.schwer ? !l.input.trim() : !l.gewaehlt.length) return
  examLoading.value = true
  examError.value = ''
  try {
    const specific = l.schwer
      ? { schwer: true, solution: l.solution, alternatives: l.alternatives, input: l.input }
      : { schwer: false, selection: l.gewaehlt, correct: l.options.map((o, i) => (o.correct ? i : -1)).filter((i) => i >= 0) }
    const res = await examBlock({
      topic: props.topic, block: props.block, section: props.section, section_compact: props.sectionCompact,
      provider: props.provider, action: 'gap_answer', question: l.sentence, cap: props.cap, ...specific,
    })
    l.done = true; l.points = res.points; l.rating = res.rating; l.feedback = res.feedback
    applyExam(res)
  } catch {
    examError.value = 'Didn\'t work — please try again.'
  } finally {
    examLoading.value = false
  }
}

function askFollowUp() {
  const text = examInput.value.trim()
  if (!text || examLoading.value) return
  asked_again.value = true  // help used → gain for this question max +1
  examMessages.value.push({ role: 'user', kind: 'nachfrage', content: text })
  examInput.value = ''
  examSend(
    { action: 'discussion', question: currentQuestion.value, last_rating: lastFeedback.value },
    (res) => examMessages.value.push({ role: 'assistant', kind: 'discussion', content: res.reply }),
  )
}

let lastFeedbackMsg = null // last shown evaluation bubble
let evalRun = 0           // only the most recent quick evaluation may display

function ratingPayload() {
  return { question: currentQuestion.value, cap: props.cap, asked_again: asked_again.value }
}

// Agent 1 (fast): show immediate level + points, then Agent 2 (precise) in the background.
async function quickEvaluate() {
  const mine = ++evalRun
  examStick.value = true
  examLoading.value = true
  examScroll()
  try {
    const res = await examBlock({
      topic: props.topic, block: props.block, section: props.section, section_compact: props.sectionCompact,
      provider: props.provider, messages: examDialog(), action: 'answer', ...ratingPayload(),
    })
    if (mine !== evalRun) return
    // Agent 1 does NOT change the score — only show feedback + expected points.
    lastFeedback.value = res.feedback || ''
    examMessages.value.push({ role: 'assistant', kind: 'feedback', content: res.feedback || '', rating: res.rating, points: res.points, checked: false })
    lastFeedbackMsg = examMessages.value[examMessages.value.length - 1]
    examPhase.value = 'bewertet'
    examScroll()
    nextTick(() => examInputEl.value?.focus())
    preciseEvaluate() // Agent 2 evaluates bindingly (sets the score)
  } catch {
    if (mine === evalRun) examMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Didn\'t work — please try again.' })
  } finally {
    if (mine === evalRun) examLoading.value = false
  }
}

// Agent 2 (precise): evaluator + critic. Corrects bubble + score (server truth, always apply).
// thorough=true: strong model, optionally with the learner's reason.
async function preciseEvaluate(thorough = false, reason = '') {
  const target = lastFeedbackMsg
  if (thorough) examLoading.value = true
  try {
    const res = await examBlock({
      topic: props.topic, block: props.block, section: props.section, section_compact: props.sectionCompact,
      provider: props.provider, messages: examDialog(), action: 'answer_check', ...ratingPayload(), thorough, reason,
    })
    applyExam(res)
    if (target) {
      target.content = res.feedback || target.content
      target.rating = res.rating
      target.points = res.points
      target.checked = true
    }
  } catch {
    if (target) target.checked = true
  } finally {
    if (thorough) examLoading.value = false
  }
}

function submitAnswer() {
  const text = examInput.value.trim()
  if (!text || examLoading.value) return
  examMessages.value.push({ role: 'user', kind: 'answer', content: text })
  examInput.value = ''
  quickEvaluate()
}

// Was there already a discussion? Then the history may be evaluated directly.
const hasDiscussion = computed(() => examMessages.value.some((m) => m.kind === 'nachfrage' || m.kind === 'discussion'))

// "Evaluate history": evaluate the dialog so far as the answer (field is ignored).
function evaluateHistory() {
  if (examLoading.value) return
  quickEvaluate()
}

// Right-click an evaluation → open the thorough field; submitting re-evaluates (with reason).
function openThorough(msg) {
  thoroughText.value = ''
  thoroughMsg.value = msg
  nextTick(() => document.getElementById('bp-thorough-input')?.focus())
}
function submitThorough() {
  if (examLoading.value) return
  lastFeedbackMsg = thoroughMsg.value || lastFeedbackMsg
  const text = thoroughText.value.trim()
  thoroughMsg.value = null
  preciseEvaluate(true, text)
}

function pointsLabel(p) {
  if (p == null) return ''
  return p > 0 ? `+${p}` : p < 0 ? String(p) : '±0'
}

// Cancel like in chat: increment the run counter → the running result (examSend
// and quickEvaluate) is discarded, buttons free immediately. Agent finishes server-side.
function examCancel() {
  if (!examLoading.value) return
  examRun++
  evalRun++
  examLoading.value = false
  examMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Cancelled.' })
}

// ESC cancels — window listener, because after a button click the focus leaves the panel.
function onWindowKey(e) {
  if (e.key === 'Escape' && examLoading.value) { e.preventDefault(); examCancel() }
}
onMounted(() => {
  if (props.mode !== 'full') return  // trigger mode (list): only tab bar, no exam/text logic
  window.addEventListener('keydown', onWindowKey)
  window.addEventListener('keydown', onExamKey)
  loadPatterns()  // GET (no agent) — sets the active form early correctly (quiz/cloze/explain)
})
onUnmounted(() => {
  window.removeEventListener('keydown', onWindowKey)
  window.removeEventListener('keydown', onExamKey)
})

// Quiz / cloze choice is a choice widget with keys 1–4; otherwise text field/bubble.
const choiceWidget = computed(() => {
  if (shownForm.value === 'quiz') return quizCurrent.value
  if (shownForm.value === 'gaptext' && clozeCurrent.value && !clozeCurrent.value.schwer) return clozeCurrent.value
  return null
})
// After an answer: on milestone crossing go to the new section, otherwise next question.
function continueAction() {
  if (sectionChange.value) toNextSection()
  else showNextQuestion()
}
// Primary action per form: no question → request · open → answer · done → continue.
function primaryAction() {
  if (examLoading.value) return
  const f = shownForm.value
  if (f === 'quiz') { if (!quizCurrent.value) requestQuestion(); else if (!quizCurrent.value.done) quizAnswer(); else continueAction(); return }
  if (f === 'gaptext') { if (!clozeCurrent.value) requestQuestion(); else if (!clozeCurrent.value.done) clozeAnswer(); else continueAction(); return }
  if (examPhase.value === 'idle') requestQuestion()
  else if (examPhase.value === 'question_offen') { if (examInput.value.trim()) submitAnswer() }
  else continueAction()
}

// 1–4 (bare): toggle option in the choice widget. Alt alone: jump to the input field.
// Alt+2 primary (answer/next) · Alt+Q view · Alt+1/3 Explain.
function onExamKey(e) {
  if (e.repeat) return
  if (props.mode !== 'full' || activeTab.value !== 'exam') return
  // Alt alone toggles the focus (BlockFocus lone-tap) → do NOT focus here,
  // otherwise keydown focus (here) and keyup toggle (focus) fight → "only while held".
  if (e.key === 'Alt') return
  if (!e.altKey) {
    // Bare 1–4: toggle the choice widget — but not while a text field has focus.
    if (['1', '2', '3', '4'].includes(e.key) && choiceWidget.value && !choiceWidget.value.done) {
      const ae = document.activeElement
      if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA')) return
      e.preventDefault()
      const i = Number(e.key) - 1
      if (i < choiceWidget.value.options.length) (shownForm.value === 'quiz' ? quizToggle : clozeToggle)(i)
    }
    return
  }
  if (e.code === 'KeyQ') {
    e.preventDefault()
    emit('setAnsicht', props.ansicht === 'compact' ? 'erklärend' : 'compact')
    return
  }
  if (e.key === '2') { e.preventDefault(); primaryAction(); return }
  // Explain special keys
  if (e.key === '1') { e.preventDefault(); if (shownForm.value === 'erklaeren' && examPhase.value === 'question_offen' && examInput.value.trim()) askFollowUp(); return }
  if (e.key === '3') { e.preventDefault(); if (shownForm.value === 'erklaeren' && examPhase.value === 'question_offen' && hasDiscussion.value) evaluateHistory() }
}
</script>

<template>
  <div class="bp">
    <div class="bp-toggles">
      <!-- View switcher (fullscreen only): controls the left guide column, left of the tabs -->
      <div v-if="mode === 'full'" class="bp-ansicht">
        <button :class="{ active: ansicht === 'compact' }" title="Key points" @click="emit('setAnsicht', 'compact')">Compact</button>
        <button :class="{ active: ansicht === 'erklärend' }" title="Detailed explanation" @click="emit('setAnsicht', 'erklärend')">Explanatory</button>
      </div>
      <button :class="{ active: activeTab === 'chat' }" @click="tabClick('chat')">
        Chat
      </button>
      <button :class="{ active: activeTab === 'exam' }" @click="tabClick('exam')">
        Exam
        <span v-if="level" class="bp-chip" :style="{ borderColor: level.farbe, color: level.farbe }" :title="`${level.label} (${cap})`">{{ level.kurz }} {{ scoreDisplay }}/{{ cap }}</span>
        <span v-else-if="score" class="bp-chip">{{ scoreDisplay }}/{{ cap }}</span>
      </button>
    </div>

    <div v-if="mode === 'full' && activeTab" class="bp-panel">
      <!-- Block chat -->
      <div v-if="activeTab === 'chat'">
        <ChatTranscript :chat="chat" hint="Frag etwas zu diesem Baustein. Der Verlauf wird nicht gespeichert."
                        placeholder="Frage zum Baustein…" />
      </div>

      <!-- Exam: guided dialog -->
      <div v-else>
        <p class="bp-hint">
          <template v-if="atCap">{{ FORM_NAME[shownForm] }} · {{ scoreDisplay }}/{{ cap }} — <strong>Max</strong>.</template>
          <template v-else-if="inRandom && !displayForm">Random · {{ scoreDisplay }}/{{ cap }}</template>
          <template v-else-if="inRandom">{{ FORM_NAME[shownForm] }} · {{ scoreDisplay }}/{{ cap }} · {{ modeRule }}</template>
          <template v-else>{{ FORM_NAME[shownForm] }} · {{ score }}/{{ bandTarget }} · {{ modeRule }}</template>
        </p>

        <!-- Quiz: question + multiple choice (widget stays even at the cap — practice without points) -->
        <template v-if="shownForm === 'quiz'">
          <div v-if="!quizCurrent" class="bp-actions">
            <button v-if="examLoading" class="bp-action cancel" title="ESC" @click="examCancel">Abbrechen</button>
            <button v-else class="bp-action primary" @click="requestQuestion">Request question</button>
          </div>
          <div v-else class="bp-quiz">
            <p class="bp-quiz-question markdown" v-html="renderMarkdownInline(quizCurrent.question)"></p>
            <div class="bp-quiz-grid">
              <button
                v-for="(o, i) in quizCurrent.options" :key="i"
                type="button"
                class="bp-quiz-opt"
                :class="{ gewaehlt: quizCurrent.gewaehlt.includes(i), correct: quizCurrent.done && o.correct, falsch: quizCurrent.done && !o.correct && quizCurrent.gewaehlt.includes(i) }"
                :disabled="quizCurrent.done"
                :title="`${i + 1}`"
                @click="quizToggle(i)"
              >
                <span class="markdown" v-html="renderMarkdownInline(o.text)"></span>
              </button>
            </div>
            <p v-if="examError" class="bp-error">{{ examError }}</p>
            <div class="bp-actions">
              <button v-if="!quizCurrent.done" class="bp-action primary" title="Alt+2" :disabled="examLoading" @click="quizAnswer">Answer</button>
              <template v-else>
                <span class="bp-tier" :class="quizCurrent.rating">{{ pointsLabel(quizCurrent.points) }}</span>
                <span class="bp-form-feedback">{{ quizCurrent.feedback }}</span>
                <button class="bp-action primary" @click="continueAction">{{ sectionChange ? 'To next section' : 'Next' }}</button>
              </template>
            </div>
          </div>
        </template>

        <!-- Cloze: sentence with gap + input -->
        <template v-else-if="shownForm === 'gaptext'">
          <div v-if="!clozeCurrent" class="bp-actions">
            <button v-if="examLoading" class="bp-action cancel" title="ESC" @click="examCancel">Abbrechen</button>
            <button v-else class="bp-action primary" @click="requestQuestion">Request question</button>
          </div>
          <div v-else class="bp-gap">
            <p class="bp-gap-sentence markdown" v-html="clozeSentenceHtml(clozeCurrent.sentence)"></p>
            <!-- hard: free text -->
            <input
              v-if="clozeCurrent.schwer"
              id="bp-gap-input"
              v-model="clozeCurrent.input"
              :disabled="clozeCurrent.done"
              placeholder="Begriff für die Lücke…"
              @keyup.enter="clozeAnswer"
            />
            <p v-if="clozeCurrent.schwer && clozeCurrent.done && !clozeCurrent.feedback.startsWith('Correct')" class="bp-gap-solution">Solution: <span class="markdown" v-html="renderMarkdownInline(clozeCurrent.solution)"></span></p>
            <!-- easy: choose term from 4 -->
            <div v-if="!clozeCurrent.schwer" class="bp-quiz-grid">
              <button
                v-for="(o, i) in clozeCurrent.options" :key="i"
                type="button"
                class="bp-quiz-opt"
                :class="{ gewaehlt: clozeCurrent.gewaehlt.includes(i), correct: clozeCurrent.done && o.correct, falsch: clozeCurrent.done && !o.correct && clozeCurrent.gewaehlt.includes(i) }"
                :disabled="clozeCurrent.done"
                :title="`${i + 1}`"
                @click="clozeToggle(i)"
              >
                <span class="markdown" v-html="renderMarkdownInline(o.text)"></span>
              </button>
            </div>
            <p v-if="examError" class="bp-error">{{ examError }}</p>
            <div class="bp-actions">
              <button v-if="!clozeCurrent.done" class="bp-action primary" title="Alt+2" :disabled="examLoading || (clozeCurrent.schwer ? !clozeCurrent.input.trim() : !clozeCurrent.gewaehlt.length)" @click="clozeAnswer">Answer</button>
              <template v-else>
                <span class="bp-tier" :class="clozeCurrent.rating">{{ pointsLabel(clozeCurrent.points) }}</span>
                <span class="bp-form-feedback">{{ clozeCurrent.feedback }}</span>
                <button class="bp-action primary" @click="continueAction">{{ sectionChange ? 'To next section' : 'Next' }}</button>
              </template>
            </div>
          </div>
        </template>

        <!-- Explain: guided dialog (existing) -->
        <template v-else>
          <div v-if="examMessages.length" ref="examMessagesEl" class="bp-messages" @scroll="onExamScroll">
            <template v-for="(m, i) in examMessages" :key="i">
              <div v-if="m.kind === 'feedback'" class="bp-feedback" :class="m.rating" title="Click: check thoroughly" @click="openThorough(m)">
                <span v-if="m.points != null" class="bp-tier">{{ pointsLabel(m.points) }}</span>{{ m.content }}<span v-if="!m.checked" class="bp-pruefend"> · being checked…</span>
                <div v-if="thoroughMsg === m" class="bp-thorough" @click.stop>
                  <input id="bp-thorough-input" v-model="thoroughText" placeholder="Warum unzufrieden? (optional)" @keyup.enter="submitThorough" />
                  <button class="bp-action primary" @click="submitThorough">Check thoroughly</button>
                  <button class="bp-action" @click="thoroughMsg = null">×</button>
                </div>
              </div>
              <div v-else-if="m.kind === 'fehler'" class="bp-error">{{ m.content }}</div>
              <div v-else-if="m.role === 'assistant'" class="bp-msg assistant markdown" v-html="renderMarkdown(m.content)"></div>
              <div v-else class="bp-msg user">{{ m.content }}</div>
            </template>
            <div v-if="examLoading" class="bp-msg assistant bp-typing">…</div>
          </div>

          <div v-if="examPhase === 'idle'" class="bp-actions">
            <button v-if="examLoading" class="bp-action cancel" title="ESC" @click="examCancel">Abbrechen</button>
            <button v-else class="bp-action primary" @click="requestQuestion">Request question</button>
          </div>

          <template v-else>
            <div v-if="examPhase === 'question_offen'" class="bp-input">
              <textarea
                ref="examInputEl"
                v-model="examInput"
                rows="2"
                placeholder="Antwort — oder nachfragen…"
              ></textarea>
            </div>
            <div class="bp-actions">
              <button v-if="examLoading" class="bp-action cancel" title="ESC" @click="examCancel">Abbrechen</button>
              <template v-else-if="examPhase === 'question_offen'">
                <button class="bp-action" title="Alt+1" :disabled="!examInput.trim()" @click="askFollowUp"><span class="bp-kbd">1</span>Ask</button>
                <button class="bp-action primary" title="Alt+2" :disabled="!examInput.trim()" @click="submitAnswer"><span class="bp-kbd">2</span>Submit answer</button>
                <button v-if="hasDiscussion" class="bp-action" title="Alt+3" @click="evaluateHistory"><span class="bp-kbd">3</span>Evaluate history</button>
              </template>
              <template v-else>
                <button class="bp-action primary" title="Alt+2" @click="continueAction"><span class="bp-kbd">2</span>{{ sectionChange ? 'To next section' : 'Next question' }}</button>
              </template>
            </div>
          </template>
        </template>
      </div>
    </div>
  </div>
</template>

<style scoped>
.bp { margin-top: 0.75rem; }

.bp-toggles { display: flex; gap: 0.4rem; }
.bp-toggles button {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.25rem 0.7rem;
  font-size: 0.8rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--panel-soft);
  color: var(--text-muted);
  cursor: pointer;
}
.bp-toggles button:hover { border-color: var(--border-strong); color: var(--text); }
.bp-toggles button.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* View switcher (Compact/Explanatory) — segmented, left of the tabs */
.bp-ansicht { display: inline-flex; margin-right: 0.5rem; }
.bp-ansicht button {
  padding: 0.25rem 0.6rem;
  font-size: 0.75rem;
  border: 1px solid var(--border);
  background: var(--panel-soft);
  color: var(--text-muted);
  cursor: pointer;
}
.bp-ansicht button:first-child { border-radius: 999px 0 0 999px; }
.bp-ansicht button:last-child { border-radius: 0 999px 999px 0; }
.bp-ansicht button + button { border-left: none; }
.bp-ansicht button:hover { color: var(--text); }
.bp-ansicht button.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }


.bp-chip {
  font-size: 0.7rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text-muted);
}
.bp-chip.done { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
.bp-chip.lila { background: color-mix(in srgb, var(--level-expert) 16%, var(--panel)); border-color: var(--level-expert); color: #6d28d9; }
.bp-chip.gold { background: color-mix(in srgb, var(--level-master) 20%, var(--panel)); border-color: var(--level-master); color: #8a6d12; }

.bp-panel {
  margin-top: 0.6rem;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-soft);
}

.bp-hint { font-size: 0.85rem; color: var(--text-muted); margin: 0 0 0.5rem; }
.bp-hint-key { font-size: 0.72rem; opacity: 0.7; white-space: nowrap; }
.bp-error { font-size: 0.85rem; color: var(--danger); margin: 0.5rem 0 0; }

.bp-action {
  margin-top: 0.5rem;
  padding: 0.3rem 0.8rem;
  font-size: 0.8rem;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
}
.bp-action:hover { border-color: var(--accent); }
.bp-action:disabled { opacity: 0.5; cursor: default; }
.bp-action.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.bp-action.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.bp-action.cancel { background: var(--danger); border-color: var(--danger); color: var(--on-accent); }

.bp-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.5rem; }
.bp-actions .bp-action { margin-top: 0; }

.bp-kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1rem; height: 1rem; padding: 0 0.2rem; margin-right: 0.35rem;
  font-size: 0.65rem; font-weight: 600; border-radius: 3px;
  background: var(--panel); border: 1px solid var(--border); color: var(--text-muted);
}
.bp-action.primary .bp-kbd { background: color-mix(in srgb, var(--on-accent) 20%, transparent); border-color: transparent; color: var(--on-accent); }

.bp-messages { display: flex; flex-direction: column; gap: 0.4rem; max-height: 320px; overflow-y: auto; }
.bp-msg {
  max-width: 88%;
  padding: 0.4rem 0.65rem;
  border-radius: 10px;
  font-size: 0.88rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.bp-msg.user { align-self: flex-end; background: var(--accent); color: var(--on-accent); white-space: pre-wrap; }
.bp-msg.assistant { align-self: flex-start; background: var(--panel); border: 1px solid var(--border); }
.bp-typing { color: var(--text-faint); font-style: italic; }

/* Evaluation of the last answer — separated above the next question */
.bp-feedback {
  align-self: flex-start;
  max-width: 88%;
  padding: 0.3rem 0.6rem;
  border-radius: 8px;
  font-size: 0.82rem;
  line-height: 1.4;
  border: 1px solid var(--border);
}
.bp-pruefend { font-style: italic; opacity: 0.7; font-size: 0.92em; }
.bp-feedback { cursor: pointer; }
.bp-feedback.gut { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
.bp-feedback.neutral { background: var(--warning-soft); border-color: var(--warning-border); color: var(--warning); }
.bp-feedback.schlecht { background: var(--danger-soft, #fee2e2); border-color: var(--danger-border, #f87171); color: var(--danger); }
.bp-tier { font-weight: 700; text-transform: uppercase; font-size: 0.62rem; letter-spacing: 0.03em; margin-right: 0.4rem; opacity: 0.85; }
.bp-tier.gut { color: var(--success); }
.bp-tier.neutral { color: var(--warning); }
.bp-tier.schlecht { color: var(--danger); }

/* Quiz: question + multiple choice */
.bp-quiz, .bp-gap { margin-top: 0.6rem; }
.bp-quiz-question, .bp-gap-sentence { font-size: 0.9rem; font-weight: 600; margin: 0 0 0.5rem; line-height: 1.5; }
.bp-luecke { font-weight: 700; letter-spacing: 1px; color: var(--accent); padding: 0 0.15rem; }
.bp-quiz-opt .markdown, .bp-quiz-question.markdown, .bp-gap-sentence.markdown { display: inline; }
.bp-quiz-opt .markdown { min-width: 0; overflow-wrap: anywhere; }
.bp-quiz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 0.5rem; }
.bp-quiz-opt {
  display: flex; gap: 0.45rem; align-items: flex-start; text-align: left;
  min-width: 0; overflow-wrap: anywhere;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--panel); color: var(--text); cursor: pointer; font-size: 0.85rem;
}
.bp-quiz-opt:disabled { cursor: default; }
.bp-quiz-opt.gewaehlt { border-color: var(--accent); background: var(--accent-soft, rgba(99, 102, 241, 0.12)); }
.bp-quiz-opt.correct { background: var(--success-soft); border-color: var(--success-border); }
.bp-quiz-opt.falsch { background: var(--danger-soft, #fee2e2); border-color: var(--danger-border, #f87171); }
.bp-form-feedback { font-size: 0.82rem; color: var(--text-muted); flex: 1; }

/* Cloze: sentence + input */
.bp-gap input {
  width: 100%; box-sizing: border-box; padding: 0.45rem 0.6rem; font: inherit; font-size: 0.88rem;
  border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--text);
}
.bp-gap input:disabled { opacity: 0.7; }
.bp-gap-solution { font-size: 0.82rem; color: var(--success); margin: 0.35rem 0 0; }
/* Thorough-check field (via right-click) inside the evaluation bubble */
.bp-thorough { display: flex; gap: 0.3rem; margin-top: 0.4rem; align-items: center; }
.bp-thorough input {
  flex: 1; min-width: 0; padding: 0.3rem 0.5rem; font: inherit; font-size: 0.8rem;
  border: 1px solid var(--border-strong); border-radius: 6px; background: var(--panel); color: var(--text);
}
.bp-thorough .bp-action { margin-top: 0; }

.bp-input { display: flex; gap: 0.4rem; margin-top: 0.55rem; align-items: flex-end; }
.bp-input textarea {
  flex: 1;
  resize: none;
  padding: 0.45rem 0.6rem;
  font: inherit;
  font-size: 0.88rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
}
.bp-input button {
  padding: 0.45rem 0.7rem;
  border: none;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}
.bp-input button:disabled { opacity: 0.5; cursor: default; }
.bp-input button.cancel { background: var(--danger); }
</style>
