<script setup>
import { computed, reactive, ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { fetchGuideContent, chatGuide, fetchBlockLearnState } from '../api.js'
import { renderMarkdown } from '../markdown.js'
import { stufeFuer, schwelle, SUB_RANK, VIEW_KURZ, VIEW_FARBE, BADGE_VIEW_RANK } from '../levels.js'
import { useChat } from '../composables/useChat.js'
import BlockPanel from './BlockPanel.vue'
import BlockFocus from './BlockFocus.vue'
import ChatTranscript from './ChatTranscript.vue'

const props = defineProps({
  previewGuide: { type: Object, default: null },
  provider: { type: String, default: 'claude' },
  themaAbgeschlossen: { type: Boolean, default: false },
  ansichtModus: { type: String, default: 'compact' }, // compact | erklärend
  stufeAnsicht: { type: [Number, String], default: 'auto' },  // 'auto' | 1=A · 2=F · 3=E · 4=V
})

const emit = defineEmits(['progressChanged', 'setAnsicht', 'openSidebar', 'fokusActive'])

// Rotating chapter accent colors (no red)
const CH_COLORS = ['#3b82f6', '#8b5cf6', '#14b8a6', '#f59e0b', '#22c55e', '#6366f1']

// --- Load content ---
const content = ref(null)
const loadError = ref(null)
const scrollEl = ref(null)
const learnstate = ref({}) // exam state per block title — BEFORE the immediate watch (loadContent uses it)

// --- Lazy render + markdown cache: parse only visible sections, each only once.
// Fixes the freeze on open (160× marked/highlight.js) and re-parse on every update. ---
const mdCache = new Map()       // `${mode}:${num}` → html
const visible = reactive({})   // num → true (stays true once ever visible)
let mdObserver = null

// Auto: Ansichtsstufe des Blocks folgt dem Prüfungs-Score (Erreicht + 1); Override = global fest.
function viewLevelFor(title) {
  if (props.stufeAnsicht !== 'auto') return Number(props.stufeAnsicht)
  // Auto = an die erreichte Badge-Stufe gekoppelt (gleiche Schwelle wie das Level-Abzeichen):
  // Advanced-Badge → advanced-Subs sichtbar. Vorher hing es an `freie_level` (Sub-Zahl × 25),
  // das bei vielen beginner-Subs SPÄTER umschaltet als der Badge (0.4 × cap) — dann zeigte der
  // Badge „Advanced", die advanced-Inhalte blieben aber verborgen.
  const st = levelOf(title)
  return st ? (BADGE_VIEW_RANK[st.key] || 1) : 1
}

function htmlFor(s) {
  const lvl = viewLevelFor(s.title)
  const key = `${props.ansichtModus}:${s.num}:${lvl}:${props.stufeAnsicht}`
  let h = mdCache.get(key)
  if (h !== undefined) return h
  const compact = props.ansichtModus === 'compact'
  if (!s.subs || !s.subs.length) {  // Legacy-Abschnitt ohne Marker → ungefiltert
    h = renderMarkdown(compact ? (s.compact || s.md) : s.md)
  } else {
    const anchor = compact ? (s.anker_compact || '') : (s.anchor || '')
    const parts = [renderMarkdown(anchor)]
    for (const sub of s.subs) {
      const rank = SUB_RANK[sub.level] || 1
      if (rank > lvl) continue
      const body = renderMarkdown(compact ? (sub.compact || sub.md) : (sub.md || sub.compact))
      if (props.stufeAnsicht === 'auto' && rank > 1 && rank === lvl) {
        // gerade neu freigeschaltete Stufe: kräftigere Färbung (Rand), ohne Badge
        parts.push(`<div class="sub-neu" style="--neu-farbe:${VIEW_FARBE[rank]}">${body}</div>`)
      } else {
        // jede Stufe A/F/E/V leicht in ihrer Farbe kennzeichnen (Rand + Färbung), ohne Badge
        parts.push(`<div class="sub-stufe" style="--stufe-farbe:${VIEW_FARBE[rank]}">${body}</div>`)
      }
    }
    h = parts.join('')
  }
  mdCache.set(key, h)
  return h
}

// Sections render only when (almost) in the viewport — observer on the scroll container.
function setupLazy() {
  mdObserver?.disconnect()
  if (!scrollEl.value) return
  mdObserver = new IntersectionObserver((entries) => {
    for (const e of entries) {
      if (!e.isIntersecting) continue
      visible[Number(e.target.dataset.num)] = true
      mdObserver.unobserve(e.target)
    }
  }, { root: scrollEl.value, rootMargin: '800px 0px' })
  for (const el of scrollEl.value.querySelectorAll('.section-card')) mdObserver.observe(el)
}

watch(content, () => nextTick(setupLazy))
onUnmounted(() => mdObserver?.disconnect())

watch(() => props.previewGuide?.id, loadContent, { immediate: true })
// Level view (E/M/S/F) changed → reload guide content with the matching depth filter.
watch(() => props.stufeAnsicht, () => mdCache.clear())

async function loadContent() {
  content.value = null
  loadError.value = null
  learnstate.value = {}
  mdCache.clear()
  for (const k in visible) delete visible[k]
  const g = props.previewGuide
  if (!g || g.status !== 'done') return
  try {
    content.value = await fetchGuideContent(g.id, 4)  // Stufen-Filterung passiert lokal (auto/Override)
  } catch (e) {
    console.error('Error loading guide:', e)
    loadError.value = 'Content unavailable — the file is missing. Regenerate the guide (▶).'
    return
  }
  try {
    learnstate.value = (await fetchBlockLearnState(g.topic)).blocks || {}
  } catch { /* offline → empty */ }
  // On open, scroll to the first not-yet-mastered checkable block.
  await nextTick()
  const target = blocks.value.find((s) => isCheckable(s) && levelOf(s.title)?.key !== 'master')
  if (target) scrollEl.value?.querySelector(`[data-num="${target.num}"]`)?.scrollIntoView({ block: 'start' })
}

// --- Block learning: exam state per block title (learnstate declared above) ---
function levelOf(title) {
  const l = learnstate.value[title]
  return l ? stufeFuer(l.good_answers || 0, l.cap || 10) : null
}
function onBlockStatus(block, status) {
  const prevKey = levelOf(block)?.key || null
  learnstate.value = { ...learnstate.value, [block]: status }
  const newKey = stufeFuer(status.good_answers || 0, status.cap || 10)?.key || null
  if (newKey !== prevKey) emit('progressChanged') // level change → reload locks/stats
}

// Replace the section in content after check/fix/rewrite + clear the markdown cache.
function onSectionUpdated({ title, compact, md }) {
  if (!content.value) return
  for (const ch of content.value.chapters) {
    for (const s of ch.sections) {
      if (s.title !== title) continue
      s.md = md
      s.compact = compact
      for (const k of [...mdCache.keys()]) if (k.endsWith(`:${s.num}`)) mdCache.delete(k)
    }
  }
}

// --- Fullscreen focus: one block large, guide left + exam right ---
const focusIndex = ref(null)        // index in the flat block list; null = closed
const focusTab = ref('exam')
const blocks = computed(() =>
  !content.value ? [] : content.value.chapters.flatMap((ch) => ch.sections),
)
const focusBlock = computed(() => (focusIndex.value === null ? null : blocks.value[focusIndex.value]))

// Report focus state to App (for the sidebar above the overlay); a topic change closes the focus.
watch(focusIndex, (v) => emit('fokusActive', v !== null))
watch(() => props.previewGuide?.id, () => { focusIndex.value = null })

function openFocus(block, tab) {
  if (!isCheckable(block)) return // read-only sections (Rest/edge) don't open an exam focus
  const i = blocks.value.findIndex((s) => s.title === block.title)
  if (i === -1) return
  focusIndex.value = i
  focusTab.value = tab || 'exam'
}
// Only jump to checkable sections in focus (FullGuide has read-only sections in between).
function focusPrev() {
  for (let i = focusIndex.value - 1; i >= 0; i--) if (isCheckable(blocks.value[i])) { focusIndex.value = i; return }
}
function focusNext() {
  for (let i = focusIndex.value + 1; i < blocks.value.length; i++) if (isCheckable(blocks.value[i])) { focusIndex.value = i; return }
}

// Experience bar: cumulative level state over all blocks (gold ⊆ purple ⊆ blue ⊆ green).
const progress = computed(() => {
  const z = { total: blocks.value.length, beginner: 0, advanced: 0, expert: 0, master: 0 }
  for (const s of blocks.value) {
    const l = learnstate.value[s.title]
    if (!l) continue
    const sc = l.good_answers || 0, cp = l.cap || 10
    if (sc >= schwelle(0.2, cp)) z.beginner++
    if (sc >= schwelle(0.4, cp)) z.advanced++
    if (sc >= schwelle(0.6, cp)) z.expert++
    if (sc >= schwelle(1.0, cp)) z.master++
  }
  return z
})

// cap per block = 4×relevant subblocks (provided by the backend in learnstate[title].cap).
function capOf(title) {
  return learnstate.value[title]?.cap || 10
}

// Section checkable? Rest never. Otherwise checkable, unless the field is explicitly false (FullGuide edge).
// Missing field (old guides without `checkable`) → checkable, so existing guides keep working.
function isCheckable(s) {
  return props.previewGuide?.format !== 'Rest' && s.checkable !== false
}

// --- Chat (mechanics in useChat; context extraction stays here) ---
const chat = useChat((msgs) => {
  const { section, outline } = extractContext()
  return chatGuide(props.previewGuide.id, {
    section, outline, messages: msgs, provider: props.provider,
  })
})
const { inputEl } = chat  // fürs Fokussieren beim Öffnen; Rendering übernimmt ChatTranscript
const chatOpen = ref(false)
const panelEl = ref(null)

function openChat() {
  chatOpen.value = true
  nextTick(() => inputEl.value?.focus())
}

function closeChat() {
  chatOpen.value = false
  chat.reset()
}

function onDocMouseDown(e) {
  if (!chatOpen.value) return
  if (panelEl.value && panelEl.value.contains(e.target)) return
  closeChat()
}

// Enter opens the chat (when closed, not in an input field); ESC closes it
function onDocKeyDown(e) {
  if (e.key === 'Escape' && chatOpen.value) {
    e.preventDefault()
    closeChat()
    return
  }
  if (e.key !== 'Enter' || chatOpen.value || !props.previewGuide) return
  const tag = document.activeElement?.tagName
  if (tag === 'INPUT' || tag === 'TEXTAREA') return
  e.preventDefault()
  openChat()
}

onMounted(() => {
  document.addEventListener('mousedown', onDocMouseDown, true)
  document.addEventListener('keydown', onDocKeyDown)
})
onUnmounted(() => {
  document.removeEventListener('mousedown', onDocMouseDown, true)
  document.removeEventListener('keydown', onDocKeyDown)
})

function extractContext() {
  if (!content.value) return { section: '', outline: '' }
  const outline = content.value.chapters
    .map((ch) => [ch.title, ...ch.sections.map((s) => '  ' + s.title)].join('\n'))
    .join('\n')
    .slice(0, 7000)

  // Current section = last card whose top edge is at or above the top of the viewport
  let section = ''
  const cards = Array.from(scrollEl.value?.querySelectorAll('.section-card') || [])
  let current = null
  for (const el of cards) {
    if (el.getBoundingClientRect().top <= 120) current = el
    else break
  }
  if (!current && cards.length) current = cards[0]
  if (current) section = current.innerText.trim().slice(0, 18000)
  return { section, outline }
}

</script>

<template>
  <div class="detail">
    <div v-if="previewGuide && content" ref="scrollEl" class="guide-scroll">
      <div class="guide-content">
        <header class="guide-head">
          <h1>{{ previewGuide.topic }}</h1>
          <span class="guide-format">{{ previewGuide.format }}</span>
          <span v-if="themaAbgeschlossen" class="thema-done" title="All blocks mastered">✓ Topic completed</span>
          <span class="gh-spacer"></span>
          <div class="ansicht-toggle" title="Toggle verbosity">
            <button :class="{ active: ansichtModus === 'compact' }" @click="$emit('setAnsicht', 'compact')">Compact</button>
            <button :class="{ active: ansichtModus === 'erklärend' }" @click="$emit('setAnsicht', 'erklärend')">Explanatory</button>
          </div>
        </header>

        <section
          v-for="(ch, ci) in content.chapters"
          :key="ch.title"
          class="chapter"
          :style="{ '--ch-accent': CH_COLORS[ci % CH_COLORS.length] }"
        >
          <h2 class="chapter-title"><span class="ch-num">{{ ci + 1 }}</span>{{ ch.title }}</h2>
          <div class="sections">
            <article
              v-for="s in ch.sections"
              :key="s.num"
              :data-num="s.num"
              class="section-card"
              :style="levelOf(s.title) ? { borderLeftColor: levelOf(s.title).farbe } : {}"
            >
              <h3 :class="{ 'block-klick': isCheckable(s) }" @click="isCheckable(s) && openFocus(s, 'exam')">
                {{ s.title }}
                <template v-if="isCheckable(s) && levelOf(s.title)">
                  <span class="block-done" :style="{ color: levelOf(s.title).farbe, borderColor: levelOf(s.title).farbe }" :title="`${levelOf(s.title).label} (${capOf(s.title)})`">{{ levelOf(s.title).kurz }} {{ levelOf(s.title).label }}</span>
                </template>
              </h3>
              <div v-if="visible[s.num]" class="section-body markdown" v-html="htmlFor(s)"></div>
              <div v-else class="section-body skeleton"></div>
              <BlockPanel
                v-if="isCheckable(s)"
                mode="trigger"
                :block="s.title"
                :status="learnstate[s.title]"
                :cap="capOf(s.title)"
                :topic="previewGuide.topic"
                @open-fokus="(tab) => openFocus(s, tab)"
              />
            </article>
          </div>
        </section>
      </div>
    </div>

    <div v-else-if="previewGuide" class="empty-preview">
      <p>{{ loadError || 'Loading content…' }}</p>
    </div>

    <div class="empty-preview" v-else>
      <p>Click a guide format to generate or open a preview.</p>
    </div>

    <BlockFocus
      v-if="focusBlock"
      :block="focusBlock"
      :topic="previewGuide.topic"
      :guide-id="previewGuide.id"
      :provider="provider"
      :fortschritt="progress"
      :status="learnstate[focusBlock.title]"
      :cap="capOf(focusBlock.title)"
      :stufe="viewLevelFor(focusBlock.title)"
      :tab="focusTab"
      :ansicht="ansichtModus"
      :has-prev="focusIndex > 0"
      :has-next="focusIndex < blocks.length - 1"
      @prev="focusPrev"
      @next="focusNext"
      @close="focusIndex = null"
      @set-ansicht="$emit('setAnsicht', $event)"
      @status-changed="(st) => onBlockStatus(st.block, st)"
      @open-sidebar="$emit('openSidebar')"
      @section-updated="onSectionUpdated"
    />

    <button v-if="previewGuide && !chatOpen && focusIndex === null" class="chat-fab" title="Questions about the guide" @click="openChat">💬</button>

    <div v-if="previewGuide && chatOpen" ref="panelEl" class="chat-panel">
      <header class="chat-header">
        <span>Questions about the guide</span>
        <button class="chat-close" title="Close chat" @click="closeChat">×</button>
      </header>
      <ChatTranscript :chat="chat" hint="Stelle eine Frage zum aktuellen Abschnitt."
                      placeholder="Frage stellen…" :rows="3" auto-grow max-height="none" />
    </div>
  </div>
</template>

<style scoped>
.detail {
  flex: 1;
  /* Flex item may shrink below its code blocks — otherwise their
     min width breaks the layout on mobile */
  min-width: 0;
  height: 100dvh;
  position: relative;
}

.guide-scroll {
  height: 100%;
  overflow-y: auto;
  /* No horizontal panning of the whole page — code blocks scroll internally */
  overflow-x: hidden;
  background: var(--bg-preview);
}

.guide-content {
  max-width: 880px;
  margin: 0 auto;
  padding: 2rem 2.5rem 5rem;
  /* Reading zoom only for the content — sidebar/chat stay unchanged */
  zoom: 1;
}

@media (max-width: 600px) {
  .guide-content {
    padding: 1.25rem 0.9rem 4rem;
  }
}

.guide-head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
  /* Sticky: the verbosity toggle stays reachable while scrolling. */
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg-preview);
  padding: 0.6rem 0 0.5rem;

  h1 {
    font-size: 1.7rem;
  }
}

.gh-spacer { flex: 1; }

.ansicht-toggle {
  align-self: center;
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.ansicht-toggle button {
  border: none;
  background: var(--panel);
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.3rem 0.7rem;
  cursor: pointer;
}
.ansicht-toggle button.active {
  background: var(--accent);
  color: var(--on-accent);
}

.guide-format {
  color: var(--text-faint);
  font-size: 0.9rem;
  font-weight: 600;
}

.thema-done {
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--level-master) 20%, var(--panel));
  border: 1px solid var(--level-master);
  color: #8a6d12;
}

.chapter {
  margin-bottom: 2.5rem;
}

.chapter-title {
  font-size: 1.25rem;
  margin-bottom: 0.9rem;
  padding-bottom: 0.4rem;
  border-bottom: 2px solid color-mix(in srgb, var(--ch-accent, var(--accent)) 35%, transparent);
  display: flex;
  align-items: center;
  gap: 10px;
}

.ch-num {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--ch-accent, var(--accent));
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  font-weight: 700;
}

.section-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
}

.block-done {
  float: right;
  margin-left: 0.5rem;
  padding: 0.12rem 0.6rem;
  font-size: 0.68em;
  font-weight: 600;
  line-height: 1.5;
  border-radius: 999px;
  background: var(--success-soft);
  border: 1px solid var(--success-border);
  color: var(--success);
  white-space: nowrap;
}

/* Completed blocks: card visibly flips to green */
.guide-content .section-card.completed {
  border-color: var(--success-border);
  border-top: 3px solid var(--success);
  background: color-mix(in srgb, var(--success) 5%, var(--panel));
}

/* Understood blocks (10/10): purple */
.block-done.understood {
  background: color-mix(in srgb, var(--level-expert) 16%, var(--panel));
  border-color: var(--level-expert);
  color: #6d28d9;
}
.guide-content .section-card.understood {
  border-color: var(--level-expert);
  border-top: 3px solid var(--level-expert);
  background: color-mix(in srgb, var(--level-expert) 7%, var(--panel));
}

/* Mastered blocks (master path 25/25): gold */
.block-done.mastered {
  background: color-mix(in srgb, var(--level-master) 20%, var(--panel));
  border-color: var(--level-master);
  color: #8a6d12;
}
.guide-content .section-card.mastered {
  border-color: var(--level-master);
  border-top: 3px solid var(--level-master);
  background: color-mix(in srgb, var(--level-master) 8%, var(--panel));
}

/* Guides: cards carry the chapter accent color */
.guide-content .section-card {
  border-top: 3px solid color-mix(in srgb, var(--ch-accent, var(--accent)) 65%, transparent);
  background: color-mix(in srgb, var(--ch-accent, var(--accent)) 3%, var(--panel));
}

.section-card {

  h3 {
    font-size: 1.02rem;
    margin-bottom: 0.5rem;
  }
}

/* Title click opens the full view. */
.section-card h3.block-klick { cursor: pointer; width: fit-content; }
.section-card h3.block-klick:hover { color: var(--accent); }

/* Placeholder for not-yet-rendered sections (lazy render). Stable height,
   so the IntersectionObserver detects the following cards in a staggered way. */
.section-body.skeleton { min-height: 160px; }

.empty-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--text-muted);
}

/* --- Markdown: base global (assets/markdown.css), here only reading-view overrides --- */

/* Wide reading view: code scrolls horizontally instead of wrapping */
.markdown :deep(pre) {
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

/* "Example" headings in cards as a subtle uppercase label */
.section-card .markdown :deep(h3) {
  font-size: 0.74em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  margin: 0.9em 0 0.35em;
}

/* Readability: ~17px body text, line height 1.6, text column max ~70 chars —
   code blocks may use the full card width */
.section-body {
  font-size: 1.0625rem;
  line-height: 1.6;
}

.section-card .markdown :deep(p),
.section-card .markdown :deep(ul),
.section-card .markdown :deep(ol) {
  max-width: 70ch;
}

/* --- Chat --- */
.chat-fab {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  width: 52px;
  height: 52px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 1.4rem;
  cursor: pointer;
  box-shadow: 0 2px 12px var(--shadow);
  z-index: 20;
}

.chat-fab:hover {
  background: var(--accent-hover);
}

.chat-panel {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  width: 360px;
  height: 500px;
  max-height: calc(100dvh - 3rem);
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 4px 24px var(--shadow);
  z-index: 20;
  overflow: hidden;
}

.chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.9rem;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  font-size: 0.9rem;
}

.chat-close {
  border: none;
  background: none;
  color: var(--on-accent);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}

/* Chat-Transkript + Eingabe leben jetzt in ChatTranscript.vue (geteilt mit BlockPanel). */

/* .sub-neu/.sub-stufe: global in assets/markdown.css — scoped greift nicht auf v-html-Inhalt. */
</style>
