/**
 * Progressionsgraphen: Sequenz-Editor (mehrere Nachfolger auf einmal), Ketten-Ansicht,
 * Varianten als eigene Knoten-Endpunkte, Schwester-Kanten gesondert, Tabelle als Fallback.
 */
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import api from '../utils/api'
import { useAuth } from '../context/AuthContext'
import ExercisePickerModal from './ExercisePickerModal'

const VIS_OPTIONS = [
  { value: 'private', label: 'Privat' },
  { value: 'club', label: 'Verein' },
  { value: 'official', label: 'Offiziell' },
]

function edgeTypeLabel(type) {
  if (type === 'next_exercise') return 'Nachfolger'
  if (type === 'sibling') return 'Schwester'
  return type || '—'
}

/** Maximale lineare Segmente aus next_exercise-Kanten (jedes Segment deckt zusammenhängende „Pfade“ ab). */
function maximalLinearChains(nextEdges) {
  if (!nextEdges?.length) return []
  const outMap = new Map()
  const inMap = new Map()
  const nodeKey = (ex, v) => `${ex}:${v ?? ''}`

  for (const e of nextEdges) {
    const f = nodeKey(e.from_exercise_id, e.from_exercise_variant_id)
    const t = nodeKey(e.to_exercise_id, e.to_exercise_variant_id)
    if (!outMap.has(f)) outMap.set(f, [])
    outMap.get(f).push(e)
    if (!inMap.has(t)) inMap.set(t, [])
    inMap.get(t).push(e)
  }

  const used = new Set()
  const chains = []

  for (const startEdge of nextEdges) {
    if (used.has(startEdge.id)) continue

    const edgesSeq = [startEdge]

    let fk = nodeKey(startEdge.from_exercise_id, startEdge.from_exercise_variant_id)
    while (true) {
      const preds = inMap.get(fk)
      if (!preds || preds.length !== 1) break
      const pred = preds[0]
      if (used.has(pred.id)) break
      edgesSeq.unshift(pred)
      fk = nodeKey(pred.from_exercise_id, pred.from_exercise_variant_id)
    }

    let tk = nodeKey(startEdge.to_exercise_id, startEdge.to_exercise_variant_id)
    while (true) {
      const outs = outMap.get(tk)
      if (!outs || outs.length !== 1) break
      const nx = outs[0]
      if (used.has(nx.id)) break
      edgesSeq.push(nx)
      tk = nodeKey(nx.to_exercise_id, nx.to_exercise_variant_id)
    }

    edgesSeq.forEach((ed) => used.add(ed.id))

    const first = edgesSeq[0]
    const nodes = [
      {
        exercise_id: first.from_exercise_id,
        variant_id: first.from_exercise_variant_id ?? null,
        title: first.from_exercise_title,
        variant_name: first.from_variant_name ?? null,
      },
    ]
    for (const ed of edgesSeq) {
      nodes.push({
        exercise_id: ed.to_exercise_id,
        variant_id: ed.to_exercise_variant_id ?? null,
        title: ed.to_exercise_title,
        variant_name: ed.to_variant_name ?? null,
      })
    }
    chains.push({ nodes, edges: edgesSeq })
  }
  return chains
}

function emptySeqStep() {
  return { exerciseId: null, exerciseTitle: '', variantId: null, variants: [] }
}

function emptyEndpoint() {
  return { exerciseId: null, exerciseTitle: '', variantId: null, variants: [] }
}

export default function ExerciseProgressionGraphPanel({
  anchorExerciseId = null,
  anchorTitle = null,
}) {
  const { user } = useAuth()
  const isSuperadmin = user?.role === 'superadmin'
  const filteredGraphVisOptions = useMemo(
    () => VIS_OPTIONS.filter((o) => o.value !== 'official' || isSuperadmin),
    [isSuperadmin],
  )

  const [graphs, setGraphs] = useState([])
  const [selectedGraphId, setSelectedGraphId] = useState(null)
  const [edges, setEdges] = useState([])
  const [busy, setBusy] = useState(false)
  const [loadErr, setLoadErr] = useState(null)

  const [newGraphName, setNewGraphName] = useState('')
  const [newGraphVisibility, setNewGraphVisibility] = useState('private')

  const [metaName, setMetaName] = useState('')
  const [metaDescription, setMetaDescription] = useState('')
  const [metaVisibility, setMetaVisibility] = useState('private')

  const [sequenceSteps, setSequenceSteps] = useState([emptySeqStep(), emptySeqStep()])
  const [sequenceBulkNotes, setSequenceBulkNotes] = useState('')
  const [pickContext, setPickContext] = useState(null)

  const [relationKind, setRelationKind] = useState('progression')
  const [firstEp, setFirstEp] = useState(emptyEndpoint)
  const [secondEp, setSecondEp] = useState(emptyEndpoint)
  const [edgeNotes, setEdgeNotes] = useState('')

  const [filterAnchorOnly, setFilterAnchorOnly] = useState(!!anchorExerciseId)
  const [editingEdgeNotes, setEditingEdgeNotes] = useState(null)
  const [notesDraft, setNotesDraft] = useState('')
  const [uiTab, setUiTab] = useState('overview')

  const refreshGraphs = useCallback(async () => {
    const list = await api.listExerciseProgressionGraphs()
    setGraphs(Array.isArray(list) ? list : [])
    return list
  }, [])

  const refreshEdges = useCallback(async (gid) => {
    if (!gid) {
      setEdges([])
      return
    }
    const list = await api.listExerciseProgressionEdges(gid)
    setEdges(Array.isArray(list) ? list : [])
  }, [])

  const loadVariantsForExercise = useCallback(async (exerciseId) => {
    if (!exerciseId) return []
    const ex = await api.getExercise(exerciseId)
    return Array.isArray(ex?.variants) ? ex.variants : []
  }, [])

  useEffect(() => {
    let cancelled = false
    ;(async () => {
      setBusy(true)
      setLoadErr(null)
      try {
        await refreshGraphs()
      } catch (e) {
        if (!cancelled) setLoadErr(e.message || String(e))
      } finally {
        if (!cancelled) setBusy(false)
      }
    })()
    return () => {
      cancelled = true
    }
  }, [refreshGraphs])

  useEffect(() => {
    if (!selectedGraphId) {
      setEdges([])
      setMetaName('')
      setMetaDescription('')
      setMetaVisibility('private')
      return
    }
    const g = graphs.find((x) => x.id === selectedGraphId)
    if (g) {
      setMetaName(g.name || '')
      setMetaDescription(g.description || '')
      setMetaVisibility(g.visibility || 'private')
    }
    let cancelled = false
    ;(async () => {
      try {
        await refreshEdges(selectedGraphId)
      } catch (e) {
        if (!cancelled) alert(e.message || String(e))
      }
    })()
    return () => {
      cancelled = true
    }
  }, [selectedGraphId, graphs, refreshEdges])

  useEffect(() => {
    let cancelled = false
    ;(async () => {
      if (!firstEp.exerciseId) return
      const vars = await loadVariantsForExercise(firstEp.exerciseId)
      if (!cancelled) setFirstEp((p) => ({ ...p, variants: vars }))
    })()
    return () => {
      cancelled = true
    }
  }, [firstEp.exerciseId, loadVariantsForExercise])

  useEffect(() => {
    let cancelled = false
    ;(async () => {
      if (!secondEp.exerciseId) return
      const vars = await loadVariantsForExercise(secondEp.exerciseId)
      if (!cancelled) setSecondEp((p) => ({ ...p, variants: vars }))
    })()
    return () => {
      cancelled = true
    }
  }, [secondEp.exerciseId, loadVariantsForExercise])

  const filteredEdges = useMemo(() => {
    if (!filterAnchorOnly || anchorExerciseId == null) return edges
    return edges.filter(
      (e) =>
        e.from_exercise_id === anchorExerciseId || e.to_exercise_id === anchorExerciseId,
    )
  }, [edges, filterAnchorOnly, anchorExerciseId])

  const nextEdgesFiltered = useMemo(
    () => filteredEdges.filter((e) => e.edge_type === 'next_exercise'),
    [filteredEdges],
  )
  const siblingEdgesFiltered = useMemo(
    () => filteredEdges.filter((e) => e.edge_type === 'sibling'),
    [filteredEdges],
  )

  const flowChains = useMemo(() => maximalLinearChains(nextEdgesFiltered), [nextEdgesFiltered])

  const handleCreateGraph = async (e) => {
    e.preventDefault()
    const name = newGraphName.trim()
    if (!name) {
      alert('Name für den Graphen eingeben')
      return
    }
    setBusy(true)
    try {
      const created = await api.createExerciseProgressionGraph({
        name,
        visibility: newGraphVisibility,
      })
      setNewGraphName('')
      await refreshGraphs()
      if (created?.id != null) setSelectedGraphId(created.id)
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const handleSaveMeta = async () => {
    if (!selectedGraphId) return
    const name = metaName.trim()
    if (!name) {
      alert('Name ist Pflicht')
      return
    }
    setBusy(true)
    try {
      await api.updateExerciseProgressionGraph(selectedGraphId, {
        name,
        description: metaDescription.trim() || null,
        visibility: metaVisibility,
      })
      await refreshGraphs()
      alert('Graph gespeichert.')
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const handleDeleteGraph = async () => {
    if (!selectedGraphId) return
    if (!confirm('Diesen Progressionsgraphen und alle Kanten wirklich löschen?')) return
    setBusy(true)
    try {
      await api.deleteExerciseProgressionGraph(selectedGraphId)
      setSelectedGraphId(null)
      await refreshGraphs()
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const patchSeqStep = (idx, patch) => {
    setSequenceSteps((prev) => prev.map((s, i) => (i === idx ? { ...s, ...patch } : s)))
  }

  const addSeqStep = () => setSequenceSteps((prev) => [...prev, emptySeqStep()])

  const removeSeqStep = (idx) => {
    setSequenceSteps((prev) => {
      if (prev.length <= 2) return prev
      return prev.filter((_, i) => i !== idx)
    })
  }

  const moveSeqStep = (idx, dir) => {
    setSequenceSteps((prev) => {
      const j = idx + dir
      if (j < 0 || j >= prev.length) return prev
      const next = [...prev]
      const t = next[idx]
      next[idx] = next[j]
      next[j] = t
      return next
    })
  }

  const submitSequence = async () => {
    if (!selectedGraphId) {
      alert('Zuerst einen Graphen wählen.')
      return
    }
    const steps = sequenceSteps.filter((s) => s.exerciseId != null)
    if (steps.length < 2) {
      alert('Mindestens zwei Schritte mit gewählter Übung.')
      return
    }
    const n = steps.length - 1
    const noteRaw = sequenceBulkNotes.trim()
    const segment_notes = Array.from({ length: n }, () => (noteRaw ? noteRaw : null))

    setBusy(true)
    try {
      await api.createExerciseProgressionSequence(selectedGraphId, {
        steps: steps.map((s) => ({
          exercise_id: s.exerciseId,
          variant_id: s.variantId || null,
        })),
        segment_notes,
      })
      setSequenceBulkNotes('')
      await refreshEdges(selectedGraphId)
      alert(`${n} Nachfolger-Kante(n) angelegt.`)
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const deleteChain = async (edgeObjs) => {
    if (!selectedGraphId || !edgeObjs?.length) return
    if (!confirm(`${edgeObjs.length} Kante(n) dieser Reihe löschen?`)) return
    setBusy(true)
    try {
      await api.deleteExerciseProgressionEdgesBatch(
        selectedGraphId,
        edgeObjs.map((e) => e.id),
      )
      await refreshEdges(selectedGraphId)
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const handleAddEdge = async () => {
    if (!selectedGraphId) {
      alert('Zuerst einen Graphen wählen.')
      return
    }
    if (!firstEp.exerciseId || !secondEp.exerciseId) {
      alert('Beide Enden müssen eine Übung haben.')
      return
    }
    if (
      firstEp.exerciseId === secondEp.exerciseId &&
      (firstEp.variantId == null ||
        secondEp.variantId == null ||
        firstEp.variantId === secondEp.variantId)
    ) {
      alert('Bei derselben Übung bitte zwei verschiedene Varianten wählen (oder unterschiedliche Übungen).')
      return
    }
    const edge_type = relationKind === 'sibling' ? 'sibling' : 'next_exercise'
    const notes = edgeNotes.trim() || null
    const body = {
      from_exercise_id: firstEp.exerciseId,
      to_exercise_id: secondEp.exerciseId,
      from_exercise_variant_id: firstEp.variantId || null,
      to_exercise_variant_id: secondEp.variantId || null,
      edge_type,
      notes,
    }
    setBusy(true)
    try {
      await api.createExerciseProgressionEdge(selectedGraphId, body)
      setEdgeNotes('')
      await refreshEdges(selectedGraphId)
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const handleDeleteEdge = async (edgeId) => {
    if (!selectedGraphId) return
    if (!confirm('Kante löschen?')) return
    setBusy(true)
    try {
      await api.deleteExerciseProgressionEdge(selectedGraphId, edgeId)
      await refreshEdges(selectedGraphId)
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const startEditNotes = (edge) => {
    setEditingEdgeNotes(edge.id)
    setNotesDraft(edge.notes || '')
  }

  const saveNotes = async (edgeId) => {
    if (!selectedGraphId) return
    setBusy(true)
    try {
      await api.updateExerciseProgressionEdge(selectedGraphId, edgeId, {
        notes: notesDraft.trim() || null,
      })
      setEditingEdgeNotes(null)
      await refreshEdges(selectedGraphId)
    } catch (err) {
      alert(err.message || String(err))
    } finally {
      setBusy(false)
    }
  }

  const swapEnds = () => {
    const a = firstEp
    setFirstEp(secondEp)
    setSecondEp(a)
  }

  const applyPickedExercise = async (ex) => {
    const title = ex.title || `Übung #${ex.id}`
    const variants = await loadVariantsForExercise(ex.id)

    if (pickContext?.kind === 'sequence') {
      patchSeqStep(pickContext.index, {
        exerciseId: ex.id,
        exerciseTitle: title,
        variantId: null,
        variants,
      })
      setPickContext(null)
      return
    }
    if (pickContext?.kind === 'single') {
      const patch = {
        exerciseId: ex.id,
        exerciseTitle: title,
        variantId: null,
        variants,
      }
      if (pickContext.slot === 'first') setFirstEp(patch)
      else setSecondEp(patch)
      setPickContext(null)
    }
  }

  function formatNodeLine(n) {
    return (
      <>
        <Link to={`/exercises/${n.exercise_id}`}>{n.title}</Link>
        {n.variant_name ? (
          <span style={{ color: 'var(--text3)', fontWeight: 400 }}>{` · ${n.variant_name}`}</span>
        ) : null}
      </>
    )
  }

  const pickerOpen = pickContext != null

  return (
    <div className="exercise-progression-panel">
      {anchorExerciseId != null && (
        <p style={{ fontSize: '13px', color: 'var(--text2)', marginBottom: '12px' }}>
          Kontext:{' '}
          <strong>{anchorTitle?.trim() || `Übung #${anchorExerciseId}`}</strong>
          {' · '}
          <Link to={`/exercises/${anchorExerciseId}`}>Ansehen</Link>
        </p>
      )}

      <p style={{ fontSize: '13px', color: 'var(--text2)', marginBottom: '16px' }}>
        Pro Graph mehrere <strong>Reihen</strong> und <strong>Alternativen</strong>: eine{' '}
        <strong>Sequenz</strong> legt automatisch alle Schritte Übung1 → Übung2 → … als Nachfolger-Kanten an.
        Optional pro Schritt eine <strong>Variante</strong> — sie wirkt wie ein eigener Knoten. Verzweigungen und
        Schwestern trennst du weiterhin mit Einzelkanten oder mehreren Sequenzen aus dem gleichen Knoten.
      </p>

      {loadErr && (
        <div className="card" style={{ borderColor: 'var(--danger)', marginBottom: '12px' }}>
          <p style={{ margin: 0, color: 'var(--danger)' }}>{loadErr}</p>
        </div>
      )}

      <div className="card" style={{ marginBottom: '12px' }}>
        <h3 style={{ marginTop: 0, fontSize: '1rem' }}>Graph auswählen</h3>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'flex-end' }}>
          <div className="form-row" style={{ flex: '1 1 220px', marginBottom: 0 }}>
            <label className="form-label">Aktiver Graph</label>
            <select
              className="form-input"
              value={selectedGraphId ?? ''}
              onChange={(e) => setSelectedGraphId(e.target.value ? parseInt(e.target.value, 10) : null)}
            >
              <option value="">— wählen —</option>
              {graphs.map((g) => (
                <option key={g.id} value={g.id}>
                  {g.name} ({g.edges_count ?? 0} Kanten)
                </option>
              ))}
            </select>
          </div>
          <button
            type="button"
            className="btn"
            disabled={busy || !selectedGraphId}
            onClick={() => refreshEdges(selectedGraphId)}
          >
            Aktualisieren
          </button>
          <button type="button" className="btn" disabled={busy || !selectedGraphId} onClick={handleDeleteGraph}>
            Graph löschen
          </button>
        </div>

        <form onSubmit={handleCreateGraph} style={{ marginTop: '16px', paddingTop: '16px', borderTop: '1px solid var(--border)' }}>
          <h4 style={{ margin: '0 0 8px', fontSize: '0.95rem' }}>Neuen Graphen anlegen</h4>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'flex-end' }}>
            <div className="form-row" style={{ flex: '2 1 200px', marginBottom: 0 }}>
              <label className="form-label">Name</label>
              <input
                className="form-input"
                value={newGraphName}
                onChange={(e) => setNewGraphName(e.target.value)}
                placeholder="z. B. Kumite-Einstieg Verein Nord"
              />
            </div>
            <div className="form-row" style={{ flex: '1 1 140px', marginBottom: 0 }}>
              <label className="form-label">Sichtbarkeit</label>
              <select
                className="form-input"
                value={newGraphVisibility}
                onChange={(e) => setNewGraphVisibility(e.target.value)}
              >
                {filteredGraphVisOptions.map((o) => (
                  <option key={o.value} value={o.value}>
                    {o.label}
                  </option>
                ))}
              </select>
            </div>
            <button type="submit" className="btn btn-primary" disabled={busy}>
              Graph erstellen
            </button>
          </div>
        </form>
      </div>

      {selectedGraphId && (
        <div className="card" style={{ marginBottom: '12px' }}>
          <h3 style={{ marginTop: 0, fontSize: '1rem' }}>Graph bearbeiten</h3>
          <div className="form-row">
            <label className="form-label">Name</label>
            <input className="form-input" value={metaName} onChange={(e) => setMetaName(e.target.value)} />
          </div>
          <div className="form-row">
            <label className="form-label">Beschreibung</label>
            <textarea
              className="form-input"
              rows={2}
              value={metaDescription}
              onChange={(e) => setMetaDescription(e.target.value)}
            />
          </div>
          <div className="form-row">
            <label className="form-label">Sichtbarkeit</label>
            <select className="form-input" value={metaVisibility} onChange={(e) => setMetaVisibility(e.target.value)}>
              {filteredGraphVisOptions.map((o) => (
                <option key={o.value} value={o.value}>
                  {o.label}
                </option>
              ))}
            </select>
          </div>
          <button type="button" className="btn btn-secondary" disabled={busy} onClick={handleSaveMeta}>
            Metadaten speichern
          </button>
        </div>
      )}

      {selectedGraphId && (
        <div
          role="tablist"
          style={{ display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap' }}
          aria-label="Darstellung"
        >
          <button
            type="button"
            className={uiTab === 'overview' ? 'btn btn-primary' : 'btn btn-secondary'}
            onClick={() => setUiTab('overview')}
          >
            Übersicht & Sequenz
          </button>
          <button
            type="button"
            className={uiTab === 'table' ? 'btn btn-primary' : 'btn btn-secondary'}
            onClick={() => setUiTab('table')}
          >
            Alle Kanten (Tabelle)
          </button>
        </div>
      )}

      {selectedGraphId && uiTab === 'overview' && (
        <>
          <div className="card" style={{ marginBottom: '12px' }}>
            <h3 style={{ marginTop: 0, fontSize: '1rem' }}>Sequenz / Reihe anlegen</h3>
            <p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: 0 }}>
              Reihenfolge von links nach rechts: jede Zeile ein Schritt. Es werden automatisch alle Nachfolger-Kanten
              zwischen benachbarten Schritten erzeugt (ein API-Vorgang).
            </p>
            {sequenceSteps.map((step, idx) => (
              <div
                key={idx}
                style={{
                  display: 'grid',
                  gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
                  gap: '10px',
                  alignItems: 'end',
                  marginBottom: '12px',
                  paddingBottom: '12px',
                  borderBottom: idx < sequenceSteps.length - 1 ? '1px dashed var(--border)' : 'none',
                }}
              >
                <div className="form-row" style={{ marginBottom: 0 }}>
                  <label className="form-label">Schritt {idx + 1}</label>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' }}>
                    <span style={{ fontSize: '13px', flex: '1 1 140px' }}>
                      {step.exerciseId ? (
                        <>
                          <strong>{step.exerciseTitle}</strong>
                          <span style={{ color: 'var(--text3)' }}> (#{step.exerciseId})</span>
                        </>
                      ) : (
                        <span style={{ color: 'var(--text3)' }}>Übung wählen</span>
                      )}
                    </span>
                    <button
                      type="button"
                      className="btn btn-secondary"
                      style={{ fontSize: '12px', padding: '4px 10px' }}
                      onClick={() => setPickContext({ kind: 'sequence', index: idx })}
                    >
                      Übung…
                    </button>
                    {anchorExerciseId != null && (
                      <button
                        type="button"
                        className="btn"
                        style={{ fontSize: '12px', padding: '4px 10px' }}
                        onClick={async () => {
                          const variants = await loadVariantsForExercise(anchorExerciseId)
                          patchSeqStep(idx, {
                            exerciseId: anchorExerciseId,
                            exerciseTitle: anchorTitle?.trim() || `Übung #${anchorExerciseId}`,
                            variantId: null,
                            variants,
                          })
                        }}
                      >
                        Diese Übung
                      </button>
                    )}
                  </div>
                </div>
                <div className="form-row" style={{ marginBottom: 0 }}>
                  <label className="form-label">Variante (optional)</label>
                  <select
                    className="form-input"
                    disabled={!step.exerciseId}
                    value={step.variantId ?? ''}
                    onChange={(e) =>
                      patchSeqStep(idx, {
                        variantId: e.target.value === '' ? null : parseInt(e.target.value, 10),
                      })
                    }
                  >
                    <option value="">Gesamte Übung (ohne feste Variante)</option>
                    {(step.variants || []).map((v) => (
                      <option key={v.id} value={v.id}>
                        {v.variant_name || `Variante #${v.id}`}
                      </option>
                    ))}
                  </select>
                </div>
                <div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
                  <button type="button" className="btn" style={{ fontSize: '12px', padding: '4px 8px' }} onClick={() => moveSeqStep(idx, -1)}>
                    ↑
                  </button>
                  <button type="button" className="btn" style={{ fontSize: '12px', padding: '4px 8px' }} onClick={() => moveSeqStep(idx, 1)}>
                    ↓
                  </button>
                  <button type="button" className="btn" style={{ fontSize: '12px', padding: '4px 8px' }} onClick={() => removeSeqStep(idx)}>
                    Zeile entfernen
                  </button>
                </div>
              </div>
            ))}
            <button type="button" className="btn btn-secondary" style={{ marginBottom: '12px' }} onClick={addSeqStep}>
              + Schritt
            </button>
            <div className="form-row">
              <label className="form-label">Entwicklungsziel für alle neuen Zwischen-Kanten (optional)</label>
              <textarea
                className="form-input"
                rows={2}
                value={sequenceBulkNotes}
                onChange={(e) => setSequenceBulkNotes(e.target.value)}
                placeholder="wird auf jedes neue Segment der Sequenz kopiert"
              />
            </div>
            <button type="button" className="btn btn-primary" disabled={busy} onClick={submitSequence}>
              Sequenz als Nachfolger-Kanten speichern
            </button>
          </div>

          <div className="card" style={{ marginBottom: '12px' }}>
            <h3 style={{ marginTop: 0, fontSize: '1rem' }}>Nachfolger als Reihen (Lesart)</h3>
            {flowChains.length === 0 ? (
              <p style={{ color: 'var(--text2)', margin: 0 }}>Keine Nachfolger-Kanten im aktuellen Filter.</p>
            ) : (
              flowChains.map((chain, ci) => (
                <div
                  key={ci}
                  style={{
                    marginBottom: '14px',
                    padding: '12px',
                    borderRadius: '10px',
                    border: '1px solid var(--border)',
                    background: 'var(--surface2)',
                  }}
                >
                  <div
                    style={{
                      display: 'flex',
                      flexWrap: 'wrap',
                      alignItems: 'center',
                      gap: '6px 10px',
                      fontSize: '13px',
                      lineHeight: 1.5,
                    }}
                  >
                    {chain.nodes.map((n, ni) => (
                      <React.Fragment key={`${n.exercise_id}-${n.variant_id}-${ni}`}>
                        {ni > 0 && (
                          <span style={{ color: 'var(--accent)', fontWeight: 700 }} aria-hidden>
                            →
                          </span>
                        )}
                        <span style={{ fontWeight: 600 }}>{formatNodeLine(n)}</span>
                      </React.Fragment>
                    ))}
                  </div>
                  <div style={{ marginTop: '10px', display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
                    <button type="button" className="btn btn-secondary" disabled={busy} onClick={() => deleteChain(chain.edges)}>
                      Diese Reihe löschen ({chain.edges.length} Kante(n))
                    </button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="card" style={{ marginBottom: '12px' }}>
            <h3 style={{ marginTop: 0, fontSize: '1rem' }}>Schwestern & Alternativen</h3>
            {siblingEdgesFiltered.length === 0 ? (
              <p style={{ color: 'var(--text2)', margin: 0 }}>Keine Schwester-Kanten im aktuellen Filter.</p>
            ) : (
              <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
                {siblingEdgesFiltered.map((row) => (
                  <li
                    key={row.id}
                    style={{
                      padding: '10px 12px',
                      marginBottom: '8px',
                      borderRadius: '8px',
                      border: '1px solid var(--border)',
                      background: 'var(--surface2)',
                      display: 'flex',
                      justifyContent: 'space-between',
                      flexWrap: 'wrap',
                      gap: '8px',
                      alignItems: 'center',
                      fontSize: '13px',
                    }}
                  >
                    <span>
                      <strong>{formatNodeLine({
                        exercise_id: row.from_exercise_id,
                        variant_id: row.from_exercise_variant_id,
                        title: row.from_exercise_title,
                        variant_name: row.from_variant_name,
                      })}</strong>
                      <span style={{ color: 'var(--text3)', margin: '0 8px' }}>· Schwester ·</span>
                      <strong>{formatNodeLine({
                        exercise_id: row.to_exercise_id,
                        variant_id: row.to_exercise_variant_id,
                        title: row.to_exercise_title,
                        variant_name: row.to_variant_name,
                      })}</strong>
                      {row.notes ? (
                        <span style={{ display: 'block', marginTop: '6px', color: 'var(--text2)', fontWeight: 400 }}>
                          {row.notes}
                        </span>
                      ) : null}
                    </span>
                    <button
                      type="button"
                      className="btn"
                      style={{ fontSize: '12px', padding: '4px 10px', background: 'var(--danger)', color: '#fff', border: 'none' }}
                      onClick={() => handleDeleteEdge(row.id)}
                    >
                      Löschen
                    </button>
                  </li>
                ))}
              </ul>
            )}
          </div>

          <details className="card" style={{ marginBottom: '12px' }}>
            <summary style={{ cursor: 'pointer', fontWeight: 600 }}>Einzelkante (Nachfolger oder Schwester)</summary>
            <div style={{ marginTop: '14px' }}>
              <div className="form-row">
                <label className="form-label">Beziehung</label>
                <select className="form-input" value={relationKind} onChange={(e) => setRelationKind(e.target.value)}>
                  <option value="progression">Nachfolger</option>
                  <option value="sibling">Schwester</option>
                </select>
              </div>

              {['first', 'second'].map((slot) => {
                const ep = slot === 'first' ? firstEp : secondEp
                const setEp = slot === 'first' ? setFirstEp : setSecondEp
                return (
                  <div key={slot} style={{ marginBottom: '12px' }}>
                    <label className="form-label">{slot === 'first' ? 'Von (Quelle)' : 'Nach (Ziel)'}</label>
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
                      <span style={{ fontSize: '13px', flex: '1 1 160px' }}>
                        {ep.exerciseId ? (
                          <>
                            <strong>{ep.exerciseTitle}</strong>
                            <span style={{ color: 'var(--text3)' }}> (#{ep.exerciseId})</span>
                          </>
                        ) : (
                          <span style={{ color: 'var(--text3)' }}>— Übung wählen —</span>
                        )}
                      </span>
                      <button
                        type="button"
                        className="btn btn-secondary"
                        onClick={() => setPickContext({ kind: 'single', slot })}
                      >
                        Übung…
                      </button>
                      {anchorExerciseId != null && (
                        <button
                          type="button"
                          className="btn"
                          style={{ fontSize: '12px', padding: '4px 10px' }}
                          onClick={async () => {
                            const variants = await loadVariantsForExercise(anchorExerciseId)
                            setEp({
                              exerciseId: anchorExerciseId,
                              exerciseTitle: anchorTitle?.trim() || `Übung #${anchorExerciseId}`,
                              variantId: null,
                              variants,
                            })
                          }}
                        >
                          Diese Übung
                        </button>
                      )}
                    </div>
                    <select
                      className="form-input"
                      disabled={!ep.exerciseId}
                      value={ep.variantId ?? ''}
                      onChange={(e) =>
                        setEp((p) => ({
                          ...p,
                          variantId: e.target.value === '' ? null : parseInt(e.target.value, 10),
                        }))
                      }
                    >
                      <option value="">Gesamte Übung</option>
                      {(ep.variants || []).map((v) => (
                        <option key={v.id} value={v.id}>
                          {v.variant_name || `Variante #${v.id}`}
                        </option>
                      ))}
                    </select>
                  </div>
                )
              })}

              {relationKind === 'progression' && (
                <button type="button" className="btn" style={{ marginBottom: '10px' }} onClick={swapEnds}>
                  Reihenfolge tauschen
                </button>
              )}

              <div className="form-row">
                <label className="form-label">Notiz</label>
                <textarea className="form-input" rows={2} value={edgeNotes} onChange={(e) => setEdgeNotes(e.target.value)} />
              </div>
              <button type="button" className="btn btn-primary" disabled={busy} onClick={handleAddEdge}>
                Kante speichern
              </button>
            </div>
          </details>
        </>
      )}

      {selectedGraphId && anchorExerciseId != null && (
        <label style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px', fontSize: '13px' }}>
          <input
            type="checkbox"
            checked={filterAnchorOnly}
            onChange={(e) => setFilterAnchorOnly(e.target.checked)}
          />
          Nur Kanten, die diese Übung betreffen (Übersicht &amp; Tabelle)
        </label>
      )}

      {selectedGraphId && uiTab === 'table' && (
        <div className="card">
          <h3 style={{ marginTop: 0, fontSize: '1rem' }}>
            Alle Kanten ({filteredEdges.length}
            {edges.length !== filteredEdges.length ? ` von ${edges.length}` : ''})
          </h3>
          {filteredEdges.length === 0 ? (
            <p style={{ color: 'var(--text2)', margin: 0 }}>Keine Kanten.</p>
          ) : (
            <div style={{ overflowX: 'auto' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '13px' }}>
                <thead>
                  <tr style={{ textAlign: 'left', borderBottom: '1px solid var(--border)' }}>
                    <th style={{ padding: '8px 6px' }}>Von</th>
                    <th style={{ padding: '8px 6px' }} />
                    <th style={{ padding: '8px 6px' }}>Nach</th>
                    <th style={{ padding: '8px 6px' }}>Art</th>
                    <th style={{ padding: '8px 6px' }}>Notiz</th>
                    <th style={{ padding: '8px 6px' }} />
                  </tr>
                </thead>
                <tbody>
                  {filteredEdges.map((row) => (
                    <tr key={row.id} style={{ borderBottom: '1px solid var(--border)' }}>
                      <td style={{ padding: '8px 6px', verticalAlign: 'top' }}>
                        <Link to={`/exercises/${row.from_exercise_id}`}>{row.from_exercise_title || `#${row.from_exercise_id}`}</Link>
                        {row.from_variant_name ? (
                          <div style={{ color: 'var(--text3)', fontSize: '12px' }}>{row.from_variant_name}</div>
                        ) : null}
                      </td>
                      <td style={{ padding: '8px 6px', color: 'var(--text3)', verticalAlign: 'top' }}>
                        {row.edge_type === 'sibling' ? '·' : '→'}
                      </td>
                      <td style={{ padding: '8px 6px', verticalAlign: 'top' }}>
                        <Link to={`/exercises/${row.to_exercise_id}`}>{row.to_exercise_title || `#${row.to_exercise_id}`}</Link>
                        {row.to_variant_name ? (
                          <div style={{ color: 'var(--text3)', fontSize: '12px' }}>{row.to_variant_name}</div>
                        ) : null}
                      </td>
                      <td style={{ padding: '8px 6px', verticalAlign: 'top' }}>{edgeTypeLabel(row.edge_type)}</td>
                      <td style={{ padding: '8px 6px', verticalAlign: 'top', maxWidth: '280px' }}>
                        {editingEdgeNotes === row.id ? (
                          <>
                            <textarea
                              className="form-input"
                              rows={2}
                              value={notesDraft}
                              onChange={(e) => setNotesDraft(e.target.value)}
                              style={{ marginBottom: '6px' }}
                            />
                            <button type="button" className="btn btn-secondary" disabled={busy} onClick={() => saveNotes(row.id)}>
                              Speichern
                            </button>
                            <button type="button" className="btn" disabled={busy} onClick={() => setEditingEdgeNotes(null)}>
                              Abbrechen
                            </button>
                          </>
                        ) : (
                          <>
                            <span style={{ whiteSpace: 'pre-wrap' }}>{row.notes || '—'}</span>
                            <button
                              type="button"
                              className="btn"
                              style={{ marginLeft: '8px', fontSize: '12px', padding: '4px 8px' }}
                              onClick={() => startEditNotes(row)}
                            >
                              Bearbeiten
                            </button>
                          </>
                        )}
                      </td>
                      <td style={{ padding: '8px 6px', verticalAlign: 'top' }}>
                        <button
                          type="button"
                          className="btn"
                          style={{ fontSize: '12px', padding: '4px 8px', background: 'var(--danger)', color: '#fff', border: 'none' }}
                          onClick={() => handleDeleteEdge(row.id)}
                        >
                          Löschen
                        </button>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </div>
      )}

      <ExercisePickerModal open={pickerOpen} onClose={() => setPickContext(null)} onSelectExercise={applyPickedExercise} />
    </div>
  )
}
