/**
 * Übungssuche mit Volltext-, KI-/Semantikfeld (aktuell gleiche Engine wie Suche) und erweiterten Filtern.
 * Paginierung bis max. 100 Treffer pro Request (API-Limit).
 */
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import api from '../utils/api'
import { useAuth } from '../context/AuthContext'
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels'
import {
  INITIAL_EXERCISE_LIST_FILTERS,
  mergeExerciseListPrefsFromApi,
  splitMnCatalogRules,
  splitScalarCatalogRules,
} from '../constants/exerciseListFilters'
import SkillTreeMultiSelect from './SkillTreeMultiSelect'
import ExerciseFocusRulePicker from './ExerciseFocusRulePicker'
import CatalogRulePicker from './CatalogRulePicker'
import ExerciseAiSuggestPreviewModal from './ExerciseAiSuggestPreviewModal'
import ExerciseAiQuickCreateOffer from './ExerciseAiQuickCreateOffer'
import { useExerciseAiQuickCreateFields } from '../hooks/useExerciseAiQuickCreateFields'
import {
  buildQuickCreateAiPreview,
  buildQuickCreateExercisePayloadFromDraft,
  aiPreviewToQuickCreateDraft,
} from '../utils/exerciseAiQuickCreate'

const PAGE_SIZE = 100
const LEVEL_FILTER_OPTS = SKILL_LEVEL_OPTIONS.filter((o) => o.level != null)

const INITIAL_FILTERS = { ...INITIAL_EXERCISE_LIST_FILTERS }

export default function ExercisePickerModal({
  open,
  onClose,
  onSelectExercise,
  multiSelect = false,
  onSelectExercises = null,
  enableQuickCreateDraft = false,
  /** Planungs-Kontext für KI-Suche (TrainingUnitEditPage o. ä.) */
  planningContext = null,
  /** Wenn gesetzt: z. B. ['simple'] oder ['combination'] — sonst alle Übungsarten */
  exerciseKindAny = undefined,
}) {
  const { user } = useAuth()
  const [catalogs, setCatalogs] = useState({
    focusAreas: [],
    styleDirections: [],
    trainingTypes: [],
    targetGroups: [],
    skills: [],
  })
  const [catalogsReady, setCatalogsReady] = useState(false)
  const [searchInput, setSearchInput] = useState('')
  const [aiSearchInput, setAiSearchInput] = useState('')
  const [debouncedSearch, setDebouncedSearch] = useState('')
  const [debouncedAi, setDebouncedAi] = useState('')
  const [filters, setFilters] = useState(() => ({ ...INITIAL_FILTERS }))
  const [filterOpen, setFilterOpen] = useState(false)
  const [list, setList] = useState([])
  const [loading, setLoading] = useState(false)
  const [loadingMore, setLoadingMore] = useState(false)
  const [hasMore, setHasMore] = useState(false)
  const [multiPicked, setMultiPicked] = useState([])
  const [quickSaving, setQuickSaving] = useState(false)
  const [quickAiError, setQuickAiError] = useState('')
  const [quickCreateDraft, setQuickCreateDraft] = useState(null)
  const [planningContextSummary, setPlanningContextSummary] = useState(null)
  const [planningTargetProfileSummary, setPlanningTargetProfileSummary] = useState(null)
  const [planningLlmRankApplied, setPlanningLlmRankApplied] = useState(false)
  const [planningQueryIntentSummary, setPlanningQueryIntentSummary] = useState(null)
  const [planningIntentResolved, setPlanningIntentResolved] = useState(null)
  const pickerScrollRef = useRef(null)

  const usePlanningSearch = Boolean(planningContext?.unitId && Number(planningContext.unitId) > 0)

  const effectivePickerQuery = useMemo(
    () => [debouncedSearch, debouncedAi].filter(Boolean).join(' ').trim(),
    [debouncedSearch, debouncedAi]
  )

  const {
    title: quickTitle,
    sketch: quickSketch,
    focusAreaId: quickFocusAreaId,
    setTitle: setQuickTitle,
    setSketch: setQuickSketch,
    setFocusAreaId: setQuickFocusAreaId,
    resetQuickCreateFields,
  } = useExerciseAiQuickCreateFields(effectivePickerQuery, { enabled: open && enableQuickCreateDraft })

  const toggleMultiPick = (ex) => {
    setMultiPicked((prev) =>
      prev.some((p) => p.id === ex.id) ? prev.filter((p) => p.id !== ex.id) : [...prev, ex]
    )
  }

  useEffect(() => {
    const t = setTimeout(() => setDebouncedSearch(searchInput.trim()), 350)
    return () => clearTimeout(t)
  }, [searchInput])

  useEffect(() => {
    const t = setTimeout(() => setDebouncedAi(aiSearchInput.trim()), 350)
    return () => clearTimeout(t)
  }, [aiSearchInput])

  const showQuickCreateOffer =
    enableQuickCreateDraft &&
    catalogsReady &&
    !loading &&
    list.length === 0 &&
    (usePlanningSearch || effectivePickerQuery.length >= 3)

  useEffect(() => {
    if (!open) return
    let cancelled = false
    ;(async () => {
      try {
        const [fa, sd, tt, tg, sk] = await Promise.all([
          api.listFocusAreas(),
          api.listStyleDirections(),
          api.listTrainingTypes(),
          api.listTargetGroups(),
          api.listSkillsCatalog(),
        ])
        if (!cancelled) {
          setCatalogs({
            focusAreas: fa,
            styleDirections: sd,
            trainingTypes: tt,
            targetGroups: tg,
            skills: sk,
          })
          setCatalogsReady(true)
        }
      } catch (e) {
        console.error(e)
        if (!cancelled) setCatalogsReady(true)
      }
    })()
    return () => {
      cancelled = true
    }
  }, [open])

  useEffect(() => {
    if (!open) {
      setSearchInput('')
      setAiSearchInput('')
      setDebouncedSearch('')
      setDebouncedAi('')
      setFilters({ ...INITIAL_FILTERS })
      setFilterOpen(false)
      setList([])
      setHasMore(false)
      setMultiPicked([])
      resetQuickCreateFields()
      setQuickSaving(false)
      setQuickAiError('')
      setQuickCreateDraft(null)
      setPlanningContextSummary(null)
      setPlanningTargetProfileSummary(null)
      setPlanningLlmRankApplied(false)
      setPlanningQueryIntentSummary(null)
      setPlanningIntentResolved(null)
      return
    }
    setFilters(mergeExerciseListPrefsFromApi(user?.exercise_list_prefs))
  }, [open, user?.exercise_list_prefs])

  const focusOptions = useMemo(
    () => catalogs.focusAreas.map((fa) => ({ id: fa.id, label: `${fa.icon || ''} ${fa.name || ''}`.trim() })),
    [catalogs.focusAreas]
  )
  const styleOptions = useMemo(
    () => catalogs.styleDirections.map((s) => ({ id: s.id, label: s.name || String(s.id) })),
    [catalogs.styleDirections]
  )
  const trainingTypeOptions = useMemo(
    () => catalogs.trainingTypes.map((t) => ({ id: t.id, label: t.name || String(t.id) })),
    [catalogs.trainingTypes]
  )
  const targetGroupOptions = useMemo(
    () => catalogs.targetGroups.map((g) => ({ id: g.id, label: g.name || String(g.id) })),
    [catalogs.targetGroups]
  )
  const visibilityOptions = useMemo(
    () => [
      { id: 'private', label: 'Privat' },
      { id: 'club', label: 'Verein' },
      { id: 'official', label: 'Offiziell' },
    ],
    []
  )
  const statusOptions = useMemo(
    () => [
      { id: 'draft', label: 'Entwurf' },
      { id: 'in_review', label: 'In Prüfung' },
      { id: 'approved', label: 'Freigegeben' },
      { id: 'archived', label: 'Archiviert' },
    ],
    []
  )

  const queryBase = useMemo(() => {
    const q = {}
    const n = (v) => (v === '' || v == null ? undefined : Number(v))
    const ids = (arr) =>
      Array.isArray(arr) && arr.length ? arr.map((x) => Number(x)).filter((x) => !Number.isNaN(x)) : undefined
    const fMn = splitMnCatalogRules(filters.focus_rules)
    if (fMn.includeIds.length) q.focus_area_must_include_ids = fMn.includeIds
    if (fMn.excludeIds.length) q.focus_area_must_exclude_ids = fMn.excludeIds
    if (filters.focus_only_without) q.focus_only_without_focus_areas = true

    const fa = ids(filters.focus_area_ids)
    if (fa?.length) q.focus_area_ids = fa

    const sdMn = splitMnCatalogRules(filters.style_direction_rules)
    if (sdMn.includeIds.length) q.style_direction_must_include_ids = sdMn.includeIds
    if (sdMn.excludeIds.length) q.style_direction_must_exclude_ids = sdMn.excludeIds
    const sdLegacy = ids(filters.style_direction_ids)
    if (sdLegacy?.length) q.style_direction_ids = sdLegacy

    const ttMn = splitMnCatalogRules(filters.training_type_rules)
    if (ttMn.includeIds.length) q.training_type_must_include_ids = ttMn.includeIds
    if (ttMn.excludeIds.length) q.training_type_must_exclude_ids = ttMn.excludeIds
    const ttLegacy = ids(filters.training_type_ids)
    if (ttLegacy?.length) q.training_type_ids = ttLegacy

    const tgMn = splitMnCatalogRules(filters.target_group_rules)
    if (tgMn.includeIds.length) q.target_group_must_include_ids = tgMn.includeIds
    if (tgMn.excludeIds.length) q.target_group_must_exclude_ids = tgMn.excludeIds
    const tgLegacy = ids(filters.target_group_ids)
    if (tgLegacy?.length) q.target_group_ids = tgLegacy

    const visMn = splitScalarCatalogRules(filters.visibility_rules)
    if (visMn.includeVals.length) q.visibility_any = visMn.includeVals
    if (visMn.excludeVals.length) q.visibility_exclude_any = visMn.excludeVals

    const stMn = splitScalarCatalogRules(filters.status_rules)
    if (stMn.includeVals.length) q.status_any = stMn.includeVals
    if (stMn.excludeVals.length) q.status_exclude_any = stMn.excludeVals

    const sk = ids(filters.skill_ids)
    if (sk?.length) q.skill_ids = sk
    if (filters.skill_min_level) q.skill_min_level = n(filters.skill_min_level)
    if (filters.skill_max_level) q.skill_max_level = n(filters.skill_max_level)
    if (filters.exclude_without_focus) q.exclude_without_focus = true
    if (filters.include_archived) q.include_archived = true
    if (debouncedSearch) q.search = debouncedSearch
    if (debouncedAi) q.ai_search = debouncedAi
    if (!debouncedSearch && debouncedAi) q.search = debouncedAi
    if (
      Array.isArray(exerciseKindAny) &&
      exerciseKindAny.length > 0
    ) {
      q.exercise_kind_any = exerciseKindAny
    }
    return q
  }, [filters, debouncedSearch, debouncedAi, exerciseKindAny])

  const reload = useCallback(async () => {
    if (!open || !catalogsReady) return
    setLoading(true)
    try {
      if (usePlanningSearch) {
        const query = effectivePickerQuery
        const res = await api.suggestPlanningExercises({
          unit_id: Number(planningContext.unitId),
          section_order_index:
            planningContext.sectionOrderIndex != null ? Number(planningContext.sectionOrderIndex) : null,
          phase_order_index:
            planningContext.phaseOrderIndex != null ? Number(planningContext.phaseOrderIndex) : null,
          parallel_stream_order_index:
            planningContext.parallelStreamOrderIndex != null
              ? Number(planningContext.parallelStreamOrderIndex)
              : null,
          anchor_exercise_id:
            planningContext.anchorExerciseId != null ? Number(planningContext.anchorExerciseId) : null,
          progression_graph_id:
            planningContext.progressionGraphId != null ? Number(planningContext.progressionGraphId) : null,
          planned_exercise_ids:
            Array.isArray(planningContext.plannedExerciseIds) && planningContext.plannedExerciseIds.length > 0
              ? planningContext.plannedExerciseIds.map((x) => Number(x)).filter((x) => Number.isFinite(x) && x > 0)
              : undefined,
          include_llm_intent: Boolean(query),
          include_llm_rank: true,
          query,
          intent_hint: planningContext.intentHint || null,
          limit: PAGE_SIZE,
          exercise_kind_any:
            Array.isArray(exerciseKindAny) && exerciseKindAny.length > 0 ? exerciseKindAny : undefined,
        })
        setPlanningContextSummary(res?.context_summary || null)
        setPlanningTargetProfileSummary(res?.target_profile_summary || null)
        setPlanningLlmRankApplied(Boolean(res?.llm_rank_applied))
        setPlanningQueryIntentSummary(res?.query_intent_summary || null)
        setPlanningIntentResolved(res?.intent_resolved || null)
        const hits = (Array.isArray(res?.hits) ? res.hits : []).map((h) => ({
          id: h.id,
          title: h.title,
          summary: h.summary,
          focus_area: h.focus_area,
          _planningScore: h.score,
          _planningReasons: Array.isArray(h.reasons) ? h.reasons : [],
          updated_at: new Date().toISOString(),
        }))
        setList(hits)
        setHasMore(false)
      } else {
        setPlanningContextSummary(null)
        setPlanningTargetProfileSummary(null)
        setPlanningLlmRankApplied(false)
        setPlanningQueryIntentSummary(null)
        setPlanningIntentResolved(null)
        const batch = await api.listExercises({
          ...queryBase,
          include_archived: true,
          include_variants: true,
          limit: PAGE_SIZE,
          offset: 0,
        })
        setList(Array.isArray(batch) ? batch : [])
        setHasMore(batch?.length === PAGE_SIZE)
      }
    } catch (e) {
      console.error(e)
      alert(e.message || 'Laden fehlgeschlagen')
      setList([])
      setHasMore(false)
      setPlanningContextSummary(null)
      setPlanningTargetProfileSummary(null)
      setPlanningLlmRankApplied(false)
      setPlanningQueryIntentSummary(null)
      setPlanningIntentResolved(null)
    } finally {
      setLoading(false)
    }
  }, [
    open,
    catalogsReady,
    queryBase,
    usePlanningSearch,
    planningContext,
    effectivePickerQuery,
    debouncedSearch,
    debouncedAi,
    exerciseKindAny,
  ])

  useEffect(() => {
    reload()
  }, [reload])

  const loadMore = async () => {
    if (!hasMore || loadingMore || loading) return
    const last = list[list.length - 1]
    if (!last?.id || last.updated_at == null) return
    setLoadingMore(true)
    try {
      const batch = await api.listExercises({
        ...queryBase,
        include_archived: true,
        include_variants: true,
        limit: PAGE_SIZE,
        cursor_updated_at:
          typeof last.updated_at === 'string'
            ? last.updated_at
            : new Date(last.updated_at).toISOString(),
        cursor_id: last.id,
      })
      setList((prev) => [...prev, ...(Array.isArray(batch) ? batch : [])])
      setHasMore(batch?.length === PAGE_SIZE)
    } catch (e) {
      console.error(e)
      alert(e.message || 'Mehr laden fehlgeschlagen')
    } finally {
      setLoadingMore(false)
    }
  }

  const rowVirtualizer = useVirtualizer({
    count: list.length,
    getScrollElement: () => pickerScrollRef.current,
    estimateSize: (index) => {
      const ex = list[index]
      const rc = ex?._planningReasons?.length || 0
      return rc > 0 ? 96 + Math.min(rc, 3) * 14 : 88
    },
    overscan: 8,
    getItemKey: (index) => String(list[index]?.id ?? index),
  })

  const resetFilters = () => setFilters({ ...INITIAL_FILTERS })

  const adoptExistingExercise = async (ex) => {
    if (!ex?.id) return
    if (multiSelect && typeof onSelectExercises === 'function') {
      await Promise.resolve(onSelectExercises([ex]))
    } else if (typeof onSelectExercise === 'function') {
      await Promise.resolve(onSelectExercise(ex))
    }
    onClose()
  }

  const runQuickCreateAiSuggest = async () => {
    const title = (quickTitle || '').trim()
    if (title.length < 3) {
      alert('Titel: mindestens 3 Zeichen.')
      return
    }
    const sketch = (quickSketch || '').trim()

    const focusId = parseInt(String(quickFocusAreaId).trim(), 10)
    if (!Number.isFinite(focusId) || focusId < 1) {
      alert('Bitte einen Fokusbereich wählen.')
      return
    }

    const focusRow = (catalogs.focusAreas || []).find((x) => Number(x.id) === focusId)
    const focusHint = (focusRow?.name || '').trim()

    setQuickAiError('')
    setQuickCreateDraft(null)
    setQuickSaving(true)
    try {
      const aiRes = await api.suggestExerciseAi({
        title,
        goal: sketch || undefined,
        execution: '',
        preparation: '',
        trainer_notes: '',
        focus_area_hint: focusHint || undefined,
        focus_areas_context: [{ focus_area_id: focusId, is_primary: true }],
        include_summary: true,
        include_skills: true,
        include_instructions: true,
      })

      const preview = buildQuickCreateAiPreview(aiRes, { sketchPlain: sketch })
      if (!preview.hasSummaryProposal && !preview.hasInstructionChoices && !preview.hasSkillChoices) {
        throw new Error('Die KI lieferte keinen verwertbaren Vorschlag.')
      }
      setQuickCreateDraft(
        aiPreviewToQuickCreateDraft(preview, { title, focusAreaId: focusId, sketchPlain: sketch }),
      )
    } catch (e) {
      console.error(e)
      const msg = e?.message || String(e)
      setQuickAiError(msg)
      alert(msg || 'KI-Vorschlag fehlgeschlagen')
    } finally {
      setQuickSaving(false)
    }
  }

  const applyQuickCreateDraft = async () => {
    if (!quickCreateDraft) return

    setQuickSaving(true)
    setQuickAiError('')
    try {
      const payload = buildQuickCreateExercisePayloadFromDraft(quickCreateDraft)
      const created = await api.createExercise(payload)
      if (!created?.id) throw new Error('Anlegen fehlgeschlagen')
      setQuickCreateDraft(null)
      await adoptExistingExercise(created)
    } catch (e) {
      console.error(e)
      const msg = e?.message || String(e)
      setQuickAiError(msg)
      alert(msg || 'Übung konnte nicht angelegt werden')
    } finally {
      setQuickSaving(false)
    }
  }

  if (!open) return null

  return (
    <div
      className="admin-modal-backdrop"
      role="presentation"
      onClick={(e) => {
        if (e.target === e.currentTarget) onClose()
      }}
    >
      <div
        className="admin-modal-sheet"
        role="dialog"
        aria-modal="true"
        style={{ maxWidth: '920px', width: '100%', maxHeight: '92vh', display: 'flex', flexDirection: 'column' }}
        onClick={(e) => e.stopPropagation()}
      >
        <div className="admin-modal-sheet__header">
          <h3 className="admin-modal-sheet__title">
            {multiSelect ? 'Übungen auswählen' : 'Übung auswählen'}
          </h3>
          <button type="button" className="btn btn-secondary admin-modal-sheet__close" onClick={onClose}>
            Schließen
          </button>
        </div>

        <div style={{ padding: '0 1rem 0.75rem', borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
          {usePlanningSearch && planningContextSummary ? (
            <div
              style={{
                marginBottom: '10px',
                padding: '8px 10px',
                borderRadius: '8px',
                background: 'var(--surface2)',
                border: '1px solid var(--border)',
                fontSize: '12px',
                color: 'var(--text2)',
                lineHeight: 1.45,
              }}
            >
              <strong style={{ color: 'var(--text1)', fontSize: '13px' }}>Planungskontext</strong>
              <div style={{ marginTop: '4px', display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                {planningContextSummary.group_name ? (
                  <span className="exercise-tag">{planningContextSummary.group_name}</span>
                ) : null}
                {planningContextSummary.unit_title ? (
                  <span className="exercise-tag">{planningContextSummary.unit_title}</span>
                ) : null}
                {planningContextSummary.section_title ? (
                  <span className="exercise-tag">{planningContextSummary.section_title}</span>
                ) : null}
                {planningContextSummary.planned_count != null ? (
                  <span className="exercise-tag">{planningContextSummary.planned_count} Übungen im Plan</span>
                ) : null}
                {planningContextSummary.anchor_title ? (
                  <span className="exercise-tag exercise-tag--accent">
                    Anker: {planningContextSummary.anchor_title}
                  </span>
                ) : null}
                {Array.isArray(planningTargetProfileSummary?.focus_areas) &&
                planningTargetProfileSummary.focus_areas.length > 0
                  ? planningTargetProfileSummary.focus_areas.map((fa) => (
                      <span key={fa} className="exercise-tag">
                        Fokus: {fa}
                      </span>
                    ))
                  : null}
                {Array.isArray(planningTargetProfileSummary?.top_skills) &&
                planningTargetProfileSummary.top_skills.length > 0
                  ? planningTargetProfileSummary.top_skills.slice(0, 3).map((sk) => (
                      <span key={sk.skill_id} className="exercise-tag">
                        {sk.name}
                      </span>
                    ))
                  : null}
              </div>
              {planningTargetProfileSummary?.has_skill_gap ? (
                <p style={{ margin: '6px 0 0', fontSize: '11px', color: 'var(--text3)' }}>
                  Skill-Lücke zum bisherigen Plan berücksichtigt
                </p>
              ) : null}
              {planningQueryIntentSummary?.rationale ? (
                <p style={{ margin: '6px 0 0', fontSize: '11px', color: 'var(--text2)' }}>
                  {planningQueryIntentSummary.rationale}
                </p>
              ) : null}
              {planningIntentResolved ? (
                <p style={{ margin: '6px 0 0', fontSize: '11px', color: 'var(--text3)' }}>
                  Modus: {planningIntentResolved.replace(/_/g, ' ')}
                  {planningQueryIntentSummary?.scenario
                    ? ` · ${String(planningQueryIntentSummary.scenario).replace(/_/g, ' ')}`
                    : null}
                  {planningLlmRankApplied ? ' · KI-Ranking aktiv' : null}
                  {planningQueryIntentSummary?.llm_applied ? ' · KI-Intent aktiv' : null}
                </p>
              ) : null}
            </div>
          ) : null}
          <div style={{ display: 'grid', gap: '0.65rem' }}>
            <div>
              <label className="form-label">
                {usePlanningSearch ? 'Planungs-Anfrage' : 'Volltextsuche'}
              </label>
              <input
                type="search"
                className="form-input"
                placeholder={
                  usePlanningSearch
                    ? 'z. B. Schlage mir die nächste Übung vor, Vertiefung, Reaktion mit Partner …'
                    : 'Stichwort, Titelfragment…'
                }
                value={searchInput}
                onChange={(e) => setSearchInput(e.target.value)}
                autoComplete="off"
              />
            </div>
            <div>
              <label className="form-label">
                {usePlanningSearch ? 'Planungs-Anfrage (Zusatz, optional)' : 'Semantisch / '}
                {!usePlanningSearch ? (
                  <span title="aktuell gleiche Datenbanksuche wie Volltext; später KI-Verfeinerung möglich">
                    KI-Feld
                  </span>
                ) : null}
              </label>
              <input
                type="search"
                className="form-input"
                placeholder={
                  usePlanningSearch
                    ? 'Alternative Formulierung — wird mit oben kombiniert'
                    : 'zweites Suchkonzept oder Umschreibung…'
                }
                value={aiSearchInput}
                onChange={(e) => setAiSearchInput(e.target.value)}
                autoComplete="off"
              />
              {usePlanningSearch ? (
                <p style={{ margin: '4px 0 0', fontSize: '11px', color: 'var(--text3)' }}>
                  Beide Felder bilden eine gemeinsame Planungs-Anfrage.
                </p>
              ) : null}
            </div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', alignItems: 'center' }}>
              <button type="button" className="btn btn-secondary" onClick={() => setFilterOpen(!filterOpen)}>
                {filterOpen ? 'Filter ausblenden' : 'Erweiterte Filter'}
              </button>
              <button type="button" className="btn" onClick={resetFilters}>
                Filter zurücksetzen
              </button>
              {loading && <span style={{ fontSize: '13px', color: 'var(--text2)' }}>Suche läuft…</span>}
            </div>

            {filterOpen && (
              <div style={{ marginTop: '0.35rem', fontSize: '13px', color: 'var(--text2)' }}>
                  <p style={{ margin: '0 0 12px 0' }}>
                  Felder gelten mit <strong>UND</strong>. Kataloge: mehrere „+“ = alle zutreffend; „−“ schließt aus.
                  Freigabelevel/Status: mehrere „+“ = eine davon (ODER); „−“ blendet aus.
                </p>
                <ExerciseFocusRulePicker
                  focusOptions={focusOptions}
                  focusRules={filters.focus_rules}
                  focusOnlyWithout={filters.focus_only_without}
                  legacyFocusAreaIds={filters.focus_area_ids}
                  onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
                />
                <div className="exercise-filters-modal-grid exercise-filters-modal-grid--catalog" style={{ marginTop: 12 }}>
                  <CatalogRulePicker
                    label="Stilrichtung"
                    hint="+ alle nötig (UND). − verbietet Zuordnung."
                    options={styleOptions}
                    rules={filters.style_direction_rules}
                    rulesFieldName="style_direction_rules"
                    placeholder="Stil …"
                    onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
                  />
                  <CatalogRulePicker
                    label="Trainingsstil"
                    hint="+ alle nötig (UND). − verbietet Zuordnung."
                    options={trainingTypeOptions}
                    rules={filters.training_type_rules}
                    rulesFieldName="training_type_rules"
                    placeholder="Trainingsstil …"
                    onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
                  />
                  <CatalogRulePicker
                    label="Zielgruppe"
                    hint="+ alle nötig (UND). − verbietet Zuordnung."
                    options={targetGroupOptions}
                    rules={filters.target_group_rules}
                    rulesFieldName="target_group_rules"
                    placeholder="Gruppe …"
                    onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
                  />
                </div>
                <div style={{ marginTop: 12 }}>
                  <label className="form-label">Fähigkeit</label>
                  <SkillTreeMultiSelect
                    value={filters.skill_ids}
                    onChange={(v) => setFilters((f) => ({ ...f, skill_ids: v }))}
                    skills={catalogs.skills}
                    placeholder="Fähigkeit …"
                  />
                  <div className="exercise-filter-skill-levels-row" style={{ marginTop: 8 }}>
                    <select
                      className="form-input exercise-filter-level-select"
                      title="von"
                      value={filters.skill_min_level}
                      onChange={(e) => setFilters((f) => ({ ...f, skill_min_level: e.target.value }))}
                    >
                      <option value="">Stufe von</option>
                      {LEVEL_FILTER_OPTS.map((o) => (
                        <option key={o.value} value={String(o.level)}>
                          {o.level}
                        </option>
                      ))}
                    </select>
                    <select
                      className="form-input exercise-filter-level-select"
                      title="bis"
                      value={filters.skill_max_level}
                      onChange={(e) => setFilters((f) => ({ ...f, skill_max_level: e.target.value }))}
                    >
                      <option value="">Stufe bis</option>
                      {LEVEL_FILTER_OPTS.map((o) => (
                        <option key={`m-${o.value}`} value={String(o.level)}>
                          {o.level}
                        </option>
                      ))}
                    </select>
                  </div>
                </div>
                <div className="exercise-filters-modal-grid exercise-filters-modal-grid--two exercise-filters-modal-grid--catalog" style={{ marginTop: 12 }}>
                  <CatalogRulePicker
                    label={EXERCISE_VISIBILITY_FIELD_LABEL}
                    options={visibilityOptions}
                    rules={filters.visibility_rules}
                    rulesFieldName="visibility_rules"
                    idKind="string"
                    placeholder="Freigabelevel …"
                    onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
                  />
                  <CatalogRulePicker
                    label="Status"
                    options={statusOptions}
                    rules={filters.status_rules}
                    rulesFieldName="status_rules"
                    idKind="string"
                    placeholder="Status …"
                    onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
                  />
                </div>
                <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 10 }}>
                  <label
                    style={{
                      display: 'flex',
                      alignItems: 'center',
                      gap: 8,
                      cursor: filters.focus_only_without ? 'not-allowed' : 'pointer',
                      opacity: filters.focus_only_without ? 0.55 : 1,
                    }}
                  >
                    <input
                      type="checkbox"
                      disabled={!!filters.focus_only_without}
                      checked={!!filters.exclude_without_focus}
                      onChange={(e) =>
                        setFilters((f) => ({
                          ...f,
                          exclude_without_focus: e.target.checked,
                          ...(e.target.checked ? { focus_only_without: false } : {}),
                        }))
                      }
                    />
                    <span>Ohne Fokus ausblenden</span>
                  </label>
                  <p style={{ margin: 0, fontSize: '12px', color: 'var(--text2)' }}>
                    Hinweis: Für die Planung werden archivierte Übungen bei der Suche immer mit eingeschlossen (bestehende
                    Zuordnungen).
                  </p>
                </div>
              </div>
            )}
          </div>
        </div>

        <div
          ref={pickerScrollRef}
          data-testid="exercise-picker-scroll"
          style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '12px 1rem' }}
        >
          {!catalogsReady || (loading && list.length === 0) ? (
            <div style={{ textAlign: 'center', padding: '2rem' }}>
              <div className="spinner" />
            </div>
          ) : list.length === 0 ? (
            showQuickCreateOffer ? (
              <ExerciseAiQuickCreateOffer
                searchLabel={effectivePickerQuery}
                title={quickTitle}
                onTitleChange={setQuickTitle}
                sketch={quickSketch}
                onSketchChange={setQuickSketch}
                focusAreaId={quickFocusAreaId}
                onFocusAreaChange={setQuickFocusAreaId}
                focusAreas={catalogs.focusAreas}
                catalogsReady={catalogsReady}
                busy={quickSaving}
                error={quickAiError}
                onRunAi={runQuickCreateAiSuggest}
              />
            ) : (
              <p style={{ color: 'var(--text2)', textAlign: 'center' }}>
                {usePlanningSearch
                  ? effectivePickerQuery
                    ? 'Keine KI-Vorschläge für diese Anfrage.'
                    : 'Keine Vorschläge — Einheit speichern und Planungskontext prüfen, oder Anfrage eingeben.'
                  : effectivePickerQuery.length >= 3
                    ? 'Keine Treffer.'
                    : 'Suchbegriff eingeben (mind. 3 Zeichen) …'}
              </p>
            )
          ) : (
            <>
              <p style={{ fontSize: '13px', color: 'var(--text2)', marginBottom: 10 }}>
                {usePlanningSearch ? `${list.length} KI-Vorschläge` : `${list.length} angezeigt`}
                {hasMore ? ' · weiter unten „Mehr laden“' : ''}
              </p>
              <div
                role="list"
                aria-label="Übungstreffer"
                style={{
                  position: 'relative',
                  width: '100%',
                  height: rowVirtualizer.getTotalSize(),
                }}
              >
                {rowVirtualizer.getVirtualItems().map((vi) => {
                  const ex = list[vi.index]
                  if (!ex) return null
                  const picked = multiPicked.some((p) => p.id === ex.id)
                  const rowInner = (
                    <>
                      <strong style={{ display: 'block' }}>{ex.title}</strong>
                      {(ex.summary || '').trim().length > 0 && (
                        <span style={{ fontSize: '12px', color: 'var(--text2)' }}>
                          {(ex.summary || '').length > 120
                            ? `${(ex.summary || '').slice(0, 120)}…`
                            : ex.summary}
                        </span>
                      )}
                      {ex.focus_area && (
                        <span className="exercise-tag exercise-tag--accent" style={{ marginTop: 6 }}>
                          {ex.focus_area}
                        </span>
                      )}
                      {(ex.exercise_kind || '').toLowerCase().trim() === 'combination' ? (
                        <span
                          className="exercise-tag"
                          style={{
                            marginTop: 6,
                            marginLeft: 6,
                            background: 'var(--accent-soft)',
                            color: 'var(--accent-dark)',
                          }}
                        >
                          Kombination
                        </span>
                      ) : null}
                      {Array.isArray(ex._planningReasons) && ex._planningReasons.length > 0 ? (
                        <ul
                          style={{
                            margin: '6px 0 0',
                            paddingLeft: '16px',
                            fontSize: '11px',
                            color: 'var(--accent-dark)',
                          }}
                        >
                          {ex._planningReasons.slice(0, 3).map((r) => (
                            <li key={r}>{r}</li>
                          ))}
                        </ul>
                      ) : null}
                    </>
                  )
                  return (
                    <div
                      key={vi.key}
                      role="listitem"
                      data-index={vi.index}
                      ref={rowVirtualizer.measureElement}
                      style={{
                        position: 'absolute',
                        top: 0,
                        left: 0,
                        width: '100%',
                        transform: `translateY(${vi.start}px)`,
                        paddingBottom: 8,
                      }}
                    >
                      {multiSelect ? (
                        <label
                          className="tu-ex-picker-multi-row"
                          style={{
                            display: 'flex',
                            gap: '10px',
                            alignItems: 'flex-start',
                            width: '100%',
                            textAlign: 'left',
                            padding: '10px 12px',
                            borderRadius: '8px',
                            border: picked ? '2px solid var(--accent)' : '1px solid var(--border)',
                            background: 'var(--surface2)',
                            cursor: 'pointer',
                            boxSizing: 'border-box',
                          }}
                        >
                          <input
                            type="checkbox"
                            checked={picked}
                            onChange={() => toggleMultiPick(ex)}
                            style={{ marginTop: '0.35rem', flexShrink: 0 }}
                            aria-label={ex.title ? `Auswahl: ${ex.title}` : 'Auswahl'}
                          />
                          <div style={{ flex: 1, minWidth: 0 }}>{rowInner}</div>
                        </label>
                      ) : (
                        <button
                          type="button"
                          onClick={() => {
                            onSelectExercise(ex)
                            onClose()
                          }}
                          style={{
                            width: '100%',
                            textAlign: 'left',
                            padding: '10px 12px',
                            borderRadius: '8px',
                            border: '1px solid var(--border)',
                            background: 'var(--surface2)',
                            cursor: 'pointer',
                          }}
                        >
                          {rowInner}
                        </button>
                      )}
                    </div>
                  )
                })}
              </div>
              {hasMore && (
                <div style={{ textAlign: 'center', marginTop: 12 }}>
                  <button type="button" className="btn btn-secondary" disabled={loadingMore} onClick={loadMore}>
                    {loadingMore ? 'Laden…' : 'Mehr laden'}
                  </button>
                </div>
              )}
              {multiSelect && typeof onSelectExercises === 'function' ? (
                <div
                  className="exercise-picker-multi-footer"
                  style={{
                    position: 'sticky',
                    bottom: 0,
                    marginTop: 16,
                    paddingTop: 12,
                    borderTop: '1px solid var(--border)',
                    background: 'var(--surface)',
                    display: 'flex',
                    flexWrap: 'wrap',
                    gap: '10px',
                    alignItems: 'center',
                    justifyContent: 'space-between',
                  }}
                >
                  <span style={{ fontSize: '0.92rem', color: 'var(--text2)' }}>
                    {multiPicked.length} ausgewählt
                  </span>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
                    <button
                      type="button"
                      className="btn btn-secondary"
                      onClick={() => setMultiPicked([])}
                      disabled={!multiPicked.length}
                    >
                      Auswahl leeren
                    </button>
                    <button
                      type="button"
                      className="btn btn-primary"
                      disabled={!multiPicked.length}
                      onClick={() => {
                        onSelectExercises([...multiPicked])
                        onClose()
                      }}
                    >
                      Übernehmen
                    </button>
                  </div>
                </div>
              ) : null}
            </>
          )}
        </div>
      </div>

      <ExerciseAiSuggestPreviewModal
        draft={quickCreateDraft}
        onDraftChange={setQuickCreateDraft}
        onDiscard={() => setQuickCreateDraft(null)}
        onApply={applyQuickCreateDraft}
        focusAreas={catalogs.focusAreas}
        skillsCatalog={catalogs.skills}
        dialogTitle="Neue Übung — KI-Entwurf bearbeiten"
        hint="Texte sind formatiert — passe Titel, Kurzfassung, Anleitung und Fähigkeiten an, dann speichern und übernehmen."
        applyLabel={quickSaving ? 'Wird angelegt…' : 'Anlegen und übernehmen'}
        applyDisabled={quickSaving}
        zIndex={2100}
      />
    </div>
  )
}
