function Reserves({ lang }) {
  const { useState, useEffect, useRef } = React;
  const T = window.T;
  const ResRow = window.ResRow;
  const t = T[lang].reserves;

  // ── Local i18n (strings not in T.js) ─────────────────────────────────────
  function tr(ca, es) { return lang === "es" ? es : ca; }

  // ── State ────────────────────────────────────────────────────────────────
  const [reservations, setReservations] = useState(null);
  const [filter, setFilter]             = useState("all");
  const [showModal, setShowModal]       = useState(false);
  const [expandedId, setExpandedId]     = useState(null);
  const [form, setForm]                 = useState(getDefaultForm);
  const [cancelTarget, setCancelTarget] = useState(null);  // reservation to cancel
  const [cancelReason, setCancelReason] = useState("");

  // ── Refs ─────────────────────────────────────────────────────────────────
  const clientIdRef    = useRef(null);
  const channelRef     = useRef(null);
  const userInteracted = useRef(false);
  const beepRef        = useRef(null);
  const newIdsRef      = useRef(new Set());
  const langRef        = useRef(lang);

  useEffect(function () { langRef.current = lang; }, [lang]);

  // ── Helpers ──────────────────────────────────────────────────────────────
  function todayStr() {
    var d = new Date();
    return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
  }

  function getDefaultForm() {
    return { date: todayStr(), time: "20:00", pax: "2", name: "", phone: "", notes: "" };
  }

  function fmtHour(iso) {
    if (!iso) return "—";
    return new Date(iso).toLocaleTimeString(lang === "es" ? "es-ES" : "ca-ES", { hour: "2-digit", minute: "2-digit" });
  }

  function fmtMeta(r) {
    var p = [];
    if (r.pax)         p.push(r.pax + " pers.");
    if (r.table_label) p.push(r.table_label);
    return p.join(" · ");
  }

  function mapSrc(source) {
    return source === "whatsapp_bot" ? "bot" : source === "web_form" ? "web" : "manual";
  }

  function buildNote(r) {
    return [r.notes, r.allergies].filter(Boolean).join(" · ");
  }

  function statusLabel(s) {
    return {
      pending:   tr("Pendent",    "Pendiente"),
      confirmed: tr("Confirmada", "Confirmada"),
      cancelled: tr("Cancel·lada","Cancelada"),
      noshow:    tr("No-show",    "No-show"),
    }[s] || s;
  }

  // ── Beep audio (generated once) ──────────────────────────────────────────
  useEffect(function () {
    var sr = 8000, dur = 0.15, freq = 880, n = Math.floor(sr * dur);
    var buf = new ArrayBuffer(44 + n);
    var v = new DataView(buf);
    function ws(o, s) { for (var i = 0; i < s.length; i++) v.setUint8(o + i, s.charCodeAt(i)); }
    ws(0, "RIFF"); v.setUint32(4, 36 + n, true); ws(8, "WAVE"); ws(12, "fmt ");
    v.setUint32(16, 16, true); v.setUint16(20, 1, true); v.setUint16(22, 1, true);
    v.setUint32(24, sr, true); v.setUint32(28, sr, true); v.setUint16(32, 1, true);
    v.setUint16(34, 8, true); ws(36, "data"); v.setUint32(40, n, true);
    for (var i = 0; i < n; i++) {
      v.setUint8(44 + i, Math.floor((Math.sin(2 * Math.PI * freq * i / sr) * 0.3 + 1) * 127.5));
    }
    var bytes = new Uint8Array(buf);
    var bin = ""; for (var j = 0; j < bytes.length; j++) bin += String.fromCharCode(bytes[j]);
    beepRef.current = new Audio("data:audio/wav;base64," + btoa(bin));
    beepRef.current.volume = 0.3;
  }, []);

  function playBeep() {
    if (userInteracted.current && beepRef.current) {
      beepRef.current.currentTime = 0;
      beepRef.current.play().catch(function () {});
    }
  }

  // Track user interaction for sound autoplay policy
  useEffect(function () {
    function mark() { userInteracted.current = true; }
    window.addEventListener("click", mark, { once: true });
    window.addEventListener("touchstart", mark, { once: true });
    return function () {
      window.removeEventListener("click", mark);
      window.removeEventListener("touchstart", mark);
    };
  }, []);

  // ── Load + Realtime ──────────────────────────────────────────────────────
  useEffect(function () {
    loadReservations();
    return function () {
      if (channelRef.current) window.SB.removeChannel(channelRef.current);
    };
  }, []);

  async function loadReservations() {
    var client = await window.SVX.getCurrentClient();
    if (!client) return;
    clientIdRef.current = client.id;

    var start = new Date(); start.setHours(0, 0, 0, 0);
    var end = new Date(start); end.setDate(end.getDate() + 90);

    var res = await window.SB
      .from("reservations")
      .select("*")
      .eq("client_id", client.id)
      .gte("reserved_at", start.toISOString())
      .lte("reserved_at", end.toISOString())
      .order("reserved_at", { ascending: true });

    if (res.error) {
      console.error("[Reserves] load:", res.error);
      window.SVX.toast(tr("Error carregant reserves", "Error cargando reservas"), "error");
      setReservations([]);
      return;
    }

    setReservations(res.data || []);
    setupRealtime(client.id);
  }

  function setupRealtime(clientId) {
    if (channelRef.current) window.SB.removeChannel(channelRef.current);

    channelRef.current = window.SB
      .channel("reserves:" + clientId)
      .on("postgres_changes", {
        event: "*",
        schema: "public",
        table: "reservations",
        filter: "client_id=eq." + clientId,
      }, function (payload) {
        if (payload.eventType === "INSERT") {
          setReservations(function (prev) {
            if (prev.some(function (r) { return r.id === payload.new.id; })) return prev;

            // Bounds check — only add if within our 90-day window
            var d = new Date(payload.new.reserved_at);
            var dayStart = new Date(); dayStart.setHours(0, 0, 0, 0);
            var dayEnd90 = new Date(dayStart); dayEnd90.setDate(dayEnd90.getDate() + 90);
            if (d < dayStart || d > dayEnd90) return prev;

            newIdsRef.current.add(payload.new.id);
            setTimeout(function () { newIdsRef.current.delete(payload.new.id); }, 500);

            if (payload.new.source === "whatsapp_bot") {
              playBeep();
              var curL = langRef.current;
              window.SVX.toast(
                curL === "es"
                  ? "Nueva reserva de " + payload.new.guest_name
                  : "Nova reserva de " + payload.new.guest_name,
                "info"
              );
            }

            var next = prev.concat(payload.new);
            next.sort(function (a, b) { return new Date(a.reserved_at) - new Date(b.reserved_at); });
            return next;
          });
        } else if (payload.eventType === "UPDATE") {
          setReservations(function (prev) {
            return prev.map(function (r) { return r.id === payload.new.id ? payload.new : r; });
          });
        } else if (payload.eventType === "DELETE") {
          setReservations(function (prev) {
            return prev.filter(function (r) { return r.id !== payload.old.id; });
          });
        }
      })
      .subscribe();
  }

  // ── Grouping ─────────────────────────────────────────────────────────────
  function getGroups() {
    if (!reservations) return [];

    var filtered = filter === "all"
      ? reservations
      : reservations.filter(function (r) { return r.status === filter; });

    // Boundaries at local midnight
    var dayStart    = new Date(); dayStart.setHours(0, 0, 0, 0);
    var todayEnd    = new Date(dayStart); todayEnd.setDate(todayEnd.getDate() + 1);
    var tomorrowEnd = new Date(todayEnd); tomorrowEnd.setDate(tomorrowEnd.getDate() + 1);

    var locale = lang === "es" ? "es-ES" : "ca-ES";

    // Fixed groups: today + tomorrow
    var todayGroup    = { key: "today",    label: t.today    || tr("Avui", "Hoy"),     items: [] };
    var tomorrowGroup = { key: "tomorrow", label: t.tomorrow || tr("Demà", "Mañana"),  items: [] };

    // Dynamic day groups: keyed by "YYYY-MM-DD"
    var dayMap = {};
    var dayOrder = [];

    function fmtDayLabel(date) {
      // "28 abril 2026" — no "de", no commas
      var raw = date.toLocaleDateString(locale, { day: "numeric", month: "long", year: "numeric" });
      // ca-ES returns "28 d'abril de 2026", es-ES returns "28 de abril de 2026"
      return raw.replace(/ de /g, " ").replace(/d'/g, "").replace(/,/g, "").trim();
    }

    for (var i = 0; i < filtered.length; i++) {
      var d = new Date(filtered[i].reserved_at);
      if (d >= dayStart && d < todayEnd) {
        todayGroup.items.push(filtered[i]);
      } else if (d >= todayEnd && d < tomorrowEnd) {
        tomorrowGroup.items.push(filtered[i]);
      } else {
        var dk = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
        if (!dayMap[dk]) {
          dayMap[dk] = { key: dk, label: fmtDayLabel(d), items: [] };
          dayOrder.push(dk);
        }
        dayMap[dk].items.push(filtered[i]);
      }
    }

    dayOrder.sort();

    var result = [];
    if (todayGroup.items.length > 0)    result.push(todayGroup);
    if (tomorrowGroup.items.length > 0) result.push(tomorrowGroup);
    for (var j = 0; j < dayOrder.length; j++) {
      result.push(dayMap[dayOrder[j]]);
    }
    return result;
  }

  // ── Actions ──────────────────────────────────────────────────────────────
  async function confirmRes(id) {
    setReservations(function (prev) {
      return prev.map(function (r) { return r.id === id ? Object.assign({}, r, { status: "confirmed" }) : r; });
    });

    var res = await window.SB
      .from("reservations")
      .update({ status: "confirmed" })
      .eq("id", id);

    if (res.error) {
      console.error("[Reserves] confirm:", res.error);
      setReservations(function (prev) {
        return prev.map(function (r) { return r.id === id ? Object.assign({}, r, { status: "pending" }) : r; });
      });
      window.SVX.toast(tr("Error en confirmar", "Error al confirmar"), "error");
    }
  }

  function cancelRes(reservation) {
    setCancelTarget(reservation);
    setCancelReason("");
  }

  async function confirmCancel() {
    var reservation = cancelTarget;
    if (!reservation) return;

    // NOTE: requires ALTER TABLE reservations ADD COLUMN IF NOT EXISTS cancellation_reason text;
    var reason = cancelReason.trim() || null;
    var prevStatus = reservation.status;
    var prevReason = reservation.cancellation_reason;

    setCancelTarget(null);
    setCancelReason("");

    setReservations(function (prev) {
      return prev.map(function (r) { return r.id === reservation.id ? Object.assign({}, r, { status: "cancelled", cancellation_reason: reason }) : r; });
    });

    var res = await window.SB
      .from("reservations")
      .update({ status: "cancelled", cancellation_reason: reason })
      .eq("id", reservation.id);

    if (res.error) {
      console.error("[Reserves] cancel:", res.error);
      setReservations(function (prev) {
        return prev.map(function (r) { return r.id === reservation.id ? Object.assign({}, r, { status: prevStatus, cancellation_reason: prevReason }) : r; });
      });
      window.SVX.toast(tr("Error en cancel·lar", "Error al cancelar"), "error");
    }
  }

  // ── Create new ───────────────────────────────────────────────────────────
  async function createRes(e) {
    e.preventDefault();
    if (!clientIdRef.current || !form.name.trim()) return;

    var dt = new Date(form.date + "T" + form.time);
    if (isNaN(dt.getTime())) {
      window.SVX.toast(tr("Data invàlida", "Fecha inválida"), "warn");
      return;
    }

    var newRes = {
      client_id:   clientIdRef.current,
      guest_name:  form.name.trim(),
      guest_phone: form.phone.trim() || null,
      reserved_at: dt.toISOString(),
      pax:         parseInt(form.pax, 10) || 1,
      notes:       form.notes.trim(),
      source:      "manual",
      status:      "confirmed",
    };

    var res = await window.SB.from("reservations").insert(newRes);
    if (res.error) {
      console.error("[Reserves] create:", res.error);
      window.SVX.toast(tr("Error en crear la reserva", "Error al crear la reserva"), "error");
      return;
    }

    setShowModal(false);
    setForm(getDefaultForm());
  }

  // ── WhatsApp contact ─────────────────────────────────────────────────────
  function waContact(r) {
    var phone = (r.guest_phone || "").replace(/\D/g, "");
    if (!phone) {
      window.SVX.toast(tr("Sense telèfon", "Sin teléfono"), "warn");
      return;
    }
    var d      = new Date(r.reserved_at);
    var locale = lang === "es" ? "es-ES" : "ca-ES";
    var dateS  = d.toLocaleDateString(locale, { weekday: "long", day: "numeric", month: "long" });
    var timeS  = d.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
    var text   = encodeURIComponent(
      lang === "es"
        ? "Hola " + r.guest_name + ", confirmamos tu reserva para " + r.pax + " personas el " + dateS + " a las " + timeS + "."
        : "Hola " + r.guest_name + ", confirmem la teva reserva per a " + r.pax + " persones el " + dateS + " a les " + timeS + "."
    );
    window.open("https://wa.me/" + phone + "?text=" + text, "_blank");
  }

  // ── Render ───────────────────────────────────────────────────────────────
  if (reservations === null) return (
    <div style={{padding: 32, color: "var(--ink-3)", fontSize: 14}}>—</div>
  );

  var groups = getGroups();
  var filterBtns = [
    { key: "all",       label: tr("Totes",       "Todas")      },
    { key: "pending",   label: tr("Pendents",    "Pendientes") },
    { key: "confirmed", label: tr("Confirmades", "Confirmadas")},
    { key: "cancelled", label: tr("Cancel·lades","Canceladas") },
  ];

  var inputStyle = {
    display: "block", width: "100%", padding: "8px 10px", border: "1px solid var(--line)",
    borderRadius: 6, fontSize: 14, fontFamily: "inherit", marginTop: 4, boxSizing: "border-box",
  };
  var labelStyle = { display: "block", fontSize: 13, color: "var(--ink-2)", marginBottom: 12 };

  return (
    <div>
      <header className="srv-page-head">
        <h1 className="t-h1">{t.title}</h1>
      </header>

      <div className="filter-row">
        {filterBtns.map(function (f) {
          return (
            <button
              key={f.key}
              className={"chip " + (filter === f.key ? "on" : "")}
              onClick={function () { setFilter(f.key); }}
            >{f.label}</button>
          );
        })}
      </div>

      {groups.length === 0 ? (
        <p className="empty-hint">
          {tr(
            "Encara no hi ha reserves. Quan n'arribi la primera, apareixerà aquí.",
            "Aún no hay reservas. Cuando llegue la primera, aparecerá aquí."
          )}
        </p>
      ) : groups.map(function (g) {
        return (
          <section key={g.key} className="res-group">
            <h3 className="res-group-heading">
              {g.label} <span style={{fontWeight: 400, letterSpacing: 0}}>· {g.items.length}</span>
            </h3>
            <ul className="res-rows">
              {g.items.map(function (r) {
                return (
                  <React.Fragment key={r.id}>
                    <ResRow
                      hour={fmtHour(r.reserved_at)}
                      name={r.guest_name}
                      meta={fmtMeta(r)}
                      src={mapSrc(r.source)}
                      note={buildNote(r)}
                      highlight={r.status === "pending"}
                      lang={lang}
                      status={r.status}
                      cancellationReason={r.cancellation_reason}
                      onConfirm={r.status === "pending" ? function () { confirmRes(r.id); } : null}
                      onCancel={r.status === "pending" || r.status === "confirmed" ? function () { cancelRes(r); } : null}
                      onDetail={function () { setExpandedId(expandedId === r.id ? null : r.id); }}
                      animClass={newIdsRef.current.has(r.id) ? "res-enter" : ""}
                    />

                    {expandedId === r.id && (
                      <li style={{
                        listStyle: "none", background: "var(--paper-1, #FAFAF8)", borderRadius: 8,
                        padding: "14px 16px", margin: "4px 0 8px", border: "1px solid var(--line)",
                        fontSize: 14, color: "var(--ink-1)",
                      }}>
                        <div style={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px 20px", marginBottom: 12}}>
                          <div><strong>{tr("Nom","Nombre")}:</strong> {r.guest_name}</div>
                          <div><strong>{tr("Telèfon","Teléfono")}:</strong> {r.guest_phone || "—"}</div>
                          <div><strong>{tr("Data","Fecha")}:</strong> {new Date(r.reserved_at).toLocaleDateString(lang === "es" ? "es-ES" : "ca-ES", {weekday:"short", day:"numeric", month:"short"})}</div>
                          <div><strong>{tr("Hora","Hora")}:</strong> {fmtHour(r.reserved_at)}</div>
                          <div><strong>{tr("Persones","Personas")}:</strong> {r.pax}</div>
                          <div><strong>{tr("Taula","Mesa")}:</strong> {r.table_label || "—"}</div>
                          <div><strong>{tr("Estat","Estado")}:</strong> {statusLabel(r.status)}</div>
                          <div><strong>{tr("Font","Fuente")}:</strong> {{ bot: "WhatsApp Bot", web: "Web", manual: "Manual" }[mapSrc(r.source)] || r.source}</div>
                        </div>
                        {r.notes && <div style={{marginBottom: 6}}><strong>{tr("Notes","Notas")}:</strong> {r.notes}</div>}
                        {r.allergies && <div style={{marginBottom: 6, color: "var(--terra-700)"}}><strong>{tr("Al·lèrgies","Alergias")}:</strong> {r.allergies}</div>}
                        <div style={{display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap"}}>
                          {r.guest_phone && (
                            <button className="btn ghost" style={{fontSize: 13}} onClick={function () { waContact(r); }}>
                              <i className="ph ph-whatsapp-logo"></i> WhatsApp
                            </button>
                          )}
                          {r.status === "pending" && (
                            <button className="btn primary" style={{fontSize: 13}} onClick={function () { confirmRes(r.id); }}>
                              <i className="ph ph-check"></i> {tr("Confirmar","Confirmar")}
                            </button>
                          )}
                          {(r.status === "pending" || r.status === "confirmed") && (
                            <button className="btn ghost" style={{fontSize: 13, color: "var(--terra-700)"}} onClick={function () { cancelRes(r); }}>
                              <i className="ph ph-x"></i> {tr("Cancel·lar","Cancelar")}
                            </button>
                          )}
                        </div>
                      </li>
                    )}
                  </React.Fragment>
                );
              })}
            </ul>
          </section>
        );
      })}

      <button className="fab" onClick={function () { setForm(getDefaultForm()); setShowModal(true); }}>
        <i className="ph ph-plus"></i>{t.newr}
      </button>

      {showModal && (
        <div
          style={{position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 200}}
          onClick={function (e) { if (e.target === e.currentTarget) setShowModal(false); }}
        >
          <div style={{background: "var(--paper)", borderRadius: 12, padding: 24, width: "90%", maxWidth: 400, boxShadow: "0 8px 30px rgba(0,0,0,0.12)"}}>
            <h2 className="t-h2" style={{marginBottom: 16}}>{tr("Nova reserva","Nueva reserva")}</h2>
            <form onSubmit={createRes}>
              <label style={labelStyle}>
                {tr("Nom","Nombre")} *
                <input required value={form.name} onChange={function (e) { setForm(function (f) { return Object.assign({}, f, {name: e.target.value}); }); }} style={inputStyle} />
              </label>
              <div style={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12}}>
                <label style={labelStyle}>
                  {tr("Data","Fecha")} *
                  <input type="date" required value={form.date} onChange={function (e) { setForm(function (f) { return Object.assign({}, f, {date: e.target.value}); }); }} style={inputStyle} />
                </label>
                <label style={labelStyle}>
                  {tr("Hora","Hora")} *
                  <input type="time" required value={form.time} onChange={function (e) { setForm(function (f) { return Object.assign({}, f, {time: e.target.value}); }); }} style={inputStyle} />
                </label>
              </div>
              <div style={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12}}>
                <label style={labelStyle}>
                  {tr("Persones","Personas")} *
                  <input type="number" min="1" max="200" required value={form.pax} onChange={function (e) { setForm(function (f) { return Object.assign({}, f, {pax: e.target.value}); }); }} style={inputStyle} />
                </label>
                <label style={labelStyle}>
                  {tr("Telèfon","Teléfono")}
                  <input type="tel" value={form.phone} onChange={function (e) { setForm(function (f) { return Object.assign({}, f, {phone: e.target.value}); }); }} style={inputStyle} />
                </label>
              </div>
              <label style={labelStyle}>
                {tr("Notes","Notas")}
                <input value={form.notes} onChange={function (e) { setForm(function (f) { return Object.assign({}, f, {notes: e.target.value}); }); }} style={inputStyle} />
              </label>
              <div style={{display: "flex", gap: 8, marginTop: 16}}>
                <button type="submit" className="btn primary">{tr("Desar","Guardar")}</button>
                <button type="button" className="btn ghost" onClick={function () { setShowModal(false); }}>{tr("Cancel·lar","Cancelar")}</button>
              </div>
            </form>
          </div>
        </div>
      )}

      {cancelTarget && (
        <div
          style={{position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 200}}
          onClick={function (e) { if (e.target === e.currentTarget) { setCancelTarget(null); setCancelReason(""); } }}
        >
          <div style={{background: "var(--paper)", borderRadius: 12, padding: 24, width: "90%", maxWidth: 400, boxShadow: "0 8px 30px rgba(0,0,0,0.12)"}}>
            <h2 className="t-h2" style={{marginBottom: 8}}>{tr("Cancel·lar reserva", "Cancelar reserva")}</h2>
            <p style={{margin: "0 0 14px", fontSize: 14, color: "var(--ink-2)"}}>{cancelTarget.guest_name} · {cancelTarget.pax} {tr("pers.", "pers.")}</p>
            <label style={labelStyle}>
              {tr("Motiu (opcional)", "Motivo (opcional)")}
              <textarea
                rows="3"
                value={cancelReason}
                onChange={function (e) { setCancelReason(e.target.value); }}
                style={Object.assign({}, inputStyle, {resize: "vertical"})}
                autoFocus
              />
            </label>
            <div style={{display: "flex", gap: 8, marginTop: 16}}>
              <button className="btn primary" style={{background: "var(--terra-700)"}} onClick={confirmCancel}>
                <i className="ph ph-x"></i> {tr("Confirmar cancel·lació", "Confirmar cancelación")}
              </button>
              <button className="btn ghost" onClick={function () { setCancelTarget(null); setCancelReason(""); }}>
                {tr("Tornar", "Volver")}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

window.Reserves = Reserves;
