/* global React, Button, Chip, Stamp, Reveal, Sticker, ShopAPI */
/* eslint-disable no-undef */
const { useState, useEffect, useRef, useCallback } = React;

/* ============================================================
   The glow shop — aftercare products, powered by Shopify
   ============================================================
   Products and cart come from the Storefront API (shop-api.js).
   Checkout punches out to Shopify's hosted checkout, same way
   every "Book" CTA punches out to Vagaro.
   ============================================================ */

// Shopify's CDN resizes on the fly — ask for what we actually render
// instead of shipping a 2000px product shot to a 320px card.
function img(url, width) {
  if (!url) return '';
  const sep = url.includes('?') ? '&' : '?';
  return `${url}${sep}width=${width}`;
}

function variantLabel(variant) {
  if (!variant) return '';
  return variant.title === 'Default Title' ? '' : variant.title;
}

function hasRealVariants(product) {
  const nodes = (product.variants && product.variants.nodes) || [];
  return nodes.length > 1 || (nodes.length === 1 && nodes[0].title !== 'Default Title');
}

/* ============================================================
   Product card
   ============================================================ */
function ProductCard({ product, onOpen, onAdd, busy }) {
  const price = product.priceRange.minVariantPrice;
  const compareAt = product.compareAtPriceRange &&
    product.compareAtPriceRange.maxVariantPrice;
  const onSale = compareAt && Number(compareAt.amount) > Number(price.amount);
  const soldOut = !product.availableForSale;
  const variants = (product.variants && product.variants.nodes) || [];
  const multi = hasRealVariants(product);

  return (
    <div className="tbb-peel tbb-shop-card" style={{
      background: 'var(--tbb-paper)', color: 'var(--tbb-ink)',
      border: '3px solid var(--tbb-ink)', borderRadius: 28,
      boxShadow: '5px 5px 0 0 var(--tbb-ink)',
      display: 'flex', flexDirection: 'column',
      // Fill the grid row so cards in a row share a bottom edge no matter
      // how many lines their titles wrap to.
      height: '100%',
      overflow: 'hidden', position: 'relative'
    }}>
      {onSale &&
        <div style={{ position: 'absolute', top: 12, left: -6, zIndex: 2 }}>
          <Stamp tone="butter" rot={-8}>✦ On sale</Stamp>
        </div>
      }
      {soldOut &&
        <div style={{ position: 'absolute', top: 12, right: -6, zIndex: 2 }}>
          <Stamp tone="paper" rot={7}>Sold out</Stamp>
        </div>
      }

      <button
        type="button"
        onClick={() => onOpen(product)}
        aria-label={`Quick view — ${product.title}`}
        style={{
          appearance: 'none', border: 0, padding: 0, margin: 0, cursor: 'pointer',
          background: 'var(--tbb-paper-warm)',
          borderBottom: '3px solid var(--tbb-ink)',
          aspectRatio: '1 / 1', width: '100%',
          display: 'block', overflow: 'hidden',
          opacity: soldOut ? 0.55 : 1
        }}>
        {product.featuredImage ?
          <img
            src={img(product.featuredImage.url, 640)}
            alt={product.featuredImage.altText || product.title}
            loading="lazy"
            style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} /> :
          <div style={{
            width: '100%', height: '100%', display: 'grid', placeItems: 'center',
            fontFamily: 'Olivita, system-ui', fontStyle: 'italic', fontSize: 28, opacity: 0.4
          }}>♡</div>
        }
      </button>

      <div className="tbb-shop-card-body" style={{ padding: '18px 20px 20px', display: 'flex', flexDirection: 'column', gap: 8, flex: 1 }}>
        {/* Olivita runs wide — at a fixed 30px a name like "Sweatpants"
            spills past the card edge in the two-column phone grid. */}
        <div
          onClick={() => onOpen(product)}
          style={{
            fontFamily: 'Olivita, system-ui', fontStyle: 'italic',
            // Real product names run long ("Vanilla Coconut Ingrown Hair
            // Exfoliating Scrub"), so the grid uses a smaller size than the
            // quick-view modal, which has room to go big.
            fontSize: 'clamp(17px, 1.6vw, 21px)', lineHeight: 1.12, cursor: 'pointer',
            // 'manual' breaks only at hyphens already in the name, so
            // "Summer-vacay" splits after "Summer-" rather than auto-
            // hyphenating into "Summer-va-cay". break-word is the last resort
            // for a single word wider than the column.
            hyphens: 'manual', overflowWrap: 'break-word'
          }}>
          {product.title}
        </div>

        <div style={{ display: 'flex', alignItems: 'baseline', gap: 10 }}>
          <div style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 20 }}>
            {ShopAPI.money(price.amount, price.currencyCode)}
          </div>
          {onSale &&
            <div style={{
              fontFamily: 'Nunito', fontWeight: 600, fontSize: 15,
              textDecoration: 'line-through', opacity: 0.5
            }}>
              {ShopAPI.money(compareAt.amount, compareAt.currencyCode)}
            </div>
          }
        </div>

        {product.description &&
          <p style={{
            fontFamily: 'Nunito', fontWeight: 500, fontSize: 14.5, lineHeight: 1.5,
            color: 'var(--fg2)', margin: 0,
            display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
            overflow: 'hidden'
          }}>
            {product.description}
          </p>
        }

        <div style={{ marginTop: 'auto', paddingTop: 12 }}>
          {soldOut ?
            <Button variant="secondary" size="md" disabled style={{ width: '100%', justifyContent: 'center', opacity: 0.6, cursor: 'not-allowed' }}>
              Sold out
            </Button> :
            multi ?
              // Multiple sizes/scents — send her to the modal to pick
              <Button variant="ink" size="md" onClick={() => onOpen(product)} style={{ width: '100%', justifyContent: 'center' }}>
                Choose yours →
              </Button> :
              <Button
                variant="primary" size="md"
                onClick={() => onAdd(variants[0].id)}
                disabled={busy}
                style={{ width: '100%', justifyContent: 'center', opacity: busy ? 0.6 : 1 }}>
                {busy ? 'Adding…' : 'Add to bag ♡'}
              </Button>
          }
        </div>
      </div>
    </div>);

}

/* ============================================================
   Quick-view modal
   ============================================================ */
function ProductModal({ product, onClose, onAdd, busy }) {
  const variants = (product.variants && product.variants.nodes) || [];
  const firstAvailable = variants.find((v) => v.availableForSale) || variants[0];
  const [variantId, setVariantId] = useState(firstAvailable ? firstAvailable.id : null);
  const [qty, setQty] = useState(1);
  const [imgIndex, setImgIndex] = useState(0);
  const closeRef = useRef(null);

  const variant = variants.find((v) => v.id === variantId) || firstAvailable;
  const images = (product.images && product.images.nodes) || [];
  const gallery = images.length ? images : product.featuredImage ? [product.featuredImage] : [];
  // Picking a variant with its own photo should swap the hero image
  const shown = variant && variant.image ?
    variant.image :
    gallery[Math.min(imgIndex, Math.max(gallery.length - 1, 0))];

  useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    if (closeRef.current) closeRef.current.focus();
    return () => {
      document.body.style.overflow = prev;
      window.removeEventListener('keydown', onKey);
    };
  }, [onClose]);

  const soldOut = !variant || !variant.availableForSale;
  const compareAt = variant && variant.compareAtPrice;
  const onSale = compareAt && variant && Number(compareAt.amount) > Number(variant.price.amount);
  // Needs the `unauthenticated_read_product_inventory` scope on the Headless
  // token. If that ever gets revoked the field comes back undefined, so treat
  // a non-number as "plenty" rather than "none" — and the stepper falls back
  // to its default cap instead of locking to zero.
  const left = variant && typeof variant.quantityAvailable === 'number' ? variant.quantityAvailable : null;

  return (
    <div
      role="dialog" aria-modal="true" aria-label={product.title}
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, zIndex: 60,
        background: 'rgba(10,10,10,0.55)',
        display: 'grid', placeItems: 'center', padding: 20,
        overflowY: 'auto'
      }}>
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: 'var(--tbb-paper)', color: 'var(--tbb-ink)',
          border: '3px solid var(--tbb-ink)', borderRadius: 32,
          boxShadow: '10px 10px 0 0 var(--tbb-ink)',
          width: '100%', maxWidth: 880, margin: 'auto',
          position: 'relative', overflow: 'hidden'
        }}>
        <button
          ref={closeRef}
          type="button" onClick={onClose} aria-label="Close"
          style={{
            position: 'absolute', top: 14, right: 14, zIndex: 3,
            width: 42, height: 42, borderRadius: 999, cursor: 'pointer',
            background: 'var(--tbb-paper)', color: 'var(--tbb-ink)',
            border: '3px solid var(--tbb-ink)',
            boxShadow: '3px 3px 0 0 var(--tbb-ink)',
            fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 18, lineHeight: 1
          }}>×</button>

        <div className="tbb-shop-modal-grid" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
          {/* Flex column so the photo stretches to whatever height the details
              side ends up being — otherwise a short product leaves a dead
              strip of cream under the image. */}
          <div style={{
            background: 'var(--tbb-paper-warm)', borderRight: '3px solid var(--tbb-ink)',
            display: 'flex', flexDirection: 'column'
          }}>
            <div style={{ flex: 1, minHeight: 280, overflow: 'hidden' }}>
              {shown ?
                <img
                  src={img(shown.url, 900)}
                  alt={shown.altText || product.title}
                  style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} /> :
                <div style={{ display: 'grid', placeItems: 'center', height: '100%', fontSize: 40, opacity: 0.3 }}>♡</div>
              }
            </div>
            {gallery.length > 1 &&
              <div style={{ display: 'flex', gap: 8, padding: 12, flexWrap: 'wrap', borderTop: '3px solid var(--tbb-ink)' }}>
                {gallery.map((g, i) =>
                  <button
                    key={g.url} type="button"
                    onClick={() => setImgIndex(i)}
                    aria-label={`View image ${i + 1}`}
                    style={{
                      width: 56, height: 56, padding: 0, cursor: 'pointer',
                      borderRadius: 12, overflow: 'hidden',
                      border: `3px solid ${i === imgIndex ? 'var(--tbb-hotpink)' : 'var(--tbb-ink)'}`,
                      background: 'var(--tbb-paper)'
                    }}>
                    <img src={img(g.url, 120)} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                  </button>
                )}
              </div>
            }
          </div>

          <div style={{ padding: '32px 30px', display: 'flex', flexDirection: 'column', gap: 14 }}>
            <div style={{ fontFamily: 'Olivita, system-ui', fontStyle: 'italic', fontSize: 'clamp(34px, 4vw, 46px)', lineHeight: 0.95, paddingRight: 40 }}>
              {product.title}
            </div>

            <div style={{ display: 'flex', alignItems: 'baseline', gap: 10 }}>
              <div style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 26 }}>
                {variant ? ShopAPI.money(variant.price.amount, variant.price.currencyCode) : ''}
              </div>
              {onSale &&
                <div style={{ fontFamily: 'Nunito', fontWeight: 600, fontSize: 17, textDecoration: 'line-through', opacity: 0.5 }}>
                  {ShopAPI.money(compareAt.amount, compareAt.currencyCode)}
                </div>
              }
            </div>

            {product.description &&
              <p style={{ fontFamily: 'Nunito', fontWeight: 500, fontSize: 15.5, lineHeight: 1.6, color: 'var(--fg2)', margin: 0, whiteSpace: 'pre-line' }}>
                {product.description}
              </p>
            }

            {hasRealVariants(product) &&
              <div>
                <div style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 12, letterSpacing: '0.14em', textTransform: 'uppercase', marginBottom: 8 }}>
                  Pick one
                </div>
                <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                  {variants.map((v) => {
                    const active = v.id === variantId;
                    return (
                      <button
                        key={v.id} type="button"
                        onClick={() => { setVariantId(v.id); setQty(1); }}
                        disabled={!v.availableForSale}
                        style={{
                          cursor: v.availableForSale ? 'pointer' : 'not-allowed',
                          fontFamily: 'Nunito Sans', fontWeight: 800, fontSize: 13.5,
                          padding: '10px 16px', borderRadius: 999,
                          border: '3px solid var(--tbb-ink)',
                          background: active ? 'var(--tbb-hotpink)' : 'var(--tbb-paper)',
                          color: active ? 'var(--tbb-paper)' : 'var(--tbb-ink)',
                          boxShadow: active ? '3px 3px 0 0 var(--tbb-ink)' : 'none',
                          opacity: v.availableForSale ? 1 : 0.4,
                          textDecoration: v.availableForSale ? 'none' : 'line-through'
                        }}>
                        {variantLabel(v) || 'Standard'}
                      </button>);

                  })}
                </div>
              </div>
            }

            <div>
              <div style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 12, letterSpacing: '0.14em', textTransform: 'uppercase', marginBottom: 8 }}>
                How many
              </div>
              <QtyStepper value={qty} onChange={setQty} max={left || 99} />
              {left !== null && left > 0 && left <= 5 &&
                <div style={{ fontFamily: 'Nunito', fontWeight: 700, fontSize: 13, color: 'var(--tbb-hotpink)', marginTop: 8 }}>
                  Only {left} left ✦
                </div>
              }
            </div>

            <div style={{ marginTop: 'auto', paddingTop: 10 }}>
              <Button
                variant="primary" size="lg"
                disabled={soldOut || busy}
                onClick={() => onAdd(variantId, qty)}
                style={{ width: '100%', justifyContent: 'center', opacity: soldOut || busy ? 0.6 : 1 }}>
                {soldOut ? 'Sold out' : busy ? 'Adding…' : 'Add to bag ♡'}
              </Button>
              <div style={{ textAlign: 'center', fontFamily: 'Nunito', fontWeight: 600, fontSize: 12.5, opacity: 0.75, marginTop: 10 }}>
                ✦ {(window.TBB_SHOP && window.TBB_SHOP.PICKUP_NOTE) || 'Pickup or shipping at checkout.'}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>);

}

/* ============================================================
   Quantity stepper
   ============================================================ */
function QtyStepper({ value, onChange, max = 99, small = false, disabled = false }) {
  const btn = {
    width: small ? 30 : 38, height: small ? 30 : 38,
    cursor: disabled ? 'wait' : 'pointer', borderRadius: 999,
    border: '3px solid var(--tbb-ink)',
    background: 'var(--tbb-paper)', color: 'var(--tbb-ink)',
    opacity: disabled ? 0.45 : 1,
    fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: small ? 14 : 17, lineHeight: 1
  };
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      <button type="button" style={btn} disabled={disabled} onClick={() => onChange(Math.max(1, value - 1))} aria-label="Decrease quantity">−</button>
      <span style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: small ? 15 : 18, minWidth: 24, textAlign: 'center' }}>{value}</span>
      <button type="button" style={btn} disabled={disabled} onClick={() => onChange(Math.min(max, value + 1))} aria-label="Increase quantity">+</button>
    </div>);

}

/* ============================================================
   Cart drawer
   ============================================================ */
function CartDrawer({ open, cart, onClose, onUpdate, onRemove, busy }) {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  const lines = (cart && cart.lines && cart.lines.nodes) || [];

  return (
    <React.Fragment>
      <div
        onClick={onClose}
        style={{
          position: 'fixed', inset: 0, zIndex: 55,
          background: 'rgba(10,10,10,0.5)',
          opacity: open ? 1 : 0,
          pointerEvents: open ? 'auto' : 'none',
          transition: 'opacity 240ms ease'
        }} />

      <aside
        aria-hidden={!open}
        aria-label="Your bag"
        style={{
          position: 'fixed', top: 0, right: 0, bottom: 0, zIndex: 56,
          width: 'min(440px, 100vw)',
          background: 'var(--tbb-paper)', color: 'var(--tbb-ink)',
          borderLeft: '3px solid var(--tbb-ink)',
          transform: open ? 'translateX(0)' : 'translateX(102%)',
          transition: 'transform 320ms cubic-bezier(0.22, 1, 0.36, 1)',
          display: 'flex', flexDirection: 'column'
        }}>
        <div style={{
          padding: '20px 24px', borderBottom: '3px solid var(--tbb-ink)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12
        }}>
          <div style={{ fontFamily: 'Olivita, system-ui', fontStyle: 'italic', fontSize: 38, lineHeight: 1 }}>
            Your bag
          </div>
          <button
            type="button" onClick={onClose} aria-label="Close bag"
            style={{
              width: 40, height: 40, borderRadius: 999, cursor: 'pointer',
              background: 'var(--tbb-paper)', color: 'var(--tbb-ink)',
              border: '3px solid var(--tbb-ink)', boxShadow: '3px 3px 0 0 var(--tbb-ink)',
              fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 17, lineHeight: 1
            }}>×</button>
        </div>

        <div style={{ flex: 1, overflowY: 'auto', padding: 20, display: 'flex', flexDirection: 'column', gap: 14 }}>
          {!lines.length &&
            <div style={{ textAlign: 'center', padding: '48px 20px' }}>
              <div style={{ fontFamily: 'Olivita, system-ui', fontStyle: 'italic', fontSize: 30, marginBottom: 8 }}>
                Nothing in here yet
              </div>
              <p style={{ fontFamily: 'Nunito', fontWeight: 500, fontSize: 15, color: 'var(--fg2)', margin: '0 0 20px' }}>
                Grab your aftercare and keep the glow going between appointments ♡
              </p>
              <Button variant="lime" size="md" onClick={onClose}>Start shopping →</Button>
            </div>
          }

          {lines.map((line) => {
            const m = line.merchandise;
            const image = m.image || (m.product && m.product.featuredImage);
            const label = variantLabel(m);
            return (
              <div key={line.id} style={{
                display: 'grid', gridTemplateColumns: '76px 1fr', gap: 14,
                background: 'var(--tbb-paper-warm)',
                border: '3px solid var(--tbb-ink)', borderRadius: 20,
                boxShadow: '3px 3px 0 0 var(--tbb-ink)',
                padding: 12
              }}>
                <div style={{
                  width: 76, height: 76, borderRadius: 12, overflow: 'hidden',
                  border: '2px solid var(--tbb-ink)', background: 'var(--tbb-paper)'
                }}>
                  {image &&
                    <img src={img(image.url, 160)} alt={image.altText || m.product.title}
                      style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                  }
                </div>

                <div style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
                  <div style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 15, lineHeight: 1.25 }}>
                    {m.product.title}
                  </div>
                  {label &&
                    <div style={{ fontFamily: 'Nunito', fontWeight: 600, fontSize: 13, opacity: 0.7 }}>{label}</div>
                  }
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, marginTop: 2 }}>
                    {/* Locked while a mutation is in flight — two fast taps
                        would otherwise race and the loser's quantity wins. */}
                    <QtyStepper
                      small
                      disabled={busy}
                      value={line.quantity}
                      onChange={(q) => onUpdate(line.id, q)} />
                    <div style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 15 }}>
                      {ShopAPI.money(line.cost.totalAmount.amount, line.cost.totalAmount.currencyCode)}
                    </div>
                  </div>
                  <button
                    type="button" onClick={() => onRemove(line.id)} disabled={busy}
                    style={{
                      alignSelf: 'flex-start', background: 'none', border: 0, padding: 0,
                      cursor: busy ? 'wait' : 'pointer', color: 'var(--fg2)',
                      opacity: busy ? 0.5 : 1,
                      fontFamily: 'Nunito', fontWeight: 700, fontSize: 12.5,
                      textDecoration: 'underline', textUnderlineOffset: 3
                    }}>
                    Remove
                  </button>
                </div>
              </div>);

          })}
        </div>

        {!!lines.length &&
          <div style={{ borderTop: '3px solid var(--tbb-ink)', padding: 20, background: 'var(--tbb-paper)' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 4 }}>
              <div style={{ fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 13, letterSpacing: '0.14em', textTransform: 'uppercase' }}>
                Subtotal
              </div>
              <div style={{ fontFamily: 'Olivita, system-ui', fontStyle: 'italic', fontSize: 34, lineHeight: 1 }}>
                {ShopAPI.money(cart.cost.subtotalAmount.amount, cart.cost.subtotalAmount.currencyCode)}
              </div>
            </div>
            <p style={{ fontFamily: 'Nunito', fontWeight: 500, fontSize: 12.5, color: 'var(--fg2)', margin: '0 0 14px' }}>
              Taxes, shipping and pickup options are picked at checkout.
            </p>
            <Button
              variant="primary" size="lg"
              as="a" href={cart.checkoutUrl}
              style={{ width: '100%', justifyContent: 'center', opacity: busy ? 0.6 : 1 }}>
              Checkout →
            </Button>
            <div style={{ textAlign: 'center', fontFamily: 'Nunito', fontWeight: 600, fontSize: 12, opacity: 0.7, marginTop: 10 }}>
              ✦ Secure checkout by Shopify
            </div>
          </div>
        }
      </aside>
    </React.Fragment>);

}

/* ============================================================
   Floating bag button
   ============================================================ */
function CartButton({ count, onClick }) {
  return (
    <button
      type="button" onClick={onClick}
      aria-label={`Open bag — ${count} item${count === 1 ? '' : 's'}`}
      className="tbb-shop-fab"
      style={{
        position: 'fixed', right: 24, bottom: 24, zIndex: 50,
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '14px 22px', borderRadius: 999, cursor: 'pointer',
        background: 'var(--tbb-hotpink)', color: 'var(--tbb-paper)',
        border: '3px solid var(--tbb-ink)',
        boxShadow: '5px 5px 0 0 var(--tbb-ink)',
        fontFamily: 'Nunito Sans', fontWeight: 900, fontSize: 14,
        letterSpacing: '0.06em', textTransform: 'uppercase'
      }}>
      Bag
      <span style={{
        display: 'grid', placeItems: 'center',
        minWidth: 26, height: 26, padding: '0 6px', borderRadius: 999,
        background: 'var(--tbb-paper)', color: 'var(--tbb-ink)',
        fontSize: 13, fontWeight: 900
      }}>{count}</span>
    </button>);

}

/* ============================================================
   Shop section — the whole storefront
   ============================================================ */
function ShopSection() {
  const [products, setProducts] = useState([]);
  const [status, setStatus] = useState('loading'); // loading | ready | empty | error | unconfigured
  const [error, setError] = useState('');
  const [cart, setCart] = useState(null);
  const [cartOpen, setCartOpen] = useState(false);
  const [active, setActive] = useState(null); // product in the quick-view modal
  const [busy, setBusy] = useState(false);
  const [toast, setToast] = useState('');

  useEffect(() => {
    let cancelled = false;

    if (!ShopAPI.isConfigured()) {
      setStatus('unconfigured');
      // Loud in the console, quiet on the page — visitors get a nice
      // "opening soon" instead of a stack trace.
      console.warn('[TBB shop] Shopify not connected. Set DOMAIN and TOKEN in shop-config.js');
      return;
    }

    (async () => {
      try {
        const [list, existing] = await Promise.all([
          ShopAPI.fetchProducts(),
          ShopAPI.getCart().catch(() => null)
        ]);
        if (cancelled) return;
        setProducts(list.products);
        setStatus(list.products.length ? 'ready' : 'empty');
        if (existing) setCart(existing);
      } catch (e) {
        if (cancelled) return;
        console.error('[TBB shop]', e);
        setError(e.message || 'Something went sideways.');
        setStatus('error');
      }
    })();

    return () => { cancelled = true; };
  }, []);

  const flash = useCallback((msg) => {
    setToast(msg);
    window.setTimeout(() => setToast(''), 2400);
  }, []);

  const add = useCallback(async (variantId, qty) => {
    if (!variantId) return;
    setBusy(true);
    try {
      const next = await ShopAPI.addLine(variantId, qty || 1);
      setCart(next);
      setActive(null);
      setCartOpen(true);
    } catch (e) {
      console.error('[TBB shop]', e);
      flash(e.message || "Couldn't add that — try again?");
    } finally {
      setBusy(false);
    }
  }, [flash]);

  const update = useCallback(async (lineId, qty) => {
    setBusy(true);
    try {
      const next = await ShopAPI.updateLine(lineId, qty);
      setCart(next);
    } catch (e) {
      console.error('[TBB shop]', e);
      flash("Couldn't update that.");
    } finally {
      setBusy(false);
    }
  }, [flash]);

  const remove = useCallback(async (lineId) => {
    setBusy(true);
    try {
      const next = await ShopAPI.removeLine(lineId);
      setCart(next);
    } catch (e) {
      console.error('[TBB shop]', e);
      flash("Couldn't remove that.");
    } finally {
      setBusy(false);
    }
  }, [flash]);

  const count = (cart && cart.totalQuantity) || 0;

  return (
    <section id="shop" style={{
      padding: '120px 48px 90px', background: 'var(--tbb-paper-warm)',
      borderBottom: '3px solid var(--tbb-ink)', position: 'relative', minHeight: '70vh'
    }}>
      <div className="tbb-halftone-light" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }} />

      <div style={{ position: 'relative', maxWidth: 1300, margin: '0 auto' }}>
        <div style={{ textAlign: 'center', marginBottom: 40 }}>
          <Chip tone="pink" rot={-3} sticker>The glow shop</Chip>
          <h1 style={{
            fontFamily: 'Olivita, system-ui', fontStyle: 'italic',
            fontSize: 'clamp(40px, 6vw, 82px)', lineHeight: 0.9, margin: '18px 0 10px'
          }}>
            Take the glow home.
          </h1>
          <p style={{
            fontFamily: 'Nunito', fontWeight: 500, fontSize: 17, lineHeight: 1.6,
            color: 'var(--fg2)', maxWidth: 620, margin: '0 auto'
          }}>
            The exact aftercare I use in the studio — for smooth skin, zero ingrowns,
            and that just-waxed feeling that actually lasts.
          </p>
          <div style={{ marginTop: 18 }}>
            <Stamp tone="butter" rot={-2}>
              ✦ {(window.TBB_SHOP && window.TBB_SHOP.PICKUP_NOTE) || 'Pickup or shipping at checkout.'}
            </Stamp>
          </div>
        </div>

        {status === 'loading' &&
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 28 }}>
            {[0, 1, 2, 3].map((i) =>
              <div key={i} style={{
                background: 'var(--tbb-paper)', border: '3px solid var(--tbb-ink)',
                borderRadius: 28, boxShadow: '5px 5px 0 0 var(--tbb-ink)',
                height: 420, opacity: 0.5,
                animation: 'tbb-shop-pulse 1.4s ease-in-out infinite',
                animationDelay: `${i * 120}ms`
              }} />
            )}
          </div>
        }

        {(status === 'unconfigured' || status === 'empty') &&
          <Sticker tone="paper" radius={32} style={{ padding: 48, textAlign: 'center', maxWidth: 620, margin: '0 auto' }}>
            <div style={{ fontFamily: 'Olivita, system-ui', fontStyle: 'italic', fontSize: 44, lineHeight: 1, marginBottom: 12 }}>
              Opening soon ♡
            </div>
            <p style={{ fontFamily: 'Nunito', fontWeight: 500, fontSize: 16, lineHeight: 1.6, color: 'var(--fg2)', margin: '0 0 22px' }}>
              The aftercare shelf is being stocked right now. In the meantime, ask me
              about products at your next appointment — I'll set you up.
            </p>
            <Button variant="primary" size="lg" as="a" href="https://www.vagaro.com/tokyobikinibar/services" target="_blank" rel="noreferrer">
              Book an appointment →
            </Button>
          </Sticker>
        }

        {status === 'error' &&
          <Sticker tone="paper" radius={32} style={{ padding: 48, textAlign: 'center', maxWidth: 620, margin: '0 auto' }}>
            <div style={{ fontFamily: 'Olivita, system-ui', fontStyle: 'italic', fontSize: 40, lineHeight: 1, marginBottom: 12 }}>
              The shop is being fussy.
            </div>
            <p style={{ fontFamily: 'Nunito', fontWeight: 500, fontSize: 16, lineHeight: 1.6, color: 'var(--fg2)', margin: '0 0 8px' }}>
              Give it a refresh — and if it keeps sulking, text me at (805) 883-0714
              and I'll sort you out.
            </p>
            <p style={{ fontFamily: 'Nunito', fontWeight: 500, fontSize: 12.5, opacity: 0.55, margin: '0 0 22px' }}>{error}</p>
            <Button variant="ink" size="lg" onClick={() => window.location.reload()}>Try again</Button>
          </Sticker>
        }

        {status === 'ready' &&
          <div className="tbb-shop-grid" style={{
            display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 28
          }}>
            {products.map((p, i) =>
              <Reveal key={p.id} delay={i * 70} style={{ height: '100%' }}>
                <ProductCard product={p} onOpen={setActive} onAdd={add} busy={busy} />
              </Reveal>
            )}
          </div>
        }
      </div>

      {active &&
        <ProductModal product={active} onClose={() => setActive(null)} onAdd={add} busy={busy} />
      }

      <CartDrawer
        open={cartOpen} cart={cart} busy={busy}
        onClose={() => setCartOpen(false)}
        onUpdate={update} onRemove={remove} />

      {count > 0 && !cartOpen &&
        <CartButton count={count} onClick={() => setCartOpen(true)} />
      }

      {toast &&
        <div style={{
          position: 'fixed', left: '50%', bottom: 28, transform: 'translateX(-50%)', zIndex: 70,
          background: 'var(--tbb-ink)', color: 'var(--tbb-paper)',
          border: '3px solid var(--tbb-ink)', borderRadius: 999,
          padding: '14px 24px', maxWidth: '90vw',
          fontFamily: 'Nunito Sans', fontWeight: 800, fontSize: 14
        }}>
          {toast}
        </div>
      }
    </section>);

}

Object.assign(window, { ShopSection, ProductCard, ProductModal, CartDrawer });
