// Grid.jsx — browse all SKUs with filters

// One collection card. Holds its own quick-add state so hovering reveals the
// size run; clicking a size opens a small quantity stepper that writes straight
// to the order (setOrderQty), no need to open the product.
function GridCard({ p, inOrder, setOrderQty, onOpenSku, dense, onOpenConcept, editing, isDragging, dragHandlers }) {
  const ec = React.useContext(window.EditCtx);
  const sizes = (p.sizes && p.sizes.length) ? p.sizes : ["ONE SIZE"];
  const qtys = (inOrder && inOrder.qtys) || {};
  const [openSize, setOpenSize] = React.useState(null);
  // focus + select the field the moment a size flips into edit mode (it cross-fades
  // in over the pill, so there's no mount to hang autoFocus on).
  const editRef = React.useRef(null);
  React.useEffect(() => {
    if (openSize && editRef.current) { editRef.current.focus(); editRef.current.select(); }
  }, [openSize]);
  const stop = (e) => e.stopPropagation();
  const setQty = (size, n) => setOrderQty(p, size, Math.max(0, n));
  const label = (s) => (s === "ONE SIZE" ? "OS" : s);
  const num = (v) => Number(String(v).replace(/[^\d.]/g, "")) || 0;
  // In-order glow: a soft light BEHIND the card (the gcard's ::before). It is
  // magnetised to the cursor — x follows freely, y follows only down to the
  // middle of the card — and drifts back to its resting spot (top centre) on
  // leave. Vars live on the .gcard root so both the behind-glow (::before on
  // the card) and the top rim (::after on the photo) inherit them.
  const moveGlow = (e) => {
    const photo = e.currentTarget, card = photo.parentElement, r = photo.getBoundingClientRect();
    const gx = (e.clientX - r.left) / r.width * 100;
    const gy = Math.min(50, Math.max(0, (e.clientY - r.top) / r.height * 100));
    card.style.setProperty("--gx", gx.toFixed(1) + "%");
    card.style.setProperty("--gy", gy.toFixed(1) + "%");
  };
  const restGlow = (e) => {
    const card = e.currentTarget.parentElement;
    card.style.setProperty("--gx", "50%");
    card.style.setProperty("--gy", "0%");
  };

  return (
    <div className={"gcard" + (inOrder ? " is-in-order" : "") + (editing ? " gcard-edit" : "") + (isDragging ? " is-dragging" : "")}
      draggable={editing}
      onDragStart={editing ? dragHandlers.onDragStart : undefined}
      onDragOver={editing ? dragHandlers.onDragOver : undefined}
      onDragEnd={editing ? dragHandlers.onDragEnd : undefined}
      onClick={() => onOpenSku(p)} onMouseLeave={() => setOpenSize(null)}>
      <div className="photo" style={{ background: p.bg }}
        onMouseMove={inOrder ? moveGlow : undefined}
        onMouseLeave={inOrder ? restGlow : undefined}
        {...window.dropProps(ec, (path) => ec.saveSku(p.id, { images: [path, ...(p.images || []).slice(1)] }))}>
        {p.hoverImage && (
          <span className="photo-hover" style={{ backgroundImage: `url("${p.hoverImage}")` }} />
        )}
        <window.EditableImage title="⤓ replace photo"
          onPick={(path) => ec.saveSku(p.id, { images: [path, ...(p.images || []).slice(1)] })} />
        {dense && (
          <div className="gcard-peek">
            <span className="gcard-peek-name">{p.name.toLowerCase()}</span>
            <span className="gcard-peek-sub">{p.colorway.name} · {p.currency}{p.ws}</span>
          </div>
        )}
        {!dense && (
          <div className="gcard-quickadd" onClick={stop}>
            <div className="qa-row">
              {sizes.map((s) => {
                const active = openSize === s;
                return (
                  <div key={s} className={"qa-slot" + (active ? " is-editing" : "")}>
                    <button type="button" ref={window.sbHaptic}
                      className={"qa-pill" + ((qtys[s] || 0) > 0 ? " has" : "")}
                      onClick={() => p.priceMode === "dtc"
                        ? setQty(s, (qtys[s] || 0) > 0 ? 0 : 1)   /* DTC: tap size = pick it */
                        : setOpenSize(s)}>
                      {label(s)}{(qtys[s] || 0) > 1 || (p.priceMode !== "dtc" && (qtys[s] || 0) > 0) ? ` ${qtys[s]}` : ""}
                    </button>
                    <input type="number" min="0" className="qa-num"
                      ref={active ? editRef : null} tabIndex={active ? 0 : -1}
                      value={qtys[s] || 0}
                      onFocus={(e) => e.target.select()}
                      onChange={(e) => setQty(s, parseInt(e.target.value || "0", 10))}
                      onBlur={() => setOpenSize(null)}
                      onKeyDown={(e) => { if (e.key === "Enter" || e.key === "Escape") setOpenSize(null); }} />
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </div>
      <div className="gcard-meta">
        <div className="gcard-name">
          {p.seriesLogo && (
            <img className="gcard-series-logo" src={p.seriesLogo} alt={p.series || ""}
              title={p.series ? "concept · " + p.series.toLowerCase() : ""}
              onClick={p.series && onOpenConcept ? (e) => { e.stopPropagation(); onOpenConcept(p.series); } : undefined}
              style={p.series && onOpenConcept ? { cursor: "pointer" } : null} />
          )}
          <window.EditableText value={p.name} style={{ textTransform: "lowercase" }}
            onSave={(v) => ec.saveSku(p.id, { name: v })} />
        </div>
        <div className="gcard-sub">
          <span className="gcard-cw">{p.colorway.name}</span>
          <span className="gcard-price">
            {p.priceMode !== "dtc" && <React.Fragment><span className="lbl">WHS</span> </React.Fragment>}
            {p.wsOriginal != null && <span className="price-strike">{p.currency}{p.wsOriginal}</span>}
            {p.currency}<window.EditableText value={String(p.ws)}
              onSave={(v) => ec.saveSku(p.id, { ws: num(v) })} />
            {p.msrp != null && (
              <span className="dim">  ·  RRP {p.currency}<window.EditableText value={String(p.msrp)}
                onSave={(v) => ec.saveSku(p.id, { msrp: num(v) })} /></span>
            )}
          </span>
        </div>
      </div>
    </div>
  );
}

function GridView({ collection, filters, setFilters, onOpenSku, orderItems, info, concepts, layout, setLayout, setOrderQty, onOpenConcept, fromLookbook }) {
  const ec = React.useContext(window.EditCtx);
  const editing = ec && ec.edit;
  // Entrance for the floating filter pill, decided once on mount: morph out of the
  // lookbook identifier pill when arriving from the lookbook, else a plain fade-up.
  const enterCls = React.useRef(fromLookbook ? "is-morph-in" : "is-enter").current;
  // light haptic tick on scroll-select: Android vibrates; on iOS sbTick toggles a
  // hidden switch programmatically (we're inside the touch-gesture window during a
  // scroll, so Safari may fire the native Taptic tick — best effort, no-op if not).
  const buzz = () => { if (window.sbTick) window.sbTick(); };
  const hap = window.sbHaptic;   // ref cb: lays an iOS haptic switch over a tap target

  // Mobile category wheel: as you scroll the vertical list, whichever category is
  // centred becomes the active filter — an iPhone-picker feel (fling far, ease onto
  // one). Desktop is unaffected (the media query keeps the row horizontal there).
  const groupRef = React.useRef(null);
  const catRef = React.useRef(filters.category || "ALL");
  React.useEffect(() => { catRef.current = filters.category || "ALL"; }, [filters.category]);
  React.useEffect(() => {
    const el = groupRef.current;
    if (!el || !(window.matchMedia && window.matchMedia("(max-width: 600px)").matches)) return;
    const act = el.querySelector(".filter-pill.is-active");
    if (act) act.scrollIntoView({ block: "center" });
    let t;
    const pick = () => {
      const gr = el.getBoundingClientRect(), mid = gr.top + gr.height / 2;
      let best = null, bestD = Infinity;
      el.querySelectorAll(".filter-pill").forEach((p) => {
        const r = p.getBoundingClientRect(), d = Math.abs((r.top + r.bottom) / 2 - mid);
        if (d < bestD) { bestD = d; best = p; }
      });
      const cat = best && best.getAttribute("data-cat");
      if (cat && cat !== catRef.current) { buzz(); setFilters({ ...filters, category: cat }); }
    };
    const onScroll = () => { clearTimeout(t); t = setTimeout(pick, 80); };
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => { el.removeEventListener("scroll", onScroll); clearTimeout(t); };
  }, [filters]);
  // Concept wheel — same vertical scroll-to-select as categories, on mobile.
  const conceptRef = React.useRef(null);
  const conceptCurRef = React.useRef(filters.concept || "");
  React.useEffect(() => { conceptCurRef.current = filters.concept || ""; }, [filters.concept]);
  React.useEffect(() => {
    const el = conceptRef.current;
    if (!el || !(window.matchMedia && window.matchMedia("(max-width: 600px)").matches)) return;
    const act = el.querySelector(".concept-pill.is-active");
    if (act) act.scrollIntoView({ block: "center" });
    let t;
    const pick = () => {
      const gr = el.getBoundingClientRect(), mid = gr.top + gr.height / 2;
      let best = null, bestD = Infinity;
      el.querySelectorAll(".concept-pill").forEach((p) => {
        const r = p.getBoundingClientRect(), d = Math.abs((r.top + r.bottom) / 2 - mid);
        if (d < bestD) { bestD = d; best = p; }
      });
      const name = best ? (best.getAttribute("data-concept") || "") : null;
      if (name !== null && name !== conceptCurRef.current) { buzz(); setFilters({ ...filters, concept: name || null }); }
    };
    const onScroll = () => { clearTimeout(t); t = setTimeout(pick, 80); };
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => { el.removeEventListener("scroll", onScroll); clearTimeout(t); };
  }, [filters]);
  // Drag-reorder (edit mode). Reorders the FULL collection so it works even with
  // a category filter active; persists via ec.commitCollection on drop.
  const dragId = React.useRef(null);
  const moved = React.useRef(false);
  const orderRef = React.useRef(null); // working order during a drag (closure-proof)
  const [dragVisual, setDragVisual] = React.useState(null);
  const startDrag = (id) => (e) => {
    dragId.current = id; moved.current = false; orderRef.current = collection.map((p) => p.id);
    setDragVisual(id);
    if (e.dataTransfer) { e.dataTransfer.effectAllowed = "move"; try { e.dataTransfer.setData("text/plain", String(id)); } catch (err) {} }
  };
  const overDrag = (id) => (e) => {
    e.preventDefault();
    const d = dragId.current; if (d == null || d === id) return;
    const arr = orderRef.current || collection.map((p) => p.id);
    const from = arr.indexOf(d), to = arr.indexOf(id);
    if (from < 0 || to < 0 || from === to) return;
    const next = window.sbMove(arr, from, to);
    orderRef.current = next; moved.current = true; ec.previewCollection(next);
  };
  const endDrag = () => {
    if (moved.current && orderRef.current) ec.commitCollection(orderRef.current);
    dragId.current = null; moved.current = false; orderRef.current = null; setDragVisual(null);
  };

  const allCats = (info && Array.isArray(info.categories) && info.categories.length)
    ? info.categories : window.CATEGORIES;
  // Concepts (series) with logos, for the second filter layer. Products carry a
  // series name that may differ in case ("PHASE" vs "phase") or wording
  // ("LIGHTWEIGHT COSMOS" vs "cosmos"), so match on a normalised contains.
  const conceptList = (concepts || []).filter((c) => c && c.name);
  const norm = (s) => String(s || "").toLowerCase().trim();
  const inConcept = (p, cn) => { const a = norm(p.series), b = norm(cn); return !!a && !!b && (a === b || a.includes(b) || b.includes(a)); };
  const setConcept = (name) => { buzz(); setFilters({ ...filters, concept: name || null }); };

  // Edit mode: drag the concept icons in the floating bar to reorder the
  // concepts themselves (same order model + save as the concepts index page).
  const cDrag = React.useRef(null);
  const cMoved = React.useRef(false);
  const cWork = React.useRef(null);
  const [cOrder, setCOrder] = React.useState(null);
  const [cDragVisual, setCDragVisual] = React.useState(null);
  React.useEffect(() => { setCOrder(null); }, [concepts]);
  const cNames = conceptList.map((c) => c.name);
  const shownConcepts = (editing && cOrder)
    ? cOrder.map((n) => conceptList.find((c) => c.name === n)).filter(Boolean)
    : conceptList;
  const cStart = (name) => (e) => {
    cDrag.current = name; cMoved.current = false; cWork.current = cNames.slice();
    setCDragVisual(name);
    if (e.dataTransfer) { e.dataTransfer.effectAllowed = "move"; try { e.dataTransfer.setData("text/plain", name); } catch (err) {} }
  };
  const cOver = (name) => (e) => {
    if (cDrag.current == null) return;
    e.preventDefault();
    const d = cDrag.current; if (d === name) return;
    const arr = cWork.current || cNames.slice();
    const from = arr.indexOf(d), to = arr.indexOf(name);
    if (from < 0 || to < 0 || from === to) return;
    cWork.current = window.sbMove(arr, from, to); cMoved.current = true; setCOrder(cWork.current);
  };
  const cEnd = () => {
    if (cMoved.current && cWork.current) {
      const seq = cWork.current;
      const reordered = seq.map((n) => (concepts || []).find((c) => c && c.name === n)).filter(Boolean);
      const rest = (concepts || []).filter((c) => !c || !c.name || !seq.includes(c.name));
      ec.saveSeries([...reordered, ...rest]);
    }
    cDrag.current = null; cMoved.current = false; cWork.current = null; setCDragVisual(null);
  };

  const filtered = collection.filter(p => {
    const pCats = (p.categories && p.categories.length) ? p.categories : [p.category];
    if (filters.category && filters.category !== "ALL" && !pCats.includes(filters.category)) return false;
    if (filters.concept && !inConcept(p, filters.concept)) return false;
    return true;
  });

  // Grid density — number of columns (persisted). More columns = smaller thumbs;
  // past a point the captions are hidden (see app.css `.grid.is-dense`).
  const [cols, setCols] = React.useState(() => {
    const saved = Number(localStorage.getItem("sb_grid_cols"));
    return saved >= 2 && saved <= 10 ? saved : 3;
  });
  const changeCols = (n) => { setCols(n); try { localStorage.setItem("sb_grid_cols", String(n)); } catch (e) {} };
  // Mobile: tapping a category opens a glossy list to pick from directly (as well
  // as the scroll wheel). Desktop taps select as usual.
  const [catOpen, setCatOpen] = React.useState(false);
  const [conceptOpen, setConceptOpen] = React.useState(false);
  const [conceptAnchor, setConceptAnchor] = React.useState(null);
  const isPhone = () => typeof window !== "undefined" && window.matchMedia && window.matchMedia("(max-width: 600px)").matches;
  const chooseCat = (c) => { buzz(); setFilters({ ...filters, category: c }); };
  const onPill = (c) => { if (isPhone()) setCatOpen(true); else chooseCat(c); };
  // Concepts mirror categories: tap opens the icon picker on phones (anchored right
  // above the pressed icon), selects directly on desktop (the wheel still scroll-selects).
  const onConceptPill = (name, e) => {
    if (!isPhone()) { setConcept(name); return; }
    const r = e.currentTarget.getBoundingClientRect();
    setConceptAnchor({ cx: r.left + r.width / 2, bottom: window.innerHeight - r.top + 10 });
    setConceptOpen(true);
  };
  // Responsive: cap the column count and shrink gaps on narrower viewports so
  // thumbnails never get uncomfortably small on tablet/phone.
  const ecv = React.useContext(window.EditCtx);
  const [winVw, setWinVw] = React.useState(() => (typeof window !== "undefined" ? window.innerWidth : 1280));
  React.useEffect(() => {
    const onResize = () => setWinVw(window.innerWidth);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  // In the mobile device frame the effective width is the phone width, not the window.
  const vw = (ecv && ecv.vw) ? ecv.vw : winVw;
  const maxCols = vw < 480 ? 2 : vw < 700 ? 3 : vw < 980 ? 4 : 10;
  const effCols = Math.min(cols, maxCols);
  const dense = effCols >= 7;
  const gapX = vw < 700 ? 12 : effCols <= 4 ? 32 : effCols <= 6 ? 20 : 12;
  const gapY = vw < 700 ? 20 : effCols <= 4 ? 48 : effCols <= 6 ? 28 : 14;
  const gridStyle = {
    columnGap: gapX,
    rowGap: gapY,
    "--card-basis": `calc((100% - ${(effCols - 1) * gapX}px) / ${effCols})`,
  };

  return (
    <section className="grid-page" data-screen-label="03 Grid">
      <div className="page-head" style={{borderBottom: "none", marginBottom: 12, paddingBottom: 0}}>
        <div>
          <h1>the collection.</h1>
          <div className="lead">{info.season}</div>
        </div>
        <div className="meta">DELIVERY · {info.deliveryWindow}<br/>BOOKING · {info.bookingWindow}</div>
      </div>

      <div className={"grid-toolbar " + enterCls}>
        <span className="filter-label">[ CATEGORY ]</span>
        <div className="group" ref={groupRef}>
          <button data-cat="ALL" className={"filter-pill" + ((filters.category || "ALL") === "ALL" ? " is-active" : "")}
            onClick={() => onPill("ALL")}>ALL</button>
          {allCats.map(c => (
            <button key={c} data-cat={c}
              className={"filter-pill" + (filters.category === c ? " is-active" : "")}
              onClick={() => onPill(c)}>{c}</button>
          ))}
        </div>

        {conceptList.length > 0 && (
          <div className="concept-filter" ref={conceptRef}>
            <button data-concept="" type="button" title="all concepts"
              className={"concept-pill concept-all" + (!filters.concept ? " is-active" : "")}
              onClick={(e) => onConceptPill("", e)}>
              <span className="concept-all-txt">ALL</span>
            </button>
            {shownConcepts.map((c) => (
              <button key={c.name} data-concept={c.name} type="button"
                className={"concept-pill" + (filters.concept === c.name ? " is-active" : "") + (cDragVisual === c.name ? " is-dragging" : "")}
                title={"concept · " + c.name + (editing ? " · drag to reorder" : "")}
                onClick={(e) => { if (!cDragVisual) onConceptPill(c.name, e); }}
                draggable={editing}
                onDragStart={editing ? cStart(c.name) : undefined}
                onDragOver={editing ? cOver(c.name) : undefined}
                onDragEnd={editing ? cEnd : undefined}
                onDrop={editing ? (e) => e.preventDefault() : undefined}>
                {c.logo ? <img src={c.logo} alt={c.name} draggable={false} /> : <span className="concept-abbr">{c.name.slice(0, 2)}</span>}
              </button>
            ))}
          </div>
        )}

        <div className="grid-toolbar-right">
          {layout !== "line" && (
            <div className="grid-size">
              <span className="filter-label">[ SIZE ]</span>
              <input type="range" className="grid-size-range" min="2" max="10" step="1"
                value={cols} onChange={(e) => changeCols(Number(e.target.value))}
                aria-label="thumbnail size" />
            </div>
          )}
          <window.ViewToggle layout={layout} setLayout={setLayout} />
          <button className="btn btn-ghost"
            onClick={() => setFilters({category: "ALL", fabric: "ALL", concept: null})}>
            [ RESET ]
          </button>
        </div>
      </div>

      {catOpen && (
        <div className="cat-sheet-scrim" onClick={() => setCatOpen(false)}>
          <div className="cat-sheet" onClick={(e) => e.stopPropagation()}>
            {["ALL", ...allCats].map((c) => (
              <button key={c} ref={hap} className={"cat-sheet-item" + ((filters.category || "ALL") === c ? " is-active" : "")}
                onClick={() => { chooseCat(c); setCatOpen(false); }}>{c}</button>
            ))}
          </div>
        </div>
      )}

      {conceptOpen && conceptAnchor && (
        <div className="cat-sheet-scrim" onClick={() => setConceptOpen(false)}>
          <div className="cat-sheet concept-sheet" onClick={(e) => e.stopPropagation()}
            style={{ left: conceptAnchor.cx, bottom: conceptAnchor.bottom, translate: "-50% 0" }}>
            <button ref={hap} title="all concepts" className={"cat-sheet-item concept-sheet-item" + (!filters.concept ? " is-active" : "")}
              onClick={() => { setConcept(""); setConceptOpen(false); }}>
              <span className="concept-all-txt">ALL</span>
            </button>
            {conceptList.map((c) => (
              <button key={c.name} ref={hap} title={"concept · " + c.name}
                className={"cat-sheet-item concept-sheet-item" + (filters.concept === c.name ? " is-active" : "")}
                onClick={() => { setConcept(c.name); setConceptOpen(false); }}>
                {c.logo ? <img src={c.logo} alt={c.name} /> : <span className="concept-abbr">{c.name.slice(0, 2)}</span>}
              </button>
            ))}
          </div>
        </div>
      )}

      {layout === "line" ? (
        <window.LineSheet rows={filtered} allProducts={collection} orderItems={orderItems}
          setOrderQty={setOrderQty} info={info} onOpenSku={onOpenSku}
          editing={editing} dragVisual={dragVisual}
          startDrag={startDrag} overDrag={overDrag} endDrag={endDrag} />
      ) : (
        <div key={"grid-" + (filters.category || "ALL") + "-" + (filters.concept || "all")} className={"grid" + (dense ? " is-dense" : "")} style={gridStyle}>
          {filtered.map(p => (
            <GridCard key={p.id} p={p} inOrder={orderItems[p.id]}
              setOrderQty={setOrderQty} onOpenSku={onOpenSku} dense={dense}
              onOpenConcept={onOpenConcept}
              editing={editing} isDragging={dragVisual === p.id}
              dragHandlers={{ onDragStart: startDrag(p.id), onDragOver: overDrag(p.id), onDragEnd: endDrag }} />
          ))}
        </div>
      )}

      {filtered.length === 0 && (
        <div style={{padding: "96px 0", textAlign: "center", color: "var(--fg-3)"}}>
          <div className="t-meta">no skus match. open the filters above.</div>
        </div>
      )}
    </section>
  );
}

window.GridView = GridView;
window.GridCard = GridCard;
