// Itinerary — several products, one booking pass (BOO-644).
//
// Only mounted when the partner has turned Itinerary mode on in Settings. With it
// off, a time chip opens BookingDrawer for that one slot exactly as before; with
// it on, a chip drops the slot in here instead.
//
// Bookable has no multi-line booking: one POST per composite. So this collects
// pre-order per item and guest details ONCE, then submits the items one at a time
// and reports each line's outcome. Sequential, not parallel — these are writes,
// and a partial failure has to name which booking didn't land.
// ponytail: sequential; an itinerary is a handful of items. Parallelise only if
// it measurably drags.
//
// Each line runs ppUsePreOrderScope, so a feed-sourced slot costs one ?live=1
// availability probe per item on open — the same call the single-slot drawer
// makes, times the itinerary length.
// ponytail: N probes; they're cached per composite+date, and the fix belongs in
// the feed (see BOO-399) rather than here.

// The floating basket: a count button, and a panel listing what's in it.
function ItineraryBar({ items, open, onToggle, onRemove, onBook }) {
  return (
    <React.Fragment>
      {open && (
        <div className="pp-itin-panel" role="dialog" aria-label="Itinerary">
          <div className="pp-itin-panel-head">Itinerary</div>
          <div className="pp-itin-panel-body">
            {items.length === 0 ? (
              <div className="pp-itin-empty">Nothing yet — pick a time on the listing to add it here.</div>
            ) : items.map(it => {
              const venue = venueById(it.venueId);
              const product = productById(it.productId);
              return (
                <div className="pp-itin-line" key={it.key}>
                  <span className="pp-itin-line-time">{it.time || '—'}</span>
                  <span className="pp-itin-line-main">
                    <span className="pp-itin-line-name">{(venue && venue.name) || it.venueId} — {(product && product.name) || it.productId}</span>
                    <span className="pp-itin-line-meta">{fmtDate(it.date)} · {it.guests} {it.guests === 1 ? 'guest' : 'guests'}</span>
                  </span>
                  <button type="button" className="pp-itin-line-rem" aria-label="Remove from itinerary"
                          onClick={() => onRemove(it.key)}>
                    <IconClose size={12}/>
                  </button>
                </div>
              );
            })}
          </div>
          {items.length > 0 && (
            <div className="pp-itin-panel-foot">
              <button type="button" className="pp-btn pp-btn--primary" onClick={onBook}>
                Book {items.length} {items.length === 1 ? 'product' : 'products'}
              </button>
            </div>
          )}
        </div>
      )}
      <button type="button" className="pp-itin-fab" onClick={onToggle}
              aria-expanded={open} aria-label="Itinerary">
        <IconCal size={15}/>
        <span>Itinerary</span>
        {items.length > 0 && <span className="pp-itin-fab-count">{items.length}</span>}
      </button>
    </React.Fragment>
  );
}

// One itinerary line's pre-order block. Owns its own selection and reports
// readiness + payload upward, so the drawer can build every POST at confirm time
// without re-deriving each item's scoped catalogue.
function ItineraryItem({ item, expanded, onToggle, onState, onRemove }) {
  const venue = venueById(item.venueId);
  const product = productById(item.productId);
  const rawProducts = (venue && venue.rawProducts) || [];
  // Prefer matching by composite — several Bookable products collapse into one
  // portal bucket and only the specific one carries the right allow-list.
  const rawProduct = rawProducts.find(rp => item.compositeId && rp.compositeId === item.compositeId)
                  || rawProducts.find(rp => rp.portalProduct === item.productId);
  const { venuePre, slotPre, liveScopeLoading } = ppUsePreOrderScope(item, venue);
  const { packagesList, menus, preOrderRequiredType, preOrderType, hasCatalogue, preOrderOptional } =
    ppScopePreOrder(venuePre, rawProduct, slotPre);

  const [preOrder, setPreOrder] = React.useState({ items: {}, packages: {}, menuId: null });
  const [openMenuId, setOpenMenuId] = React.useState(null);
  const openMenu = openMenuId ? menus.find(m => m.id === openMenuId) : null;

  const setItemQty = (itemId, qty) => setPreOrder(s => {
    const items = { ...s.items };
    if (qty <= 0) delete items[itemId]; else items[itemId] = qty;
    return { ...s, items };
  });
  const setPackageQty = (packageId, qty) => setPreOrder(s => {
    const pkgs = { ...s.packages };
    if (qty <= 0) delete pkgs[packageId]; else pkgs[packageId] = qty;
    return { ...s, packages: pkgs };
  });
  const openMenuDetail = (menuId) => {
    setPreOrder(s => s.menuId === menuId ? s : { ...s, menuId });
    setOpenMenuId(menuId);
  };

  const packagesQty = Object.values(preOrder.packages).reduce((n, q) => n + (q || 0), 0);
  const itemsQty = Object.values(preOrder.items).reduce((n, q) => n + (q || 0), 0);
  const selectedMenu = menus.find(m => m.id === preOrder.menuId) || null;
  const subtotal = packagesList.reduce((n, p) => n + (preOrder.packages[p.id] || 0) * (typeof p.price === 'number' ? p.price : 0), 0)
    + ((selectedMenu && selectedMenu.items) || []).reduce((n, it) => n + (preOrder.items[it.id] || 0) * (typeof it.price === 'number' ? it.price : 0), 0);
  const required = preOrderRequiredType !== 'none';
  const ready = ppPreOrderSatisfied(preOrderRequiredType, packagesQty, itemsQty);
  // Required, but there is nothing published to satisfy it with — the partner
  // cannot act on this line, so the drawer has to say so rather than just
  // disabling its button (see the footer).
  const noOptions = required && !hasCatalogue && !liveScopeLoading;
  const payload = ppPreOrderPayload(preOrderType, preOrder, packagesList, selectedMenu, subtotal);

  // ponytail: payload identity is tracked by serialisation — it's a fresh object
  // every render, and the alternative is memoising four nested maps for no gain.
  const payloadKey = JSON.stringify(payload);
  React.useEffect(() => {
    onState(item.key, { ready, required, noOptions, subtotal, lines: packagesQty + itemsQty, payload, payloadKey });
    // eslint-disable-next-line react-hooks/exhaustive-deps -- payloadKey stands in for payload
  }, [item.key, ready, required, noOptions, subtotal, packagesQty, itemsQty, payloadKey, onState]);

  const G = glyphFor(item.productId);
  const blocked = required && !ready;
  return (
    <section className={"pp-itin-item" + (blocked ? " is-blocked" : "")}>
      <header className="pp-itin-item-head">
        <span className="pp-itin-item-glyph"><G size={14}/></span>
        <span className="pp-itin-item-text">
          <span className="pp-itin-item-name">{(venue && venue.name) || item.venueId} — {(product && product.name) || item.productId}</span>
          <span className="pp-itin-item-meta">
            {fmtDate(item.date)}{item.time ? ' · ' + item.time : ''} · {item.guests} {item.guests === 1 ? 'guest' : 'guests'}
            {subtotal > 0 ? ' · pre-order £' + subtotal.toFixed(2) : ''}
          </span>
        </span>
        {required && <span className="pp-itin-item-req">{ready ? 'Pre-order added' : 'Pre-order required'}</span>}
        {hasCatalogue && (
          <button type="button" className="pp-btn pp-btn--ghost pp-btn--xs" onClick={() => onToggle(item.key)}>
            {expanded ? 'Hide' : (packagesQty + itemsQty > 0 ? 'Edit pre-order' : 'Add pre-order')}
          </button>
        )}
        <button type="button" className="pp-itin-line-rem" aria-label="Remove from itinerary"
                onClick={() => onRemove(item.key)}>
          <IconClose size={12}/>
        </button>
      </header>

      {!hasCatalogue && !liveScopeLoading && (
        <div className="pp-itin-item-note">
          {noOptions
            ? 'This product needs a pre-order, but the operator has published no options for this date. Remove this line to book the rest, or book it on its own.'
            : 'No pre-order options for this product.'}
        </div>
      )}

      {expanded && hasCatalogue && (
        <div className="pp-itin-item-body">
          {openMenu ? (
            <React.Fragment>
              <button type="button" className="pp-btn pp-btn--ghost pp-btn--xs" onClick={() => setOpenMenuId(null)}>
                <IconArrowL size={12}/><span>Back to {preOrderOptional ? 'options' : 'catalogue'}</span>
              </button>
              <MenuDetailView menu={openMenu} preOrder={preOrder} setItemQty={setItemQty}/>
            </React.Fragment>
          ) : (
            <CatalogView packages={packagesList} menus={menus} preOrder={preOrder}
                         setPackageQty={setPackageQty} openMenuDetail={openMenuDetail}
                         loading={liveScopeLoading}/>
          )}
        </div>
      )}
    </section>
  );
}

// Has anything the submit actually reads changed? payloadKey MUST be part of
// this: swapping one £10 starter for a different £10 starter leaves ready,
// required, subtotal and lines identical, so comparing only those would keep the
// previous selection in state and POST the wrong pre-order.
function ppItemStateChanged(cur, next) {
  if (!cur) return true;
  return cur.ready !== next.ready
      || cur.required !== next.required
      || cur.subtotal !== next.subtotal
      || cur.lines !== next.lines
      || cur.noOptions !== next.noOptions
      || cur.payloadKey !== next.payloadKey;
}

function partnerRef(ref, index, total) {
  if (!ref) return '';
  return total > 1 ? ref + '-' + (index + 1) : ref;
}

function ItineraryDrawer({ open, items, onClose, onRemove, onConfirm, onBooked, onGoToBookings }) {
  const [step, setStep] = React.useState(1);
  const [expandedKey, setExpandedKey] = React.useState(null);
  const [state, setState] = React.useState({});   // item.key → { ready, required, payload, … }
  const [form, setForm] = React.useState({
    firstName: '', lastName: '', email: '', phone: '', notes: '',
    partnerBookingId: '',
    sendConfirm: true,
  });
  const [submitting, setSubmitting] = React.useState(false);
  const [results, setResults] = React.useState(null);  // [{ item, booking } | { item, error }]

  // Every open starts clean: guest details and the partner's reference are
  // per-booking, and a finished run must not carry into the next one.
  React.useEffect(() => {
    if (!open) return;
    setStep(1);
    setExpandedKey(null);
    setResults(null);
    setSubmitting(false);
    setForm({ firstName: '', lastName: '', email: '', phone: '', notes: '', partnerBookingId: '', sendConfirm: true });
  }, [open]);

  const onState = React.useCallback((key, next) => {
    setState(prev => ppItemStateChanged(prev[key], next) ? { ...prev, [key]: next } : prev);
  }, []);

  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape' && !submitting) onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose, submitting]);

  // Lock the page behind the drawer so wheel events on the scrim don't scroll
  // the listing underneath.
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, [open]);

  if (!open) return null;

  const blocking = items.filter(it => {
    const st = state[it.key];
    return st && st.required && !st.ready;
  });
  // A blocked line with nothing published to satisfy it can only be removed.
  const unsatisfiable = blocking.filter(it => state[it.key].noOptions);
  const preOrderTotal = items.reduce((n, it) => n + ((state[it.key] && state[it.key].subtotal) || 0), 0);
  const customerValid = form.firstName && form.lastName && form.email && form.phone;

  const submit = async () => {
    setSubmitting(true);
    const out = [];
    // One POST per product, in order, so a failure names its own line.
    for (const it of items) {
      const st = state[it.key] || {};
      const result = await onConfirm({
        ...it,
        customer: form.firstName + ' ' + form.lastName,
        email: form.email,
        phone: form.phone,
        notes: form.notes,
        // One reference across N bookings would collide on the operator's side —
        // it's a per-booking id (BOO-601). Suffix it so each line stays traceable.
        partnerBookingId: partnerRef(form.partnerBookingId.trim(), items.indexOf(it), items.length),
        preOrder: st.payload || null,
      });
      out.push({ item: it, ...(result || {}) });
      setResults(out.slice());
    }
    setSubmitting(false);
    setStep(3);
    if (onBooked) onBooked();
  };

  const failed = (results || []).filter(r => r.error);
  const footer = step === 1
    ? (blocking.length
        ? { label: unsatisfiable.length === blocking.length
              ? 'Remove ' + unsatisfiable.length + (unsatisfiable.length === 1 ? ' unbookable line' : ' unbookable lines')
              : 'Add pre-order to ' + blocking.length + (blocking.length === 1 ? ' product' : ' products'),
            disabled: true, onClick: () => {} }
        : { label: 'Continue to guest details →', disabled: items.length === 0, onClick: () => setStep(2) })
    : { label: submitting ? 'Confirming…' : 'Confirm ' + items.length + ' ' + (items.length === 1 ? 'booking' : 'bookings'),
        disabled: !customerValid || submitting || items.length === 0, onClick: submit };

  return (
    <React.Fragment>
      <div className="pp-drawer-scrim" onClick={() => !submitting && onClose()}/>
      <aside className="pp-drawer pp-drawer--xl" role="dialog" aria-modal="true" aria-label="Book itinerary">
        <div className="pp-drawer-head">
          <div className="pp-drawer-head-title">
            <div className="pp-drawer-eyebrow">New booking</div>
            <h2>{step === 3 ? 'Itinerary booked' : 'Itinerary · ' + items.length + (items.length === 1 ? ' product' : ' products')}</h2>
          </div>
          <button className="pp-icon-btn" onClick={() => !submitting && onClose()} aria-label="Close">
            <IconClose size={16}/>
          </button>
        </div>

        <div className="pp-drawer-body">
          {step === 3 ? (
            <div className="pp-itin-results">
              <div className="pp-confirmed-mark" style={failed.length ? { background: 'var(--pp-warn)', color: 'var(--pp-warn-ink)' } : undefined}>
                <IconCheck size={28}/>
              </div>
              <div className="pp-confirmed-title">
                {failed.length === 0
                  ? (results.length === 1 ? 'Booking confirmed' : results.length + ' bookings confirmed')
                  : (results.length - failed.length) + ' of ' + results.length + ' confirmed'}
              </div>
              <div className="pp-confirmed-sub">
                {failed.length === 0
                  ? <span>All for {form.firstName} {form.lastName}. {form.sendConfirm ? 'A confirmation email is on its way to the customer.' : 'No customer email was sent.'}</span>
                  : <span>The venues marked below didn't accept the booking. They're saved in your Bookings as pending — check there before trying again.</span>}
              </div>
              {results.map(r => {
                const venue = venueById(r.item.venueId);
                const product = productById(r.item.productId);
                const ref = r.booking && r.booking.operatorBookingId;
                return (
                  <div className={"pp-itin-result" + (r.error ? " is-failed" : "")} key={r.item.key}>
                    <span className="pp-itin-result-main">
                      <span className="pp-itin-item-name">{(venue && venue.name) || r.item.venueId} — {(product && product.name) || r.item.productId}</span>
                      <span className="pp-itin-item-meta">{fmtDate(r.item.date)}{r.item.time ? ' · ' + r.item.time : ''}</span>
                    </span>
                    {r.error
                      ? <span className="pp-itin-result-err">Not confirmed · <span className="pp-mono">{r.error}</span></span>
                      // Only the operator's own reference, never an invented one
                      // — a made-up code the operator can't search is worse than
                      // none (BOO-601).
                      : ref ? <span className="pp-mono">{ref}</span>
                      : <span className="pp-muted">Confirmed</span>}
                  </div>
                );
              })}
              <div className="pp-confirmed-actions">
                <button className="pp-btn pp-btn--primary" onClick={onGoToBookings}>View in bookings</button>
              </div>
            </div>
          ) : step === 1 ? (
            <React.Fragment>
              <div className="pp-form-section-title">Pre-order</div>
              {items.length === 0 && (
                <div className="pp-itin-empty">Nothing in this itinerary — pick a time on the listing.</div>
              )}
              {items.map(it => (
                <ItineraryItem key={it.key}
                               item={it}
                               expanded={expandedKey === it.key}
                               onToggle={(k) => setExpandedKey(prev => prev === k ? null : k)}
                               onState={onState}
                               onRemove={onRemove}/>
              ))}
            </React.Fragment>
          ) : (
            <React.Fragment>
              <div className="pp-form-section-title">Guest details</div>
              <p className="pp-muted" style={{ margin: '-4px 0 4px', fontSize: 12.5 }}>
                Used for every product in this itinerary.
              </p>
              <DetailsForm form={form} setForm={setForm}/>
            </React.Fragment>
          )}
        </div>

        {step !== 3 && (
          <footer className="pp-drawer-foot">
            <div className="pp-drawer-foot-total">
              <span className="pp-drawer-foot-label">Pre-order</span>
              <span className="pp-drawer-foot-amt">£{preOrderTotal.toFixed(2)}</span>
            </div>
            <button className="pp-btn pp-btn--primary" disabled={footer.disabled} onClick={footer.onClick}>
              {footer.label}
            </button>
          </footer>
        )}
      </aside>
    </React.Fragment>
  );
}

Object.assign(window, { ItineraryBar, ItineraryDrawer });
