/* Tableau page: bracket + list view, main + consolation, multi-set scoring */

const { useState: uST, useEffect: uE, useMemo } = React;

function ScoreCells({ sets, side, winnerIdx }) {
  const cells = [];
  for (let i = 0; i < 3; i++) {
    const s = sets && sets[i];
    const v = s ? (side === 0 ? s.s1 : s.s2) : null;
    const isMTB = i === 2;
    const setW = setWinnerIdx(s, isMTB);
    const isWin = setW === side;
    cells.push(
      <span key={i} className={`set-cell ${isWin ? 'set-win' : ''} ${isMTB ? 'mtb' : ''}`}>
        {v == null ? '' : v}
      </span>
    );
  }
  return <div className="score-row">{cells}</div>;
}

function MatchCard({ match, regsById, seeds, editable, onEdit }) {
  const r1 = regsById[match.p1];
  const r2 = regsById[match.p2];
  const w = matchWinnerIdx(match);
  const empty = !r1 && !r2;
  const wo = (match.wo === 0 || match.wo === 1) ? match.wo : null;
  // Trostrunde-Matches haben IDs 'T-...' (vs 'M-...' fuer Hauptrunde).
  // In der Trostrunde wird die Setzliste komplett ignoriert — Setz-
  // Nummern erscheinen ausschliesslich im Hauptbracket, auch wenn ein
  // gesetztes Team in die Trostrunde rutscht.
  const isCons = (match.id || '').startsWith('T-');
  const renderPair = (reg, side) => {
    const id = side === 0 ? match.p1 : match.p2;
    const isFreilos = id === 'FREILOS';
    const seed = reg && !isCons ? seeds[reg.id] : null;
    // Status-Klassen: 'win' = Sieger steht fest, fett. 'lose' = Verlierer, grau.
    // 'pending-seeded' = nur in der 1. Runde noch nicht gespieltes Setz-
    // Match → Name fett, damit man die 8 Gesetzten auf einen Blick sieht.
    // In allen späteren Runden (Achtelfinale +) wird ein gesetzter Spieler
    // NICHT mehr automatisch fett — ab dann gilt nur noch die Sieger/
    // Verlierer-Hervorhebung. Die Setz-Nummer (nm-seed) bleibt aber davor.
    const isR1 = match.round === 0;
    const cls = w === side ? 'win'
              : w != null   ? 'lose'
              : seed && isR1 ? 'pending-seeded'
              : '';
    // nm und nm-meta werden IMMER gerendert (auch leer) — sonst wäre eine
    // leere Karte halb so hoch wie eine besetzte und das Print-Layout
    // verschiebt sich. Leere Slots bekommen ein nbsp als Platzhalter.
    return (
      <div className="match-row">
        <div className="nm-block">
          <div className={`nm ${cls}`}>
            {reg
              ? <>{seed && <span className="nm-seed">{seed}</span>}{pairLabel(reg)}</>
              : isFreilos ? <span className="nm-freilos">Freilos</span> : ' '}
          </div>
          <div className="nm-meta">
            {reg ? (
              <>
                <span>{pairClubs(reg)}</span>
              </>
            ) : ' '}
          </div>
        </div>
        {wo === side ? (
          <div style={{display:'flex', alignItems:'center', gap: 6}}>
            <span className="wo-tag wo-loser">W.O.</span>
            {match.sets && match.sets.length > 0 && (
              <ScoreCells sets={match.sets} side={side} winnerIdx={w}/>
            )}
          </div>
        ) : (
          <ScoreCells sets={match.sets} side={side} winnerIdx={w}/>
        )}
      </div>
    );
  };
  // match-foot wird IMMER gerendert (auch wenn time/court leer sind),
  // damit die Trennlinie unter den Spielerzeilen und der Footer-Bereich
  // konstant bleiben — sowohl auf dem Bildschirm als auch im Druck.
  // Sonst sind Karten ohne Spielplan plötzlich kürzer und die Bracket-
  // Verbindungslinien rutschen.
  return (
    <div className={`match ${empty ? 'empty' : ''} ${editable ? 'editable' : ''}`}
         onClick={() => editable && onEdit(match)}>
      {renderPair(r1, 0)}
      {renderPair(r2, 1)}
      <div className="match-foot">
        <span>{match.time || ' '}</span>
        <span>{match.court || ' '}</span>
      </div>
    </div>
  );
}

function BracketView({ rounds, roundNames, regsById, seeds, editable, onEdit, interactive = true }) {
  // Click-and-Drag-Scroll nur fuer interaktive Bracket-Views (Screen-
  // Variante). Print-BracketViews bekommen interactive=false, damit die
  // 6 Print-Sektionen keine Window-Listener anhaengen — sonst summieren
  // sich beim Hard-Refresh schnell viele Listener + DOM-Subscriptions,
  // was das Tab unresponsiv machen kann (vor allem im Admin-Modus, wo
  // editable=true das Match-Click-Routing extra noch aktiv ist).
  const wrapRef = React.useRef(null);
  const dragRef = React.useRef({ active: false, startX: 0, startScroll: 0, maxMoved: 0 });

  const onMouseDown = !interactive ? undefined : (e) => {
    if (e.button !== 0) return;
    const wrap = wrapRef.current;
    if (!wrap) return;
    dragRef.current.active = true;
    dragRef.current.startX = e.pageX;
    dragRef.current.startScroll = wrap.scrollLeft;
    dragRef.current.maxMoved = 0;
  };

  React.useEffect(() => {
    if (!interactive) return;
    const onMove = (e) => {
      if (!dragRef.current.active) return;
      const wrap = wrapRef.current;
      if (!wrap) return;
      const dx = e.pageX - dragRef.current.startX;
      if (Math.abs(dx) > dragRef.current.maxMoved) dragRef.current.maxMoved = Math.abs(dx);
      wrap.scrollLeft = dragRef.current.startScroll - dx;
      if (Math.abs(dx) > 5) wrap.classList.add('is-dragging');
    };
    const onUp = () => {
      if (!dragRef.current.active) return;
      const wasDrag = dragRef.current.maxMoved > 5;
      dragRef.current.active = false;
      if (wrapRef.current) wrapRef.current.classList.remove('is-dragging');
      if (wasDrag) {
        const stopOne = (ev) => {
          ev.stopPropagation();
          ev.preventDefault();
          window.removeEventListener('click', stopOne, true);
        };
        window.addEventListener('click', stopOne, true);
      }
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
  }, [interactive]);

  return (
    <div className="bracket-wrap" ref={wrapRef} onMouseDown={onMouseDown}>
      <div className="bracket">
        {rounds.map((round, ri) => (
          <div key={ri} className="bracket-col">
            <div className="bracket-col-head">{roundNames[ri]}</div>
            <div className="bracket-col-body">
              {round.map(m => (
                <div key={m.id} className="match-wrap">
                  <MatchCard match={m} regsById={regsById} seeds={seeds} editable={editable} onEdit={onEdit}/>
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// Maximalpunktwert je Satz in der Score-Auswahl: Sätze 1+2 als regulär
// gespielter Tennissatz (0-7 deckt 6:0..7:6 ab); 3. Satz als Match-
// Tiebreak (0-20 deckt die übliche 10er-Spannweite mit Reserve ab).
// Werte darüber hinaus (z. B. exotische 28:26-MTBs) lassen sich pro
// Zelle per "…"-Option auf manuelle Eingabe umschalten.
const SET_MAX = 7;
const MTB_MAX = 20;
const setMaxFor = (i) => i === 2 ? MTB_MAX : SET_MAX;

function ResultModal({ match, regsById, registrations, seeds, bracket, bucket, onSave, onDelete, onClose }) {
  uE(() => { lockBodyScroll(); return unlockBodyScroll; }, []);
  const [confirmDelete, setConfirmDelete] = uST(false);
  const init = [0,1,2].map(i => match.sets && match.sets[i] ? {s1: match.sets[i].s1 ?? '', s2: match.sets[i].s2 ?? ''} : {s1:'', s2:''});
  const [sets, setSets] = uST(init);
  // Pro Score-Zelle merken, ob der User manuelle Eingabe statt der
  // Dropdown-Auswahl will. Default: auto an, wenn ein bestehender Wert
  // außerhalb des regulären Bereichs liegt (z. B. 28 im MTB).
  const [manualCell, setManualCell] = uST(() => {
    const out = [[false, false], [false, false], [false, false]];
    for (let i = 0; i < 3; i++) {
      for (let side = 0; side < 2; side++) {
        const v = init[i][side === 0 ? 's1' : 's2'];
        const n = Number(v);
        if (v !== '' && (isNaN(n) || n < 0 || n > setMaxFor(i))) {
          out[i][side] = true;
        }
      }
    }
    return out;
  });
  const [court, setCourt] = uST(match.court ?? '');
  // Existierender match.time ist im Format "Sa 09:00" — Tag und Uhrzeit
  // beim Bearbeiten splitten, beim Speichern wieder kombinieren.
  const initTimeParts = (match.time ?? '').split(' ');
  const [day, setDay] = uST(initTimeParts[0] || '');
  const [hour, setHour] = uST(initTimeParts[1] || '');
  const [winner, setWinner] = uST(match.winner === 0 || match.winner === 1 ? match.winner : null);
  const [wo, setWo] = uST(match.wo === 0 || match.wo === 1 ? match.wo : null);
  const [p1Id, setP1Id] = uST(match.p1 ?? '');
  const [p2Id, setP2Id] = uST(match.p2 ?? '');

  const r1 = p1Id && p1Id !== 'FREILOS' ? regsById[p1Id] : null;
  const r2 = p2Id && p2Id !== 'FREILOS' ? regsById[p2Id] : null;
  const p1Freilos = p1Id === 'FREILOS';
  const p2Freilos = p2Id === 'FREILOS';

  const updSet = (i, side, val) => {
    const next = sets.map(s => ({...s}));
    next[i][side] = val;
    setSets(next);
  };
  const setCellManual = (i, side, on) => {
    setManualCell(m => {
      const next = m.map(row => [...row]);
      next[i][side] = on;
      return next;
    });
  };

  // Render-Helper für eine Score-Zelle. Default: Dropdown mit den
  // regulären Punktwerten 0..7 (Sätze) bzw. 0..20 (MTB) plus einer
  // "…"-Option, die auf manuelle Eingabe umschaltet. Aus dem Manual-
  // Modus kommt man mit dem ↩-Button zurück zum Dropdown (löscht den
  // aktuellen Wert, damit kein out-of-range-Wert hängen bleibt).
  const renderScoreCell = (i, side) => {
    const sideKey = side === 0 ? 's1' : 's2';
    const value = sets[i][sideKey];
    const max = setMaxFor(i);
    // Score-Eingabe nur bei Freilos sperren — bei W.O. erlauben wir
    // einen Teil-Spielstand (Aufgabe nach Satzgewinn / Verletzung).
    const disabled = p1Freilos || p2Freilos;
    if (manualCell[i][side]) {
      return (
        <div className="score-manual" style={{display:'flex', gap: 4, alignItems:'center'}}>
          <input className="input score-input" type="number" min="0"
                 disabled={disabled}
                 value={value}
                 onChange={e => updSet(i, sideKey, e.target.value)}/>
          <button type="button" className="link-btn"
                  style={{padding: '2px 6px', fontSize: 11}}
                  disabled={disabled}
                  title="Zurück zur Auswahl"
                  onClick={() => { updSet(i, sideKey, ''); setCellManual(i, side, false); }}>↩</button>
        </div>
      );
    }
    const onChange = (e) => {
      const v = e.target.value;
      if (v === '__manual__') {
        setCellManual(i, side, true);
      } else {
        updSet(i, sideKey, v);
      }
    };
    return (
      <select className="select score-input"
              disabled={disabled}
              value={value === '' || value == null ? '' : String(value)}
              onChange={onChange}>
        <option value="">—</option>
        {Array.from({length: max + 1}, (_, n) => (
          <option key={n} value={String(n)}>{n}</option>
        ))}
        <option value="__manual__">…</option>
      </select>
    );
  };

  // Wenn ein Spieler ausgewählt wird, der bereits dem anderen Slot zugewiesen
  // ist, den anderen Slot leeren — keine doppelte Belegung. Freilos ist
  // immer auf einer Seite zulässig (z. B. wenn ein Team kurzfristig
  // absagt und kein Ersatz mehr kommt) — die andere Seite gewinnt
  // automatisch (Sätze geleert, kein Walkover-Tag, weil keine Aufgabe).
  const setFreilosSideWins = (winSide) => {
    setWinner(winSide);
    setWo(null);
    setSets([{s1:'', s2:''}, {s1:'', s2:''}, {s1:'', s2:''}]);
  };
  const setP1 = (id) => {
    setP1Id(id);
    if (id && id === p2Id) setP2Id('');
    if (id === 'FREILOS') setFreilosSideWins(1);
  };
  const setP2 = (id) => {
    setP2Id(id);
    if (id && id === p1Id) setP1Id('');
    if (id === 'FREILOS') setFreilosSideWins(0);
  };

  // W.O.-Checkbox an/ab. Setzt wo auf den Verlierer (= opposite vom
  // gewaehlten Sieger). Funktioniert nur, wenn ein Sieger ausgewaehlt
  // ist — sonst weiss man nicht, wer aufgegeben hat. Bereits
  // eingetragene Saetze bleiben erhalten (Aufgabe mitten im Match).
  const toggleWOCheckbox = (e) => {
    if (e.target.checked && winner != null) {
      setWo(winner === 0 ? 1 : 0);
    } else {
      setWo(null);
    }
  };

  // Sobald irgendein Satz-Wert eingetragen ist, muss auch ein Sieger
  // feststehen — sonst lässt sich das Match nicht speichern. Walkover
  // und Freilos setzen den Winner automatisch und gelten als gültig.
  // Ohne eingetragene Sätze ist das Speichern jederzeit erlaubt
  // (z. B. um nur Platz/Uhrzeit oder die Paarung zu hinterlegen).
  const hasAnyScore = sets.some(s => s.s1 !== '' || s.s2 !== '');
  const winnerImplied = winner != null || wo != null || p1Freilos || p2Freilos;
  const canSave = !hasAnyScore || winnerImplied;

  // "Partie löschen" ist nur sinnvoll, wenn das persistierte Match
  // überhaupt Inhalt hat — Paarung, Sätze, Sieger, W.O., Platz oder
  // Zeit. Lokale (noch nicht gespeicherte) Eingaben zählen NICHT,
  // weil "Löschen" sich auf den persistierten Stand bezieht.
  const hasPersistedContent = !!(
    match.p1 || match.p2 ||
    (match.sets && match.sets.length > 0) ||
    match.winner != null || match.wo != null ||
    match.court || match.time
  );

  const save = () => {
    if (!canSave) return;
    const cleaned = sets
      .map(s => ({ s1: s.s1 === '' ? null : Number(s.s1), s2: s.s2 === '' ? null : Number(s.s2) }))
      .filter(s => s.s1 != null || s.s2 != null);
    // Tag + Uhrzeit zusammenbauen ("Sa 09:00"). Wenn beides leer → null,
    // wenn nur eins gesetzt → trotzdem speichern (z. B. "Sa" oder "09:00").
    const composedTime = [day, hour].filter(Boolean).join(' ') || null;
    onSave({
      ...match,
      p1: p1Id || null,
      p2: p2Id || null,
      // Bei Freilos werden keine Sätze gespielt → leeren. Bei W.O.
      // bleibt der Teil-Spielstand erhalten (z. B. 6:2 0:3 W.O.).
      sets: (p1Freilos || p2Freilos) ? [] : cleaned,
      winner,
      wo,
      court: court || null,
      time: composedTime,
    });
  };

  // Dropdown-Optionen: gesetzte Paarungen oben (Setzliste 1..8), dann der Rest
  // in LK-Reihenfolge — beides liefert sortedBySeed.
  // Ab R2 (Achtelfinale) trennen wir die Auswahl in zwei optgroups:
  //   "Logisch möglich" — nur die Paarungen, die laut Bracket-Stand noch
  //                        in dieses Match laufen können
  //   "Andere Paarungen" — alle anderen Paarungen, falls aus Sonderfall
  //                        (Verletzung etc.) doch jemand Bereits-Aus-
  //                        geschiedenes nachrücken soll
  // Bereits gesetzte p1/p2 werden in die "Logisch möglich"-Gruppe
  // aufgenommen, damit eine schon eingetragene Paarung weiter wählbar
  // bleibt, auch wenn sie strenggenommen aus dem Bracket-Pfad gefallen
  // wäre.
  const eligibleIds = eligibleRegIdsForMatch(bracket, bucket, match.round, match.idx);
  if (eligibleIds && match.p1 && match.p1 !== 'FREILOS') eligibleIds.add(match.p1);
  if (eligibleIds && match.p2 && match.p2 !== 'FREILOS') eligibleIds.add(match.p2);
  const sortedRegs = sortedBySeed(registrations);
  const eligibleRegs = eligibleIds ? sortedRegs.filter(r => eligibleIds.has(r.id)) : sortedRegs;
  const otherRegs = eligibleIds ? sortedRegs.filter(r => !eligibleIds.has(r.id)) : [];
  const regOption = (r) => {
    const s = seeds[r.id];
    return `${s ? `[${s}] ` : ''}${r.p1.last} / ${r.p2.last}`;
  };
  const renderOptions = () => (
    <>
      {eligibleIds ? (
        <>
          {eligibleRegs.length > 0 && (
            <optgroup label="Logisch möglich">
              {eligibleRegs.map(r => <option key={r.id} value={r.id}>{regOption(r)}</option>)}
            </optgroup>
          )}
          {otherRegs.length > 0 && (
            <optgroup label="Andere Paarungen (Sonderfall)">
              {otherRegs.map(r => <option key={r.id} value={r.id}>{regOption(r)}</option>)}
            </optgroup>
          )}
        </>
      ) : (
        sortedRegs.map(r => <option key={r.id} value={r.id}>{regOption(r)}</option>)
      )}
    </>
  );

  return (
    <>
    <div className="modal-overlay">
      <div className="modal" onClick={e=>e.stopPropagation()} style={{maxWidth: 680}}>
        <button type="button" className="modal-close" onClick={onClose} aria-label="Schließen"><Icon name="x" size={18}/></button>
        <h3 style={{marginBottom: 14}}>Match bearbeiten</h3>

        <div className="card card-pad" style={{padding: 14, marginTop: 14}}>
          <div className="label" style={{color:'var(--red)', marginBottom: 10}}>Paarung</div>
          <div className="field">
            <label className="field-label">Paar 1</label>
            <select className="select" value={p1Id} onChange={e => setP1(e.target.value)}>
              <option value="">— frei —</option>
              <option value="FREILOS">Freilos (Paar 2 rückt auf)</option>
              {renderOptions()}
            </select>
          </div>
          <div className="field">
            <label className="field-label">Paar 2</label>
            <select className="select" value={p2Id} onChange={e => setP2(e.target.value)}>
              <option value="">— frei —</option>
              <option value="FREILOS">Freilos (Paar 1 rückt auf)</option>
              {renderOptions()}
            </select>
          </div>
        </div>

        <div className="score-grid" style={{marginTop: 14, opacity: (p1Freilos || p2Freilos) ? 0.4 : 1}}>
          <div className="sg-head"></div>
          <div className="sg-head sg-set">Satz 1</div>
          <div className="sg-head sg-set">Satz 2</div>
          <div className="sg-head sg-set sg-mtb">MTB</div>

          <div className="sg-name">{p1Freilos ? 'Freilos' : (r1 ? pairFull(r1) : '—')}</div>
          {[0,1,2].map(i => <div key={i}>{renderScoreCell(i, 0)}</div>)}

          <div className="sg-name">{p2Freilos ? 'Freilos' : (r2 ? pairFull(r2) : '—')}</div>
          {[0,1,2].map(i => <div key={i}>{renderScoreCell(i, 1)}</div>)}
        </div>

        <div className="winner-picker">
          <div className="field-label">Sieger</div>
          <div className="winner-options">
            <button type="button"
                    className={`winner-opt ${winner === 0 ? 'on' : ''}`}
                    disabled={!r1 || p1Freilos}
                    onClick={() => { setWinner(winner === 0 ? null : 0); if (winner !== 0) setWo(null); }}>
              <Icon name="trophy" size={12}/> {p1Freilos ? 'Freilos' : (r1 ? pairLabel(r1) : '—')}
            </button>
            <button type="button"
                    className={`winner-opt ${winner === 1 ? 'on' : ''}`}
                    disabled={!r2 || p2Freilos}
                    onClick={() => { setWinner(winner === 1 ? null : 1); if (winner !== 1) setWo(null); }}>
              <Icon name="trophy" size={12}/> {p2Freilos ? 'Freilos' : (r2 ? pairLabel(r2) : '—')}
            </button>
          </div>
          {/* W.O.-Checkbox direkt unter dem Sieger — wenn aktiv, gilt
              die Niederlage der anderen Seite als Walkover (Aufgabe).
              Funktioniert nur, wenn ein Sieger ausgewaehlt ist. */}
          <label className="wo-checkbox" style={{display:'inline-flex', alignItems:'center', gap: 8, marginTop: 10, cursor: winner == null ? 'not-allowed' : 'pointer', opacity: winner == null ? 0.5 : 1}}>
            <input type="checkbox"
                   checked={wo != null}
                   disabled={winner == null || p1Freilos || p2Freilos}
                   onChange={toggleWOCheckbox}/>
            <span style={{fontSize: 13, color: 'var(--text-2)'}}>W.O. — Verlierer hat aufgegeben</span>
          </label>
        </div>

        <div className="field-grid-3" style={{marginTop: 18, display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap: 12}}>
          <div className="field">
            <label className="field-label">Platz</label>
            <select className="select" value={court} onChange={e=>setCourt(e.target.value)}>
              <option value="">— frei —</option>
              {COURT_OPTIONS.map(c => <option key={c} value={c}>{c}</option>)}
            </select>
          </div>
          <div className="field">
            <label className="field-label">Tag</label>
            <select className="select" value={day} onChange={e=>setDay(e.target.value)}>
              <option value="">— frei —</option>
              {DAY_OPTIONS.map(d => <option key={d} value={d}>{d}</option>)}
            </select>
          </div>
          <div className="field">
            <label className="field-label">Uhrzeit</label>
            <select className="select" value={hour} onChange={e=>setHour(e.target.value)}>
              <option value="">— frei —</option>
              {HOUR_OPTIONS.map(h => <option key={h} value={h}>{h}</option>)}
            </select>
          </div>
        </div>

        <div style={{marginTop: 18}}>
          {!canSave && (
            <div style={{color:'var(--loss)', fontSize: 12, marginBottom: 8, textAlign:'right'}}>
              Sätze eingetragen — bitte Sieger oder Walkover wählen, um zu speichern.
            </div>
          )}
          <div style={{display:'flex', justifyContent:'space-between', gap: 10, alignItems:'center'}}>
            {/* Lösch-Button nur sichtbar, wenn die persistierte Partie
                überhaupt etwas enthält (Paarung, Sätze, Sieger, W.O.,
                Platz oder Zeit). Sonst gibt's nichts zu löschen — der
                User soll dann "Abbrechen" drücken. */}
            {hasPersistedContent ? (
              <button className="link-btn link-danger"
                      style={{fontSize: 13}}
                      onClick={() => setConfirmDelete(true)}
                      title="Partie aus dem Tableau entfernen">
                <Icon name="trash" size={12}/> Partie löschen
              </button>
            ) : <span/>}
            <div style={{display:'flex', gap: 10}}>
              <button className="btn btn-ghost" onClick={onClose}>Abbrechen</button>
              <button className="btn btn-primary" onClick={save} disabled={!canSave}>
                <Icon name="check" size={14}/> Speichern
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>

    {confirmDelete && (
      <div className="modal-overlay">
        <div className="modal" onClick={e=>e.stopPropagation()} style={{maxWidth: 460}}>
          <button type="button" className="modal-close" onClick={() => setConfirmDelete(false)} aria-label="Schließen"><Icon name="x" size={18}/></button>
          <h3>Partie wirklich löschen?</h3>
          <div className="sub">
            Alle Eingaben dieser Partie gehen verloren — Paarung, Sätze,
            Sieger, Walkover, Platz und Uhrzeit. Die Folgerunden werden
            automatisch aufgeräumt (Sieger der gelöschten Partie ist
            danach nicht mehr in der nächsten Runde gesetzt).
          </div>
          <div style={{display:'flex', justifyContent:'space-between', marginTop: 12, gap: 10}}>
            <button className="btn btn-ghost" onClick={() => setConfirmDelete(false)}>Abbrechen</button>
            <button className="btn btn-primary" style={{background:'var(--loss)', borderColor:'var(--loss)'}}
                    onClick={() => { setConfirmDelete(false); onDelete(); }}>
              <Icon name="trash" size={14}/> Löschen
            </button>
          </div>
        </div>
      </div>
    )}
    </>
  );
}

// seeds wird im App-Container via seedsFromBracket(bracket) abgeleitet
// und als Prop reingereicht — Single Source of Truth ist der Bracket
// (Position 0 = Setz 1, Position 31 = Setz 2, etc.). Lokales
// seedsFor(registrations) wäre falsch: dabei würde die LK-basierte
// Rangliste auch Nicht-Bracket-Teams oder veränderte Reg-Listen
// einbeziehen und vom tatsächlichen Bracket-Layout abweichen.
function TableauPage({ bracket, registrations, seeds, isAdmin, bracketPublished, onTogglePublish, onUpdateMatch, onDeleteMatch }) {
  // ALLE Hooks müssen vor jedem möglichen Early-Return stehen, sonst
  // ändert sich beim Wechsel des Login-Status die Hook-Reihenfolge
  // (Rules of Hooks-Verletzung) und React wirft "Rendered more hooks
  // than during the previous render" — die Seite hängt dann, bis ein
  // Hard-Refresh den React-Tree neu aufbaut.
  const [bucket, setBucket] = uST('main');
  const [editing, setEditing] = uST(null);
  const regsById = useMemo(() => {
    const o = {};
    registrations.forEach(r => { o[r.id] = r; });
    return o;
  }, [registrations]);

  // Solange das Tableau nicht freigegeben ist, sehen öffentliche Besucher
  // nur einen Platzhalter. Admin sieht das Tableau jederzeit (und kann
  // es vorbereiten / freigeben).
  if (!bracketPublished && !isAdmin) {
    return (
      <div className="page">
        <div className="page-eyebrow">Tableau</div>
        <h1 className="page-title">Tableau</h1>
        <div className="card card-pad" style={{padding:'48px 30px', textAlign:'center', marginTop: 24}}>
          <div style={{width: 72, height: 72, borderRadius:'50%', background:'var(--cream-2)', color:'var(--text-3)', display:'inline-grid', placeItems:'center', marginBottom: 18}}>
            <Icon name="trophy" size={28}/>
          </div>
          <h2 className="page-title" style={{fontSize: 28, marginTop: 0}}>Tableau wird rechtzeitig online gestellt.</h2>
          <p className="page-sub" style={{margin:'0 auto 0', maxWidth: 560}}>
            Die Auslosung erfolgt nach Meldeschluss am 05.08.2026 um 18:00 Uhr.
            Sobald das Tableau steht, findet ihr es hier.
          </p>
        </div>
      </div>
    );
  }

  // Auf Hard-Refresh im Admin-Modus setzt der App-Init-Effect zuerst
  // setIsAdmin(true) und erst danach (nach Promise.all-Load) Bracket +
  // Registrations. In dem Render-Window dazwischen ist bracket noch null,
  // und ohne diesen Guard kracht die volle UI weiter unten beim
  // Zugriff auf bracket.main / bracket.cons — die Seite bleibt
  // blank. Non-admin trifft das nicht, weil der If oben dann
  // schon zum Placeholder fuehrt. */
  if (!bracket) {
    return (
      <div className="page">
        <div className="page-eyebrow">Tableau</div>
        <h1 className="page-title">Tableau</h1>
        <p className="page-sub">Lade Bracket-Daten…</p>
      </div>
    );
  }

  // printAs (window.printAs) ist global in components.jsx definiert und
  // wird auch von der Meldeliste fuer die Teilnehmer-Druckliste genutzt.

  return (
    <div className="page">
      {/* Toolbar als Grid: Title / Actions / PDF. CSS-Template-Areas
          steuern Reihenfolge je Viewport — Desktop: Title|Actions oben,
          PDF darunter. Mobile: Title -> PDF -> Actions (User wollte
          den PDF-Hinweis explizit OBERHALB der Hauptrunde/Trostrunde-
          Umschalt-Tabs). */}
      <div className="tableau-toolbar">
        <div className="tableau-title">
          <div className="page-eyebrow">Turnierbaum</div>
          <h1 className="page-title">Tableau</h1>
          <p className="page-sub page-sub-wide">
            Hauptrunde mit 32 Paarungen im KO-System. Verlierer der 1. Runde rutschen in die Trostrunde.
            {isAdmin && !bracketPublished && <span style={{color:'var(--loss)', fontWeight:600}}> Tableau ist NICHT öffentlich — Besucher sehen aktuell nur den Platzhalter.</span>}
          </p>
        </div>
        <div className="tableau-actions no-print">
          <div className="tab-toggle">
            <button className={bucket==='main'?'on':''} onClick={()=>setBucket('main')}>Hauptrunde</button>
            <button className={bucket==='cons'?'on':''} onClick={()=>setBucket('cons')}>Trostrunde</button>
          </div>
          {isAdmin && (
            /* Print-Button als Split-Button: linke Hälfte druckt A4, rechte
               Hälfte druckt A3. Visuell ein zusammenhaengender Button mit
               Trennlinie in der Mitte. */
            <div className="btn-split" role="group" aria-label="PDF drucken">
              <button className="btn-split-half" onClick={() => printAs('a4')}
                      title="A4: Hauptrunde in zwei Hochformat-Haelften, Bruecken-Seite mit HF+Finale, Trostrunde quer">
                <Icon name="print" size={14}/> A4
              </button>
              <button className="btn-split-half" onClick={() => printAs('a3')}
                      title="A3: komplettes Tableau auf einer Querformat-Seite, Trostrunde auf einer zweiten">
                <Icon name="print" size={14}/> A3
              </button>
            </div>
          )}
        </div>
        {/* PDF-Slot: Public bekommt den PDF-Hinweis, Admin bekommt den
            Öffentlich/Veröffentlichen-Toggle im selben CTA-Banner-Look. */}
        {isAdmin ? (
          /* Zustands-Farbe: noch nicht öffentlich = roter Aufruf-zum-
             Handeln, schon öffentlich = cremefarben (Status-Anzeige,
             nicht mehr CTA). */
          <button type="button"
                  className={`tableau-pdf cta-banner no-print ${bracketPublished ? 'cta-banner-light' : ''}`}
                  onClick={onTogglePublish}
                  title={bracketPublished ? 'Tableau ist öffentlich — klick zum Verstecken' : 'Tableau veröffentlichen — wird dadurch öffentlich sichtbar'}>
            <Icon name={bracketPublished ? 'check' : 'lock'} size={18}/>
            <span className="cta-banner-title">{bracketPublished ? 'Tableau ist öffentlich' : 'Tableau veröffentlichen'}</span>
          </button>
        ) : (
          <button type="button" className="tableau-pdf cta-banner no-print" onClick={() => printAs('a3')}>
            <Icon name="print" size={18}/>
            <span className="cta-banner-title">Vollständiges Tableau als PDF anzeigen</span>
          </button>
        )}
      </div>

      {/* Bildschirm: vollständiges Bracket pro Bucket (per Tab umschaltbar). */}
      <div className={`bracket-screen bracket-section ${bucket==='main' ? '' : 'hide-on-screen'}`}>
        <BracketView rounds={bracket.main} roundNames={MAIN_ROUNDS} regsById={regsById} seeds={seeds} editable={isAdmin} onEdit={setEditing}/>
      </div>
      <div className={`bracket-screen bracket-section ${bucket==='cons' ? '' : 'hide-on-screen'}`}>
        <BracketView rounds={bracket.cons} roundNames={CONS_ROUNDS} regsById={regsById} seeds={seeds} editable={isAdmin} onEdit={setEditing}/>
      </div>

      {/* Druck A4: Hochformat. Hauptrunde in obere/untere Hälfte ohne Finale,
          dann eine separate Brücken-Seite mit beiden Halbfinals + Finale.
          Idee: nach dem Druck können die Seiten zusammengeklebt werden — die
          Brückenseite verbindet HF1 (rechte Kante obere Hälfte) und HF2
          (rechte Kante untere Hälfte) zum Finale. So führt keine Linie mehr
          ins Nichts. Trostrunde bleibt als eigene Seite. */}
      <div className="bracket-print bracket-print-a4">
        <div className="bracket-section bracket-a4-upper">
          <BracketView
            rounds={[
              bracket.main[0].slice(0, 8),
              bracket.main[1].slice(0, 4),
              bracket.main[2].slice(0, 2),
            ]}
            roundNames={MAIN_ROUNDS.slice(0, 3)}
            regsById={regsById} seeds={seeds} editable={false} onEdit={()=>{}} interactive={false}/>
          <PrintFooter subtitle="Hauptrunde"/>
        </div>
        <div className="bracket-section bracket-a4-lower">
          <BracketView
            rounds={[
              bracket.main[0].slice(8, 16),
              bracket.main[1].slice(4, 8),
              bracket.main[2].slice(2, 4),
            ]}
            roundNames={MAIN_ROUNDS.slice(0, 3)}
            regsById={regsById} seeds={seeds} editable={false} onEdit={()=>{}} interactive={false}/>
          <PrintFooter subtitle="Hauptrunde"/>
        </div>
        <div className="bracket-section bracket-bridge">
          <BracketView
            rounds={[
              bracket.main[3],
              bracket.main[4],
            ]}
            roundNames={MAIN_ROUNDS.slice(3, 5)}
            regsById={regsById} seeds={seeds} editable={false} onEdit={()=>{}} interactive={false}/>
          <PrintFooter subtitle="Halbfinale · Finale"/>
        </div>
        <div className="bracket-section bracket-trostrunde">
          <div className="bracket-trostrunde-inner">
            <BracketView rounds={bracket.cons} roundNames={CONS_ROUNDS} regsById={regsById} seeds={seeds} editable={false} onEdit={()=>{}} interactive={false}/>
            {/* Footer INSIDE des rotierten Inneren — wird mitrotiert und
                liest sich damit in derselben Richtung wie das Bracket
                (Landscape-Leselayout). Position oben-rechts im Inneren =
                Landscape-Top-Right. */}
            <PrintFooter subtitle="Trostrunde"/>
          </div>
        </div>
      </div>

      {/* Druck A3: Querformat. Komplettes Hauptbracket auf einer Seite,
          Trostrunde auf einer zweiten. Keine Aufteilung, keine Rotation —
          alles passt in 5 bzw. 4 Spalten quer auf einen A3-Bogen. */}
      <div className="bracket-print bracket-print-a3">
        <div className="bracket-section bracket-a3-main">
          <BracketView rounds={bracket.main} roundNames={MAIN_ROUNDS} regsById={regsById} seeds={seeds} editable={false} onEdit={()=>{}} interactive={false}/>
          <PrintFooter subtitle="Hauptrunde"/>
        </div>
        <div className="bracket-section bracket-a3-cons">
          <BracketView rounds={bracket.cons} roundNames={CONS_ROUNDS} regsById={regsById} seeds={seeds} editable={false} onEdit={()=>{}} interactive={false}/>
          <PrintFooter subtitle="Trostrunde"/>
        </div>
      </div>

      {editing && (
        <ResultModal
          match={editing}
          regsById={regsById}
          registrations={registrations}
          seeds={seeds}
          bracket={bracket}
          bucket={bucket}
          onClose={()=>setEditing(null)}
          onSave={(m) => { onUpdateMatch(bucket, m); setEditing(null); }}
          onDelete={() => { onDeleteMatch(bucket, editing); setEditing(null); }}
        />
      )}
    </div>
  );
}

Object.assign(window, { TableauPage });
