/* ══════════════════════════════════════════════════════════════════
   AUGENSCHEIN V&L — React-App (Stufe 1)
   Portierung des HTML-Prototyps nach React mit Babel-Standalone.
   Visuell und funktional identisch zum Ausgangsmaterial.

   Struktur:
   1. Icons (wiederverwendbare SVG-Komponenten)
   2. Beispieldaten (3 Projekte als Konstante)
   3. UI-Bausteine (Pill, Card, ProgressRow, etc.)
   4. Views (Projektliste, Dashboard, Auftrag, Gutachten)
   5. Top-Level App mit zentralem State und Navigation
   6. Mount via ReactDOM
════════════════════════════════════════════════════════════════════ */

const { useState, useEffect, useCallback, useMemo, useRef, Fragment } = React;

// ══════════════════════════════════════════════════════════════════
// 1 · ICONS
// ══════════════════════════════════════════════════════════════════
const Icon = ({ path, size = 16, stroke = "currentColor", strokeWidth = 2, className = "" }) => (
  <svg className={`icon icon-${size} ${className}`} width={size} height={size}
       viewBox="0 0 24 24" fill="none" stroke={stroke} strokeWidth={strokeWidth}
       strokeLinecap="round" strokeLinejoin="round">
    {path}
  </svg>
);

const IconChevronLeft = (p) => <Icon {...p} path={<path d="M15 18l-6-6 6-6"/>} />;
const IconChevronRight = (p) => <Icon {...p} path={<path d="M9 18l6-6-6-6"/>} />;
const IconChevronDown = (p) => <Icon {...p} path={<path d="M6 9l6 6 6-6"/>} />;
const IconDocument = (p) => <Icon {...p} path={<><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></>} />;
const IconUpload = (p) => <Icon {...p} path={<><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></>} strokeWidth={1.5} />;
const IconExternalLink = (p) => <Icon {...p} path={<><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></>} strokeWidth={2} />;
const IconProvenance = (p) => <Icon {...p} path={<><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="15" x2="15" y2="15"/></>} strokeWidth={1.8} />;
const IconMic = (p) => <Icon {...p} path={<><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10v2a7 7 0 0 0 14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="8" y1="22" x2="16" y2="22"/></>} strokeWidth={2} />;
const IconRoute = (p) => <Icon {...p} path={<><circle cx="6" cy="19" r="3"/><path d="M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15"/><circle cx="18" cy="5" r="3"/></>} strokeWidth={2} />;
const IconCamera = (p) => <Icon {...p} path={<><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></>} strokeWidth={2} />;
const IconClose = (p) => <Icon {...p} path={<><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></>} strokeWidth={2} />;
const IconStop = (p) => <Icon {...p} path={<rect x="5" y="5" width="14" height="14" rx="2"/>} strokeWidth={2} />;
const IconEye = (p) => <Icon {...p} path={<><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></>} strokeWidth={2} />;

// ══════════════════════════════════════════════════════════════════
// 2 · DATA-LAYER (Supabase)
// Ersetzt die hartcodierten PROJEKTE durch echte DB-Queries.
//
// Strategie:
//   - Worker liefert unter /api/config die Supabase-Credentials
//   - Supabase-JS wird per <script> im HTML geladen (globale `supabase`-Lib)
//   - Adapter transformiert DB-Rows in das gleiche Datenobjekt-Format
//     wie früher die PROJEKTE-Konstante, damit die Komponenten unverändert
//     bleiben
// ══════════════════════════════════════════════════════════════════

// Worker-URL — zeigt in Dev auf lokalen Worker, in Prod auf Cloudflare
const WORKER_URL = window.location.hostname === 'custom.augenschein.app'
  ? 'https://api-custom.augenschein.app'
  : 'https://api-custom.augenschein.app'; // Dev: für jetzt auch Prod-Worker, später überschreibbar

// Supabase-Client wird nach Config-Fetch initialisiert
let supabaseClient = null;

async function initSupabase() {
  if (supabaseClient) return supabaseClient;

  const res = await fetch(`${WORKER_URL}/api/config`);
  if (!res.ok) throw new Error('Worker /api/config nicht erreichbar');
  const cfg = await res.json();

  // window.supabase kommt aus dem CDN-Script in vl-index.html
  if (!window.supabase) throw new Error('Supabase-JS nicht geladen (window.supabase fehlt)');

  supabaseClient = window.supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
    auth: { persistSession: true, autoRefreshToken: true },
  });

  return supabaseClient;
}

// ────────────────────────────────────────────────────────────────────
// Adapter: Supabase-Rows → Frontend-Datenobjekt
// Übersetzt das Datenbank-Schema ins Format, das die Komponenten erwarten.
// ────────────────────────────────────────────────────────────────────

function adaptAuftrag(row) {
  if (!row) return {};
  // Fallback-Kette für Auftragsnummer: akte > aktenzeichen > geschaeftszeichen
  const akteValue = row.akte || row.aktenzeichen || row.geschaeftszeichen || '';
  return {
    auftrag_id: row.id || null,
    akte: akteValue,
    auftragsart: auftragsartLabel(row.auftragsart),
    auftragsart_raw: row.auftragsart,  // 'gericht' | 'privat' | …  für UI-Branching
    auftragstyp: auftragstypLabel(row.auftragstyp),
    verfahren: row.verfahrensart || null,
    verfahrensart: row.verfahrensart || null,
    auftraggeber: row.auftraggeber || '',
    auftragseingang: formatDate(row.auftragseingang),
    fristIntern: formatDate(row.abgabe_intern),
    fristExtern: formatDate(row.abgabe_extern),
    fristRestDays: row.abgabe_extern ? daysUntil(row.abgabe_extern) : null,
    kostenvorschuss: row.kostenvorschuss ? Number(row.kostenvorschuss) : null,
    ausfertigungen: row.ausfertigungen || 1,
    gerichtstyp: row.gerichtstyp || null,
    sache: row.sache || null,
    wegen: row.wegen || null,
    zweck: row.zweck || null,
    belastungenAbt2: row.belastungen_abt_2 || null,
  };
}

function adaptBeteiligte(rows) {
  return (rows || []).map(r => ({
    id: r.id,
    rolle: r.rolle,
    name: r.name,
    detail: [r.anschrift, r.anwalt_name ? `RA: ${r.anwalt_name}` : null, r.aktenzeichen ? `Az. ${r.aktenzeichen}` : null]
      .filter(Boolean).join(' · '),
  }));
}

function adaptObjekt(row) {
  return {
    id: row.id,
    bezeichnung: row.bezeichnung,
    objekttyp: objekttypLabel(row.objekttyp),
    flur: row.flur || '',
    flurstueck: row.flurstueck || '',
    gemarkung: row.gemarkung || '',
    groesse: row.groesse_qm ? `${row.groesse_qm} m²` : null,
    wohnflaeche: row.wohnflaeche || '',
    bruchteilseigentum: row.bruchteilseigentum || null,
    mea: row.mea || null,
    baujahr: row.baujahr || '',
    abt_2_eintragungen: row.abt_2_eintragungen || null,
  };
}

function adaptGutachten(gRow, objekteRows) {
  const objekte = (objekteRows || [])
    .filter(o => o.gutachten_id === gRow.id)
    .sort((a, b) => a.sort_order - b.sort_order)
    .map(adaptObjekt);

  return {
    id: gRow.id,
    titel: gRow.titel || '',
    adresse: gRow.adresse || '',
    ortsterminStatus: gRow.ortstermin_status || 'geplant',
    entwurfPct: gRow.entwurf_pct || 0,
    unterlagenDone: 0,   // wird später aus dokumente berechnet
    unterlagenTotal: 0,  // wird später aus dokumente berechnet
    objekte,
  };
}

function adaptDokumente(rows) {
  return (rows || []).map(d => ({
    id: d.id,
    typ: dokumenttypLabel(d.typ),
    typ_raw: d.typ,
    scope: d.scope,
    status: d.extract_status === 'done' ? 'done' : 'fehlt',
    label: d.titel || '(unbenannt)',
    gutachten_id: d.gutachten_id || null,
    objekt_id: d.objekt_id || null,
  }));
}

function adaptProjekt(projRow, auftragRow, beteiligteRows, gutachtenRows, objekteRows, dokumenteRows) {
  const gutachten = (gutachtenRows || [])
    .sort((a, b) => a.sort_order - b.sort_order)
    .map(g => adaptGutachten(g, objekteRows));

  // Unterlagen-Zählung pro Gutachten nachtragen
  const dokByGutachten = {};
  (dokumenteRows || []).forEach(d => {
    if (d.gutachten_id) {
      if (!dokByGutachten[d.gutachten_id]) dokByGutachten[d.gutachten_id] = { done: 0, total: 0 };
      dokByGutachten[d.gutachten_id].total++;
      if (d.extract_status === 'done') dokByGutachten[d.gutachten_id].done++;
    }
  });
  gutachten.forEach(g => {
    const stats = dokByGutachten[g.id] || { done: 0, total: 0 };
    // Platzhalter: wenn noch keine Dokumente angelegt sind, zeige 0/12 als Richtwert
    g.unterlagenDone = stats.done;
    g.unterlagenTotal = stats.total > 0 ? stats.total : 12;
  });

  // Ortstermin-Aggregation für Dashboard
  const ortstermine = gutachten
    .map((g, i) => {
      const row = gutachtenRows.find(r => r.id === g.id);
      if (!row?.ortstermin_datum) return null;
      const ortDisplay = g.adresse.split(',').pop()?.trim() || '';
      const d = formatDate(row.ortstermin_datum);
      const t = row.ortstermin_uhrzeit ? row.ortstermin_uhrzeit.substring(0, 5) : '';
      return `${ortDisplay}: ${d}${t ? ', ' + t : ''}`;
    })
    .filter(Boolean);

  return {
    id: projRow.id,
    name: projRow.name,
    status: projRow.status === 'in_bearbeitung' ? 'in Bearbeitung' :
            projRow.status === 'offen' ? 'offen' :
            projRow.status === 'abgeschlossen' ? 'abgeschlossen' : projRow.status,
    ...adaptAuftrag(auftragRow),
    ortstermin: ortstermine.length > 0 ? ortstermine.join(' · ') : '—',
    beteiligte: adaptBeteiligte(beteiligteRows),
    gutachten,
    dokumente: adaptDokumente(dokumenteRows),
  };
}

// ────────────────────────────────────────────────────────────────────
// Label-Helpers (DB-Enums → Anzeigetexte)
// ────────────────────────────────────────────────────────────────────
function auftragsartLabel(v) {
  return { privat: 'Privatauftrag', gericht: 'Gerichtsauftrag', gutachterausschuss: 'Gutachterausschuss', notariat: 'Notarauftrag' }[v] || v || '';
}
function auftragstypLabel(v) {
  return { verkehrswert: 'Verkehrswertgutachten', miete: 'Mietwertgutachten', minderwert: 'Minderwertgutachten', beleihung: 'Beleihungswertgutachten', marktwert: 'Marktwertindikation', ueberwachung: 'Überwachungsgutachten' }[v] || v || '';
}
function objekttypLabel(v) {
  return { efh: 'EFH', etw: 'Eigentumswohnung', mfh: 'MFH', dhh: 'DHH/Reihenhaus', gewerbe: 'Gewerbe', grundstueck: 'Grundstück', stellplatz: 'Stellplatz', keller: 'Kellerraum' }[v] || v || '';
}
function dokumenttypLabel(v) {
  const m = {
    gerichtsbeschluss: 'Gerichtsbeschluss',
    anschreiben: 'Anschreiben',
    auftragsanfrage: 'Auftragsanfrage',
    grundbuchauszug: 'Grundbuchauszug',
    bauplan: 'Baupläne',
    teilungserklaerung: 'Teilungserklärung',
    bebauungsplan: 'Bebauungsplan',
    lagekarte: 'Lagekarte',
    energieausweis: 'Energieausweis',
    mietvertrag: 'Mietvertrag',
  };
  return m[v] || v || '';
}
function formatDate(iso) {
  if (!iso) return '';
  const [y, m, d] = iso.split('-');
  return `${d}.${m}.${y}`;
}
function daysUntil(iso) {
  if (!iso) return null;
  const target = new Date(iso);
  const now = new Date();
  return Math.ceil((target - now) / 86400000);
}

// ────────────────────────────────────────────────────────────────────
// Daten-Loader
// ────────────────────────────────────────────────────────────────────
async function ladeProjektliste() {
  const sb = await initSupabase();
  const { data, error } = await sb
    .from('projects')
    .select(`
      id, name, status, created_at,
      auftraege ( akte, auftragsart, auftragstyp, verfahrensart, auftraggeber, abgabe_extern, abgabe_intern ),
      gutachten ( id, adresse, sort_order, bewertungsobjekte ( id ) )
    `)
    .is('deleted_at', null)
    .order('created_at', { ascending: false });

  if (error) throw error;

  return data.map(p => {
    const auftrag = p.auftraege?.[0] || {};
    const gutachtenCount = (p.gutachten || []).length;
    const objektCount = (p.gutachten || []).reduce((s, g) => s + (g.bewertungsobjekte?.length || 0), 0);

    const standort = gutachtenCount === 0
      ? ''
      : gutachtenCount === 1
        ? (p.gutachten[0].adresse?.split(',').pop().trim() || '')
        : p.gutachten.map(g => g.adresse?.split(',').pop().trim() || '').join(' + ');

    const fristRest = auftrag.abgabe_extern ? daysUntil(auftrag.abgabe_extern) : null;

    return {
      id: p.id,
      name: p.name,
      akte: auftrag.akte || '',
      auftragsart: auftragsartLabel(auftrag.auftragsart),
      auftragstyp: auftragstypLabel(auftrag.auftragstyp),
      verfahren: auftrag.verfahrensart,
      auftraggeber: auftrag.auftraggeber || '',
      standort,
      gutachtenCount,
      objektCount,
      abgabeExtern: auftrag.abgabe_extern || null,
      abgabeIntern: auftrag.abgabe_intern || null,
      fristRestDays: fristRest,
      status: p.status,
      createdAt: p.created_at,
    };
  });
}

// ────────────────────────────────────────────────────────────────────
// createProjekt — legt Projekt + Auftrag + Gutachten + Bewertungsobjekt an
// Parameter:
//   name: string (Pflicht, z.B. "Schmidt, Familie")
//   auftragsart: 'privat' | 'gericht' | 'gutachterausschuss' | 'notariat'
//   auftragstyp: 'verkehrswert' | 'miete' | …
//   optional: auftraggeber, akte, verfahrensart, abgabe_extern, notizen
// ────────────────────────────────────────────────────────────────────
async function createProjekt({
  name,
  auftragsart = 'privat',
  auftragstyp = 'verkehrswert',
  auftraggeber = '',
  akte = '',
  verfahrensart = null,
  abgabeExtern = null,
  abgabeIntern = null,
  ortsterminDatum = null,
  erstesGutachtenAdresse = '',
}) {
  if (!name || !name.trim()) {
    throw new Error('Projektname ist Pflicht');
  }
  const sb = await initSupabase();

  // 1) organization_id des aktuellen Users holen (wird für RLS gebraucht)
  const { data: { user }, error: userErr } = await sb.auth.getUser();
  if (userErr || !user) throw new Error('Nicht angemeldet');

  const { data: profile, error: profErr } = await sb
    .from('user_profiles')
    .select('organization_id, id')
    .eq('id', user.id)
    .single();
  if (profErr || !profile) throw new Error('User-Profil nicht gefunden');

  // 2) Projekt anlegen
  const { data: newProjekt, error: projErr } = await sb
    .from('projects')
    .insert({
      organization_id: profile.organization_id,
      name: name.trim(),
      status: 'offen',
    })
    .select()
    .single();
  if (projErr) throw new Error('Projekt anlegen fehlgeschlagen: ' + projErr.message);

  const projektId = newProjekt.id;

  try {
    // 3) Auftrag anlegen (1:1 zum Projekt)
    const auftragRow = {
      project_id: projektId,
      auftragsart,
      auftragstyp,
      auftraggeber: auftraggeber?.trim() || null,
      akte: akte?.trim() || null,
      verfahrensart: verfahrensart || null,
      abgabe_extern: abgabeExtern || null,
      abgabe_intern: abgabeIntern || null,
      auftragseingang: new Date().toISOString().split('T')[0],
      ausfertigungen: 1,
    };
    const { error: aufErr } = await sb.from('auftraege').insert(auftragRow);
    if (aufErr) throw new Error('Auftrag anlegen fehlgeschlagen: ' + aufErr.message);

    // 4) Erstes Gutachten anlegen (optional mit Adresse)
    const { data: newGut, error: gutErr } = await sb
      .from('gutachten')
      .insert({
        project_id: projektId,
        titel: `${name.trim()} — ${auftragstypLabel(auftragstyp)}`,
        adresse: erstesGutachtenAdresse?.trim() || null,
        sort_order: 0,
        ortstermin_datum: ortsterminDatum || null,
      })
      .select()
      .single();
    if (gutErr) throw new Error('Gutachten anlegen fehlgeschlagen: ' + gutErr.message);

    // 5) Erstes Bewertungsobjekt anlegen (leer, kann später ausgefüllt werden)
    // Hinweis: bewertungsobjekte hat KEIN project_id — die Verbindung läuft über gutachten_id.
    const { error: objErr } = await sb.from('bewertungsobjekte').insert({
      gutachten_id: newGut.id,
      bezeichnung: 'Objekt 1',
      sort_order: 0,
    });
    if (objErr) throw new Error('Bewertungsobjekt anlegen fehlgeschlagen: ' + objErr.message);

    return projektId;
  } catch (err) {
    // Bei Fehler: Projekt wieder löschen (Cascade räumt auch abhängige Zeilen auf)
    try { await sb.from('projects').delete().eq('id', projektId); } catch {}
    throw err;
  }
}

async function ladeProjektDetail(projektId) {
  const sb = await initSupabase();

  // Parallel laden
  const [projRes, auftragRes, beteiligteRes, gutachtenRes, dokumenteRes] = await Promise.all([
    sb.from('projects').select('*').eq('id', projektId).single(),
    sb.from('auftraege').select('*').eq('project_id', projektId).maybeSingle(),
    sb.from('beteiligte').select('*').eq('project_id', projektId).order('sort_order'),
    sb.from('gutachten').select('*').eq('project_id', projektId).order('sort_order'),
    sb.from('dokumente').select('*').eq('project_id', projektId).order('sort_order'),
  ]);

  if (projRes.error) throw projRes.error;
  if (gutachtenRes.error) throw gutachtenRes.error;

  // Bewertungsobjekte aller Gutachten dieses Projekts
  const gutachtenIds = (gutachtenRes.data || []).map(g => g.id);
  let objekteRows = [];
  if (gutachtenIds.length > 0) {
    const { data, error } = await sb
      .from('bewertungsobjekte')
      .select('*')
      .in('gutachten_id', gutachtenIds)
      .order('sort_order');
    if (error) throw error;
    objekteRows = data || [];
  }

  return adaptProjekt(
    projRes.data,
    auftragRes.data,
    beteiligteRes.data,
    gutachtenRes.data,
    objekteRows,
    dokumenteRes.data
  );
}

// ────────────────────────────────────────────────────────────────────
// Feld-Herkunft laden (Welcher Wert kommt aus welchem Dokument?)
// Liefert eine Map: "tabelle:rowId:feld" → { dokument_id, titel, typ, applied_at }
// ────────────────────────────────────────────────────────────────────
async function ladeHerkunft(projektId, session, workerUrl) {
  if (!projektId || !session?.access_token) return {};
  try {
    const res = await fetch(
      `${workerUrl}/api/feld-herkunft?project_id=${encodeURIComponent(projektId)}`,
      { headers: { Authorization: `Bearer ${session.access_token}` } }
    );
    if (!res.ok) return {};
    const data = await res.json();
    const map = {};
    for (const h of (data.herkunft || [])) {
      const key = `${h.tabelle}:${h.row_id}:${h.feld}`;
      map[key] = {
        dokument_id: h.dokument_id,
        titel: h.dokumente?.titel || 'Dokument',
        typ: h.dokumente?.typ || '',
        applied_at: h.applied_at,
      };
    }
    return map;
  } catch (e) {
    console.warn('Herkunft-Load failed:', e);
    return {};
  }
}

// ────────────────────────────────────────────────────────────────────
// Dokument-Ansicht: Signed-URL holen und in neuem Tab öffnen
// ────────────────────────────────────────────────────────────────────
async function oeffneDokument(dokumentId, session, workerUrl) {
  if (!dokumentId || !session?.access_token) return;
  try {
    const res = await fetch(
      `${workerUrl}/api/document-url?id=${encodeURIComponent(dokumentId)}`,
      { headers: { Authorization: `Bearer ${session.access_token}` } }
    );
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      alert('Dokument konnte nicht geöffnet werden: ' + (err.error || res.statusText));
      return;
    }
    const data = await res.json();
    if (data.url) {
      window.open(data.url, '_blank', 'noopener,noreferrer');
    }
  } catch (e) {
    alert('Fehler beim Öffnen: ' + (e.message || e));
  }
}

// Wie oeffneDokument, aber gibt die URL zurück statt Tab zu öffnen.
// Für eingebetteten PDF-Viewer (iframe).
async function ladeDokumentUrl(dokumentId, session, workerUrl) {
  if (!dokumentId || !session?.access_token) return null;
  try {
    const res = await fetch(
      `${workerUrl}/api/document-url?id=${encodeURIComponent(dokumentId)}`,
      { headers: { Authorization: `Bearer ${session.access_token}` } }
    );
    if (!res.ok) return null;
    const data = await res.json();
    return data.url || null;
  } catch {
    return null;
  }
}

// ────────────────────────────────────────────────────────────────────
// Notes-Datenlader (Ortstermin-Notizen)
// Supabase-RLS schützt automatisch auf Organisations-Ebene
// ────────────────────────────────────────────────────────────────────
async function ladeNotes(gutachtenId) {
  if (!gutachtenId) return [];
  const sb = await initSupabase();
  const { data, error } = await sb
    .from('notes')
    .select('*')
    .eq('gutachten_id', gutachtenId)
    .order('sort_order', { ascending: true })
    .order('created_at', { ascending: true });
  if (error) {
    console.warn('Notes load failed:', error.message);
    return [];
  }
  return data || [];
}

async function insertNote(noteRow) {
  const sb = await initSupabase();
  const { data, error } = await sb
    .from('notes')
    .insert(noteRow)
    .select()
    .single();
  if (error) throw error;
  return data;
}

async function updateNote(id, patch) {
  const sb = await initSupabase();
  const { data, error } = await sb
    .from('notes')
    .update(patch)
    .eq('id', id)
    .select()
    .single();
  if (error) throw error;
  return data;
}

async function deleteNote(id) {
  const sb = await initSupabase();
  const { error } = await sb.from('notes').delete().eq('id', id);
  if (error) throw error;
}

async function insertNotesBulk(rows) {
  if (!rows || rows.length === 0) return [];
  const sb = await initSupabase();
  const { data, error } = await sb.from('notes').insert(rows).select();
  if (error) throw error;
  return data || [];
}

// ────────────────────────────────────────────────────────────────────
// Foto-Upload: Blob → Worker → storage_path → öffentliche URL via signed-url
// ────────────────────────────────────────────────────────────────────
async function uploadPhotoBlob(blob, projektId, gutachtenId, session, workerUrl) {
  const formData = new FormData();
  formData.append('file', blob, `photo-${Date.now()}.jpg`);
  formData.append('project_id', projektId);
  formData.append('gutachten_id', gutachtenId);
  const res = await fetch(`${workerUrl}/api/photo-upload`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${session.access_token}` },
    body: formData,
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.error || `Upload fehlgeschlagen: ${res.status}`);
  }
  return await res.json();  // { photo_id, storage_path, size }
}

// Alle möglichen Beteiligten-Rollen für den "+ Hinzufügen"-Menü
const BETEILIGTE_ROLLEN_VORSCHLAEGE = [
  'Gericht', 'Richter', 'Rechtspfleger',
  'betreibender Gläubiger', 'Gläubiger', 'RA Gläubiger',
  'Schuldner', 'RA Schuldner',
  'Antragsteller', 'Antragsgegner', 'RA Antragsteller', 'RA Antragsgegner',
  'Kläger', 'Beklagter', 'RA Kläger', 'RA Beklagter',
  'Notar', 'Zwangsverwalter', 'Insolvenzverwalter',
  'Käufer', 'Mieter', 'Streithelfer', 'Sonstiger Beteiligter',
];

// ══════════════════════════════════════════════════════════════════
// 2.5 · ORTSTERMIN-CORE (Voice-Recording, Rundgang, Kapitel)
// Portiert aus der Augenschein-Ortstermin-App für V&L-Immobilien
// ══════════════════════════════════════════════════════════════════

// ────────────────────────────────────────────────────────────────────
// V&L-Kapitel-Tags (Immobilien-VWG-Struktur)
// Unterschied zur Alt-App: spezifisch für Verkehrswertgutachten
// ────────────────────────────────────────────────────────────────────
const VL_KAPITEL_TAGS = [
  { id: 'aussen', label: 'Außen / Grundstück', rooms: ['*'] },
  { id: 'grundstueck', label: 'Grundstück', rooms: ['*'] },
  { id: 'gebaeude', label: 'Gebäude (außen)', rooms: ['*'] },
  { id: 'dach', label: 'Dach', rooms: ['*'] },
  { id: 'keller', label: 'Kellergeschoss', rooms: ['keller'] },
  { id: 'eg', label: 'Erdgeschoss', rooms: ['eg'] },
  { id: 'og1', label: '1. Obergeschoss', rooms: ['1og'] },
  { id: 'og2', label: '2. Obergeschoss', rooms: ['2og'] },
  { id: 'dg', label: 'Dachgeschoss', rooms: ['dg'] },
  { id: 'garage', label: 'Garage / Stellplatz', rooms: ['*'] },
  { id: 'technik_heizung', label: 'Technik — Heizung', rooms: ['keller', 'eg'] },
  { id: 'technik_elektro', label: 'Technik — Elektro', rooms: ['*'] },
  { id: 'technik_wasser', label: 'Technik — Wasser/Sanitär', rooms: ['*'] },
  { id: 'maengel', label: 'Mängel / Schäden', rooms: ['*'] },
  { id: 'wohnwert', label: 'Wohnwertprüfung', rooms: ['*'] },
];

// ────────────────────────────────────────────────────────────────────
// Signalphrasen für Live-Raum-Erkennung aus Web-Speech-Text
// ────────────────────────────────────────────────────────────────────
const RUNDGANG_SEGMENT_PATTERNS = [
  { regex: /\bich beginne (jetzt )?(im|mit dem|in der|auf dem) ([a-zäöü]+geschoss|keller|dachboden|dach|grundstück|außen)/i,
    priority: 100, captureGroup: 3 },
  { regex: /\b(kurz )?zum (gebäude|grundstück|außenbereich|dach)/i,
    priority: 95, captureGroup: 2 },
  { regex: /\b(wir|ich) befinden? (uns|mich) (jetzt |nun )?(im|in der|auf dem) ([a-zäöü]+)/i,
    priority: 90, captureGroup: 5 },
  { regex: /\b(dann )?geht es (jetzt |nun |weiter )?(in den|in die|ins|auf den) ([a-zäöü]+)/i,
    priority: 80, captureGroup: 4 },
  { regex: /\bhier (sind|haben) wir (den|die|das) ([a-zäöü]+)/i,
    priority: 70, captureGroup: 3 },
  { regex: /\b(kellergeschoss|kellerflur|kellerraum)\b/i, priority: 50, targetId: "keller" },
  { regex: /\b(erdgeschoss|eg[\s.,])\b/i, priority: 50, targetId: "eg" },
  { regex: /\b(1\.\s*og|erstes obergeschoss|erster stock)\b/i, priority: 50, targetId: "og1" },
  { regex: /\b(2\.\s*og|zweites obergeschoss)\b/i, priority: 50, targetId: "og2" },
  { regex: /\b(dachgeschoss|dachboden|spitzboden)\b/i, priority: 50, targetId: "dg" },
  { regex: /\b(auf dem dach|dachfläche|dacheindeckung)\b/i, priority: 50, targetId: "dach" },
  { regex: /\b(außenbereich|fassade|einfriedigung|grundstück|garten|zufahrt)\b/i, priority: 45, targetId: "aussen" },
  { regex: /\b(garage|carport|stellplatz)\b/i, priority: 50, targetId: "garage" },
];

const RUNDGANG_KEYWORD_TO_TAG = {
  "keller": "keller", "kellergeschoss": "keller", "kellerflur": "keller", "kellerraum": "keller", "untergeschoss": "keller",
  "erdgeschoss": "eg", "eg": "eg", "parterre": "eg",
  "obergeschoss": "og1", "og1": "og1", "1og": "og1", "erster": "og1", "ersten": "og1",
  "og2": "og2", "2og": "og2", "zweiter": "og2", "zweiten": "og2",
  "dachgeschoss": "dg", "dachboden": "dg", "spitzboden": "dg", "dg": "dg",
  "dach": "dach", "dachfläche": "dach", "dacheindeckung": "dach",
  "außenbereich": "aussen", "fassade": "aussen", "grundstück": "aussen", "garten": "aussen",
  "zufahrt": "aussen", "hauseingang": "aussen", "einfriedigung": "aussen",
  "garage": "garage", "carport": "garage", "stellplatz": "garage",
  "gebäude": "gebaeude",
};

const RUNDGANG_MARKER_PATTERNS = [
  /\bdas muss ich noch (schauen|prüfen|klären|verifizieren|nachsehen)/i,
  /\bden rest kann ich (dann )?erst/i,
  /\b(noch zu |muss noch )(klären|prüfen|ergänzen)/i,
  /\bdas ergänze ich\b/i,
  /\bhier muss ich nochmal/i,
  /\b(offener? punkt|offen)\b/i,
];

function detectSegmentBoundary(text, availableTags) {
  if (!text || text.length < 8) return null;
  const validTagIds = new Set(availableTags.map(t => t.id));
  const sorted = [...RUNDGANG_SEGMENT_PATTERNS].sort((a, b) => b.priority - a.priority);
  for (const pattern of sorted) {
    const match = pattern.regex.exec(text);
    if (!match) continue;
    let tagId = pattern.targetId;
    if (!tagId && pattern.captureGroup > 0) {
      const captured = (match[pattern.captureGroup] || "").toLowerCase().trim();
      tagId = RUNDGANG_KEYWORD_TO_TAG[captured];
      if (!tagId) {
        for (const [keyword, id] of Object.entries(RUNDGANG_KEYWORD_TO_TAG)) {
          if (captured.startsWith(keyword) || keyword.startsWith(captured)) {
            tagId = id;
            break;
          }
        }
      }
    }
    if (tagId && validTagIds.has(tagId)) {
      return { tagId, confidence: pattern.priority / 100, matchedPhrase: match[0] };
    }
  }
  return null;
}

function detectMarkerTrigger(text) {
  if (!text) return null;
  for (const regex of RUNDGANG_MARKER_PATTERNS) {
    const match = regex.exec(text);
    if (match) return { type: "auto_todo", phrase: match[0] };
  }
  return null;
}

// ────────────────────────────────────────────────────────────────────
// Objekt-Wechsel-Detection (V&L-spezifisch)
// Erkennt aus dem Live-Text, ob der Gutachter zu einem anderen
// Bewertungsobjekt wechselt. Beispiel: "jetzt zum Stellplatz" bei
// Müller-Gutachten mit ETW + Stellplatz + Keller.
// ────────────────────────────────────────────────────────────────────
function detectObjektSwitch(text, availableObjekte) {
  if (!text || text.length < 8 || !Array.isArray(availableObjekte) || availableObjekte.length < 2) {
    return null;
  }
  const lc = text.toLowerCase();
  // Normalize für Vergleich: Stellplatz, Keller, ETW, etc.
  for (const obj of availableObjekte) {
    const bezeichnung = (obj.bezeichnung || '').toLowerCase();
    if (!bezeichnung) continue;
    // Trigger-Phrases: "jetzt zum X", "nun beim X", "kommen zum X"
    const regex = new RegExp(
      `\\b(jetzt|nun|weiter|kommen|gehe|gehen|wechsle|wir sind) (zum|zur|ins|in den|in die|beim|bei der|bei dem) ${bezeichnung.split(/\s+/)[0]}`,
      'i'
    );
    if (regex.test(lc)) {
      return { objektId: obj.id, bezeichnung: obj.bezeichnung };
    }
    // Auch kurze Form: Objekttyp steht selbst im Text
    if (obj.objekttyp) {
      const typ = (obj.objekttyp || '').toLowerCase();
      if (typ && lc.includes(typ) && /\b(jetzt|nun|beim|im|bei der)\b/i.test(lc)) {
        return { objektId: obj.id, bezeichnung: obj.bezeichnung };
      }
    }
  }
  return null;
}

// ────────────────────────────────────────────────────────────────────
// dedupeAtOverlap — entfernt Overlap zwischen aufeinanderfolgenden
// Audio-Chunks (gpt-4o-transcribe transkribiert Chunk-Überlappung)
// ────────────────────────────────────────────────────────────────────
function dedupeAtOverlap(prevText, nextText) {
  if (!prevText || !nextText) return nextText || "";
  const norm = (s) => s.toLowerCase().replace(/\s+/g, " ").replace(/[^a-zäöüß0-9 ]/g, "");
  const prevTail = norm(prevText.slice(-160));
  const nextHead = norm(nextText.slice(0, 400));
  if (!prevTail || !nextHead) return nextText;

  // Suche längste Suffix-Prefix-Übereinstimmung
  let bestMatchLen = 0;
  const minMatch = 15;
  for (let len = Math.min(prevTail.length, nextHead.length); len >= minMatch; len--) {
    const suffix = prevTail.slice(-len);
    if (nextHead.startsWith(suffix)) {
      bestMatchLen = len;
      break;
    }
  }
  if (bestMatchLen < minMatch) return nextText;

  // Finde die entsprechende Position im unnormalisierten nextText
  let normChars = 0;
  let cutPos = 0;
  for (let i = 0; i < nextText.length; i++) {
    const c = nextText[i].toLowerCase();
    if (/[a-zäöüß0-9 ]/.test(c) || /\s/.test(nextText[i])) normChars++;
    if (normChars >= bestMatchLen) {
      cutPos = i + 1;
      break;
    }
  }
  return nextText.slice(cutPos).trimStart();
}

// ────────────────────────────────────────────────────────────────────
// useLocalWhisper — Offline-Fallback via transformers.js (Stub für jetzt)
// In Stufe 4 initial nicht aktiviert, kommt mit Offline-Support später.
// ────────────────────────────────────────────────────────────────────
function useLocalWhisper() {
  return {
    isReady: false,
    isLoading: false,
    transcribe: async () => { throw new Error('Local Whisper nicht aktiviert'); },
  };
}

// ────────────────────────────────────────────────────────────────────
// useVoice — MediaRecorder + SpeechRecognition Dual-Channel
// Live-Transkript über SpeechRecognition, Audio-Blob für spätere
// Whisper-Korrektur via Worker. Portiert aus Augenschein-Alt-App.
// ────────────────────────────────────────────────────────────────────
async function requestWakeLock() {
  try {
    if ('wakeLock' in navigator) {
      return await navigator.wakeLock.request('screen');
    }
  } catch (e) {
    console.warn('[WakeLock] failed:', e.message);
  }
  return null;
}

function useVoice(localWhisper, getTranscribeContext, workerUrl, session) {
  const [isRec, setIsRec] = useState(false);
  const [text, setText] = useState('');
  const [interim, setInterim] = useState('');
  const [isTranscribing, setIsTranscribing] = useState(false);
  const [previewLive, setPreviewLive] = useState(false);
  const [micError, setMicError] = useState(null);
  const [correctionInfo, setCorrectionInfo] = useState(null);

  const ref = useRef(null);
  const wl = useRef(null);
  const mediaRef = useRef(null);
  const chunksRef = useRef([]);
  const stoppedByUser = useRef(false);
  const accumulatedText = useRef('');
  const restartCount = useRef(0);
  const lastAudioIdRef = useRef(null);
  const getContextRef = useRef(getTranscribeContext);
  getContextRef.current = getTranscribeContext;

  const startSpeechRecognition = useCallback(() => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) return;
    try { ref.current?.abort(); } catch (e) {}
    const r = new SR();
    r.lang = 'de-DE';
    r.continuous = true;
    r.interimResults = true;
    r.onresult = (e) => {
      let f = '', i = '';
      for (let x = e.resultIndex; x < e.results.length; x++) {
        if (e.results[x].isFinal) f += e.results[x][0].transcript + ' ';
        else i += e.results[x][0].transcript;
      }
      if (f) accumulatedText.current = (accumulatedText.current + ' ' + f).trim();
      setText(accumulatedText.current);
      setInterim(i);
    };
    r.onerror = (e) => {
      if (e.error === 'no-speech' || e.error === 'aborted') return;
      setPreviewLive(false);
    };
    r.onend = () => {
      setInterim('');
      setPreviewLive(false);
      if (!stoppedByUser.current && restartCount.current < 50) {
        restartCount.current++;
        setTimeout(() => {
          if (!stoppedByUser.current) startSpeechRecognition();
        }, 300);
      }
    };
    ref.current = r;
    try { r.start(); setPreviewLive(true); } catch (e) { setPreviewLive(false); }
  }, []);

  const start = useCallback(async () => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) setMicError('browser');

    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      setMicError(null);
      stoppedByUser.current = false;
      accumulatedText.current = '';
      restartCount.current = 0;
      lastAudioIdRef.current = null;
      wl.current = await requestWakeLock();

      const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus'
        : MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm'
        : MediaRecorder.isTypeSupported('audio/mp4;codecs=aac') ? 'audio/mp4;codecs=aac'
        : MediaRecorder.isTypeSupported('audio/mp4') ? 'audio/mp4'
        : undefined;
      const mr = new MediaRecorder(stream, mimeType ? { mimeType } : {});
      chunksRef.current = [];
      mr.ondataavailable = e => { if (e.data.size > 0) chunksRef.current.push(e.data); };
      mr.start();
      mediaRef.current = { recorder: mr, stream, mimeType };

      setText('');
      setInterim('');
      setIsRec(true);
      if (SR) startSpeechRecognition();
    } catch (e) {
      console.error('[Voice] getUserMedia failed:', e);
      setMicError(e.name === 'NotAllowedError' ? 'permission' : 'unavailable');
    }
  }, [startSpeechRecognition]);

  const stop = useCallback(async () => {
    stoppedByUser.current = true;
    setIsRec(false);

    try { ref.current?.stop(); } catch (e) {}
    try { if (wl.current) { wl.current.release(); wl.current = null; } } catch {}

    const mediaContext = mediaRef.current;
    if (!mediaContext) return { text: accumulatedText.current, audioBlob: null };

    const { recorder, stream, mimeType } = mediaContext;

    // Recorder stoppen und auf letzten Chunk warten
    const audioBlob = await new Promise((resolve) => {
      recorder.onstop = () => {
        try { stream.getTracks().forEach(t => t.stop()); } catch {}
        const blob = new Blob(chunksRef.current, { type: mimeType || 'audio/webm' });
        resolve(blob);
      };
      try { recorder.stop(); } catch { resolve(null); }
    });

    mediaRef.current = null;

    // Whisper-Upload (wenn Worker-URL + Session + Audio vorhanden)
    if (audioBlob && audioBlob.size > 1024 && workerUrl && session?.access_token) {
      setIsTranscribing(true);
      try {
        const ext = (mimeType || '').includes('webm') ? 'webm' : 'mp4';
        const filename = `voice-${Date.now()}.${ext}`;
        const formData = new FormData();
        formData.append('audio', audioBlob, filename);
        const ctx = getContextRef.current ? getContextRef.current() : {};
        formData.append('context', JSON.stringify(ctx));

        const res = await fetch(`${workerUrl}/api/transcribe`, {
          method: 'POST',
          headers: { Authorization: `Bearer ${session.access_token}` },
          body: formData,
        });
        if (res.ok) {
          const data = await res.json();
          if (data.text) {
            setText(data.text);
            accumulatedText.current = data.text;
            setCorrectionInfo({
              corrected: data.corrected === true,
              rawText: data.raw_text || null,
            });
          }
        } else {
          console.warn('[Voice] Transcribe failed:', res.status);
        }
      } catch (e) {
        console.warn('[Voice] Transcribe error:', e);
      } finally {
        setIsTranscribing(false);
      }
    }

    return {
      text: accumulatedText.current,
      audioBlob,
      mimeType,
    };
  }, [workerUrl, session]);

  const reset = useCallback(() => {
    accumulatedText.current = '';
    setText('');
    setInterim('');
    setCorrectionInfo(null);
  }, []);

  const clearMicError = useCallback(() => setMicError(null), []);

  return {
    isRec, text, interim, isTranscribing, previewLive,
    start, stop, reset, setText,
    lastAudioId: lastAudioIdRef, micError, clearMicError, correctionInfo,
  };
}

// ────────────────────────────────────────────────────────────────────
// useRundgang — Rundgang-Hook mit Segment-Detection, Photo-Markern
// ────────────────────────────────────────────────────────────────────
function useRundgang({ availableTags, kapitelTags }) {
  const [segments, setSegments] = useState([]);
  const [markers, setMarkers] = useState([]);
  const [photoMarkers, setPhotoMarkers] = useState([]);
  const [currentTagId, setCurrentTagId] = useState(null);
  const [currentObjektId, setCurrentObjektId] = useState(null);
  const [coverage, setCoverage] = useState({});

  const startMsRef = useRef(null);
  const lastProcessedLenRef = useRef(0);
  const lastTagSwitchMsRef = useRef(0);
  const lastObjektSwitchMsRef = useRef(0);
  const currentTagIdRef = useRef(null);
  const currentObjektIdRef = useRef(null);
  const availableObjekteRef = useRef([]);

  const setAvailableObjekte = useCallback((objekte) => {
    availableObjekteRef.current = objekte || [];
  }, []);

  const reset = useCallback(() => {
    setSegments([]);
    setMarkers([]);
    setPhotoMarkers([]);
    setCurrentTagId(null);
    setCurrentObjektId(null);
    setCoverage({});
    startMsRef.current = null;
    lastProcessedLenRef.current = 0;
    lastTagSwitchMsRef.current = 0;
    lastObjektSwitchMsRef.current = 0;
    currentTagIdRef.current = null;
    currentObjektIdRef.current = null;
  }, []);

  const onStart = useCallback(() => {
    reset();
    startMsRef.current = Date.now();
  }, [reset]);

  const addPhotoMarker = useCallback((marker) => {
    setPhotoMarkers(prev => [...prev, marker]);
  }, []);

  const updatePhotoMarker = useCallback((id, patch) => {
    setPhotoMarkers(prev => prev.map(m => m.id === id ? { ...m, ...patch } : m));
  }, []);

  const getCurrentAudioPositionMs = useCallback(() => {
    return startMsRef.current ? (Date.now() - startMsRef.current) : 0;
  }, []);

  const onLiveText = useCallback((fullText) => {
    if (!startMsRef.current) return;
    const newContent = fullText.slice(lastProcessedLenRef.current);
    if (newContent.length < 10) return;
    lastProcessedLenRef.current = fullText.length;

    const now = Date.now();
    const elapsedMs = now - startMsRef.current;

    // Segment-Boundary (Kapitel-Wechsel)
    const boundary = detectSegmentBoundary(newContent, availableTags);
    if (boundary && boundary.tagId !== currentTagIdRef.current) {
      if (now - lastTagSwitchMsRef.current >= 5000) {
        const tagLabel = availableTags.find(t => t.id === boundary.tagId)?.label || boundary.tagId;
        lastTagSwitchMsRef.current = now;
        currentTagIdRef.current = boundary.tagId;
        setSegments(prev => {
          const closed = prev.length > 0
            ? prev.slice(0, -1).concat({ ...prev[prev.length - 1], endMs: elapsedMs })
            : prev;
          return closed.concat({
            tagId: boundary.tagId,
            label: tagLabel,
            startMs: elapsedMs,
            endMs: null,
            textStart: fullText.length,
            objektId: currentObjektIdRef.current,
          });
        });
        setCurrentTagId(boundary.tagId);
      }
    }

    // Objekt-Wechsel (V&L-spezifisch)
    const objektSwitch = detectObjektSwitch(newContent, availableObjekteRef.current);
    if (objektSwitch && objektSwitch.objektId !== currentObjektIdRef.current) {
      if (now - lastObjektSwitchMsRef.current >= 5000) {
        lastObjektSwitchMsRef.current = now;
        currentObjektIdRef.current = objektSwitch.objektId;
        setCurrentObjektId(objektSwitch.objektId);
      }
    }

    // Marker (Self-Notes)
    const markerTrigger = detectMarkerTrigger(newContent);
    if (markerTrigger) {
      setMarkers(prev => [...prev, {
        phrase: markerTrigger.phrase,
        timestampMs: elapsedMs,
        type: markerTrigger.type,
        auto: true,
      }]);
    }
  }, [availableTags]);

  return {
    segments, markers, photoMarkers, currentTagId, currentObjektId, coverage,
    reset, onStart, onLiveText, addPhotoMarker, updatePhotoMarker,
    getCurrentAudioPositionMs, setAvailableObjekte,
  };
}

// ────────────────────────────────────────────────────────────────────
// Foto-Bildkomprimierung (vor Upload) — Portiert aus Alt-App
// ────────────────────────────────────────────────────────────────────
function compressImage(file, maxW = 1200, q = 0.8) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        const scale = Math.min(1, maxW / img.width);
        const w = img.width * scale;
        const h = img.height * scale;
        const canvas = document.createElement('canvas');
        canvas.width = w;
        canvas.height = h;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0, w, h);
        canvas.toBlob((blob) => {
          if (blob) resolve(blob);
          else reject(new Error('toBlob failed'));
        }, 'image/jpeg', q);
      };
      img.onerror = reject;
      img.src = e.target.result;
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}

// ────────────────────────────────────────────────────────────────────
// Call /api/split-rundgang nach Rundgang-Ende
// Liefert Segment-Array und Todo-Array, sonst Live-Segmente als Fallback
// ────────────────────────────────────────────────────────────────────
async function splitRundgangMitKI(fullTranscript, liveSegments, markers, projectContext, session, workerUrl) {
  try {
    const res = await fetch(workerUrl + '/api/split-rundgang', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${session.access_token}`,
      },
      body: JSON.stringify({
        transcript: fullTranscript,
        live_segments: liveSegments,
        markers: markers,
        project_context: projectContext,
      }),
    });
    if (!res.ok) throw new Error('Split API ' + res.status);
    const data = await res.json();
    return {
      ok: true,
      segments: data.segments || [],
      todos: data.todos || [],
    };
  } catch (e) {
    console.warn('[Rundgang] KI-Split fehlgeschlagen, Live-Fallback:', e.message);
    return {
      ok: false,
      segments: (liveSegments || []).map(s => ({
        tagId: s.tagId,
        label: s.label,
        text: s.text || '(Segment nicht lesbar)',
        isBeurteilung: false,
        objektId: s.objektId || null,
      })).filter(s => s.text && s.text.length > 5),
      todos: (markers || []).map(m => ({ text: m.phrase })),
    };
  }
}

// ══════════════════════════════════════════════════════════════════
// 3 · UI-BAUSTEINE
// ══════════════════════════════════════════════════════════════════

const Pill = ({ variant = '', children, style }) => (
  <span className={`pill ${variant ? 'pill-' + variant : ''}`} style={style}>{children}</span>
);

const ProgressRow = ({ label, value, total, variant = '' }) => {
  const pct = total === 0 ? 0 : Math.round((value / total) * 100);
  const displayValue = total === 100 ? `${value}%` : `${value}/${total}`;
  return (
    <div className="progress-row">
      <span className="progress-label">{label}</span>
      <div className="progress-bar">
        <div className={`progress-bar-fill ${variant}`} style={{ width: `${pct}%` }}></div>
      </div>
      <span className="progress-value">{displayValue}</span>
    </div>
  );
};

const PrototypeHint = ({ children }) => (
  <div className="prototype-hint-box">{children}</div>
);

// ──────────────────────────────────────────────────────────────────
// ProvenanceBadge — zeigt "aus Dokument X" bei extrahierten Feldern
// Klick öffnet das Quelldokument in neuem Tab.
// ──────────────────────────────────────────────────────────────────
const ProvenanceBadge = ({ herkunft, onOpen, onOpenInline, size = 'sm' }) => {
  if (!herkunft?.dokument_id) return null;
  const titel = herkunft.titel || 'Dokument';
  const label = titel.length > 40 ? titel.substring(0, 37) + '…' : titel;

  // onOpenInline hat Vorrang: inline-Viewer statt neuer Tab
  const handleClick = (e) => {
    e.stopPropagation();
    e.preventDefault();
    if (onOpenInline) onOpenInline(herkunft.dokument_id);
    else if (onOpen) onOpen(herkunft.dokument_id);
  };

  return (
    <button
      type="button"
      className="provenance-badge"
      onClick={handleClick}
      title={`Aus: ${titel} — Klicken zum Anzeigen`}
      style={{
        display: 'inline-flex',
        alignItems: 'center',
        gap: 4,
        padding: size === 'sm' ? '2px 8px' : '3px 10px',
        background: 'var(--success-bg)',
        color: 'var(--success)',
        border: '1px solid transparent',
        borderRadius: 'var(--radius-sm)',
        fontSize: size === 'sm' ? 11 : 12,
        fontWeight: 600,
        textTransform: 'uppercase',
        letterSpacing: '0.06em',
        cursor: 'pointer',
        transition: 'border-color 0.12s, background 0.12s',
      }}
      onMouseEnter={(e) => {
        e.currentTarget.style.borderColor = 'var(--success)';
      }}
      onMouseLeave={(e) => {
        e.currentTarget.style.borderColor = 'transparent';
      }}
    >
      <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
        <polyline points="14 2 14 8 20 8"/>
      </svg>
      <span>{label}</span>
    </button>
  );
};

// ══════════════════════════════════════════════════════════════════
// 4.1 · VIEW: PROJEKTLISTE
// Liest Projekte aus Supabase via ladeProjektliste()
// ══════════════════════════════════════════════════════════════════

// ──────────────────────────────────────────────────────────────────
// Helper: Frist-Info mit Farb-Code (basierend auf Restdays)
// ──────────────────────────────────────────────────────────────────
function getFristInfo(fristRestDays, abgabeExtern) {
  if (fristRestDays == null || !abgabeExtern) return null;
  const diff = fristRestDays;
  const label = diff < 0
    ? `${Math.abs(diff)} Tage überfällig`
    : diff === 0 ? 'Heute'
    : diff === 1 ? 'Morgen'
    : `${diff} Tage`;
  const color = diff < 0 ? 'var(--danger)'
    : diff <= 3 ? '#F59E0B'
    : diff <= 7 ? '#EAB308'
    : diff <= 21 ? 'var(--success)'
    : 'var(--text-tertiary)';
  return { diff, label, color, datum: abgabeExtern };
}

// ──────────────────────────────────────────────────────────────────
// ProjektKpiTile — klickbare KPI-Kachel
// ──────────────────────────────────────────────────────────────────
const ProjektKpiTile = ({ label, value, sub, color, active, onClick }) => (
  <div
    onClick={onClick}
    style={{
      background: active ? `${color}12` : 'var(--surface)',
      borderRadius: 'var(--radius-md)',
      border: `1.5px solid ${active ? `${color}44` : 'var(--border-light)'}`,
      padding: 'var(--space-4) var(--space-5)',
      cursor: onClick ? 'pointer' : 'default',
      transition: 'all 0.2s',
    }}
  >
    <div style={{
      fontSize: 28, fontWeight: 800, color, letterSpacing: '-0.02em', lineHeight: 1,
    }}>
      {value}
    </div>
    <div style={{ fontSize: 12, fontWeight: 600, marginTop: 6, color: 'var(--text-primary)' }}>
      {label}
    </div>
    {sub && (
      <div style={{
        fontSize: 11, color: 'var(--text-tertiary)', marginTop: 2,
        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
      }}>
        {sub}
      </div>
    )}
  </div>
);

// ──────────────────────────────────────────────────────────────────
// FristenTimeline — horizontaler Zeitstrahl mit Projekt-Markern
// ──────────────────────────────────────────────────────────────────
const FristenTimeline = ({ projekte, onOpen }) => {
  const [selected, setSelected] = useState(null);
  const [hovered, setHovered] = useState(null);
  const scrolledOnce = useRef(false);
  const containerRef = useRef(null);

  const today = useMemo(() => { const d = new Date(); d.setHours(0,0,0,0); return d; }, []);
  const rangeStart = useMemo(() => { const d = new Date(today); d.setDate(d.getDate() - 14); return d; }, [today]);
  const rangeEnd = useMemo(() => { const d = new Date(today); d.setDate(d.getDate() + 60); return d; }, [today]);
  const totalDays = Math.ceil((rangeEnd - rangeStart) / 86400000);
  const DAY_W = 28;
  const totalW = totalDays * DAY_W;
  const todayX = Math.ceil((today - rangeStart) / 86400000) * DAY_W;

  // Wochen-Linien (Montags)
  const tlWeeks = useMemo(() => {
    const ws = [];
    const d = new Date(rangeStart);
    d.setDate(d.getDate() - ((d.getDay() + 6) % 7));
    while (d < rangeEnd) {
      ws.push({
        offset: Math.ceil((d - rangeStart) / 86400000),
        label: d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' }),
      });
      d.setDate(d.getDate() + 7);
    }
    return ws;
  }, [rangeStart, rangeEnd]);

  // Marker auf Zeilen verteilen (bei engen Terminen)
  const tlMarkers = useMemo(() => {
    const items = projekte
      .filter(p => p.abgabeExtern)
      .map(p => {
        const dd = new Date(p.abgabeExtern); dd.setHours(0,0,0,0);
        const dayOff = Math.ceil((dd - rangeStart) / 86400000);
        return {
          ...p,
          dayOff,
          x: dayOff * DAY_W,
          info: getFristInfo(p.fristRestDays, p.abgabeExtern),
        };
      })
      .filter(m => m.dayOff >= -2 && m.dayOff <= totalDays + 2)
      .sort((a, b) => a.dayOff - b.dayOff);
    const rowEnds = [];
    return items.map(m => {
      let row = 0;
      for (let r = 0; r < rowEnds.length; r++) {
        if (m.x - rowEnds[r] > 140) { row = r; break; }
        row = r + 1;
      }
      rowEnds[row] = m.x;
      return { ...m, row };
    });
  }, [projekte, rangeStart, totalDays]);

  const maxRow = tlMarkers.reduce((m, r) => Math.max(m, r.row), 0);
  const ROW_H = 72;
  const chartH = 44 + (maxRow + 1) * ROW_H + 20;

  // Auto-Scroll zu heute
  useEffect(() => {
    if (containerRef.current && !scrolledOnce.current && tlMarkers.length > 0) {
      scrolledOnce.current = true;
      containerRef.current.scrollLeft = Math.max(0, todayX - containerRef.current.clientWidth / 3);
    }
  }, [tlMarkers.length, todayX]);

  return (
    <div style={{
      background: 'var(--surface)',
      borderRadius: 'var(--radius-md)',
      border: '1px solid var(--border-light)',
      overflow: 'hidden',
      marginBottom: 'var(--space-5)',
    }}>
      <div style={{
        padding: '14px 20px 10px',
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        borderBottom: '1px solid var(--border-light)',
      }}>
        <div style={{ fontSize: 13, fontWeight: 700 }}>Fristen-Zeitstrahl</div>
        <div style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
          {tlMarkers.length} Projekt{tlMarkers.length !== 1 ? 'e' : ''} mit Frist
        </div>
      </div>

      {tlMarkers.length === 0 ? (
        <div style={{ padding: '32px 20px', textAlign: 'center' }}>
          <div style={{ fontSize: 13, color: 'var(--text-tertiary)', marginBottom: 4 }}>
            Keine Projekte mit Abgabefrist
          </div>
          <div style={{ fontSize: 12, color: 'var(--text-tertiary)', opacity: 0.7 }}>
            Fristen lassen sich unter „Auftrag" oder beim Anlegen setzen.
          </div>
        </div>
      ) : (
        <div ref={containerRef} style={{ overflowX: 'auto', overflowY: 'hidden' }}>
          <div style={{ width: totalW, height: chartH, position: 'relative' }}>
            {tlWeeks.map((w, i) => (
              <div key={i} style={{
                position: 'absolute', left: w.offset * DAY_W, top: 0, bottom: 0,
                borderLeft: '1px solid var(--border-light)', opacity: 0.5,
              }}>
                <div style={{
                  position: 'absolute', top: 6, left: 6,
                  fontSize: 10, color: 'var(--text-tertiary)', whiteSpace: 'nowrap', fontWeight: 500,
                }}>
                  {w.label}
                </div>
              </div>
            ))}
            {/* HEUTE-Linie */}
            <div style={{
              position: 'absolute', left: todayX, top: 0, bottom: 0, width: 2,
              background: 'var(--vl-orange, #F59E0B)', zIndex: 5, opacity: 0.7,
            }}>
              <div style={{
                position: 'absolute', top: 4, left: -16,
                background: 'var(--vl-orange, #F59E0B)', color: 'white',
                fontSize: 9, fontWeight: 800, padding: '2px 6px', borderRadius: 4, whiteSpace: 'nowrap',
              }}>
                HEUTE
              </div>
            </div>
            <div style={{
              position: 'absolute', left: 0, right: 0, top: 36, height: 1,
              background: 'var(--border-light)',
            }} />
            {tlMarkers.map(m => {
              const isActive = m.id === selected;
              const isHov = m.id === hovered;
              const y = 44 + m.row * ROW_H;
              const dotSz = isActive || isHov ? 14 : 10;
              const c = m.info?.color || 'var(--text-tertiary)';
              return (
                <div
                  key={m.id}
                  style={{
                    position: 'absolute', left: m.x - dotSz / 2, top: y,
                    zIndex: isActive ? 10 : isHov ? 8 : 1, cursor: 'pointer', transition: 'all 0.2s',
                  }}
                  onClick={() => setSelected(selected === m.id ? null : m.id)}
                  onMouseEnter={() => setHovered(m.id)}
                  onMouseLeave={() => setHovered(null)}
                >
                  {/* Verbindungslinie nach oben zur Achse */}
                  <div style={{
                    position: 'absolute', left: dotSz / 2 - 0.5, top: -(y - 36),
                    width: 1, height: y - 36,
                    background: c, opacity: isActive || isHov ? 0.5 : 0.15, transition: 'opacity 0.2s',
                  }} />
                  {/* Punkt */}
                  <div style={{
                    width: dotSz, height: dotSz, borderRadius: dotSz,
                    background: c,
                    border: isActive ? '2px solid var(--text-primary)' : `2px solid ${c}`,
                    boxShadow: isActive ? `0 0 12px ${c}66` : isHov ? `0 0 8px ${c}44` : 'none',
                    transition: 'all 0.2s',
                  }} />
                  {/* Tooltip */}
                  <div style={{
                    position: 'absolute', top: dotSz + 4, left: -40,
                    width: isActive ? 220 : 120,
                    background: isActive || isHov ? 'var(--surface)' : 'transparent',
                    border: isActive || isHov ? '1px solid var(--border-light)' : '1px solid transparent',
                    borderRadius: 8, padding: isActive ? 12 : isHov ? '6px 8px' : '4px 8px',
                    transition: 'all 0.2s',
                    boxShadow: isActive ? '0 8px 24px rgba(0,0,0,0.12)' : 'none',
                  }}>
                    <div style={{
                      fontSize: 11, fontWeight: 700,
                      color: isActive || isHov ? 'var(--text-primary)' : 'var(--text-tertiary)',
                      overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                      marginBottom: isActive ? 4 : 0,
                    }}>
                      {m.name}
                    </div>
                    <div style={{ fontSize: 10, fontWeight: 600, color: c }}>
                      {m.info?.label}
                    </div>
                    {isActive && (
                      <div style={{ marginTop: 6 }}>
                        <div style={{ height: 1, background: 'var(--border-light)', marginBottom: 6 }} />
                        {m.auftraggeber && (
                          <div style={{ fontSize: 11, color: 'var(--text-primary)', marginBottom: 3 }}>
                            {m.auftraggeber}
                          </div>
                        )}
                        {m.standort && (
                          <div style={{ fontSize: 11, color: 'var(--text-secondary)', marginBottom: 3 }}>
                            {m.standort}
                          </div>
                        )}
                        <div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginBottom: 8 }}>
                          {m.gutachtenCount} Gutachten · {m.objektCount} Objekt{m.objektCount !== 1 ? 'e' : ''}
                        </div>
                        <button
                          onClick={e => { e.stopPropagation(); onOpen(m.id); }}
                          className="btn btn-primary btn-sm"
                          style={{ width: '100%', fontSize: 12 }}
                        >
                          Projekt öffnen
                        </button>
                      </div>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
};

// ──────────────────────────────────────────────────────────────────
// ProjektRow (Card im Listen-Layout)
// ──────────────────────────────────────────────────────────────────
const ProjektRow = ({ projekt, onOpen }) => {
  const { gutachtenCount, objektCount } = projekt;
  const fristInfo = getFristInfo(projekt.fristRestDays, projekt.abgabeExtern);

  let statValue, statLabel;
  if (gutachtenCount === 1 && objektCount === 1) {
    statValue = '1 Gutachten';
    statLabel = 'Standard';
  } else if (gutachtenCount === 1 && objektCount > 1) {
    statValue = `1 Gutachten · ${objektCount} Objekte`;
    statLabel = 'WEG-Aufteilung';
  } else if (gutachtenCount > 1) {
    statValue = `${gutachtenCount} Gutachten`;
    statLabel = objektCount > gutachtenCount ? 'mehrere Objekte' : 'mehrere Standorte';
  } else {
    statValue = 'Kein Gutachten';
    statLabel = 'leer';
  }

  return (
    <div className="project-row" onClick={() => onOpen(projekt.id)}>
      <div className="project-row-main">
        <div className="project-row-title">{projekt.name}</div>
        <div className="project-row-meta">
          {projekt.akte && <><span className="akte">{projekt.akte}</span><span>·</span></>}
          <span>{projekt.auftragsart}{projekt.auftragstyp ? ' · ' + projekt.auftragstyp : ''}</span>
          {projekt.standort && <><span>·</span><span>{projekt.standort}</span></>}
        </div>
      </div>
      {fristInfo && (
        <span style={{
          fontSize: 11, fontWeight: 600, color: fristInfo.color,
          padding: '3px 10px', borderRadius: 50,
          background: `${fristInfo.color}15`, border: `1px solid ${fristInfo.color}33`,
          whiteSpace: 'nowrap',
        }}>
          {fristInfo.label}
        </span>
      )}
      <div className="project-row-stat">
        <div className="project-row-stat-value">{statValue}</div>
        <div className="project-row-stat-label">{statLabel}</div>
      </div>
      <IconChevronRight size={20} stroke="var(--text-tertiary)" />
    </div>
  );
};

const ProjektlisteView = ({ onOpen, onNewProjekt }) => {
  const [projekte, setProjekte] = useState(null);
  const [error, setError] = useState(null);
  const [filter, setFilter] = useState('alle');  // 'alle' | 'ueberfaellig' | 'diese_woche' | 'ohne_frist'
  const [search, setSearch] = useState('');
  const [sort, setSort] = useState('deadline');  // 'deadline' | 'name' | 'created'

  useEffect(() => {
    let active = true;
    ladeProjektliste()
      .then(data => { if (active) setProjekte(data); })
      .catch(err => { if (active) setError(err.message || String(err)); });
    return () => { active = false; };
  }, []);

  // KPI-Gruppen berechnen
  const overdue = useMemo(
    () => (projekte || []).filter(p => p.fristRestDays != null && p.fristRestDays < 0),
    [projekte]
  );
  const thisWeek = useMemo(
    () => (projekte || []).filter(p => p.fristRestDays != null && p.fristRestDays >= 0 && p.fristRestDays <= 7),
    [projekte]
  );
  const noFrist = useMemo(
    () => (projekte || []).filter(p => p.fristRestDays == null),
    [projekte]
  );
  const nextDeadline = useMemo(() => {
    return (projekte || [])
      .filter(p => p.fristRestDays != null && p.fristRestDays >= 0)
      .sort((a, b) => a.fristRestDays - b.fristRestDays)[0];
  }, [projekte]);

  // Gefilterte + sortierte Liste
  const gefiltert = useMemo(() => {
    if (!projekte) return [];
    let list = projekte;
    if (filter === 'ueberfaellig') list = overdue;
    else if (filter === 'diese_woche') list = thisWeek;
    else if (filter === 'ohne_frist') list = noFrist;

    if (search.trim()) {
      const q = search.trim().toLowerCase();
      list = list.filter(p =>
        (p.name || '').toLowerCase().includes(q) ||
        (p.akte || '').toLowerCase().includes(q) ||
        (p.standort || '').toLowerCase().includes(q) ||
        (p.auftraggeber || '').toLowerCase().includes(q)
      );
    }

    return [...list].sort((a, b) => {
      if (sort === 'deadline') {
        const da = a.abgabeExtern ? new Date(a.abgabeExtern).getTime() : Infinity;
        const db = b.abgabeExtern ? new Date(b.abgabeExtern).getTime() : Infinity;
        return da - db;
      }
      if (sort === 'name') return (a.name || '').localeCompare(b.name || '');
      if (sort === 'created') return new Date(b.createdAt || 0) - new Date(a.createdAt || 0);
      return 0;
    });
  }, [projekte, filter, search, sort, overdue, thisWeek, noFrist]);

  return (
    <div className="view-wrapper">
      <div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
        <div>
          <div className="page-title">Projekte</div>
          <div className="page-subtitle">
            {projekte
              ? `${projekte.length} aktive${projekte.length === 1 ? 's' : ''} Projekt${projekte.length === 1 ? '' : 'e'}`
              : 'Lade Projekte…'}
          </div>
        </div>
        <button
          className="btn btn-primary"
          onClick={onNewProjekt}
          style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}
        >
          + Neuer Auftrag
        </button>
      </div>

      {error && (
        <PrototypeHint>
          <strong>Fehler beim Laden:</strong> {error}
        </PrototypeHint>
      )}

      {!projekte && !error && (
        <div style={{ padding: 'var(--space-8)', textAlign: 'center', color: 'var(--text-tertiary)' }}>
          Lade…
        </div>
      )}

      {projekte && projekte.length === 0 && (
        <div className="card" style={{ textAlign: 'center', padding: 'var(--space-8)' }}>
          <div style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>
            Noch keine Projekte angelegt
          </div>
          <div style={{ color: 'var(--text-secondary)', fontSize: 14, marginBottom: 'var(--space-4)' }}>
            Starte mit einem neuen Auftrag.
          </div>
          <button className="btn btn-primary" onClick={onNewProjekt}>
            + Neuer Auftrag
          </button>
        </div>
      )}

      {projekte && projekte.length > 0 && (
        <>
          {/* KPI-Kacheln */}
          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(4, 1fr)',
            gap: 'var(--space-3)',
            marginBottom: 'var(--space-5)',
          }}>
            <ProjektKpiTile
              label="Aktive Projekte"
              value={projekte.length}
              sub={`Ø ${projekte.length > 0 ? Math.round(projekte.reduce((s, p) => s + (p.objektCount || 0), 0) / projekte.length) : 0} Objekte/Projekt`}
              color="var(--vl-blue, #0A2540)"
              active={filter === 'alle'}
              onClick={() => setFilter('alle')}
            />
            <ProjektKpiTile
              label="Überfällig"
              value={overdue.length}
              sub={overdue.length > 0 ? overdue.map(p => p.name.split(',')[0]).slice(0, 3).join(', ') : null}
              color={overdue.length > 0 ? 'var(--danger)' : 'var(--text-tertiary)'}
              active={filter === 'ueberfaellig'}
              onClick={() => setFilter(filter === 'ueberfaellig' ? 'alle' : 'ueberfaellig')}
            />
            <ProjektKpiTile
              label="Diese Woche"
              value={thisWeek.length}
              sub={thisWeek.length > 0 ? thisWeek.map(p => p.name.split(',')[0]).slice(0, 3).join(', ') : null}
              color={thisWeek.length > 0 ? '#F59E0B' : 'var(--text-tertiary)'}
              active={filter === 'diese_woche'}
              onClick={() => setFilter(filter === 'diese_woche' ? 'alle' : 'diese_woche')}
            />
            <ProjektKpiTile
              label="Nächste Frist"
              value={nextDeadline
                ? (nextDeadline.fristRestDays === 0 ? 'Heute'
                   : nextDeadline.fristRestDays === 1 ? 'Morgen'
                   : `${nextDeadline.fristRestDays} T`)
                : '—'}
              sub={nextDeadline ? nextDeadline.name.split(',')[0] : 'Keine Frist gesetzt'}
              color={nextDeadline
                ? (nextDeadline.fristRestDays <= 3 ? '#F59E0B' : 'var(--success)')
                : 'var(--text-tertiary)'}
              active={false}
            />
          </div>

          {/* Timeline */}
          <FristenTimeline projekte={projekte} onOpen={onOpen} />

          {/* Search + Sort */}
          <div style={{
            display: 'flex', alignItems: 'center', gap: 'var(--space-3)',
            marginBottom: 'var(--space-3)',
          }}>
            <div style={{ position: 'relative', flex: 1, maxWidth: 360 }}>
              <svg
                width="14" height="14" viewBox="0 0 24 24" fill="none"
                stroke="var(--text-tertiary)" strokeWidth="2"
                style={{
                  position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)',
                }}
              >
                <circle cx="11" cy="11" r="8"/>
                <line x1="21" y1="21" x2="16.65" y2="16.65"/>
              </svg>
              <input
                type="text"
                placeholder="Projekt, Akte, Auftraggeber, Standort…"
                value={search}
                onChange={e => setSearch(e.target.value)}
                style={{
                  width: '100%',
                  padding: '10px 14px 10px 36px',
                  borderRadius: 'var(--radius-md)',
                  border: '1.5px solid var(--border-light)',
                  background: 'var(--surface)',
                  fontSize: 13, outline: 'none',
                }}
              />
              {search && (
                <button
                  onClick={() => setSearch('')}
                  style={{
                    position: 'absolute', right: 10, top: '50%', transform: 'translateY(-50%)',
                    background: 'none', border: 'none', color: 'var(--text-tertiary)',
                    cursor: 'pointer', fontSize: 16, padding: 2,
                  }}
                >
                  ×
                </button>
              )}
            </div>
            <div style={{ display: 'flex', gap: 4 }}>
              {[
                { id: 'deadline', label: 'Frist' },
                { id: 'name', label: 'Name' },
                { id: 'created', label: 'Neueste' },
              ].map(s => (
                <button
                  key={s.id}
                  onClick={() => setSort(s.id)}
                  style={{
                    padding: '7px 12px', borderRadius: 'var(--radius-sm)',
                    fontSize: 12, fontWeight: 600, cursor: 'pointer',
                    border: `1px solid ${sort === s.id ? 'var(--vl-blue, #0A2540)' : 'var(--border-light)'}`,
                    background: sort === s.id ? 'var(--vl-blue-dim, rgba(10,37,64,0.08))' : 'transparent',
                    color: sort === s.id ? 'var(--vl-blue, #0A2540)' : 'var(--text-secondary)',
                    transition: 'all 0.15s',
                  }}
                >
                  {s.label}
                </button>
              ))}
            </div>
          </div>

          {/* Filter-Hinweis falls aktiv */}
          {filter !== 'alle' && (
            <div style={{
              padding: 'var(--space-2) var(--space-3)',
              background: 'var(--surface)',
              border: '1px solid var(--border-light)',
              borderRadius: 'var(--radius-sm)',
              marginBottom: 'var(--space-3)',
              fontSize: 12, color: 'var(--text-secondary)',
              display: 'flex', alignItems: 'center', gap: 'var(--space-2)',
            }}>
              <span>
                Gefiltert: {filter === 'ueberfaellig' ? 'Überfällig'
                  : filter === 'diese_woche' ? 'Diese Woche'
                  : 'Ohne Frist'}
                {' '}({gefiltert.length} Ergebnis{gefiltert.length !== 1 ? 'se' : ''})
              </span>
              <button
                onClick={() => setFilter('alle')}
                style={{
                  marginLeft: 'auto', background: 'none', border: 'none',
                  color: 'var(--vl-blue-light)', cursor: 'pointer', fontSize: 12,
                }}
              >
                Filter löschen
              </button>
            </div>
          )}

          {/* Projektliste */}
          <div className="project-list">
            {gefiltert.length === 0 ? (
              <div style={{
                padding: 'var(--space-6)', textAlign: 'center',
                color: 'var(--text-tertiary)', fontSize: 14,
                background: 'var(--surface)',
                borderRadius: 'var(--radius-md)',
                border: '1px dashed var(--border-light)',
              }}>
                Keine Projekte entsprechen dem Filter.
              </div>
            ) : (
              gefiltert.map(p => (
                <ProjektRow key={p.id} projekt={p} onOpen={onOpen} />
              ))
            )}
          </div>
        </>
      )}
    </div>
  );
};

// ══════════════════════════════════════════════════════════════════
// 4.2 · VIEW: DASHBOARD
// ══════════════════════════════════════════════════════════════════

const DashboardHeader = ({ p }) => (
  <div className="dashboard-header">
    <div className="dashboard-title-block">
      <div className="dashboard-akte">{p.akte}</div>
      <h1 className="dashboard-title">{p.name}</h1>
      <div className="dashboard-subtitle">
        <span>{p.auftragsart}</span>
        <span className="sep">·</span>
        <span>{p.auftragstyp}</span>
        {p.verfahren && <>
          <span className="sep">·</span>
          <span>{p.verfahren}</span>
        </>}
        <span className="sep">·</span>
        <Pill variant="active" style={{ fontSize: 11 }}>{p.status}</Pill>
      </div>
    </div>
    <div className="dashboard-deadline">
      <div className="dashboard-deadline-label">Abgabe extern</div>
      <div className="dashboard-deadline-value">{p.fristExtern}</div>
      <div className="dashboard-deadline-counter">⚠ {p.fristRestDays} Tage</div>
    </div>
  </div>
);

const AuftragKachel = ({ p, onOpen }) => (
  <div className="kachel">
    <div className="kachel-header">
      <span className="kachel-title">Auftrag</span>
      <span className="kachel-count">{p.auftragsart === 'Gerichtsauftrag' ? 'Gericht' : p.auftragsart}</span>
    </div>
    <div className="kachel-body">
      <div className="kachel-entry">
        <span className="kachel-entry-label">Auftraggeber</span>
        <span className="kachel-entry-value">{p.auftraggeber.split(',')[0]}</span>
      </div>
      {p.verfahren && (
        <div className="kachel-entry">
          <span className="kachel-entry-label">Verfahren</span>
          <span className="kachel-entry-value">{p.verfahren.split(' ')[0]}</span>
        </div>
      )}
      <div className="kachel-entry">
        <span className="kachel-entry-label">Ortstermin</span>
        <span className="kachel-entry-value">{p.ortstermin}</span>
      </div>
      {p.kostenvorschuss && (
        <div className="kachel-entry">
          <span className="kachel-entry-label">Kostenvorschuss</span>
          <span className="kachel-entry-value">€{p.kostenvorschuss.toLocaleString('de-DE')}</span>
        </div>
      )}
      <div className="kachel-entry">
        <span className="kachel-entry-label">Ausfertigungen</span>
        <span className="kachel-entry-value">{p.ausfertigungen}× Druck</span>
      </div>
    </div>
    <div className="kachel-footer">
      <button className="kachel-footer-link" onClick={onOpen}>
        Details öffnen <IconChevronRight size={16} />
      </button>
    </div>
  </div>
);

const BeteiligteKachel = ({ p, onOpen }) => (
  <div className="kachel">
    <div className="kachel-header">
      <span className="kachel-title">Beteiligte</span>
      <span className="kachel-count">{p.beteiligte.length} Rollen</span>
    </div>
    <div className="kachel-body">
      <div className="kachel-list">
        {p.beteiligte.slice(0, 4).map((b, i) => (
          <div key={i} className="kachel-list-item">
            <span className="kachel-list-item-role">{b.rolle}</span>
            {b.name}
          </div>
        ))}
        {p.beteiligte.length > 4 && (
          <div className="kachel-list-item" style={{ color: 'var(--text-tertiary)', fontSize: 13 }}>
            + {p.beteiligte.length - 4} weitere
          </div>
        )}
      </div>
    </div>
    <div className="kachel-footer">
      <button className="kachel-footer-link" onClick={onOpen}>
        Alle bearbeiten <IconChevronRight size={16} />
      </button>
    </div>
  </div>
);

const GutachtenKachel = ({ gutachten, onOpen }) => {
  const g = gutachten;
  return (
    <div className="kachel kachel-gutachten">
      <div className="kachel-header">
        <span className="kachel-title">Gutachten</span>
        <span className="kachel-count">
          {g.objekte.length} Objekt{g.objekte.length === 1 ? '' : 'e'}
        </span>
      </div>
      <div className="kachel-body">
        <div style={{ fontWeight: 600, color: 'var(--text-primary)', fontSize: 15 }}>{g.adresse}</div>
        {g.objekte.length > 1 && (
          <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 4 }}>
            {g.objekte.map(o => o.bezeichnung).join(' · ')}
          </div>
        )}
        <div style={{ marginTop: 8 }}>
          <ProgressRow label="Unterlagen" value={g.unterlagenDone} total={g.unterlagenTotal} />
          <ProgressRow label="Ortstermin" value={g.ortsterminStatus === 'begangen' ? 1 : 0} total={1} variant="accent" />
          <ProgressRow label="Entwurf" value={g.entwurfPct} total={100} variant={g.entwurfPct >= 80 ? 'success' : ''} />
        </div>
      </div>
      <div className="kachel-footer">
        <button className="kachel-footer-link" onClick={onOpen}>
          Gutachten öffnen <IconChevronRight size={16} />
        </button>
      </div>
    </div>
  );
};

const GutachtenCard = ({ gutachten, idx, onOpen }) => {
  const g = gutachten;
  return (
    <div className="gutachten-card" onClick={() => onOpen(idx)}>
      <div className="gutachten-card-num">Gutachten {idx + 1}</div>
      <div className="gutachten-card-title">{g.titel}</div>
      <div className="gutachten-card-address">{g.adresse}</div>
      <div className="gutachten-card-stats">
        <span><strong>{g.objekte.length}</strong> Objekt{g.objekte.length === 1 ? '' : 'e'}</span>
        <span>·</span>
        <span>Unterlagen <strong>{g.unterlagenDone}/{g.unterlagenTotal}</strong></span>
        <span>·</span>
        <span>Entwurf <strong>{g.entwurfPct}%</strong></span>
      </div>
    </div>
  );
};

const DokumentePanel = ({ dokumente, onUpload, onOpenDocument }) => {
  const scopeLabel = (scope) => scope === 'auftrag' ? 'Auftrag' : scope === 'gutachten' ? 'Gutachten' : 'Objekt';
  const statusLabel = (status) => status === 'done' ? '✓ vorhanden' : 'fehlt';

  return (
    <div className="card">
      <div className="card-header">
        <span className="card-title">Dokumente ({dokumente.length})</span>
        <button className="btn btn-accent btn-sm" onClick={onUpload}>+ Hinzufügen</button>
      </div>
      <div className="doc-list">
        {dokumente.map((d, i) => {
          const clickable = !!d.id && !!onOpenDocument;
          return (
            <div
              key={d.id || i}
              className="doc-row"
              onClick={clickable ? () => onOpenDocument(d.id) : undefined}
              style={clickable ? { cursor: 'pointer' } : undefined}
              title={clickable ? 'Klicken zum Öffnen' : undefined}
            >
              <div className="doc-icon"><IconDocument size={20} /></div>
              <div>
                <div className="doc-name">{d.label}</div>
                <div className="doc-meta">{d.typ}</div>
              </div>
              <span className={`doc-scope doc-scope-${d.scope}`}>{scopeLabel(d.scope)}</span>
              <span className={`doc-status doc-status-${d.status}`}>{statusLabel(d.status)}</span>
              <IconChevronRight size={16} stroke="var(--text-tertiary)" />
            </div>
          );
        })}
      </div>
    </div>
  );
};

const DashboardView = ({ p, onOpenAuftrag, onOpenGutachten, onOpenUpload, session, workerUrl }) => {
  const multiGutachten = p.gutachten.length > 1;
  const [viewerDokId, setViewerDokId] = useState(null);
  const viewerActive = !!viewerDokId;

  const handleOpenInline = useCallback((dokumentId) => {
    setViewerDokId(dokumentId);
    if (typeof window.scrollTo === 'function') {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    }
  }, []);

  const mainContent = (
    <>
      <DashboardHeader p={p} />

      <div className={`dashboard-grid ${multiGutachten ? 'two-col' : ''}`}>
        <AuftragKachel p={p} onOpen={onOpenAuftrag} />
        <BeteiligteKachel p={p} onOpen={onOpenAuftrag} />
        {!multiGutachten && (
          <GutachtenKachel gutachten={p.gutachten[0]} onOpen={() => onOpenGutachten(0)} />
        )}
      </div>

      {multiGutachten ? (
        <div className="gutachten-card-wrap">
          <div className="gutachten-card-wrap-header">
            <span className="gutachten-card-wrap-title">Gutachten ({p.gutachten.length})</span>
            <button className="btn btn-secondary btn-sm">+ Weiteres Gutachten</button>
          </div>
          <div className="gutachten-grid">
            {p.gutachten.map((g, i) => (
              <GutachtenCard key={g.id || i} gutachten={g} idx={i} onOpen={onOpenGutachten} />
            ))}
          </div>
        </div>
      ) : (
        <div style={{ marginBottom: 'var(--space-8)' }}>
          <button
            className="btn-subtle-add"
            onClick={() => alert('Prototyp: Legt ein weiteres Gutachten unter demselben Auftrag an.')}
          >
            + Weiteres Gutachten im gleichen Auftrag
          </button>
        </div>
      )}

      <DokumentePanel
        dokumente={p.dokumente}
        onUpload={onOpenUpload}
        onOpenDocument={handleOpenInline}
      />
    </>
  );

  if (viewerActive) {
    return (
      <div className="view-wrapper">
        <div style={{
          display: 'grid',
          gridTemplateColumns: '1fr 1fr',
          gap: 'var(--space-5)',
          alignItems: 'start',
        }}>
          <div>{mainContent}</div>
          <div style={{ position: 'sticky', top: 'var(--space-4)' }}>
            <DokumentViewer
              dokumente={p.dokumente || []}
              session={session}
              workerUrl={workerUrl}
              activeId={viewerDokId}
              onChangeActive={setViewerDokId}
            />
          </div>
        </div>
      </div>
    );
  }

  return <div className="view-wrapper">{mainContent}</div>;
};

// ══════════════════════════════════════════════════════════════════
// 4.3 · VIEW: AUFTRAG
// ══════════════════════════════════════════════════════════════════

const ObjTable = ({ children }) => <div className="obj-table">{children}</div>;

const ObjTableRow = ({ label, children, herkunft, onOpenDocument, onOpenInline }) => (
  <div className="obj-row">
    <div className="obj-label">{label}</div>
    <div className="obj-value" style={{ justifyContent: 'space-between' }}>
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
        {children}
      </span>
      {herkunft && (onOpenDocument || onOpenInline) && (
        <ProvenanceBadge
          herkunft={herkunft}
          onOpen={onOpenDocument}
          onOpenInline={onOpenInline}
        />
      )}
    </div>
  </div>
);

const AuftragView = ({ p, herkunft, onOpenDocument, session, workerUrl }) => {
  // Inline-Viewer-State
  const [viewerDokId, setViewerDokId] = useState(null);
  const viewerActive = !!viewerDokId;

  // Scope-Filter: nur Dokumente, die zum Auftrag gehören
  const auftragDokumente = useMemo(
    () => (p.dokumente || []).filter(d => d.scope === 'auftrag'),
    [p.dokumente]
  );

  // Helfer: Herkunft für ein Feld in auftraege-Tabelle
  const h = (feld) => {
    if (!p.auftrag_id) return null;
    return herkunft?.[`auftraege:${p.auftrag_id}:${feld}`];
  };

  // Klick auf Provenance-Badge → Dokument rechts einblenden
  const handleOpenInline = useCallback((dokumentId) => {
    setViewerDokId(dokumentId);
    // Sanftes Scroll-to-top, damit der Viewer sichtbar ist
    if (typeof window.scrollTo === 'function') {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    }
  }, []);

  const dataColumn = (
    <>
      <div className="card" style={{ marginBottom: 'var(--space-5)' }}>
        <div className="card-header">
          <span className="card-title">Stammdaten</span>
        </div>
        <ObjTable>
          <ObjTableRow label="Auftragsnummer" herkunft={h('akte')} onOpenInline={handleOpenInline}>
            {p.akte || <span className="obj-value-empty">—</span>}
          </ObjTableRow>
          <ObjTableRow label="Auftragsart" herkunft={h('auftragsart')} onOpenInline={handleOpenInline}>
            {p.auftragsart}
          </ObjTableRow>
          <ObjTableRow label="Auftragstyp" herkunft={h('auftragstyp')} onOpenInline={handleOpenInline}>
            {p.auftragstyp}
          </ObjTableRow>
          <ObjTableRow label="Auftraggeber" herkunft={h('auftraggeber')} onOpenInline={handleOpenInline}>
            {p.auftraggeber}
          </ObjTableRow>
          <ObjTableRow label="Zweck / Verfahren" herkunft={h('verfahrensart') || h('zweck')} onOpenInline={handleOpenInline}>
            {p.verfahren || p.zweck || (p.auftragsart_raw === 'privat' ? 'Privater Auftrag' : <span className="obj-value-empty">—</span>)}
          </ObjTableRow>
        </ObjTable>
      </div>

      <div className="card" style={{ marginBottom: 'var(--space-5)' }}>
        <div className="card-header">
          <span className="card-title">Fristen &amp; Termine</span>
        </div>
        <ObjTable>
          <ObjTableRow label="Auftragseingang" herkunft={h('auftragseingang')} onOpenInline={handleOpenInline}>
            {p.auftragseingang || <span className="obj-value-empty">—</span>}
          </ObjTableRow>
          <ObjTableRow label="Abgabe intern">{p.fristIntern || <span className="obj-value-empty">—</span>}</ObjTableRow>
          <ObjTableRow label="Abgabe extern" herkunft={h('abgabe_extern')} onOpenInline={handleOpenInline}>
            {p.fristExtern ? (
              <>
                <strong>{p.fristExtern}</strong>
                {p.fristRestDays != null && (
                  <Pill variant="warning" style={{ fontSize: 11 }}>{p.fristRestDays} Tage</Pill>
                )}
              </>
            ) : <span className="obj-value-empty">—</span>}
          </ObjTableRow>
          <ObjTableRow label="Ortstermin(e)">{p.ortstermin || <span className="obj-value-empty">—</span>}</ObjTableRow>
        </ObjTable>
      </div>

      {p.auftragsart_raw === 'gericht' && (
        <div className="card" style={{ marginBottom: 'var(--space-5)' }}>
          <div className="card-header">
            <span className="card-title">Juristischer Rahmen</span>
          </div>
          <ObjTable>
            <ObjTableRow label="Gerichtstyp" herkunft={h('gerichtstyp')} onOpenInline={handleOpenInline}>
              {p.gerichtstyp
                ? (p.gerichtstyp.charAt(0).toUpperCase() + p.gerichtstyp.slice(1))
                : <span className="obj-value-empty">—</span>}
            </ObjTableRow>
            <ObjTableRow label="Art des Verfahrens" herkunft={h('verfahrensart')} onOpenInline={handleOpenInline}>
              {p.verfahrensart || <span className="obj-value-empty">—</span>}
            </ObjTableRow>
            {p.sache && (
              <ObjTableRow label="Sache" herkunft={h('sache')} onOpenInline={handleOpenInline}>
                {p.sache}
              </ObjTableRow>
            )}
            {p.wegen && (
              <ObjTableRow label="Wegen" herkunft={h('wegen')} onOpenInline={handleOpenInline}>
                {p.wegen}
              </ObjTableRow>
            )}
            <ObjTableRow label="Kostenvorschuss" herkunft={h('kostenvorschuss')} onOpenInline={handleOpenInline}>
              {p.kostenvorschuss ? `€${p.kostenvorschuss.toLocaleString('de-DE')}` : <span className="obj-value-empty">—</span>}
            </ObjTableRow>
            <ObjTableRow label="Ausfertigungen (Druck)" herkunft={h('ausfertigungen')} onOpenInline={handleOpenInline}>
              {p.ausfertigungen}×
            </ObjTableRow>
            <ObjTableRow label="Belastungen Abt. II" herkunft={h('belastungen_abt_2')} onOpenInline={handleOpenInline}>
              {p.belastungenAbt2 || <span className="obj-value-empty">— keine eingetragen —</span>}
            </ObjTableRow>
          </ObjTable>
        </div>
      )}

      <div className="card">
        <div className="card-header">
          <span className="card-title">Nachgelagerte Schritte</span>
          <span style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Widerspruch · Anhörung · Zuschlag</span>
        </div>
        <div style={{ padding: 'var(--space-4)', textAlign: 'center', color: 'var(--text-tertiary)', fontSize: 14 }}>
          Noch nichts erfasst. Felder öffnen sich automatisch, sobald das Gutachten abgegeben wurde.
        </div>
      </div>
    </>
  );

  const beteiligtePanel = (
    <BeteiligtePanel
      beteiligte={p.beteiligte}
      herkunft={herkunft}
      onOpenInline={handleOpenInline}
    />
  );

  // Layout-Modi:
  // A) Viewer aktiv: 3 Spalten wirken zu eng → 2 Spalten: (Daten + Beteiligte darunter) | Viewer
  // B) Viewer inaktiv: bewährtes 2-Spalten-Layout (Daten | Beteiligte)
  return (
    <div className="view-wrapper">
      {viewerActive ? (
        <div style={{
          display: 'grid',
          gridTemplateColumns: '1fr 1fr',
          gap: 'var(--space-5)',
          alignItems: 'start',
        }}>
          <div>
            {dataColumn}
            {beteiligtePanel}
          </div>
          <div style={{ position: 'sticky', top: 'var(--space-4)' }}>
            <DokumentViewer
              dokumente={auftragDokumente}
              session={session}
              workerUrl={workerUrl}
              activeId={viewerDokId}
              onChangeActive={setViewerDokId}
              placeholder="Keine Auftragsdokumente hochgeladen. Gerichtsbeschluss oder Anschreiben laden, um diese hier zu referenzieren."
            />
          </div>
        </div>
      ) : (
        <div className="auftrag-layout">
          <div>{dataColumn}</div>
          {beteiligtePanel}
        </div>
      )}
    </div>
  );
};

const BeteiligtePanel = ({ beteiligte, herkunft, onOpenDocument, onOpenInline }) => (
  <div>
    <div className="card">
      <div className="card-header">
        <span className="card-title">Beteiligte ({beteiligte.length})</span>
      </div>
      <div className="beteiligte-list">
        {beteiligte.map((b, i) => {
          const h = herkunft?.[`beteiligte:${b.id}:_record`];
          return (
            <div key={b.id || i} className="beteiligte-item">
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="beteiligte-rolle">{b.rolle}</div>
                  <div className="beteiligte-name">{b.name}</div>
                  {b.detail && <div className="beteiligte-detail">{b.detail}</div>}
                </div>
                {h && (onOpenDocument || onOpenInline) && (
                  <div style={{ flexShrink: 0 }}>
                    <ProvenanceBadge
                      herkunft={h}
                      onOpen={onOpenDocument}
                      onOpenInline={onOpenInline}
                    />
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>

      <div className="beteiligte-add-menu">
        <div className="beteiligte-add-title">+ Beteiligten hinzufügen</div>
        <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 'var(--space-3)' }}>
          Wähle eine Rolle (Vorschlag) oder gib eine eigene ein.
        </div>
        <div className="beteiligte-add-roles">
          {BETEILIGTE_ROLLEN_VORSCHLAEGE.map(rolle => (
            <button key={rolle} className="beteiligte-role-btn">{rolle}</button>
          ))}
          <button className="beteiligte-role-btn" style={{ color: 'var(--vl-blue-light)' }}>
            + Eigene Rolle
          </button>
        </div>
      </div>
    </div>
  </div>
);

// ══════════════════════════════════════════════════════════════════
// 4.4 · VIEW: GUTACHTEN
// ══════════════════════════════════════════════════════════════════

const GutachtenSwitcher = ({ p, aktiverIdx, onSwitch }) => {
  const [open, setOpen] = useState(false);
  const switcherRef = useRef(null);

  useEffect(() => {
    const onClickOutside = (e) => {
      if (switcherRef.current && !switcherRef.current.contains(e.target)) {
        setOpen(false);
      }
    };
    document.addEventListener('click', onClickOutside);
    return () => document.removeEventListener('click', onClickOutside);
  }, []);

  return (
    <div className="gutachten-switcher" ref={switcherRef}>
      <button
        className="gutachten-switcher-button"
        onClick={(e) => { e.stopPropagation(); setOpen(!open); }}
      >
        Gutachten {aktiverIdx + 1} von {p.gutachten.length}
        <IconChevronDown size={16} />
      </button>
      {open && (
        <div className="gutachten-switcher-dropdown">
          {p.gutachten.map((gg, i) => (
            <div
              key={i}
              className={`gutachten-switcher-item ${i === aktiverIdx ? 'current' : ''}`}
              onClick={() => { onSwitch(i); setOpen(false); }}
            >
              <div className="gutachten-switcher-item-title">
                Gutachten {i + 1} — {gg.titel.split('—')[0].trim()}
              </div>
              <div className="gutachten-switcher-item-sub">{gg.adresse}</div>
            </div>
          ))}
          <div
            className="gutachten-switcher-item gutachten-switcher-item-add"
            onClick={() => { setOpen(false); alert('Prototyp: Legt ein weiteres Gutachten an.'); }}
          >
            + Weiteres Gutachten anlegen
          </div>
        </div>
      )}
    </div>
  );
};

const GutachtenToolbar = ({ p, g, aktiverIdx, onSwitchGutachten, onOpenAuftrag }) => {
  const multiG = p.gutachten.length > 1;
  return (
    <div className="gutachten-toolbar">
      <div className="gutachten-toolbar-title">{g.titel}</div>
      {multiG && <GutachtenSwitcher p={p} aktiverIdx={aktiverIdx} onSwitch={onSwitchGutachten} />}
      <button className="btn btn-ghost" onClick={onOpenAuftrag}>Auftrag öffnen →</button>
    </div>
  );
};

const GutachtenTabs = ({ active, onChange }) => (
  <div className="gutachten-tabs">
    <button
      className={`gutachten-tab ${active === 'unterlagen' ? 'active' : ''}`}
      onClick={() => onChange('unterlagen')}
    >Unterlagen</button>
    <button
      className={`gutachten-tab ${active === 'stammdaten' ? 'active' : ''}`}
      onClick={() => onChange('stammdaten')}
    >Stammdaten</button>
    <button
      className={`gutachten-tab ${active === 'ortstermin' ? 'active' : ''}`}
      onClick={() => onChange('ortstermin')}
    >Ortstermin</button>
    <button className="gutachten-tab disabled">Fotos</button>
    <button className="gutachten-tab disabled">Entwurf</button>
    <button className="gutachten-tab disabled">Gutachten</button>
  </div>
);

const StammdatenTab = ({ g, aktivesObjekt, onSwitchObjekt, herkunft, dokumente, session, workerUrl }) => {
  const multiObj = g.objekte.length > 1;
  const obj = g.objekte[aktivesObjekt];

  // Inline-Viewer-State
  const [viewerDokId, setViewerDokId] = useState(null);
  const viewerActive = !!viewerDokId;

  // Scope-Filter: Gutachten-Dokumente (dieses Gutachten) + Objekt-Dokumente (aktives Objekt)
  const relevanteDokumente = useMemo(() => {
    const all = dokumente || [];
    return all.filter(d => {
      if (d.scope === 'gutachten' && d.gutachten_id === g.id) return true;
      if (d.scope === 'objekt' && obj?.id && d.objekt_id === obj.id) return true;
      return false;
    });
  }, [dokumente, g.id, obj?.id]);

  // Herkunft für Gutachten-Felder
  const gh = (feld) => herkunft?.[`gutachten:${g.id}:${feld}`];
  // Herkunft für Objekt-Felder
  const oh = (feld) => obj?.id ? herkunft?.[`bewertungsobjekte:${obj.id}:${feld}`] : null;

  const handleOpenInline = useCallback((dokumentId) => {
    setViewerDokId(dokumentId);
  }, []);

  const dataColumn = (
    <>
      {multiObj && (
        <div className="objekt-switcher">
          {g.objekte.map((o, i) => (
            <button
              key={o.id || i}
              className={`objekt-chip ${i === aktivesObjekt ? 'active' : ''}`}
              onClick={() => onSwitchObjekt(i)}
            >
              {o.bezeichnung}
            </button>
          ))}
          <button
            className="objekt-chip objekt-chip-add"
            onClick={() => alert('Prototyp: Legt ein weiteres Bewertungsobjekt in diesem Gutachten an.')}
          >
            + Weiteres Objekt
          </button>
        </div>
      )}

      <div className="obj-section">
        <div className="obj-section-title">Lage &amp; Adresse (Gutachten-weit)</div>
        <ObjTable>
          <ObjTableRow label="Objektanschrift" herkunft={gh('adresse')} onOpenInline={handleOpenInline}>
            {g.adresse || <span className="obj-value-empty">—</span>}
          </ObjTableRow>
          <ObjTableRow label="Makrolage">
            <span className="obj-value-empty">— wird beim Entwurf ergänzt —</span>
          </ObjTableRow>
          <ObjTableRow label="Mikrolage">
            <span className="obj-value-empty">— wird beim Entwurf ergänzt —</span>
          </ObjTableRow>
        </ObjTable>
      </div>

      <div className="obj-section">
        <div className="obj-section-title">
          {multiObj ? `Objekt-Stammdaten — ${obj.bezeichnung}` : 'Objekt-Stammdaten'}
        </div>
        <ObjTable>
          <ObjTableRow label="Bezeichnung">{obj.bezeichnung}</ObjTableRow>
          <ObjTableRow label="Objekttyp" herkunft={oh('objekttyp')} onOpenInline={handleOpenInline}>
            {obj.objekttyp}
          </ObjTableRow>
          <ObjTableRow label="Flur / Flurstück" herkunft={oh('flur') || oh('flurstueck')} onOpenInline={handleOpenInline}>
            {obj.flur || '—'} / {obj.flurstueck || '—'}
          </ObjTableRow>
          <ObjTableRow label="Gemarkung" herkunft={oh('gemarkung')} onOpenInline={handleOpenInline}>
            {obj.gemarkung || <span className="obj-value-empty">—</span>}
          </ObjTableRow>
          {obj.groesse && (
            <ObjTableRow label="Grundstücksgröße" herkunft={oh('groesse_qm')} onOpenInline={handleOpenInline}>
              {obj.groesse}
            </ObjTableRow>
          )}
          <ObjTableRow label="Wohn-/Nutzfläche" herkunft={oh('wohnflaeche')} onOpenInline={handleOpenInline}>
            {obj.wohnflaeche || <span className="obj-value-empty">—</span>}
          </ObjTableRow>
          {obj.mea && (
            <ObjTableRow label="Miteigentumsanteile (MEA)" herkunft={oh('mea')} onOpenInline={handleOpenInline}>
              {obj.mea}
            </ObjTableRow>
          )}
          {obj.bruchteilseigentum && (
            <ObjTableRow label="Bruchteilseigentum" herkunft={oh('bruchteilseigentum')} onOpenInline={handleOpenInline}>
              {obj.bruchteilseigentum}
            </ObjTableRow>
          )}
          <ObjTableRow label="Baujahr" herkunft={oh('baujahr')} onOpenInline={handleOpenInline}>
            {obj.baujahr || <span className="obj-value-empty">—</span>}
          </ObjTableRow>
          <ObjTableRow label="Eintragungen Abt. II" herkunft={oh('abt_2_eintragungen')} onOpenInline={handleOpenInline}>
            {obj.abt_2_eintragungen || <span className="obj-value-empty">— keine —</span>}
          </ObjTableRow>
        </ObjTable>
      </div>
    </>
  );

  // Layout
  if (viewerActive) {
    return (
      <div style={{
        display: 'grid',
        gridTemplateColumns: '1fr 1fr',
        gap: 'var(--space-5)',
        alignItems: 'start',
      }}>
        <div>{dataColumn}</div>
        <div style={{ position: 'sticky', top: 'var(--space-4)' }}>
          <DokumentViewer
            dokumente={relevanteDokumente}
            session={session}
            workerUrl={workerUrl}
            activeId={viewerDokId}
            onChangeActive={setViewerDokId}
            placeholder={
              multiObj
                ? `Keine passenden Dokumente für ${obj?.bezeichnung || 'dieses Objekt'}. Grundbuch, Baupläne oder andere objektspezifische Unterlagen laden.`
                : 'Keine Gutachten- oder Objekt-Dokumente vorhanden. Grundbuch, Baupläne oder Teilungserklärung laden, um diese hier zu referenzieren.'
            }
          />
        </div>
      </div>
    );
  }
  return <>{dataColumn}</>;
};

const UnterlagenTab = ({ onUpload }) => {
  const rows = [
    { name: 'Teilungserklaerung_Mstr12.pdf', typ: 'Teilungserklärung', status: 'done' },
    { name: 'Bebauungsplan', typ: 'Bebauungsplan (Auszug Stadtplanungsamt)', status: 'fehlt' },
    { name: 'Lagekarte', typ: 'Lagekarte Makrolage (BayernAtlas-Export)', status: 'done' },
  ];
  return (
    <>
      <PrototypeHint>
        <strong>Gutachten-weite Unterlagen:</strong> Hier werden Unterlagen hochgeladen, die für dieses Gutachten als Ganzes gelten — Teilungserklärung, Lagekarte, Bebauungsplan, Altlastenauskunft. Objektspezifische Unterlagen (Grundbuch, Baupläne, Mietverträge) gehören in den Stammdaten-Tab je Bewertungsobjekt.
      </PrototypeHint>

      <div className="card">
        <div className="card-header">
          <span className="card-title">Unterlagen (gutachtenweit)</span>
          <button className="btn btn-accent btn-sm" onClick={onUpload}>+ Hinzufügen</button>
        </div>
        <div className="doc-list">
          {rows.map((r, i) => (
            <div key={i} className="doc-row">
              <div className="doc-icon"><IconDocument size={20} /></div>
              <div>
                <div className="doc-name">{r.name}</div>
                <div className="doc-meta">{r.typ}</div>
              </div>
              <span className="doc-scope doc-scope-gutachten">Gutachten</span>
              <span className={`doc-status doc-status-${r.status}`}>
                {r.status === 'done' ? '✓ vorhanden' : 'fehlt'}
              </span>
              <IconChevronRight size={16} stroke="var(--text-tertiary)" />
            </div>
          ))}
        </div>
      </div>
    </>
  );
};

// ══════════════════════════════════════════════════════════════════
// ORTSTERMIN-TAB: vollständige Implementierung mit Voice/Rundgang
// ══════════════════════════════════════════════════════════════════

// ────────────────────────────────────────────────────────────────────
// Dokument-Viewer (iframe mit Signed URL)
// Controlled: activeId + onChangeActive Props steuern, welches Dokument gezeigt wird.
// Fallback: wenn activeId undefined, nutzt internen State + Auto-Select.
// ────────────────────────────────────────────────────────────────────
const DokumentViewer = ({ dokumente, session, workerUrl, activeId, onChangeActive, placeholder }) => {
  const [internalId, setInternalId] = useState(null);
  const [url, setUrl] = useState(null);
  const [loading, setLoading] = useState(false);

  // Controlled vs uncontrolled: activeId hat Vorrang wenn explizit gesetzt
  const isControlled = activeId !== undefined;
  const selectedId = isControlled ? activeId : internalId;
  const setSelectedId = isControlled
    ? (id) => onChangeActive?.(id)
    : setInternalId;

  useEffect(() => {
    if (!selectedId) { setUrl(null); return; }
    let active = true;
    setLoading(true);
    ladeDokumentUrl(selectedId, session, workerUrl)
      .then(u => { if (active) { setUrl(u); setLoading(false); } })
      .catch(() => { if (active) setLoading(false); });
    return () => { active = false; };
  }, [selectedId, session, workerUrl]);

  // Auto-select nur im uncontrolled-Modus
  useEffect(() => {
    if (isControlled) return;
    if (dokumente.length > 0 && !internalId) {
      const preferred = dokumente.find(d => d.scope === 'objekt' || d.scope === 'gutachten')
                     || dokumente[0];
      if (preferred?.id) setInternalId(preferred.id);
    }
  }, [dokumente, internalId, isControlled]);

  if (dokumente.length === 0) {
    return (
      <div style={{
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        padding: 'var(--space-8)', color: 'var(--text-tertiary)', fontSize: 14,
        background: 'var(--surface-light)', borderRadius: 'var(--radius-md)',
        height: 'calc(100vh - 160px)', minHeight: 400, border: '1px dashed var(--border-light)',
      }}>
        <IconDocument size={48} stroke="var(--text-tertiary)" />
        <div style={{ marginTop: 'var(--space-3)', fontSize: 14 }}>
          Noch keine passenden Dokumente
        </div>
        <div style={{ fontSize: 12, marginTop: 4, textAlign: 'center', maxWidth: 260 }}>
          {placeholder || 'Lade ein passendes Dokument hoch, um es hier zu referenzieren.'}
        </div>
      </div>
    );
  }

  if (!selectedId) {
    return (
      <div style={{
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        padding: 'var(--space-8)', color: 'var(--text-tertiary)', fontSize: 14,
        background: 'var(--surface-light)', borderRadius: 'var(--radius-md)',
        height: 'calc(100vh - 160px)', minHeight: 400, border: '1px dashed var(--border-light)',
      }}>
        <IconEye size={40} stroke="var(--text-tertiary)" />
        <div style={{ marginTop: 'var(--space-3)', fontSize: 14, textAlign: 'center', maxWidth: 280 }}>
          {placeholder || 'Klicke links auf einen Dokument-Verweis, um ihn hier anzuzeigen.'}
        </div>
      </div>
    );
  }

  return (
    <div style={{
      display: 'flex', flexDirection: 'column',
      height: 'calc(100vh - 160px)', minHeight: 500,
      border: '1px solid var(--border-light)',
      borderRadius: 'var(--radius-md)',
      background: 'var(--surface-light)', overflow: 'hidden',
    }}>
      <div style={{
        padding: 'var(--space-2) var(--space-3)',
        borderBottom: '1px solid var(--border-light)',
        background: 'var(--surface)',
        display: 'flex', gap: 'var(--space-2)', alignItems: 'center',
      }}>
        <label style={{ fontSize: 12, color: 'var(--text-secondary)', whiteSpace: 'nowrap' }}>
          Dokument:
        </label>
        <select
          className="form-select"
          value={selectedId || ''}
          onChange={e => setSelectedId(e.target.value || null)}
          style={{ flex: 1, fontSize: 13, padding: '4px 8px' }}
        >
          {dokumente.map(d => (
            <option key={d.id} value={d.id}>
              {d.label} — {d.typ}
            </option>
          ))}
        </select>
        {isControlled && (
          <button
            type="button"
            onClick={() => setSelectedId(null)}
            title="Viewer schließen"
            style={{
              background: 'none', border: '1px solid var(--border-light)',
              borderRadius: 'var(--radius-sm)', padding: '4px 6px', cursor: 'pointer',
              color: 'var(--text-secondary)',
            }}
          >
            <IconClose size={14} />
          </button>
        )}
      </div>
      <div style={{ flex: 1, background: '#fafaf8', position: 'relative', minHeight: 0 }}>
        {loading && (
          <div style={{
            position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: 'var(--text-tertiary)', fontSize: 13,
          }}>
            Lade Dokument…
          </div>
        )}
        {url && (
          <>
            <iframe
              key={url}
              src={url}
              style={{
                display: 'block',
                width: '100%',
                height: '100%',
                border: 'none',
                background: '#fafaf8',
              }}
              title="Dokument"
            />
            <a
              href={url}
              target="_blank"
              rel="noopener noreferrer"
              title="In neuem Tab öffnen"
              style={{
                position: 'absolute', top: 8, right: 8,
                background: 'rgba(255,255,255,0.95)',
                border: '1px solid var(--border-light)',
                borderRadius: 'var(--radius-sm)',
                padding: '6px 10px', fontSize: 11, fontWeight: 600,
                color: 'var(--vl-blue, #0A2540)',
                textDecoration: 'none',
                display: 'inline-flex', alignItems: 'center', gap: 4,
                boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
              }}
            >
              <IconExternalLink size={12} />
              Neuer Tab
            </a>
          </>
        )}
      </div>
    </div>
  );
};

// ────────────────────────────────────────────────────────────────────
// Einzelne Notiz-Card (mit Edit, Delete, Foto-Anzeige)
// ────────────────────────────────────────────────────────────────────
const NoteCard = ({ note, session, workerUrl, onEdit, onDelete, objekte }) => {
  const [editing, setEditing] = useState(false);
  const [editText, setEditText] = useState(note.text || '');
  const [photoUrl, setPhotoUrl] = useState(null);

  // Foto-URL lazily laden (signed URL)
  useEffect(() => {
    if (note.type !== 'photo' || !note.image_url) return;
    let active = true;
    (async () => {
      const sb = await initSupabase();
      const { data } = await sb.storage.from('ortstermin-photos').createSignedUrl(note.image_url, 600);
      if (active && data?.signedUrl) setPhotoUrl(data.signedUrl);
    })();
    return () => { active = false; };
  }, [note.image_url, note.type]);

  const kapitel = VL_KAPITEL_TAGS.find(k => k.id === note.kapitel);
  const objekt = note.objekt_id ? objekte.find(o => o.id === note.objekt_id) : null;

  const handleSaveEdit = async () => {
    if (editText.trim() === (note.text || '').trim()) {
      setEditing(false);
      return;
    }
    try {
      await onEdit(note.id, { text: editText.trim() });
      setEditing(false);
    } catch (e) {
      alert('Fehler beim Speichern: ' + (e.message || e));
    }
  };

  return (
    <div style={{
      padding: 'var(--space-3) var(--space-4)',
      background: 'var(--surface)',
      border: '1px solid var(--border-light)',
      borderRadius: 'var(--radius-md)',
      marginBottom: 'var(--space-2)',
    }}>
      <div style={{
        display: 'flex', gap: 'var(--space-2)', alignItems: 'center',
        marginBottom: 'var(--space-2)', flexWrap: 'wrap', fontSize: 11,
      }}>
        {kapitel && (
          <Pill variant="blue" style={{ fontSize: 10 }}>
            {kapitel.label}
          </Pill>
        )}
        {objekt && (
          <Pill variant="orange" style={{ fontSize: 10 }}>
            {objekt.bezeichnung}
          </Pill>
        )}
        {note.type === 'photo' && (
          <Pill style={{ fontSize: 10 }}>Foto</Pill>
        )}
        {note.type === 'voice' && (
          <Pill style={{ fontSize: 10 }}>Voice</Pill>
        )}
        <span style={{ marginLeft: 'auto', color: 'var(--text-tertiary)' }}>
          {new Date(note.created_at).toLocaleString('de-DE', {
            day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit',
          })}
        </span>
      </div>

      {photoUrl && (
        <img src={photoUrl} alt="Notiz-Foto" style={{
          maxWidth: '100%', maxHeight: 240, borderRadius: 'var(--radius-sm)',
          marginBottom: note.text ? 'var(--space-2)' : 0,
        }} />
      )}

      {editing ? (
        <>
          <textarea
            value={editText}
            onChange={e => setEditText(e.target.value)}
            style={{
              width: '100%', minHeight: 80, padding: 'var(--space-2)',
              border: '1px solid var(--border-light)', borderRadius: 'var(--radius-sm)',
              fontFamily: 'inherit', fontSize: 14, resize: 'vertical',
            }}
            autoFocus
          />
          <div style={{ display: 'flex', gap: 'var(--space-2)', marginTop: 'var(--space-2)' }}>
            <button className="btn btn-primary btn-sm" onClick={handleSaveEdit}>Speichern</button>
            <button className="btn btn-ghost btn-sm" onClick={() => { setEditing(false); setEditText(note.text || ''); }}>
              Abbrechen
            </button>
          </div>
        </>
      ) : (
        <>
          <div style={{
            fontSize: 14, lineHeight: 1.5, whiteSpace: 'pre-wrap',
            color: note.text ? 'var(--text-primary)' : 'var(--text-tertiary)',
          }}>
            {note.text || '(Leere Notiz)'}
          </div>
          <div style={{
            display: 'flex', gap: 'var(--space-2)', marginTop: 'var(--space-2)',
            fontSize: 11, color: 'var(--text-tertiary)',
          }}>
            <button
              onClick={() => setEditing(true)}
              style={{ background: 'none', border: 'none', color: 'var(--vl-blue-light)', cursor: 'pointer', fontSize: 11 }}
            >
              Bearbeiten
            </button>
            <button
              onClick={() => {
                if (confirm('Notiz löschen?')) onDelete(note.id);
              }}
              style={{ background: 'none', border: 'none', color: 'var(--danger)', cursor: 'pointer', fontSize: 11 }}
            >
              Löschen
            </button>
          </div>
        </>
      )}
    </div>
  );
};

// ────────────────────────────────────────────────────────────────────
// Live-Recording-Overlay während Einzel-Notiz oder Rundgang
// ────────────────────────────────────────────────────────────────────
const RecordingOverlay = ({ voice, rundgang, mode, onStop, kontextGutachten, objekte }) => {
  const [elapsed, setElapsed] = useState(0);

  useEffect(() => {
    if (!voice.isRec) return;
    const interval = setInterval(() => {
      setElapsed((Date.now() - (rundgang.getCurrentAudioPositionMs() ? Date.now() - rundgang.getCurrentAudioPositionMs() : Date.now())) / 1000 | 0);
    }, 1000);
    return () => clearInterval(interval);
  }, [voice.isRec, rundgang]);

  // Forward live text to Rundgang
  useEffect(() => {
    if (mode === 'rundgang') {
      rundgang.onLiveText(voice.text);
    }
  }, [voice.text, mode, rundgang]);

  const currentKapitel = rundgang.currentTagId
    ? VL_KAPITEL_TAGS.find(k => k.id === rundgang.currentTagId)
    : null;
  const currentObjekt = rundgang.currentObjektId
    ? objekte.find(o => o.id === rundgang.currentObjektId)
    : null;

  return (
    <div style={{
      position: 'fixed',
      bottom: 0, left: 0, right: 0,
      background: 'var(--vl-blue-dark, #0A2540)',
      color: 'white',
      padding: 'var(--space-4) var(--space-5)',
      boxShadow: '0 -8px 24px rgba(0,0,0,0.3)',
      zIndex: 100,
      borderTop: '3px solid var(--vl-orange)',
    }}>
      <div style={{
        maxWidth: 1200, margin: '0 auto',
        display: 'grid', gridTemplateColumns: 'auto 1fr auto', gap: 'var(--space-4)',
        alignItems: 'center',
      }}>
        <div>
          <div style={{
            width: 48, height: 48, borderRadius: 24,
            background: '#DC2626', display: 'flex', alignItems: 'center', justifyContent: 'center',
            animation: 'pulse 1.5s infinite',
          }}>
            <div style={{ width: 16, height: 16, borderRadius: 8, background: 'white' }} />
          </div>
        </div>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 12, opacity: 0.7, marginBottom: 2 }}>
            {mode === 'rundgang' ? 'Rundgang läuft' : 'Aufnahme läuft'}
            {currentKapitel && <span> · {currentKapitel.icon} {currentKapitel.label}</span>}
            {currentObjekt && <span> · Objekt: {currentObjekt.bezeichnung}</span>}
          </div>
          <div style={{
            fontSize: 14, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
          }}>
            {voice.text || voice.interim || <em style={{ opacity: 0.5 }}>Hört zu…</em>}
          </div>
        </div>
        <button
          onClick={onStop}
          className="btn"
          style={{
            background: '#DC2626', color: 'white', fontWeight: 600,
            padding: '10px 18px', fontSize: 14, border: 'none',
            borderRadius: 'var(--radius-md)', cursor: 'pointer',
          }}
        >
          Stop
        </button>
      </div>
    </div>
  );
};

// ────────────────────────────────────────────────────────────────────
// Rundgang-Ergebnis-Review (nach Stop, vor Speichern)
// ────────────────────────────────────────────────────────────────────
const RundgangResult = ({ splitResult, objekte, onAccept, onDiscard }) => {
  const [edited, setEdited] = useState(() => splitResult.segments.map(s => ({ ...s })));

  const updateSegment = (idx, patch) => {
    setEdited(prev => prev.map((s, i) => i === idx ? { ...s, ...patch } : s));
  };

  const removeSegment = (idx) => {
    setEdited(prev => prev.filter((_, i) => i !== idx));
  };

  return (
    <div style={{ padding: 'var(--space-4)' }}>
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        marginBottom: 'var(--space-4)',
      }}>
        <div>
          <h3 style={{ fontSize: 18, fontWeight: 600, marginBottom: 4 }}>
            Rundgang-Ergebnis prüfen
          </h3>
          <div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
            {edited.length} Segment{edited.length !== 1 ? 'e' : ''} erkannt.
            Prüfe Inhalt und Zuordnung, korrigiere wenn nötig.
          </div>
        </div>
      </div>

      {splitResult.todos && splitResult.todos.length > 0 && (
        <div style={{
          padding: 'var(--space-3)', marginBottom: 'var(--space-4)',
          background: 'var(--warning-bg, #FEF3C7)',
          border: '1px solid var(--warning, #F59E0B)',
          borderRadius: 'var(--radius-md)',
        }}>
          <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--warning)', marginBottom: 4 }}>
            {splitResult.todos.length} offene Punkt{splitResult.todos.length !== 1 ? 'e' : ''} erkannt
          </div>
          {splitResult.todos.map((t, i) => (
            <div key={i} style={{ fontSize: 13, marginTop: 2 }}>→ {t.text}</div>
          ))}
        </div>
      )}

      {edited.map((seg, idx) => {
        const kapitel = VL_KAPITEL_TAGS.find(k => k.id === seg.tagId);
        return (
          <div key={idx} style={{
            padding: 'var(--space-3)',
            background: 'var(--surface)',
            border: '1px solid var(--border-light)',
            borderRadius: 'var(--radius-md)',
            marginBottom: 'var(--space-3)',
          }}>
            <div style={{
              display: 'flex', gap: 'var(--space-2)', alignItems: 'center',
              marginBottom: 'var(--space-2)', flexWrap: 'wrap',
            }}>
              <select
                value={seg.tagId || 'aussen'}
                onChange={e => updateSegment(idx, { tagId: e.target.value })}
                style={{
                  padding: '4px 8px', fontSize: 12,
                  border: '1px solid var(--border-light)',
                  borderRadius: 'var(--radius-sm)',
                }}
              >
                {VL_KAPITEL_TAGS.map(k => (
                  <option key={k.id} value={k.id}>{k.label}</option>
                ))}
              </select>

              {objekte.length > 1 && (
                <select
                  value={seg.objektId || ''}
                  onChange={e => updateSegment(idx, { objektId: e.target.value || null })}
                  style={{
                    padding: '4px 8px', fontSize: 12,
                    border: '1px solid var(--border-light)',
                    borderRadius: 'var(--radius-sm)',
                  }}
                >
                  <option value="">— kein Objekt —</option>
                  {objekte.map(o => (
                    <option key={o.id} value={o.id}>{o.bezeichnung}</option>
                  ))}
                </select>
              )}

              {seg.isBeurteilung && (
                <Pill variant="orange" style={{ fontSize: 10 }}>Beurteilung</Pill>
              )}

              <button
                onClick={() => removeSegment(idx)}
                style={{
                  marginLeft: 'auto', background: 'none', border: 'none',
                  color: 'var(--danger)', cursor: 'pointer', fontSize: 12,
                }}
              >
                Verwerfen
              </button>
            </div>
            <textarea
              value={seg.text || ''}
              onChange={e => updateSegment(idx, { text: e.target.value })}
              style={{
                width: '100%', minHeight: 80, padding: 'var(--space-2)',
                border: '1px solid var(--border-light)', borderRadius: 'var(--radius-sm)',
                fontFamily: 'inherit', fontSize: 13, resize: 'vertical',
                background: 'var(--surface-light)',
              }}
            />
          </div>
        );
      })}

      <div style={{
        display: 'flex', gap: 'var(--space-3)', marginTop: 'var(--space-4)',
        justifyContent: 'flex-end',
      }}>
        <button className="btn btn-ghost" onClick={onDiscard}>
          Alles verwerfen
        </button>
        <button
          className="btn btn-primary"
          onClick={() => onAccept(edited)}
          disabled={edited.length === 0}
        >
          {edited.length} Notiz{edited.length !== 1 ? 'en' : ''} speichern
        </button>
      </div>
    </div>
  );
};

// ────────────────────────────────────────────────────────────────────
// Haupt-OrtsterminTab: Split-Screen mit Notes + Dokument
// ────────────────────────────────────────────────────────────────────
const OrtsterminTab = ({ p, g, aktiverGutachtenIdx, session, workerUrl, userProfile }) => {
  const multiG = p.gutachten.length > 1;
  const objekte = g.objekte || [];
  const objekteForRundgang = objekte.length > 0 ? objekte : [];

  const [notes, setNotes] = useState([]);
  const [notesLoading, setNotesLoading] = useState(false);
  const [mode, setMode] = useState('idle');  // 'idle' | 'recording-single' | 'recording-rundgang' | 'processing' | 'review'
  const [splitResult, setSplitResult] = useState(null);

  // Kontext für Voice-Hook
  const recentNotesForContext = useMemo(
    () => notes.slice(-3).map(n => n.text).filter(Boolean),
    [notes]
  );

  const getTranscribeContext = useCallback(() => ({
    profession: 'bewertung',
    objectType: objekte[0]?.bezeichnung || '',
    projectAddress: g.adresse || '',
    recentNotes: recentNotesForContext,
  }), [g.adresse, objekte, recentNotesForContext]);

  const voice = useVoice(null, getTranscribeContext, workerUrl, session);
  const rundgang = useRundgang({
    availableTags: VL_KAPITEL_TAGS,
    kapitelTags: VL_KAPITEL_TAGS,
  });

  // Objekte für Rundgang-Detection setzen
  useEffect(() => {
    rundgang.setAvailableObjekte(objekteForRundgang);
  }, [objekteForRundgang, rundgang]);

  // Notes initial laden
  useEffect(() => {
    if (!g.id) return;
    let active = true;
    setNotesLoading(true);
    ladeNotes(g.id)
      .then(rows => { if (active) { setNotes(rows); setNotesLoading(false); } })
      .catch(err => { console.warn(err); if (active) setNotesLoading(false); });
    return () => { active = false; };
  }, [g.id]);

  const refreshNotes = async () => {
    const rows = await ladeNotes(g.id);
    setNotes(rows);
  };

  // ─── Einzel-Notiz starten ───
  const startSingleRecording = async () => {
    rundgang.reset();
    voice.reset();
    setMode('recording-single');
    await voice.start();
  };

  // ─── Rundgang starten ───
  const startRundgangRecording = async () => {
    rundgang.reset();
    voice.reset();
    rundgang.onStart();
    setMode('recording-rundgang');
    await voice.start();
  };

  // ─── Aufnahme stoppen ───
  const stopRecording = async () => {
    const prevMode = mode;
    setMode('processing');
    const result = await voice.stop();
    const finalText = result?.text || voice.text || '';

    if (prevMode === 'recording-single') {
      // Einzelnotiz direkt speichern
      if (finalText.trim().length > 0) {
        try {
          await insertNote({
            project_id: p.id,
            gutachten_id: g.id,
            objekt_id: objekte.length === 1 ? objekte[0].id : null,
            kapitel: rundgang.currentTagId || null,
            type: 'voice',
            text: finalText.trim(),
            user_id: userProfile?.id || null,
            sort_order: notes.length,
          });
          await refreshNotes();
        } catch (err) {
          alert('Fehler beim Speichern: ' + (err.message || err));
        }
      }
      setMode('idle');
      voice.reset();
      return;
    }

    if (prevMode === 'recording-rundgang') {
      // KI-Split auslösen
      if (finalText.trim().length < 50) {
        alert('Rundgang zu kurz (< 50 Zeichen). Bitte länger aufnehmen.');
        setMode('idle');
        voice.reset();
        return;
      }
      try {
        const res = await splitRundgangMitKI(
          finalText,
          rundgang.segments,
          rundgang.markers,
          {
            objectType: objekte[0]?.bezeichnung || '',
            address: g.adresse || '',
            objekte: objekte.map(o => ({ id: o.id, bezeichnung: o.bezeichnung })),
          },
          session, workerUrl
        );
        setSplitResult(res);
        setMode('review');
      } catch (err) {
        console.error('[Rundgang] Split-Fehler:', err);
        alert('KI-Split fehlgeschlagen, Aufnahme-Text bleibt erhalten. Als Einzelnotiz speichern?');
        setMode('idle');
      }
    }
  };

  // ─── Rundgang-Ergebnis akzeptieren ───
  const acceptRundgangResult = async (segments) => {
    try {
      const rows = segments.map((s, i) => ({
        project_id: p.id,
        gutachten_id: g.id,
        objekt_id: s.objektId || (objekte.length === 1 ? objekte[0].id : null),
        kapitel: s.tagId || null,
        type: 'voice',
        text: s.text,
        user_id: userProfile?.id || null,
        sort_order: notes.length + i,
      }));
      await insertNotesBulk(rows);

      // Todos optional als eigene Notes speichern
      if (splitResult.todos && splitResult.todos.length > 0) {
        const todoRows = splitResult.todos.map((t, i) => ({
          project_id: p.id,
          gutachten_id: g.id,
          kapitel: 'maengel',
          type: 'text',
          text: 'TODO: ' + t.text,
          user_id: userProfile?.id || null,
          sort_order: notes.length + segments.length + i,
        }));
        await insertNotesBulk(todoRows);
      }

      await refreshNotes();
      setSplitResult(null);
      setMode('idle');
      voice.reset();
      rundgang.reset();
    } catch (err) {
      alert('Fehler beim Speichern: ' + (err.message || err));
    }
  };

  const discardRundgangResult = () => {
    if (!confirm('Alle Rundgang-Segmente verwerfen?')) return;
    setSplitResult(null);
    setMode('idle');
    voice.reset();
    rundgang.reset();
  };

  // ─── Foto-Aufnahme ───
  const fileInputRef = useRef(null);
  const handlePhotoCapture = async (e) => {
    const file = e.target.files?.[0];
    e.target.value = '';
    if (!file) return;
    try {
      const blob = await compressImage(file, 1600, 0.82);
      const upResult = await uploadPhotoBlob(blob, p.id, g.id, session, workerUrl);
      await insertNote({
        project_id: p.id,
        gutachten_id: g.id,
        objekt_id: objekte.length === 1 ? objekte[0].id : null,
        type: 'photo',
        image_url: upResult.storage_path,
        user_id: userProfile?.id || null,
        sort_order: notes.length,
      });
      await refreshNotes();
    } catch (err) {
      alert('Foto-Upload fehlgeschlagen: ' + (err.message || err));
    }
  };

  // ─── Edit/Delete handlers ───
  const handleEditNote = async (id, patch) => {
    await updateNote(id, patch);
    await refreshNotes();
  };
  const handleDeleteNote = async (id) => {
    await deleteNote(id);
    await refreshNotes();
  };

  // ─── Notes nach Kapitel gruppieren ───
  const notesByKapitel = useMemo(() => {
    const groups = {};
    for (const n of notes) {
      const key = n.kapitel || '_none';
      if (!groups[key]) groups[key] = [];
      groups[key].push(n);
    }
    return groups;
  }, [notes]);

  const kapitelList = VL_KAPITEL_TAGS.filter(k => (notesByKapitel[k.id] || []).length > 0);
  const hasUnkategorisiert = (notesByKapitel['_none'] || []).length > 0;

  const disabled = mode !== 'idle';
  const recording = mode === 'recording-single' || mode === 'recording-rundgang';

  return (
    <>
      {multiG && (
        <PrototypeHint>
          <strong>Kontext:</strong> Alle Aufnahmen und Fotos in diesem Tab werden <strong>Gutachten {aktiverGutachtenIdx + 1}</strong> ({g.adresse.split(',').pop()?.trim() || g.adresse}) zugeordnet.
        </PrototypeHint>
      )}

      {mode === 'review' && splitResult ? (
        <div className="card">
          <RundgangResult
            splitResult={splitResult}
            objekte={objekte}
            onAccept={acceptRundgangResult}
            onDiscard={discardRundgangResult}
          />
        </div>
      ) : (
        <>
          {/* Toolbar */}
          <div className="card" style={{ marginBottom: 'var(--space-4)' }}>
            <div className="card-header">
              <span className="card-title">Ortstermin — {g.adresse || 'Ohne Adresse'}</span>
              <Pill variant={g.ortsterminStatus === 'begangen' ? 'success' : ''}>
                {g.ortsterminStatus || 'geplant'}
              </Pill>
            </div>
            <div style={{
              padding: 'var(--space-3) var(--space-4)',
              display: 'flex', gap: 'var(--space-3)', flexWrap: 'wrap',
            }}>
              <button
                className="btn btn-primary"
                onClick={startSingleRecording}
                disabled={disabled}
                style={{ minWidth: 180, display: 'inline-flex', alignItems: 'center', gap: 8 }}
              >
                <IconMic size={16} />
                Einzelne Notiz aufnehmen
              </button>
              <button
                className="btn btn-accent"
                onClick={startRundgangRecording}
                disabled={disabled}
                style={{ minWidth: 180, display: 'inline-flex', alignItems: 'center', gap: 8 }}
              >
                <IconRoute size={16} />
                Rundgang starten
              </button>
              <input
                ref={fileInputRef}
                type="file"
                accept="image/*"
                capture="environment"
                style={{ display: 'none' }}
                onChange={handlePhotoCapture}
              />
              <button
                className="btn btn-secondary"
                onClick={() => fileInputRef.current?.click()}
                disabled={disabled}
                style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}
              >
                <IconCamera size={16} />
                Foto
              </button>

              {mode === 'processing' && (
                <div style={{
                  marginLeft: 'auto', padding: '10px 16px',
                  fontSize: 13, color: 'var(--text-secondary)',
                  display: 'flex', alignItems: 'center', gap: 'var(--space-2)',
                }}>
                  <div style={{
                    width: 14, height: 14, borderRadius: 7,
                    border: '2px solid var(--border)', borderTopColor: 'var(--vl-blue)',
                    animation: 'spin 0.8s linear infinite',
                  }} />
                  {voice.isTranscribing ? 'Transkription läuft…' : 'Verarbeitung…'}
                </div>
              )}
            </div>

            {voice.micError && (
              <div style={{
                padding: 'var(--space-3) var(--space-4)',
                margin: '0 var(--space-4) var(--space-4)',
                background: 'var(--danger-bg)', color: 'var(--danger)',
                borderRadius: 'var(--radius-sm)', fontSize: 13,
              }}>
                {voice.micError === 'permission'
                  ? 'Mikrofon-Zugriff verweigert. Bitte in den Browser-Einstellungen erlauben und Seite neu laden.'
                  : voice.micError === 'browser'
                  ? 'Dein Browser unterstützt keine Spracherkennung. Für Voice-Memos bitte Chrome oder Edge verwenden.'
                  : 'Mikrofon nicht verfügbar.'}
                <button
                  onClick={voice.clearMicError}
                  style={{ marginLeft: 'var(--space-2)', background: 'none', border: 'none', color: 'var(--danger)', cursor: 'pointer', textDecoration: 'underline' }}
                >
                  Schließen
                </button>
              </div>
            )}
          </div>

          {/* Notes — volle Breite */}
          <div className="card" style={{ minHeight: 400 }}>
            <div className="card-header">
              <span className="card-title">Notizen ({notes.length})</span>
            </div>
            <div style={{ padding: 'var(--space-4)' }}>
              {notesLoading ? (
                <div style={{ textAlign: 'center', color: 'var(--text-tertiary)', padding: 'var(--space-5)' }}>
                  Lade…
                </div>
              ) : notes.length === 0 ? (
                <div style={{ textAlign: 'center', color: 'var(--text-tertiary)', padding: 'var(--space-5)', fontSize: 14 }}>
                  Noch keine Notizen. Starte eine Aufnahme oder mache ein Foto.
                </div>
              ) : (
                <>
                  {kapitelList.map(kap => (
                    <div key={kap.id} style={{ marginBottom: 'var(--space-4)' }}>
                      <div style={{
                        fontSize: 11, fontWeight: 700, textTransform: 'uppercase',
                        letterSpacing: '0.08em', color: 'var(--text-secondary)',
                        marginBottom: 'var(--space-2)',
                      }}>
                        {kap.label} ({notesByKapitel[kap.id].length})
                      </div>
                      {notesByKapitel[kap.id].map(n => (
                        <NoteCard
                          key={n.id}
                          note={n}
                          session={session}
                          workerUrl={workerUrl}
                          objekte={objekte}
                          onEdit={handleEditNote}
                          onDelete={handleDeleteNote}
                        />
                      ))}
                    </div>
                  ))}
                  {hasUnkategorisiert && (
                    <div>
                      <div style={{
                        fontSize: 11, fontWeight: 700, textTransform: 'uppercase',
                        letterSpacing: '0.08em', color: 'var(--text-secondary)',
                        marginBottom: 'var(--space-2)',
                      }}>
                        Unkategorisiert ({notesByKapitel['_none'].length})
                      </div>
                      {notesByKapitel['_none'].map(n => (
                        <NoteCard
                          key={n.id}
                          note={n}
                          session={session}
                          workerUrl={workerUrl}
                          objekte={objekte}
                          onEdit={handleEditNote}
                          onDelete={handleDeleteNote}
                        />
                      ))}
                    </div>
                  )}
                </>
              )}
            </div>
          </div>
        </>
      )}

      {/* Recording Overlay — unten fixiert während Aufnahme */}
      {recording && (
        <RecordingOverlay
          voice={voice}
          rundgang={rundgang}
          mode={mode === 'recording-rundgang' ? 'rundgang' : 'single'}
          onStop={stopRecording}
          kontextGutachten={g}
          objekte={objekte}
        />
      )}

      <style>{`
        @keyframes pulse {
          0%, 100% { opacity: 1; }
          50% { opacity: 0.6; }
        }
        @keyframes spin {
          from { transform: rotate(0deg); }
          to { transform: rotate(360deg); }
        }
      `}</style>
    </>
  );
};

const GutachtenView = ({
  p, aktiverGutachtenIdx, aktivesObjekt, gutachtenTab,
  onSwitchGutachten, onSwitchObjekt, onSwitchTab,
  onOpenAuftrag, onOpenUpload,
  herkunft, onOpenDocument,
  session, workerUrl, userProfile,
}) => {
  const g = p.gutachten[aktiverGutachtenIdx];

  return (
    <div className="view-wrapper">
      <GutachtenToolbar
        p={p} g={g} aktiverIdx={aktiverGutachtenIdx}
        onSwitchGutachten={onSwitchGutachten}
        onOpenAuftrag={onOpenAuftrag}
      />
      <GutachtenTabs active={gutachtenTab} onChange={onSwitchTab} />
      <div>
        {gutachtenTab === 'stammdaten' && (
          <StammdatenTab
            g={g}
            aktivesObjekt={aktivesObjekt}
            onSwitchObjekt={onSwitchObjekt}
            herkunft={herkunft}
            dokumente={p.dokumente}
            session={session}
            workerUrl={workerUrl}
          />
        )}
        {gutachtenTab === 'unterlagen' && <UnterlagenTab onUpload={onOpenUpload} />}
        {gutachtenTab === 'ortstermin' && (
          <OrtsterminTab
            p={p}
            g={g}
            aktiverGutachtenIdx={aktiverGutachtenIdx}
            session={session}
            workerUrl={workerUrl}
            userProfile={userProfile}
          />
        )}
      </div>
    </div>
  );
};

// ══════════════════════════════════════════════════════════════════
// UPLOAD MODAL
// ══════════════════════════════════════════════════════════════════

const UPLOAD_TYPES = [
  { group: 'Zum Auftrag', scope: 'auftrag', items: [
    { id: 'gerichtsbeschluss', label: 'Gerichtsbeschluss', extractable: true },
    { id: 'anschreiben', label: 'Anschreiben', extractable: true },
    { id: 'bestellungsurkunde', label: 'Gerichtliche Bestellungsurkunde', extractable: false },
  ]},
  { group: 'Zum Gutachten', scope: 'gutachten', items: [
    { id: 'bebauungsplan', label: 'Bebauungsplan', extractable: false },
    { id: 'lagekarte', label: 'Lagekarte', extractable: false },
    { id: 'teilungserklaerung', label: 'Teilungserklärung', extractable: false },
    { id: 'aufteilungsplan', label: 'Aufteilungsplan', extractable: false },
    { id: 'altlasten', label: 'Altlastenauskunft', extractable: false },
    { id: 'baulasten', label: 'Baulastenauskunft', extractable: false },
    { id: 'bodenrichtwert', label: 'Bodenrichtwertkarte', extractable: false },
    { id: 'erschliessung', label: 'Erschließungsbeitragsbescheid', extractable: false },
  ]},
  { group: 'Zum Bewertungsobjekt', scope: 'objekt', items: [
    { id: 'grundbuchauszug', label: 'Grundbuchauszug', extractable: true },
    { id: 'bauplan', label: 'Bauplan (Grundriss, Schnitt, Ansicht)', extractable: false },
    { id: 'bautechnik', label: 'Bautechnische Berechnungen', extractable: false },
    { id: 'baugenehmigung', label: 'Baugenehmigung / Bauabnahme', extractable: false },
    { id: 'mietvertrag', label: 'Mietvertrag', extractable: false },
    { id: 'energieausweis', label: 'Energieausweis', extractable: false },
    { id: 'feuerstaette', label: 'Feuerstättenbescheid', extractable: false },
    { id: 'bewilligung', label: 'Bewilligungsurkunde', extractable: false },
    { id: 'weg-protokoll', label: 'WEG-Eigentümerversammlung', extractable: false },
  ]},
  { group: 'Sonstiges', scope: 'flex', items: [
    { id: 'sonstiges', label: 'Sonstiges Dokument', extractable: false },
  ]},
];

const SCOPE_HELP_TEXT = {
  auftrag: <>Wird dem <strong>Auftrag</strong> zugeordnet. Gerichtsbeschluss und Anschreiben füllen Aktenzeichen, Beteiligte und Fristen automatisch aus.</>,
  gutachten: <>Wird dem <strong>aktiven Gutachten</strong> zugeordnet. Gilt für alle Objekte innerhalb des Gutachtens.</>,
  objekt: <>Wird einem <strong>einzelnen Bewertungsobjekt</strong> zugeordnet.</>,
  flex: <>Wird als sonstiges Dokument abgelegt. Die KI versucht, Basis-Felder (Datum, Absender) zu extrahieren.</>,
};

// Feld-Beschriftungen für den Review-Dialog (deutsche Labels)
const FELD_LABELS = {
  aktenzeichen: 'Aktenzeichen',
  gerichtstyp: 'Gerichtstyp',
  auftragsart: 'Auftragsart',
  auftragstyp: 'Auftragstyp',
  auftraggeber: 'Auftraggeber',
  verfahrensart: 'Verfahrensart',
  sache: 'Sache',
  wegen: 'Wegen',
  kostenvorschuss: 'Kostenvorschuss',
  ausfertigungen: 'Ausfertigungen',
  abgabe_extern: 'Abgabefrist (extern)',
  auftragseingang: 'Auftragseingang',
  zweck: 'Zweck',
  objektadresse: 'Objektadresse',
  flur: 'Flur',
  flurstueck: 'Flurstück',
  gemarkung: 'Gemarkung',
  groesse_qm: 'Grundstücksgröße (m²)',
  bruchteilseigentum: 'Bruchteilseigentum',
  mea: 'Miteigentumsanteile',
  sondereigentumseinheit: 'Sondereigentumseinheit',
  abt_2_eintragungen: 'Eintragungen Abt. II',
  objekttyp: 'Objekttyp',
};

function formatFieldValue(key, value) {
  if (value == null) return <span style={{ color: 'var(--text-tertiary)' }}>—</span>;
  if (typeof value === 'number') {
    if (key === 'kostenvorschuss') return `€${value.toLocaleString('de-DE')}`;
    if (key === 'groesse_qm') return `${value} m²`;
    return String(value);
  }
  if (key.startsWith('abgabe_') || key === 'auftragseingang') {
    // ISO-Date → DD.MM.YYYY
    const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
    if (m) return `${m[3]}.${m[2]}.${m[1]}`;
  }
  return String(value);
}

// Liefert die Liste der Haupt-Felder (ohne Listen wie beteiligte, eigentuemer)
function getScalarFields(fields, typ) {
  const skip = new Set(['beteiligte', 'eigentuemer', 'notizen', 'kontakt_anschrift']);
  // Für Grundbuch: objekttyp_vermutung → objekttyp umbenennen für UX
  return Object.keys(fields).filter(k => !skip.has(k) && fields[k] != null);
}

// ══════════════════════════════════════════════════════════════════
// UPLOAD-FLOW
// Drei Phasen: form → progress → review → applied
// ══════════════════════════════════════════════════════════════════

const UploadModal = ({
  onClose, onApplied, objektContext,
  projektId, aktiverGutachtenId, session,
  workerUrl,
  prefillFile, prefillTyp,
}) => {
  // Phase-Management
  const [phase, setPhase] = useState('form');  // 'form' | 'progress' | 'review' | 'applied'

  // Form-State
  const [selectedTypeId, setSelectedTypeId] = useState(prefillTyp || 'gerichtsbeschluss');
  const [selectedObjektIdx, setSelectedObjektIdx] = useState(0);
  const [file, setFile] = useState(prefillFile || null);
  const [formError, setFormError] = useState(null);

  // Progress-State
  const [progressStep, setProgressStep] = useState('uploading');
  const [progressPct, setProgressPct] = useState(0);
  const [progressError, setProgressError] = useState(null);
  const [isMock, setIsMock] = useState(false);

  // Review-State
  const [dokumentId, setDokumentId] = useState(null);
  const [extractedFields, setExtractedFields] = useState(null);
  const [acceptedFields, setAcceptedFields] = useState(new Set());
  const [acceptedBeteiligteIdx, setAcceptedBeteiligteIdx] = useState(new Set());
  const [customValues, setCustomValues] = useState({});
  const [applyError, setApplyError] = useState(null);
  const [applying, setApplying] = useState(false);

  // Applied-State
  const [appliedSummary, setAppliedSummary] = useState(null);

  const fileInputRef = useRef(null);

  const objekte = objektContext || [];
  const selectedType = UPLOAD_TYPES
    .flatMap(g => g.items.map(item => ({ ...item, scope: g.scope })))
    .find(t => t.id === selectedTypeId);
  const scope = selectedType?.scope || 'auftrag';
  const extractable = selectedType?.extractable === true;
  // Picker zeigen wenn objekt-scope UND mehr als ein Objekt zur Auswahl steht
  const showObjektPicker = scope === 'objekt' && objekte.length > 1;
  // Ausgewähltes Objekt: bei Single-Objekt automatisch das erste, sonst vom Nutzer gewählt
  const selectedObjektId = scope === 'objekt' && objekte.length > 0
    ? objekte[Math.min(selectedObjektIdx, objekte.length - 1)]?.id
    : null;

  const handleFileSelect = (e) => {
    const f = e.target.files?.[0];
    if (f) {
      setFile(f);
      setFormError(null);
    }
  };

  const handleUpload = async () => {
    if (!file) {
      setFormError('Bitte eine Datei auswählen');
      return;
    }
    if (!extractable) {
      setFormError('Dieser Dokumenttyp ist in der aktuellen Version noch nicht extrahierbar. Bitte einen anderen wählen oder später nachreichen.');
      return;
    }
    // Scope-Validierung: für objekt-scope brauchen wir ein konkretes Objekt
    if (scope === 'objekt' && !selectedObjektId) {
      setFormError('Für diesen Dokumenttyp muss ein Bewertungsobjekt existieren. Lege zuerst ein Gutachten mit Objekt an, dann den Upload wiederholen.');
      return;
    }
    if ((scope === 'gutachten' || scope === 'objekt') && !aktiverGutachtenId) {
      setFormError('Für diesen Dokumenttyp muss ein Gutachten existieren. Lege zuerst ein Gutachten an, dann den Upload wiederholen.');
      return;
    }
    setFormError(null);
    setPhase('progress');
    setProgressStep('uploading');
    setProgressPct(0);
    setProgressError(null);

    try {
      const formData = new FormData();
      formData.append('file', file);
      formData.append('project_id', projektId);
      formData.append('scope', scope);
      formData.append('typ', selectedTypeId);
      if (scope === 'gutachten' || scope === 'objekt') {
        formData.append('gutachten_id', aktiverGutachtenId);
      }
      if (scope === 'objekt' && selectedObjektId) {
        formData.append('objekt_id', selectedObjektId);
      }

      const res = await fetch(`${workerUrl}/api/extract/document`, {
        method: 'POST',
        headers: { Authorization: `Bearer ${session.access_token}` },
        body: formData,
      });

      if (!res.ok) {
        const errBody = await res.json().catch(() => ({}));
        throw new Error(errBody.error || `HTTP ${res.status}`);
      }

      // SSE-Stream lesen
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';
      let done = false;

      while (!done) {
        const { value, done: d } = await reader.read();
        done = d;
        if (value) buffer += decoder.decode(value, { stream: true });

        // Events aus Buffer parsen (doppelte Newline terminiert Event)
        let eventEnd;
        while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
          const block = buffer.slice(0, eventEnd);
          buffer = buffer.slice(eventEnd + 2);

          const eventMatch = block.match(/^event:\s*(\S+)/m);
          const dataMatch = block.match(/^data:\s*(.*)$/m);
          if (!eventMatch || !dataMatch) continue;

          const eventName = eventMatch[1];
          let data;
          try { data = JSON.parse(dataMatch[1]); } catch { continue; }

          if (eventName === 'progress') {
            setProgressStep(data.step || 'extracting');
            setProgressPct(data.pct || 0);
          } else if (eventName === 'result') {
            setDokumentId(data.dokument_id);
            const env = data.extracted_fields;
            setExtractedFields(env);
            setIsMock(env?.is_mock === true);

            // Default: alle nicht-null skalaren Felder akzeptiert
            const scalars = getScalarFields(env.fields, selectedTypeId);
            setAcceptedFields(new Set(scalars));

            // Beteiligte: alle akzeptiert als Default
            if (Array.isArray(env.fields.beteiligte)) {
              setAcceptedBeteiligteIdx(new Set(env.fields.beteiligte.map((_, i) => i)));
            }

            setPhase('review');
          } else if (eventName === 'error') {
            throw new Error(data.message || 'Unbekannter Extraktionsfehler');
          }
        }
      }
    } catch (err) {
      setProgressError(err.message || String(err));
    }
  };

  // Auto-Start wenn prefillFile übergeben wurde (KI-Flow aus NeuerAuftragModal)
  const autoStarted = useRef(false);
  useEffect(() => {
    if (prefillFile && !autoStarted.current && phase === 'form') {
      autoStarted.current = true;
      // Einen Tick warten, damit der Modal renderst ist und state stabil
      setTimeout(() => { handleUpload(); }, 50);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [prefillFile]);

  const toggleField = (key) => {
    setAcceptedFields(prev => {
      const next = new Set(prev);
      if (next.has(key)) next.delete(key);
      else next.add(key);
      return next;
    });
  };

  const toggleBeteiligter = (idx) => {
    setAcceptedBeteiligteIdx(prev => {
      const next = new Set(prev);
      if (next.has(idx)) next.delete(idx);
      else next.add(idx);
      return next;
    });
  };

  const handleApply = async () => {
    setApplying(true);
    setApplyError(null);
    try {
      const body = {
        dokument_id: dokumentId,
        applied_fields: Array.from(acceptedFields),
        custom_values: customValues,
        applied_beteiligte_idx: Array.from(acceptedBeteiligteIdx),
      };
      const res = await fetch(`${workerUrl}/api/apply-extraction`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${session.access_token}`,
        },
        body: JSON.stringify(body),
      });
      if (!res.ok) {
        const errBody = await res.json().catch(() => ({}));
        throw new Error(errBody.error || `HTTP ${res.status}`);
      }
      const result = await res.json();
      setAppliedSummary(result);
      setPhase('applied');
    } catch (err) {
      setApplyError(err.message || String(err));
    } finally {
      setApplying(false);
    }
  };

  const handleFinalClose = () => {
    if (onApplied) onApplied();
    onClose();
  };

  // ══════ RENDERING ══════

  if (phase === 'applied') {
    return (
      <div className="modal-backdrop" onClick={handleFinalClose}>
        <div className="modal" onClick={e => e.stopPropagation()}>
          <div className="modal-header">
            <div className="modal-title">Fertig</div>
            <button className="modal-close" onClick={handleFinalClose}>×</button>
          </div>
          <div className="modal-body">
            <div style={{ textAlign: 'center', padding: 'var(--space-5) 0' }}>
              <div style={{ fontSize: 48, color: 'var(--success)' }}>✓</div>
              <div style={{ fontSize: 17, fontWeight: 600, marginTop: 'var(--space-2)' }}>
                Übernommen
              </div>
              <div style={{ color: 'var(--text-secondary)', fontSize: 14, marginTop: 4 }}>
                {appliedSummary?.written_count || 0} Felder wurden aktualisiert.
              </div>
              {appliedSummary?.writes?.length > 0 && (
                <div style={{
                  marginTop: 'var(--space-4)',
                  fontSize: 12, color: 'var(--text-tertiary)',
                  textAlign: 'left', padding: 'var(--space-3)',
                  background: 'var(--surface-light)', borderRadius: 'var(--radius-md)',
                }}>
                  {appliedSummary.writes.map((w, i) => (
                    <div key={i}>
                      → {w.target}: {w.count ? `${w.count} Einträge` : w.fields?.join(', ')}
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
          <div className="modal-footer">
            <button className="btn btn-primary" onClick={handleFinalClose}>Schließen</button>
          </div>
        </div>
      </div>
    );
  }

  if (phase === 'progress') {
    const STEPS = {
      uploading: 'Datei wird hochgeladen…',
      extracting: 'KI liest das Dokument…',
      persisting: 'Ergebnis wird gespeichert…',
    };
    return (
      <div className="modal-backdrop">
        <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 480 }}>
          <div className="modal-header">
            <div className="modal-title">Dokument wird verarbeitet</div>
          </div>
          <div className="modal-body" style={{ padding: 'var(--space-6)' }}>
            {progressError ? (
              <div>
                <div style={{
                  background: 'var(--danger-bg)', color: 'var(--danger)',
                  padding: 'var(--space-3)', borderRadius: 'var(--radius-md)',
                  fontSize: 13, marginBottom: 'var(--space-4)',
                }}>
                  <strong>Fehler:</strong> {progressError}
                </div>
                <button className="btn btn-secondary" onClick={() => setPhase('form')}>
                  Zurück zum Upload
                </button>
              </div>
            ) : (
              <>
                <div style={{ fontSize: 15, fontWeight: 500, marginBottom: 'var(--space-3)' }}>
                  {STEPS[progressStep] || 'Verarbeitung läuft…'}
                </div>
                <div className="progress-bar" style={{ height: 8 }}>
                  <div className="progress-bar-fill" style={{ width: `${progressPct}%` }}></div>
                </div>
                <div style={{
                  marginTop: 'var(--space-3)', fontSize: 12,
                  color: 'var(--text-tertiary)', textAlign: 'center',
                }}>
                  Das dauert meist 10-30 Sekunden. Bitte nicht schließen.
                </div>
              </>
            )}
          </div>
        </div>
      </div>
    );
  }

  if (phase === 'review' && extractedFields) {
    const fields = extractedFields.fields;
    const scalarKeys = getScalarFields(fields, selectedTypeId);
    const beteiligteArr = Array.isArray(fields.beteiligte) ? fields.beteiligte : [];
    const eigentuemerArr = Array.isArray(fields.eigentuemer) ? fields.eigentuemer : [];

    const allAccepted = scalarKeys.length > 0 && scalarKeys.every(k => acceptedFields.has(k))
                        && (beteiligteArr.length === 0 || beteiligteArr.every((_, i) => acceptedBeteiligteIdx.has(i)));
    const noneAccepted = acceptedFields.size === 0 && acceptedBeteiligteIdx.size === 0;

    const toggleAll = () => {
      if (allAccepted) {
        setAcceptedFields(new Set());
        setAcceptedBeteiligteIdx(new Set());
      } else {
        setAcceptedFields(new Set(scalarKeys));
        setAcceptedBeteiligteIdx(new Set(beteiligteArr.map((_, i) => i)));
      }
    };

    return (
      <div className="modal-backdrop">
        <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 720 }}>
          <div className="modal-header">
            <div>
              <div className="modal-title">Extraktions-Ergebnis prüfen</div>
              <div style={{ fontSize: 13, color: 'var(--text-secondary)', marginTop: 2 }}>
                Wähle die Felder, die übernommen werden sollen.
              </div>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)' }}>
              {dokumentId && (
                <button
                  type="button"
                  className="btn btn-ghost btn-sm"
                  onClick={() => oeffneDokument(dokumentId, session, workerUrl)}
                  style={{ fontSize: 12 }}
                  title="Original-PDF in neuem Tab öffnen"
                >
                  Original ansehen
                </button>
              )}
              <button className="modal-close" onClick={onClose}>×</button>
            </div>
          </div>
          <div className="modal-body" style={{ padding: 0 }}>
            {isMock && (
              <div style={{
                background: 'var(--warning-bg)', color: 'var(--warning)',
                padding: 'var(--space-3) var(--space-5)', fontSize: 13,
                borderBottom: '1px solid var(--border-light)',
              }}>
                <strong>Dev-Modus:</strong> Diese Extraktion zeigt Mock-Daten, weil kein ANTHROPIC_API_KEY konfiguriert ist. Für echte Extraktion den Worker-Secret setzen.
              </div>
            )}

            <div style={{
              display: 'flex', justifyContent: 'space-between',
              padding: 'var(--space-3) var(--space-5)',
              borderBottom: '1px solid var(--border-light)',
              alignItems: 'center',
            }}>
              <div style={{ fontSize: 12, color: 'var(--text-secondary)' }}>
                {acceptedFields.size + acceptedBeteiligteIdx.size} von {scalarKeys.length + beteiligteArr.length} Einträgen akzeptiert
              </div>
              <button className="btn btn-ghost btn-sm" onClick={toggleAll}>
                {allAccepted ? 'Alle abwählen' : 'Alle auswählen'}
              </button>
            </div>

            {/* Skalare Felder */}
            {scalarKeys.length > 0 && (
              <div style={{ padding: 'var(--space-4) var(--space-5)' }}>
                <div className="obj-section-title" style={{ marginBottom: 'var(--space-3)' }}>
                  Felder
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                  {scalarKeys.map(key => {
                    const accepted = acceptedFields.has(key);
                    return (
                      <label
                        key={key}
                        style={{
                          display: 'grid',
                          gridTemplateColumns: '28px 200px 1fr',
                          gap: 'var(--space-3)',
                          padding: 'var(--space-2) var(--space-3)',
                          alignItems: 'center',
                          borderRadius: 'var(--radius-sm)',
                          cursor: 'pointer',
                          background: accepted ? 'var(--success-bg)' : 'transparent',
                          transition: 'background 0.12s',
                        }}
                      >
                        <input
                          type="checkbox"
                          checked={accepted}
                          onChange={() => toggleField(key)}
                          style={{ accentColor: 'var(--success)' }}
                        />
                        <span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
                          {FELD_LABELS[key] || key}
                        </span>
                        <span style={{ fontSize: 14, fontWeight: 500 }}>
                          {formatFieldValue(key, fields[key])}
                        </span>
                      </label>
                    );
                  })}
                </div>
              </div>
            )}

            {/* Beteiligte */}
            {beteiligteArr.length > 0 && (
              <div style={{ padding: 'var(--space-4) var(--space-5)', borderTop: '1px solid var(--border-light)' }}>
                <div className="obj-section-title" style={{ marginBottom: 'var(--space-3)' }}>
                  Beteiligte ({beteiligteArr.length})
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {beteiligteArr.map((b, i) => {
                    const accepted = acceptedBeteiligteIdx.has(i);
                    return (
                      <label
                        key={i}
                        style={{
                          display: 'grid',
                          gridTemplateColumns: '28px 1fr',
                          gap: 'var(--space-3)',
                          padding: 'var(--space-3)',
                          alignItems: 'start',
                          borderRadius: 'var(--radius-md)',
                          cursor: 'pointer',
                          border: `1px solid ${accepted ? 'var(--success)' : 'var(--border-light)'}`,
                          background: accepted ? 'var(--success-bg)' : 'transparent',
                        }}
                      >
                        <input
                          type="checkbox"
                          checked={accepted}
                          onChange={() => toggleBeteiligter(i)}
                          style={{ marginTop: 2, accentColor: 'var(--success)' }}
                        />
                        <div>
                          <div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase',
                                        letterSpacing: '0.1em', color: 'var(--vl-orange-dark)' }}>
                            {b.rolle}
                          </div>
                          <div style={{ fontSize: 15, fontWeight: 600 }}>{b.name}</div>
                          {b.anschrift && (
                            <div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>{b.anschrift}</div>
                          )}
                          {b.aktenzeichen && (
                            <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>
                              Az. {b.aktenzeichen}
                            </div>
                          )}
                        </div>
                      </label>
                    );
                  })}
                </div>
              </div>
            )}

            {/* Eigentümer (Grundbuch) */}
            {eigentuemerArr.length > 0 && (
              <div style={{ padding: 'var(--space-4) var(--space-5)', borderTop: '1px solid var(--border-light)' }}>
                <label style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', cursor: 'pointer' }}>
                  <input
                    type="checkbox"
                    checked={acceptedFields.has('eigentuemer')}
                    onChange={() => toggleField('eigentuemer')}
                    style={{ accentColor: 'var(--success)' }}
                  />
                  <span className="obj-section-title" style={{ margin: 0 }}>
                    Eigentümer ({eigentuemerArr.length}) — als Beteiligte anlegen
                  </span>
                </label>
                <div style={{ marginLeft: 28, marginTop: 'var(--space-2)', fontSize: 14 }}>
                  {eigentuemerArr.map((e, i) => (
                    <div key={i} style={{ padding: '4px 0', color: 'var(--text-secondary)' }}>
                      <strong style={{ color: 'var(--text-primary)' }}>{e.name}</strong>
                      {e.anteil && ` (${e.anteil})`}
                      {e.anschrift && ` · ${e.anschrift}`}
                    </div>
                  ))}
                </div>
              </div>
            )}

            {applyError && (
              <div style={{
                margin: 'var(--space-4) var(--space-5)',
                background: 'var(--danger-bg)', color: 'var(--danger)',
                padding: 'var(--space-3)', borderRadius: 'var(--radius-md)',
                fontSize: 13,
              }}>
                <strong>Fehler:</strong> {applyError}
              </div>
            )}
          </div>

          <div className="modal-footer">
            <button className="btn btn-ghost" onClick={onClose} disabled={applying}>
              Abbrechen
            </button>
            <button
              className="btn btn-primary"
              onClick={handleApply}
              disabled={applying || noneAccepted}
            >
              {applying ? 'Übernehme…' : `${acceptedFields.size + acceptedBeteiligteIdx.size} Einträge übernehmen`}
            </button>
          </div>
        </div>
      </div>
    );
  }

  // Phase: form (Default)
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <div className="modal-title">Dokument hinzufügen</div>
          <button className="modal-close" onClick={onClose}>×</button>
        </div>
        <div className="modal-body">
          <div className="form-field">
            <label className="form-label">Dokumenttyp</label>
            <select
              className="form-select"
              value={selectedTypeId}
              onChange={e => setSelectedTypeId(e.target.value)}
            >
              {UPLOAD_TYPES.map(group => (
                <optgroup key={group.group} label={group.group}>
                  {group.items.map(item => (
                    <option key={item.id} value={item.id}>
                      {item.label}{!item.extractable ? ' (noch keine KI-Extraktion)' : ''}
                    </option>
                  ))}
                </optgroup>
              ))}
            </select>
            <div className="form-help">
              {SCOPE_HELP_TEXT[scope]}
            </div>
            {!extractable && (
              <div style={{
                marginTop: 'var(--space-2)',
                fontSize: 12, color: 'var(--warning)',
                padding: 'var(--space-2) var(--space-3)',
                background: 'var(--warning-bg)',
                borderRadius: 'var(--radius-sm)',
              }}>
                Für diesen Dokumenttyp ist die KI-Extraktion in der aktuellen Version noch nicht aktiv. Die Datei wird nur abgelegt.
              </div>
            )}
          </div>

          {showObjektPicker && (
            <div className="form-field">
              <label className="form-label">Zu welchem Bewertungsobjekt?</label>
              <select
                className="form-select"
                value={selectedObjektIdx}
                onChange={e => setSelectedObjektIdx(parseInt(e.target.value, 10))}
              >
                {objekte.map((o, i) => (
                  <option key={i} value={i}>Objekt {i + 1} — {o.bezeichnung}</option>
                ))}
              </select>
            </div>
          )}

          {scope === 'objekt' && !showObjektPicker && objekte.length === 1 && (
            <div style={{
              padding: 'var(--space-2) var(--space-3)',
              background: 'var(--surface-blue)',
              borderRadius: 'var(--radius-sm)',
              fontSize: 12, color: 'var(--vl-blue)',
              marginBottom: 'var(--space-4)',
            }}>
              Zuordnung: <strong>{objekte[0].bezeichnung}</strong>
            </div>
          )}

          {scope === 'objekt' && objekte.length === 0 && (
            <div style={{
              padding: 'var(--space-2) var(--space-3)',
              background: 'var(--warning-bg)',
              borderRadius: 'var(--radius-sm)',
              fontSize: 12, color: 'var(--warning)',
              marginBottom: 'var(--space-4)',
            }}>
              Keine Bewertungsobjekte im aktuellen Gutachten. Dieses Dokument kann erst nach Anlage eines Objekts hochgeladen werden.
            </div>
          )}

          <div className="form-field">
            <label className="form-label">Datei</label>
            <input
              ref={fileInputRef}
              type="file"
              accept="application/pdf,image/jpeg,image/png"
              style={{ display: 'none' }}
              onChange={handleFileSelect}
            />
            <div
              className="upload-zone"
              onClick={() => fileInputRef.current?.click()}
            >
              {file ? (
                <>
                  <div style={{ fontWeight: 500, color: 'var(--vl-blue)' }}>{file.name}</div>
                  <div style={{ fontSize: 12, marginTop: 4, color: 'var(--text-secondary)' }}>
                    {(file.size / 1024).toFixed(0)} KB · Andere Datei wählen…
                  </div>
                </>
              ) : (
                <>
                  <IconUpload size={32} stroke="var(--text-tertiary)" />
                  <div style={{ fontWeight: 500, color: 'var(--text-primary)', marginTop: 8 }}>
                    PDF, JPG oder PNG hierher ziehen
                  </div>
                  <div style={{ fontSize: 12, marginTop: 4 }}>
                    oder <span style={{ color: 'var(--vl-blue-light)' }}>durchsuchen</span>
                  </div>
                </>
              )}
            </div>
          </div>

          {formError && (
            <div style={{
              background: 'var(--danger-bg)', color: 'var(--danger)',
              padding: 'var(--space-3)', borderRadius: 'var(--radius-md)',
              fontSize: 13,
            }}>
              {formError}
            </div>
          )}
        </div>
        <div className="modal-footer">
          <button className="btn btn-ghost" onClick={onClose}>Abbrechen</button>
          <button
            className="btn btn-primary"
            onClick={handleUpload}
            disabled={!file || !extractable}
          >
            Hochladen und extrahieren
          </button>
        </div>
      </div>
    </div>
  );
};

// ══════════════════════════════════════════════════════════════════
// TOP BAR (mit dynamischem Breadcrumb)
// ══════════════════════════════════════════════════════════════════

const Breadcrumb = ({ view, projekt, aktiverGutachtenIdx, gutachtenCount, onNavigate }) => {
  if (view === 'projektliste') {
    return (
      <button className="breadcrumb-back" disabled>
        <IconChevronLeft size={16} /> Projekte
      </button>
    );
  }

  // Fallback-Name während projekt noch lädt
  const projektName = projekt?.name || 'Lade…';

  if (view === 'dashboard') {
    return (
      <>
        <button className="breadcrumb-back" onClick={() => onNavigate('projektliste')}>
          <IconChevronLeft size={16} /> Projekte
        </button>
        <span className="breadcrumb-separator">/</span>
        <span className="breadcrumb-current">{projektName}</span>
      </>
    );
  }

  if (view === 'auftrag') {
    return (
      <>
        <button className="breadcrumb-back" onClick={() => onNavigate('dashboard')}>
          <IconChevronLeft size={16} /> {projektName}
        </button>
        <span className="breadcrumb-separator">/</span>
        <span className="breadcrumb-current">Auftrag</span>
      </>
    );
  }

  if (view === 'gutachten') {
    const label = gutachtenCount > 1
      ? `Gutachten ${aktiverGutachtenIdx + 1} von ${gutachtenCount}`
      : 'Gutachten';
    return (
      <>
        <button className="breadcrumb-back" onClick={() => onNavigate('dashboard')}>
          <IconChevronLeft size={16} /> {projektName}
        </button>
        <span className="breadcrumb-separator">/</span>
        <span className="breadcrumb-current">{label}</span>
      </>
    );
  }

  return null;
};

const TopBar = ({ view, projekt, aktiverGutachtenIdx, gutachtenCount, onNavigate, userProfile, onLogout }) => (
  <div className="top-bar">
    <div>
      <div className="logo">VÖLKEL<span className="pipe">|</span>LANG</div>
      <div className="logo-subtitle">Sachverständige</div>
    </div>
    <div className="top-bar-separator"></div>
    <div className="breadcrumb">
      <Breadcrumb
        view={view}
        projekt={projekt}
        aktiverGutachtenIdx={aktiverGutachtenIdx}
        gutachtenCount={gutachtenCount}
        onNavigate={onNavigate}
      />
    </div>
    <div className="top-bar-actions">
      {userProfile && (
        <span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
          {userProfile.full_name || userProfile.email}
        </span>
      )}
      {onLogout && (
        <button
          className="btn btn-ghost btn-sm"
          onClick={onLogout}
          title="Abmelden"
          style={{ fontSize: 12 }}
        >
          Abmelden
        </button>
      )}
    </div>
  </div>
);

// ══════════════════════════════════════════════════════════════════
// 5 · LOGIN
// Einfacher Email/Passwort-Login gegen Supabase-Auth.
// Wird gezeigt, solange keine Session aktiv ist.
// ══════════════════════════════════════════════════════════════════

const LoginView = ({ onLoginSuccess }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    try {
      const sb = await initSupabase();
      const { error: authError } = await sb.auth.signInWithPassword({ email, password });
      if (authError) {
        setError(authError.message);
        setLoading(false);
        return;
      }
      onLoginSuccess();
    } catch (err) {
      setError(err.message || String(err));
      setLoading(false);
    }
  };

  return (
    <div className="view-wrapper" style={{ maxWidth: 420, margin: '80px auto' }}>
      <div className="card">
        <div style={{ textAlign: 'center', marginBottom: 'var(--space-6)' }}>
          <div className="logo" style={{ fontSize: 22, marginBottom: 4 }}>
            VÖLKEL<span className="pipe">|</span>LANG
          </div>
          <div className="logo-subtitle">Sachverständige · Augenschein</div>
        </div>

        <form onSubmit={handleSubmit}>
          <div className="form-field">
            <label className="form-label">E-Mail</label>
            <input
              className="form-input"
              type="email"
              value={email}
              onChange={e => setEmail(e.target.value)}
              required
              autoFocus
            />
          </div>
          <div className="form-field">
            <label className="form-label">Passwort</label>
            <input
              className="form-input"
              type="password"
              value={password}
              onChange={e => setPassword(e.target.value)}
              required
            />
          </div>

          {error && (
            <div style={{
              background: 'var(--danger-bg)', color: 'var(--danger)',
              padding: 'var(--space-3)', borderRadius: 'var(--radius-md)',
              fontSize: 13, marginBottom: 'var(--space-4)',
            }}>
              {error}
            </div>
          )}

          <button
            type="submit"
            className="btn btn-primary"
            disabled={loading || !email || !password}
            style={{ width: '100%', padding: '10px 14px' }}
          >
            {loading ? 'Anmelden…' : 'Anmelden'}
          </button>
        </form>
      </div>
    </div>
  );
};

// ══════════════════════════════════════════════════════════════════
// NEUER-AUFTRAG-MODAL
// Zwei Wege: manuell anlegen oder aus Gerichtsbeschluss (KI-Extraktion)
// ══════════════════════════════════════════════════════════════════

const NeuerAuftragModal = ({ onClose, onCreated, session, workerUrl }) => {
  const [tab, setTab] = useState('manuell');  // 'manuell' | 'ki'
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState(null);

  // Form-State
  const [name, setName] = useState('');
  const [auftragsart, setAuftragsart] = useState('privat');
  const [auftragstyp, setAuftragstyp] = useState('verkehrswert');
  const [auftraggeber, setAuftraggeber] = useState('');
  const [akte, setAkte] = useState('');
  const [abgabeExtern, setAbgabeExtern] = useState('');
  const [abgabeIntern, setAbgabeIntern] = useState('');
  const [adresse, setAdresse] = useState('');

  // KI-Upload-State
  const [kiFile, setKiFile] = useState(null);
  const [kiDokTyp, setKiDokTyp] = useState('gerichtsbeschluss');
  const fileInputRef = useRef(null);

  const canSubmitManual = name.trim().length >= 2;
  const canSubmitKi = name.trim().length >= 2 && !!kiFile;

  const handleSubmitManual = async () => {
    if (!canSubmitManual || saving) return;
    setSaving(true);
    setError(null);
    try {
      const projektId = await createProjekt({
        name,
        auftragsart,
        auftragstyp,
        auftraggeber,
        akte,
        abgabeExtern: abgabeExtern || null,
        abgabeIntern: abgabeIntern || null,
        erstesGutachtenAdresse: adresse,
      });
      onCreated(projektId);
    } catch (err) {
      setError(err.message || String(err));
      setSaving(false);
    }
  };

  const handleSubmitKi = async () => {
    if (!canSubmitKi || saving) return;
    setSaving(true);
    setError(null);
    try {
      // Projekt anlegen (leere Defaults — Felder werden per KI-Extraktion gefüllt)
      const projektId = await createProjekt({
        name,
        auftragsart: kiDokTyp === 'gerichtsbeschluss' ? 'gericht' : 'privat',
        auftragstyp: 'verkehrswert',
      });
      // Datei an App übergeben — dort läuft der bestehende Upload+Extract-Flow
      onCreated(projektId, { kiFile, kiDokTyp });
    } catch (err) {
      setError(err.message || String(err));
      setSaving(false);
    }
  };

  const inputStyle = {
    width: '100%',
    padding: '10px 12px',
    border: '1px solid var(--border-light)',
    borderRadius: 'var(--radius-sm)',
    fontSize: 14, fontFamily: 'inherit', outline: 'none',
    background: 'var(--surface)',
  };
  const labelStyle = {
    display: 'block', fontSize: 12, fontWeight: 600,
    marginBottom: 6, color: 'var(--text-secondary)',
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 560 }}>
        <div className="modal-header">
          <div>
            <div className="modal-title">Neuer Auftrag</div>
            <div style={{ fontSize: 13, color: 'var(--text-secondary)', marginTop: 2 }}>
              Projekt + Auftrag + erstes Gutachten in einem Schritt anlegen.
            </div>
          </div>
          <button className="modal-close" onClick={onClose}>×</button>
        </div>

        <div className="modal-body" style={{ padding: 0 }}>
          {/* Tab-Switch */}
          <div style={{
            display: 'flex',
            borderBottom: '1px solid var(--border-light)',
          }}>
            <button
              type="button"
              onClick={() => setTab('manuell')}
              style={{
                flex: 1,
                padding: '14px 20px',
                background: 'transparent',
                border: 'none',
                borderBottom: tab === 'manuell' ? '2px solid var(--vl-orange, #F59E0B)' : '2px solid transparent',
                fontSize: 13, fontWeight: 600,
                color: tab === 'manuell' ? 'var(--text-primary)' : 'var(--text-secondary)',
                cursor: 'pointer',
              }}
            >
              Manuell anlegen
            </button>
            <button
              type="button"
              onClick={() => setTab('ki')}
              style={{
                flex: 1,
                padding: '14px 20px',
                background: 'transparent',
                border: 'none',
                borderBottom: tab === 'ki' ? '2px solid var(--vl-orange, #F59E0B)' : '2px solid transparent',
                fontSize: 13, fontWeight: 600,
                color: tab === 'ki' ? 'var(--text-primary)' : 'var(--text-secondary)',
                cursor: 'pointer',
              }}
            >
              Aus Dokument (KI)
            </button>
          </div>

          <div style={{ padding: 'var(--space-5)' }}>
            {/* Projektname — bei beiden Wegen Pflicht */}
            <div style={{ marginBottom: 'var(--space-4)' }}>
              <label style={labelStyle}>
                Projektname <span style={{ color: 'var(--danger)' }}>*</span>
              </label>
              <input
                type="text"
                value={name}
                onChange={e => setName(e.target.value)}
                placeholder="z.B. Schmidt, Familie"
                style={inputStyle}
                autoFocus
              />
              <div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 4 }}>
                Für die interne Referenz. Kann später unter „Auftrag" geändert werden.
              </div>
            </div>

            {tab === 'manuell' ? (
              <>
                <div style={{
                  display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-4)',
                  marginBottom: 'var(--space-4)',
                }}>
                  <div>
                    <label style={labelStyle}>Auftragsart</label>
                    <select
                      value={auftragsart}
                      onChange={e => setAuftragsart(e.target.value)}
                      style={inputStyle}
                    >
                      <option value="privat">Privatauftrag</option>
                      <option value="gericht">Gerichtsauftrag</option>
                      <option value="gutachterausschuss">Gutachterausschuss</option>
                      <option value="notariat">Notarauftrag</option>
                    </select>
                  </div>
                  <div>
                    <label style={labelStyle}>Auftragstyp</label>
                    <select
                      value={auftragstyp}
                      onChange={e => setAuftragstyp(e.target.value)}
                      style={inputStyle}
                    >
                      <option value="verkehrswert">Verkehrswertgutachten</option>
                      <option value="miete">Mietwertgutachten</option>
                      <option value="minderwert">Minderwertgutachten</option>
                      <option value="beleihung">Beleihungswertgutachten</option>
                      <option value="marktwert">Marktwertindikation</option>
                      <option value="ueberwachung">Überwachungsgutachten</option>
                    </select>
                  </div>
                </div>

                <div style={{ marginBottom: 'var(--space-4)' }}>
                  <label style={labelStyle}>Auftraggeber</label>
                  <input
                    type="text"
                    value={auftraggeber}
                    onChange={e => setAuftraggeber(e.target.value)}
                    placeholder="z.B. Amtsgericht Ansbach oder Privatperson"
                    style={inputStyle}
                  />
                </div>

                <div style={{ marginBottom: 'var(--space-4)' }}>
                  <label style={labelStyle}>Aktenzeichen (optional)</label>
                  <input
                    type="text"
                    value={akte}
                    onChange={e => setAkte(e.target.value)}
                    placeholder="z.B. 2 K 40/25"
                    style={inputStyle}
                  />
                </div>

                <div style={{
                  display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-4)',
                  marginBottom: 'var(--space-4)',
                }}>
                  <div>
                    <label style={labelStyle}>Abgabe intern</label>
                    <input
                      type="date"
                      value={abgabeIntern}
                      onChange={e => setAbgabeIntern(e.target.value)}
                      style={inputStyle}
                    />
                  </div>
                  <div>
                    <label style={labelStyle}>Abgabe extern</label>
                    <input
                      type="date"
                      value={abgabeExtern}
                      onChange={e => setAbgabeExtern(e.target.value)}
                      style={inputStyle}
                    />
                  </div>
                </div>

                <div style={{ marginBottom: 'var(--space-4)' }}>
                  <label style={labelStyle}>Objektadresse (optional)</label>
                  <input
                    type="text"
                    value={adresse}
                    onChange={e => setAdresse(e.target.value)}
                    placeholder="z.B. Bamberger Straße 48, 96049 Bamberg"
                    style={inputStyle}
                  />
                  <div style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 4 }}>
                    Wird dem ersten Gutachten zugeordnet. Lässt sich später im Gutachten-Stammdaten-Tab ändern.
                  </div>
                </div>
              </>
            ) : (
              <>
                <div style={{ marginBottom: 'var(--space-4)' }}>
                  <label style={labelStyle}>Dokumenttyp</label>
                  <select
                    value={kiDokTyp}
                    onChange={e => setKiDokTyp(e.target.value)}
                    style={inputStyle}
                  >
                    <option value="gerichtsbeschluss">Gerichtsbeschluss</option>
                    <option value="anschreiben">Anschreiben / Auftragserteilung</option>
                  </select>
                </div>

                <div style={{ marginBottom: 'var(--space-4)' }}>
                  <label style={labelStyle}>
                    PDF-Datei <span style={{ color: 'var(--danger)' }}>*</span>
                  </label>
                  <input
                    ref={fileInputRef}
                    type="file"
                    accept="application/pdf"
                    style={{ display: 'none' }}
                    onChange={e => setKiFile(e.target.files?.[0] || null)}
                  />
                  {kiFile ? (
                    <div style={{
                      padding: 'var(--space-3)',
                      border: '1px solid var(--success)',
                      background: 'var(--success-bg)',
                      borderRadius: 'var(--radius-sm)',
                      display: 'flex', alignItems: 'center', gap: 'var(--space-3)',
                    }}>
                      <IconDocument size={24} stroke="var(--success)" />
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{
                          fontWeight: 600, fontSize: 13,
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                        }}>
                          {kiFile.name}
                        </div>
                        <div style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
                          {(kiFile.size / 1024).toFixed(0)} KB
                        </div>
                      </div>
                      <button
                        type="button"
                        onClick={() => { setKiFile(null); if (fileInputRef.current) fileInputRef.current.value = ''; }}
                        style={{
                          background: 'none', border: 'none', color: 'var(--danger)',
                          cursor: 'pointer', fontSize: 12,
                        }}
                      >
                        Entfernen
                      </button>
                    </div>
                  ) : (
                    <button
                      type="button"
                      onClick={() => fileInputRef.current?.click()}
                      style={{
                        width: '100%',
                        padding: 'var(--space-5)',
                        border: '2px dashed var(--border-light)',
                        borderRadius: 'var(--radius-md)',
                        background: 'var(--surface-light)',
                        cursor: 'pointer',
                        display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
                        color: 'var(--text-secondary)',
                      }}
                    >
                      <IconUpload size={32} stroke="var(--text-tertiary)" />
                      <div style={{ fontSize: 13, fontWeight: 600 }}>PDF hierher ziehen oder klicken</div>
                      <div style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
                        Nur PDF-Dokumente, max. 20 MB
                      </div>
                    </button>
                  )}
                </div>

                <div style={{
                  padding: 'var(--space-3)',
                  background: 'var(--info-bg, rgba(59, 130, 246, 0.08))',
                  border: '1px solid var(--info, #3B82F6)',
                  borderRadius: 'var(--radius-sm)',
                  fontSize: 12,
                  color: 'var(--text-secondary)',
                  marginBottom: 'var(--space-4)',
                }}>
                  <strong>Nach dem Anlegen</strong> startest du die KI-Extraktion im Dashboard des neuen Projekts.
                  Aktenzeichen, Auftraggeber, Fristen und Beteiligte werden aus dem Dokument ausgelesen und
                  zur Prüfung vorgeschlagen.
                </div>
              </>
            )}

            {error && (
              <div style={{
                padding: 'var(--space-3)',
                background: 'var(--danger-bg)',
                color: 'var(--danger)',
                borderRadius: 'var(--radius-sm)',
                fontSize: 13,
                marginBottom: 'var(--space-4)',
              }}>
                {error}
              </div>
            )}
          </div>

          <div style={{
            display: 'flex',
            justifyContent: 'flex-end',
            gap: 'var(--space-3)',
            padding: 'var(--space-4) var(--space-5)',
            borderTop: '1px solid var(--border-light)',
            background: 'var(--surface-light)',
          }}>
            <button
              className="btn btn-ghost"
              onClick={onClose}
              disabled={saving}
            >
              Abbrechen
            </button>
            <button
              className="btn btn-primary"
              onClick={tab === 'manuell' ? handleSubmitManual : handleSubmitKi}
              disabled={saving || (tab === 'manuell' ? !canSubmitManual : !canSubmitKi)}
            >
              {saving
                ? (tab === 'ki' ? 'Lade Dokument hoch…' : 'Wird angelegt…')
                : (tab === 'ki' ? 'Anlegen & Dokument hochladen' : 'Projekt anlegen')}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ══════════════════════════════════════════════════════════════════
// 6 · TOP-LEVEL APP
// ══════════════════════════════════════════════════════════════════

const App = () => {
  // Auth-State
  const [session, setSession] = useState(undefined); // undefined = loading, null = unauth, object = auth
  const [userProfile, setUserProfile] = useState(null);

  // Navigation-State
  const [view, setView] = useState('projektliste');
  const [projektId, setProjektId] = useState(null);
  const [projekt, setProjekt] = useState(null);            // Vollständiges Projekt-Objekt (nach ladeProjektDetail)
  const [projektLoading, setProjektLoading] = useState(false);
  const [projektError, setProjektError] = useState(null);
  const [aktiverGutachtenIdx, setAktiverGutachtenIdx] = useState(0);
  const [aktivesObjekt, setAktivesObjekt] = useState(0);
  const [gutachtenTab, setGutachtenTab] = useState('stammdaten');
  const [uploadModalOpen, setUploadModalOpen] = useState(false);
  const [newProjektModalOpen, setNewProjektModalOpen] = useState(false);

  // Pre-fill für Upload-Modal (wird vom NeuerAuftragModal-KI-Tab gesetzt)
  const [uploadPrefill, setUploadPrefill] = useState(null);  // { file, typ } | null

  // Herkunft-Map: "tabelle:row_id:feld" → { dokument_id, titel, typ, applied_at }
  const [herkunft, setHerkunft] = useState({});

  // Auth-Check beim Mount + Auth-Listener
  useEffect(() => {
    let active = true;
    (async () => {
      try {
        const sb = await initSupabase();
        const { data: { session: s } } = await sb.auth.getSession();
        if (!active) return;
        setSession(s);

        // Auth-Changes beobachten
        sb.auth.onAuthStateChange((_event, newSession) => {
          setSession(newSession);
          if (!newSession) {
            setProjektId(null);
            setProjekt(null);
            setView('projektliste');
          }
        });
      } catch (err) {
        console.error('initSupabase failed:', err);
        if (active) setSession(null);
      }
    })();
    return () => { active = false; };
  }, []);

  // User-Profile nach Login laden (für Organisation, Rolle, etc.)
  useEffect(() => {
    if (!session) { setUserProfile(null); return; }
    let active = true;
    (async () => {
      const sb = await initSupabase();
      const { data } = await sb
        .from('user_profiles')
        .select('*, organizations (*)')
        .eq('id', session.user.id)
        .maybeSingle();
      if (active) setUserProfile(data);
    })();
    return () => { active = false; };
  }, [session]);

  // Projekt-Detail laden, wenn sich projektId ändert
  const refreshProjekt = useCallback(async () => {
    if (!projektId) return;
    setProjektLoading(true);
    setProjektError(null);
    try {
      const [p, h] = await Promise.all([
        ladeProjektDetail(projektId),
        ladeHerkunft(projektId, session, WORKER_URL),
      ]);
      setProjekt(p);
      setHerkunft(h);
    } catch (err) {
      setProjektError(err.message || String(err));
    } finally {
      setProjektLoading(false);
    }
  }, [projektId, session]);

  useEffect(() => {
    if (!projektId) { setProjekt(null); setHerkunft({}); return; }
    let active = true;
    setProjektLoading(true);
    setProjektError(null);
    Promise.all([
      ladeProjektDetail(projektId),
      ladeHerkunft(projektId, session, WORKER_URL),
    ])
      .then(([p, h]) => {
        if (!active) return;
        setProjekt(p);
        setHerkunft(h);
        setProjektLoading(false);
      })
      .catch(err => {
        if (!active) return;
        setProjektError(err.message || String(err));
        setProjektLoading(false);
      });
    return () => { active = false; };
  }, [projektId, session]);

  // Beim View-Wechsel nach oben scrollen
  useEffect(() => {
    if (typeof window.scrollTo === 'function') window.scrollTo(0, 0);
  }, [view, projektId]);

  const navigate = useCallback((nextView, id, opts = {}) => {
    if (id !== undefined) setProjektId(id);
    if (opts.gutachtenIdx !== undefined) {
      setAktiverGutachtenIdx(opts.gutachtenIdx);
      setAktivesObjekt(0);
      setGutachtenTab('stammdaten');
    }
    setView(nextView);
  }, []);

  const openProjekt = useCallback((id) => navigate('dashboard', id), [navigate]);
  const openAuftrag = useCallback(() => navigate('auftrag'), [navigate]);
  const openGutachten = useCallback((idx) => navigate('gutachten', undefined, { gutachtenIdx: idx }), [navigate]);
  const openUpload = useCallback(() => setUploadModalOpen(true), []);
  const closeUpload = useCallback(() => setUploadModalOpen(false), []);

  const openNewProjekt = useCallback(() => setNewProjektModalOpen(true), []);
  const closeNewProjekt = useCallback(() => setNewProjektModalOpen(false), []);

  // Nach erfolgreicher Anlage: Modal schließen und ins neue Projekt navigieren.
  // Bei KI-Flow (extras.kiFile) öffnen wir zusätzlich den UploadModal vorbefüllt.
  const handleProjektCreated = useCallback((projektId, extras = {}) => {
    setNewProjektModalOpen(false);
    navigate('dashboard', projektId);
    if (extras.kiFile) {
      setUploadPrefill({ file: extras.kiFile, typ: extras.kiDokTyp || 'gerichtsbeschluss' });
      // Minimale Verzögerung, damit projekt-Detail erst laden kann
      setTimeout(() => setUploadModalOpen(true), 300);
    }
  }, [navigate]);

  // Wenn UploadModal schließt: auch Prefill zurücksetzen
  const closeUploadWithReset = useCallback(() => {
    setUploadModalOpen(false);
    setUploadPrefill(null);
  }, []);

  // Dokument in neuem Tab öffnen (Signed URL vom Worker)
  const openDocument = useCallback((dokumentId) => {
    oeffneDokument(dokumentId, session, WORKER_URL);
  }, [session]);

  const switchGutachten = useCallback((idx) => {
    setAktiverGutachtenIdx(idx);
    setAktivesObjekt(0);
    setGutachtenTab('stammdaten');
  }, []);

  const handleLogout = useCallback(async () => {
    const sb = await initSupabase();
    await sb.auth.signOut();
  }, []);

  // Objekt-Kontext für Upload-Dialog.
  // Immer verfügbar wenn ein Projekt geladen ist. Bei view='gutachten' die Objekte
  // des aktiven Gutachtens, sonst die des ersten Gutachtens (Default fürs Dashboard).
  const kontextGutachten = projekt?.gutachten?.[
    view === 'gutachten' ? aktiverGutachtenIdx : 0
  ];
  const objektContext = kontextGutachten?.objekte || [];

  // ─── Rendering-Gates ───
  if (session === undefined) {
    return (
      <div style={{ padding: 80, textAlign: 'center', color: 'var(--text-tertiary)' }}>
        Lade…
      </div>
    );
  }

  if (session === null) {
    return <LoginView onLoginSuccess={() => { /* Auth-Listener übernimmt */ }} />;
  }

  // Dashboard-Daten sind nur verfügbar, wenn Projekt geladen ist
  const projektBereit = view === 'projektliste' || (projekt && !projektLoading);

  return (
    <>
      <TopBar
        view={view}
        projekt={projekt}
        aktiverGutachtenIdx={aktiverGutachtenIdx}
        gutachtenCount={projekt?.gutachten?.length || 0}
        onNavigate={navigate}
        userProfile={userProfile}
        onLogout={handleLogout}
      />

      <div className="app-container">
        {projektError && (
          <PrototypeHint>
            <strong>Fehler beim Laden:</strong> {projektError}
          </PrototypeHint>
        )}

        {view === 'projektliste' && (
          <ProjektlisteView onOpen={openProjekt} onNewProjekt={openNewProjekt} />
        )}

        {view !== 'projektliste' && projektLoading && (
          <div style={{ padding: 'var(--space-12)', textAlign: 'center', color: 'var(--text-tertiary)' }}>
            Lade Projekt…
          </div>
        )}

        {view === 'dashboard' && projekt && !projektLoading && (
          <DashboardView
            p={projekt}
            onOpenAuftrag={openAuftrag}
            onOpenGutachten={openGutachten}
            onOpenUpload={openUpload}
            session={session}
            workerUrl={WORKER_URL}
          />
        )}
        {view === 'auftrag' && projekt && !projektLoading && (
          <AuftragView
            p={projekt}
            herkunft={herkunft}
            onOpenDocument={openDocument}
            session={session}
            workerUrl={WORKER_URL}
          />
        )}
        {view === 'gutachten' && projekt && !projektLoading && (
          <GutachtenView
            p={projekt}
            aktiverGutachtenIdx={aktiverGutachtenIdx}
            aktivesObjekt={aktivesObjekt}
            gutachtenTab={gutachtenTab}
            onSwitchGutachten={switchGutachten}
            onSwitchObjekt={setAktivesObjekt}
            onSwitchTab={setGutachtenTab}
            onOpenAuftrag={openAuftrag}
            onOpenUpload={openUpload}
            herkunft={herkunft}
            onOpenDocument={openDocument}
            session={session}
            workerUrl={WORKER_URL}
            userProfile={userProfile}
          />
        )}
      </div>

      {uploadModalOpen && projekt && (
        <UploadModal
          onClose={closeUploadWithReset}
          onApplied={refreshProjekt}
          objektContext={objektContext}
          projektId={projekt.id}
          aktiverGutachtenId={kontextGutachten?.id}
          session={session}
          workerUrl={WORKER_URL}
          prefillFile={uploadPrefill?.file}
          prefillTyp={uploadPrefill?.typ}
        />
      )}

      {newProjektModalOpen && (
        <NeuerAuftragModal
          onClose={closeNewProjekt}
          onCreated={handleProjektCreated}
          session={session}
          workerUrl={WORKER_URL}
        />
      )}
    </>
  );
};

// ══════════════════════════════════════════════════════════════════
// 7 · MOUNT
// ══════════════════════════════════════════════════════════════════
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
