// ===== Clubhuisscherm: de tee sheet van vandaag op een tv ===============================
// Aanleiding, letterlijk: Daniel (UP Events) op 24 augustus 2026 over de demo: "en een duidelijk
// tee sheet die we kunnen draaien in cluphuis". Datzelfde punt stond al in het demoverslag van
// 21 augustus 2026 (kernpunt 4): aanmelden verplicht, de tee sheet op een scherm in of achter de
// bar, groen betekent aangemeld en betaald.
//
// DIT IS EEN DERDE WEERGAVE, naast de balie (bedienen) en de kiosk (aanmelden). Hier valt niets te
// bedienen: geen knop, geen invoerveld, geen datumkiezer. Alleen kijken.
//
// WAAROM HET BINNEN DE BALIE-APP ZIT EN NIET IN DE GASTVOORKANT.
// Eigen meting op staging, 24 augustus 2026:
//   1. `teesheet` is de enige functie die naam, aantal spelers, betaalstatus en incheckstand per
//      starttijd teruggeeft, en die begint met `if not is_medewerker() then raise 42501`. Aan `anon`
//      is geen EXECUTE gegeven.
//   2. Er is geen enkele andere aan `anon` toegekende functie die dit kan. `bezette_plekken` geeft
//      alleen een getal, `zoek_boeking` en `boeking_bij_naam_email` werken per reservering.
//   3. Rechtstreeks lezen kan ook niet: `boekingen` en `deelnemers` hebben RLS aan en hun enige
//      policies eisen `is_medewerker()` of een eigen lid_id. Anoniem levert dat nul rijen op.
//      `tijdsloten` mag `anon` wel lezen (vanaf vandaag), maar daar staan geen namen in.
// Een gastpagina zou dus een NIEUWE publieke functie vragen die namen en betaalstand vrijgeeft, en
// dat is precies wat je op een open URL niet wilt hebben. Vandaar: een extra scherm binnen de balie,
// net als de kiosk. Een keer inloggen op de tv, daarna blijft het staan.
//
// HOE VAAK HET ZICHZELF VERVERST, EN WAAROM.
// Data: elke 60 seconden. De starttijden liggen 9 minuten uit elkaar, dus een minuut is ruim binnen
// de kleinste eenheid die op dit scherm iets betekent; een gast die zich aan de bar aanmeldt staat
// binnen een minuut groen. Op een openingsdag van negen uur is dat 540 aanroepen op een tablet, wat
// verwaarloosbaar is naast wat de balie zelf doet.
// Klok en het nu-anker: elke 10 seconden. Dat is fijner dan de datalus, zodat de scheidingslijn
// tussen geweest en komend niet tot een minuut achterloopt, en het kost geen enkele netwerkaanroep.
// Het tijdstip van de laatste geslaagde verversing staat altijd in beeld: een bevroren scherm moet
// je kunnen ZIEN, anders staat de bar naar oude gegevens te kijken zonder het te weten.
//
// TESTBAARHEID. `bron` en `nuBron` zijn optionele props. Zonder die props doet dit scherm precies
// wat het in productie doet (db.rpc("teesheet") en de echte klok). Ze bestaan omdat het ingelogde
// baliescherm niet met een echt wachtwoord te openen is in een meetopstelling, en het scherm dus
// gemeten wordt met de uitvoer van `teesheet` als vaste invoer. Zie meet-clubhuis.mjs.

function clubhuisStand(bk, slotVoorbij){
  const spelers = bk.spelers || [];
  const binnen  = spelers.filter(p=>p.ingecheckt).length;
  const totaal  = spelers.length;
  if(bk.status==="no_show" || bk.no_show) return {klas:"st-noshow", woord:"No-show", binnen, totaal};
  if(bk.betaalstatus==="open")            return {klas:"st-onbetaald", woord:"Niet betaald", binnen, totaal};
  if(totaal>0 && binnen===totaal)         return {klas:"st-aangemeld", woord:"Aangemeld", binnen, totaal};
  if(binnen>0)                            return {klas:"st-deels", woord:binnen+" van de "+totaal+" binnen", binnen, totaal};
  if(slotVoorbij)                         return {klas:"st-nietgemeld", woord:"Nog niet gemeld", binnen, totaal};
  return {klas:"st-verwacht", woord:"Verwacht", binnen, totaal};
}

// Welke maatstap de naam krijgt. CSS kan niet meten hoe lang een naam is, dus gebeurt dat hier, op
// het aantal tekens. Waarom niet gewoon afkappen met een ellipsis: dat stond er eerst, en op 1280
// breed werd "Bedrijfsuitje Kroonenberg" daardoor "Bedrijfsuitje Kroone...". Aan een muur klikt
// niemand door om de rest te lezen. De naam wikkelt nu over meer regels; deze stap houdt alleen het
// aantal regels binnen de perken bij een echt lange bedrijfsnaam.
// De grenzen zijn nagemeten op 1280x800, de smalste maat waarop dit scherm bedoeld is: bij 22 tekens
// past een naam daar nog op een regel, daarboven niet meer.
function clubhuisNaamMaat(naam){
  const n = (naam||"").length;
  if(n > 34) return " cl-xlang";
  if(n > 22) return " cl-lang";
  return "";
}

// Hoeveel regels er op het scherm PASSEN. Dit is geen smaakgetal maar een rekensom op nagemeten
// maten: de kop plus de cijferbalk zijn samen 172 pixels hoog en een regel herhaalt zich elke 86
// pixels (gemeten in de browser op 24 augustus 2026, 1920x1080). Boven de 1280 pixels breed staan
// er twee kolommen naast elkaar, dus passen er twee keer zoveel regels.
// WAAROM DIT ERTOE DOET. Aan een muur hangt niemand te scrollen. Wat niet past is dus niet
// "eronder", het is weg. Daarom vallen bij te weinig ruimte eerst de regels weg die niets meer
// vragen (voorbij EN volledig aangemeld); die zijn klaar. Een regel die nog iets van de bar wil
// (verwacht, niet betaald, no-show, nog niet gemeld) blijft altijd staan, en onderaan staat hoeveel
// afgeronde regels er niet meer in beeld zijn.
// De hoogte van de kop wordt NIET geraden maar bij elke weergave uit de browser gelezen (de
// bovenkant van het blad). Dat moest wel: op 1280 breed valt de legenda op een tweede regel en is de
// kop hoger dan op 1920, en met een vast getal van 172 stak het blad daar vijftig pixels onder het
// beeld uit. Gemeten, niet aangenomen.
const CLUBHUIS_KOPHOOGTE = 172;   // alleen de startwaarde, voor de allereerste weergave
const CLUBHUIS_REGELPITCH = 86;   // startwaarde; de echte pitch wordt hieronder ook gemeten, want
                                  // onder 1600 pixels breed is een regel kleiner en zou een vast
                                  // getal regels verbergen waar wel ruimte voor is
function clubhuisRegelruimte(kop, pitch){
  const b = typeof window==="undefined" ? 1920 : window.innerWidth;
  const h = typeof window==="undefined" ? 1080 : window.innerHeight;
  const kolommen = b >= 1280 ? 2 : 1;
  const perKolom = Math.max(4, Math.floor((h - (kop||CLUBHUIS_KOPHOOGTE) - 8) / (pitch||CLUBHUIS_REGELPITCH)));
  return perKolom * kolommen;
}

function Clubhuis({ banen, bron, nuBron }){
  const klok = nuBron || (()=>new Date());
  const [nu,setNu]       = useState(klok());
  const [rijen,setRijen] = useState(null);
  const [bij,setBij]     = useState(null);     // tijdstip laatste geslaagde verversing
  const [f,setF]         = useState(null);
  const [tel,setTel]     = useState({sloten:0,bezetSloten:0,spelers:0,vrij:0});
  const [kop,setKop]       = useState(CLUBHUIS_KOPHOOGTE);
  const [pitch,setPitch]   = useState(CLUBHUIS_REGELPITCH);
  const blad = React.useRef(null);
  const ruimte = clubhuisRegelruimte(kop, pitch);

  const datum = iso(nu);

  const haal = useCallback(async ()=>{
    const lijst = banen||[];
    if(!lijst.length) return;
    const dag = iso(klok());
    let sloten = [];
    try{
      for(const b of lijst){
        const data = bron ? await bron(b.id, dag)
                          : await db.rpc("teesheet",{p_baan_id:b.id,p_datum:dag}).then(r=>{ if(r.error) throw r.error; return r.data||[]; });
        sloten = sloten.concat(data||[]);
      }
    }catch(e){ setF(fout(e)); return; }   // laat staan wat er stond, meld alleen dat het niet ververste
    setF(null);
    sloten.sort((a,b)=> a.starttijd<b.starttijd ? -1 : a.starttijd>b.starttijd ? 1 : 0);
    const open = sloten.filter(s=>s.status==="open");
    setTel({
      sloten: sloten.length,
      bezetSloten: sloten.filter(s=>(s.boekingen||[]).length>0).length,
      spelers: sloten.reduce((n,s)=>n+(s.boekingen||[]).reduce((m,b)=>m+(b.spelers||[]).length,0),0),
      vrij: open.reduce((n,s)=>n+Math.max(0,s.max_spelers-s.bezet),0)
    });
    const uit=[];
    sloten.forEach(s=>{ (s.boekingen||[]).forEach(b=>{ uit.push({slot:s, bk:b}); }); });
    setRijen(uit);
    setBij(klok());
  },[banen,bron]);  // eslint-disable-line

  useEffect(()=>{ haal(); const t=setInterval(haal,60000); return ()=>clearInterval(t); },[haal]);
  useEffect(()=>{ const t=setInterval(()=>setNu(klok()),10000); return ()=>clearInterval(t); },[]);  // eslint-disable-line
  // De bovenkant van het blad meten en alleen bijwerken als hij echt veranderd is. Zonder die
  // vergelijking zet elke weergave de state opnieuw en draait het scherm rond in zichzelf.
  useEffect(()=>{
    function meet(){ if(!blad.current) return;
      const t=Math.round(blad.current.getBoundingClientRect().top);
      setKop(v=> Math.abs(v-t)>2 ? t : v);
      // De HOOGSTE regel bepaalt de pitch, niet de eerste. Sinds namen mogen wikkelen zijn niet alle
      // regels even hoog, en met de eerste regel als maat zou het scherm te veel regels toelaten en
      // onderaan buiten beeld lopen. Liever een regel te weinig (die wordt geteld in de voettekst)
      // dan een regel die half onder de rand hangt waar niemand bij kan scrollen.
      const rr=blad.current.querySelectorAll(".cl-rij");
      if(rr.length){ let hoog=0;
        rr.forEach(el=>{ const h=el.getBoundingClientRect().height; if(h>hoog) hoog=h; });
        const p=Math.round(hoog)+10;   // 10 is de marge onder een regel
        setPitch(v=> Math.abs(v-p)>2 ? p : v); } }
    meet();
    window.addEventListener("resize",meet); return ()=>window.removeEventListener("resize",meet);
  });

  const nuTijd = nu.toTimeString().slice(0,8);
  const voorbijVan = r => r.slot.starttijd < nuTijd;
  // De eerste regel die nog moet komen is het anker. Alles daarboven is geweest.
  let ankerIndex = -1;
  if(rijen) for(let i=0;i<rijen.length;i++){ if(!voorbijVan(rijen[i])){ ankerIndex=i; break; } }

  let toon = rijen||[];
  let verborgen = 0;
  if(toon.length > ruimte){
    const weg = toon.length - ruimte;
    const klaar = [];
    for(let i=0;i<toon.length && klaar.length<weg;i++){
      const st = clubhuisStand(toon[i].bk, true);
      if(voorbijVan(toon[i]) && st.klas==="st-aangemeld") klaar.push(i);
    }
    verborgen = klaar.length;
    toon = toon.filter((_,i)=>klaar.indexOf(i)<0);
    if(ankerIndex>=0){
      const anker = rijen[ankerIndex];
      ankerIndex = toon.indexOf(anker);
    }
  }

  const klokTekst = nu.toLocaleTimeString("nl-NL",{hour:"2-digit",minute:"2-digit"});
  const bijTekst  = bij ? bij.toLocaleTimeString("nl-NL",{hour:"2-digit",minute:"2-digit",second:"2-digit"}) : "nog niet";

  return (<div className="clubhuis">
    <div className="clubhuis-kop">
      <div className="clubhuis-merk">
        <span className="clubhuis-up">UP</span>
        <span className="clubhuis-streep" aria-hidden="true"></span>
        <span className="clubhuis-merktekst">
          <span className="clubhuis-naam">Golf Weesp</span>
          <span className="clubhuis-sub">Premium booking</span>
        </span>
      </div>
      <div className="clubhuis-titel">
        <span className="clubhuis-titel-woord">Tee sheet</span>
        <span className="clubhuis-datum">{langDat(datum)}</span>
      </div>
      <div className="clubhuis-klok">
        <span className="clubhuis-tijd">{klokTekst}</span>
        <span className="clubhuis-bijgewerkt">Bijgewerkt {bijTekst}</span>
      </div>
    </div>

    <div className="clubhuis-balk">
      <div className="clubhuis-cijfers">
        <span className="clubhuis-cijfer"><b>{tel.spelers}</b> spelers</span>
        <span className="clubhuis-cijfer"><b>{tel.bezetSloten}</b> van de {tel.sloten} starttijden bezet</span>
        <span className="clubhuis-cijfer"><b>{tel.vrij}</b> plekken vrij</span>
      </div>
      <div className="clubhuis-legenda">
        <span className="cl-item"><span className="cl-swatch st-aangemeld"/>Aangemeld en betaald</span>
        <span className="cl-item"><span className="cl-swatch st-verwacht"/>Verwacht</span>
        <span className="cl-item"><span className="cl-swatch st-nietgemeld"/>Nog niet gemeld</span>
        <span className="cl-item"><span className="cl-swatch st-onbetaald"/>Niet betaald</span>
        <span className="cl-item"><span className="cl-swatch st-noshow"/>No-show</span>
      </div>
    </div>

    {f && <div className="clubhuis-storing">Geen verse gegevens. Laatste stand van {bijTekst}. {f}</div>}
    {rijen===null && !f && <div className="clubhuis-leeg">Laden…</div>}
    {rijen!==null && rijen.length===0 && <div className="clubhuis-leeg">Vandaag staan er geen reserveringen.</div>}

    {toon.length>0 && <div className="clubhuis-blad" ref={blad}>
      {toon.map((r,i)=>{
        const voorbij = voorbijVan(r);
        const anker = i===ankerIndex;
        const st = clubhuisStand(r.bk, voorbij);
        return (<div className={"cl-rij "+st.klas+(voorbij?" voorbij":"")+(anker?" anker":"")} key={r.bk.boeking_id}
            data-tijd={r.slot.starttijd.slice(0,5)} data-stand={st.klas}>
          <div className="cl-tijd">{r.slot.starttijd.slice(0,5)}
            {anker && <span className="cl-nu">Nu aan de beurt</span>}</div>
          <div className="cl-naam">
            <span className={"cl-contact"+clubhuisNaamMaat(r.bk.contact)}>{r.bk.contact}</span>
            <span className="cl-sport">{sportLabel(r.slot.sport)}</span>
          </div>
          <div className="cl-aantal">{st.totaal}<span className="cl-aantal-woord">{st.totaal===1?"speler":"spelers"}</span></div>
          <div className="cl-stand">{st.woord}</div>
        </div>);
      })}
    </div>}

    {verborgen>0 && <div className="clubhuis-voet">{verborgen} eerdere starttijden zijn afgerond en staan niet meer in beeld.</div>}
  </div>);
}
